| name | state-machine-transition |
| description | Add new states or transitions to Bob The Skull's state machine. Use when adding new states, defining transitions, implementing state behavior, or modifying state machine logic. |
| allowed-tools | Read, Edit, Grep |
State Machine Transitions
Adds new states or transitions to Bob The Skull's finite state machine that coordinates system behavior.
When to Use
- Adding new states to the state machine
- Defining valid state transitions
- Implementing state entry/exit actions
- Adding timeout handling for states
- Documenting state behavior
State Machine Overview
Bob uses a finite state machine (FSM) to coordinate all system behavior:
State Machine receives events → Decides transitions → Coordinates responses
Current States:
IDLE- Waiting for wake wordWAKE_LISTENING- Brief pause after wake wordGREETING- Playing greeting audioLISTENING- Recording user speechPROCESSING- LLM thinkingSPEAKING- Bob responding (TTS)OBSERVING- Vision-only modeERROR- Error state with recovery
State Machine Files
Primary file: state_machine/state_machine.py
Key components:
Stateenum - Define all statesBobStateMachineclass - Core logic_setup_event_handlers()- Event subscriptions_transition_to()- State transition logicon_<event>()methods - Event handlers_enter_<state>()methods - State entry actions_exit_<state>()methods - State exit actions
Adding a New State
1. Define State in Enum
class State(Enum):
"""Bob's finite state machine states"""
IDLE = "idle"
WAKE_LISTENING = "wake_listening"
GREETING = "greeting"
LISTENING = "listening"
PROCESSING = "processing"
SPEAKING = "speaking"
OBSERVING = "observing"
YOUR_NEW_STATE = "your_new_state" # Add here
ERROR = "error"
2. Define Valid Transitions
# Valid state transitions
VALID_TRANSITIONS = {
State.IDLE: [State.WAKE_LISTENING, State.OBSERVING, State.ERROR],
State.WAKE_LISTENING: [State.GREETING, State.LISTENING, State.IDLE, State.ERROR],
State.GREETING: [State.LISTENING, State.IDLE, State.ERROR],
State.LISTENING: [State.PROCESSING, State.IDLE, State.ERROR],
State.PROCESSING: [State.SPEAKING, State.IDLE, State.ERROR],
State.SPEAKING: [State.IDLE, State.OBSERVING, State.ERROR],
State.OBSERVING: [State.IDLE, State.WAKE_LISTENING, State.ERROR],
State.YOUR_NEW_STATE: [State.IDLE, State.ERROR], # Add here
State.ERROR: [State.IDLE]
}
3. Define State Timeouts
# State timeout configuration (seconds)
STATE_TIMEOUTS = {
State.WAKE_LISTENING: 1.0, # Brief pause
State.GREETING: 10.0, # Max greeting time
State.LISTENING: 30.0, # Max listening time
State.PROCESSING: 60.0, # Max LLM time
State.SPEAKING: 120.0, # Max TTS time
State.OBSERVING: None, # No timeout (continuous)
State.YOUR_NEW_STATE: 15.0, # Add appropriate timeout
State.IDLE: None, # No timeout
State.ERROR: 5.0 # Error recovery time
}
4. Implement Entry Action
def _enter_your_new_state(self):
"""Entry action for YOUR_NEW_STATE"""
logger.info("Entering YOUR_NEW_STATE")
# Start operations for this state
# Example: Start a component, publish event, etc.
# Publish state change event
self.event_bus.publish(StateChangedEvent(
old_state=self.previous_state.value if self.previous_state else None,
new_state=State.YOUR_NEW_STATE.value,
timestamp=datetime.now()
))
5. Implement Exit Action (Optional)
def _exit_your_new_state(self):
"""Exit action for YOUR_NEW_STATE"""
logger.info("Exiting YOUR_NEW_STATE")
# Cleanup operations
# Example: Stop component, clear state, etc.
6. Add Event Handlers
def _setup_event_handlers(self):
"""Subscribe to events"""
# ... existing subscriptions ...
self.event_bus.subscribe(YourTriggerEvent, self.on_your_trigger_event)
def on_your_trigger_event(self, event: YourTriggerEvent):
"""Handle event that triggers transition to YOUR_NEW_STATE"""
logger.info(f"Received YourTriggerEvent: {event}")
if self.current_state == State.IDLE:
# Transition to new state
self._transition_to(State.YOUR_NEW_STATE)
7. Add Timeout Handler (If Needed)
def _on_timeout(self, state: State):
"""Handle state timeout"""
logger.warning(f"Timeout in state: {state.value}")
if state == State.YOUR_NEW_STATE:
# Handle timeout for your state
logger.error("YOUR_NEW_STATE timed out")
self._transition_to(State.IDLE) # Or appropriate fallback
# ... handle other states ...
State Transition Pattern
def _transition_to(self, new_state: State):
"""
Transition to a new state
Args:
new_state: Target state
"""
# Validate transition
if new_state not in VALID_TRANSITIONS.get(self.current_state, []):
logger.error(f"Invalid transition: {self.current_state.value} -> {new_state.value}")
return False
# Exit current state
exit_method = getattr(self, f'_exit_{self.current_state.value}', None)
if exit_method:
try:
exit_method()
except Exception as e:
logger.error(f"Error in exit action: {e}", exc_info=True)
# Update state
self.previous_state = self.current_state
self.current_state = new_state
self.state_start_time = time.time()
# Enter new state
entry_method = getattr(self, f'_enter_{new_state.value}', None)
if entry_method:
try:
entry_method()
except Exception as e:
logger.error(f"Error in entry action: {e}", exc_info=True)
self._transition_to(State.ERROR)
return False
# Start timeout timer if configured
timeout = STATE_TIMEOUTS.get(new_state)
if timeout:
self._start_timeout_timer(timeout)
logger.info(f"State transition: {self.previous_state.value} -> {new_state.value}")
return True
Common State Patterns
Transient States (Brief, automatic exit)
State.WAKE_LISTENING # 1 second pause, then auto-transition
State.GREETING # Play audio, then transition
Characteristics:
- Short timeout
- Entry action starts operation
- Auto-transition when operation completes
Active States (Wait for events)
State.LISTENING # Wait for speech or silence
State.PROCESSING # Wait for LLM response
Characteristics:
- Moderate timeout
- Entry action starts component
- Exit on component completion event
Continuous States (No timeout)
State.IDLE # Wait indefinitely for wake word
State.OBSERVING # Vision-only mode, no timeout
Characteristics:
- No timeout (None)
- Only exit on specific events
- May have periodic actions
Error State (Recovery)
State.ERROR # Brief error state, then return to IDLE
Characteristics:
- Short timeout (5s)
- Entry logs error details
- Auto-transition to IDLE on timeout
Event-Driven Transitions
Most transitions are triggered by events:
# Wake word detected → WAKE_LISTENING
def on_wake_word_detected(self, event: WakeWordDetectedEvent):
if self.current_state == State.IDLE:
self._transition_to(State.WAKE_LISTENING)
# Speech recognized → PROCESSING
def on_speech_recognized(self, event: SpeechRecognizedEvent):
if self.current_state == State.LISTENING:
self.user_input = event.transcript
self._transition_to(State.PROCESSING)
# LLM response → SPEAKING
def on_llm_response_ready(self, event: LLMResponseReadyEvent):
if self.current_state == State.PROCESSING:
self._transition_to(State.SPEAKING)
Configuration Integration
States can be configured via BobConfig.py:
# State machine configuration
STATE_MACHINE_WAKE_LISTENING_DURATION: float = 1.0
STATE_MACHINE_LISTENING_TIMEOUT: int = 30
STATE_MACHINE_PROCESSING_TIMEOUT: int = 60
STATE_MACHINE_SPEAKING_TIMEOUT: int = 120
STATE_MACHINE_ERROR_RECOVERY_TIME: int = 5
STATE_MACHINE_YOUR_NEW_STATE_TIMEOUT: int = 15 # Add config
Reference in state machine:
STATE_TIMEOUTS = {
State.YOUR_NEW_STATE: getattr(config, 'STATE_MACHINE_YOUR_NEW_STATE_TIMEOUT', 15.0)
}
State Diagram Documentation
Update state diagram in documentation:
[IDLE] --wake word--> [WAKE_LISTENING] --pause complete--> [GREETING]
[GREETING] --greeting done--> [LISTENING]
[LISTENING] --speech recognized--> [PROCESSING]
[PROCESSING] --LLM response--> [SPEAKING]
[SPEAKING] --speech done--> [IDLE]
[YOUR_NEW_STATE] --trigger--> [IDLE] # Document new transitions
Testing State Transitions
# test_state_machine.py
def test_your_new_state_transition():
"""Test transition to YOUR_NEW_STATE"""
state_machine = BobStateMachine(event_bus, config)
# Start in IDLE
assert state_machine.current_state == State.IDLE
# Trigger transition
event_bus.publish(YourTriggerEvent(...))
# Verify transition
assert state_machine.current_state == State.YOUR_NEW_STATE
# Verify timeout
time.sleep(16) # Wait for timeout
assert state_machine.current_state == State.IDLE
Checklist
- Add state to State enum
- Add valid transitions to VALID_TRANSITIONS
- Add timeout to STATE_TIMEOUTS
- Implement
_enter_<state>()method - Implement
_exit_<state>()method (if needed) - Add event handlers for transitions
- Add timeout handler in
_on_timeout() - Add configuration parameters (if needed)
- Update state diagram documentation
- Write unit tests for state
- Test transitions from all valid states
- Test timeout behavior
- Test error recovery
Pro Tips
- Keep states focused - Each state should have one clear purpose
- Define all transitions - Explicitly list valid from→to pairs
- Add timeouts - Every active state should have a timeout for recovery
- Log everything - State transitions are critical for debugging
- Test edge cases - What if event arrives in wrong state?
- Document intent - Explain why state exists in docstring
- Handle errors - All entry/exit actions should handle exceptions