State Transitions and Operations Guide¶
This guide documents how houmao-server state values change over time and what operations are appropriate in each state. It is diagram-first: read the statecharts and sequence diagram to understand the state model visually, then consult the prose sections for operational details.
For the definition of each individual state value, see the State Reference Guide. For the internal pipeline architecture, see State Tracking.
Source of truth: Tracker-owned transition logic is implemented in
src/houmao/shared_tui_tracking/session.pytogether with the shared detector/profile contract insrc/houmao/shared_tui_tracking/detectors.pyand app-owned implementations such assrc/houmao/shared_tui_tracking/apps/codex_tui/. The live server tracker insrc/houmao/server/tui/tracking.pyfeeds that shared session and then merges the result with server-owned diagnostics and lifecycle metadata.
Diagnostics Availability¶
All five diagnostics.availability values and their transitions. The entry state is unknown. Only available enables meaningful surface and turn tracking — all other states degrade surface and turn values.
stateDiagram-v2
[*] --> unknown : initial state
unknown --> available : tmux up +<br/>TUI process running +<br/>parse success +<br/>surface present
unknown --> unavailable : tmux session<br/>not found
unknown --> tui_down : tmux up but<br/>TUI process absent
unknown --> error : probe failed or<br/>parse failed
available --> tui_down : TUI process exits<br/>while tmux stays up
available --> unavailable : tmux session<br/>disappears
available --> error : probe failure or<br/>parse failure<br/>on current cycle
tui_down --> available : TUI process restarts +<br/>parse success
tui_down --> unavailable : tmux session<br/>disappears
tui_down --> error : probe failure<br/>on current cycle
unavailable --> unknown : tmux session<br/>reappears
unavailable --> error : probe failure<br/>on current cycle
error --> available : next cycle succeeds<br/>fully
error --> unavailable : tmux gone on<br/>next cycle
error --> tui_down : tmux up but<br/>TUI gone on next cycle
error --> unknown : partial recovery<br/>on next cycle
note right of available : Gateway state —<br/>only here are surface/turn<br/>values meaningful
Turn Phase¶
All three turn.phase values and their transitions. The entry state is unknown.
stateDiagram-v2
[*] --> unknown : initial state
unknown --> ready : diagnostics available +<br/>ready_posture=yes +<br/>no active evidence +<br/>no ambiguous surface
unknown --> active : anchor armed or<br/>active evidence detected
ready --> active : explicit input anchor armed<br/>or active evidence detected
ready --> unknown : diagnostics degraded<br/>(error / tui_down / unavailable)<br/>or ambiguous interactive surface
active --> ready : turn completed —<br/>success settled +<br/>ready_posture=yes<br/>OR interrupted/failure +<br/>ready surface<br/>OR host active recovery
active --> unknown : diagnostics degraded<br/>(error / tui_down / unavailable)
note right of unknown : Not an error —<br/>means the system<br/>cannot confidently classify<br/>the current posture
Last-Turn Result¶
All four last_turn.result values and their transitions. The entry state is none. The last_turn value is sticky — it only changes when a tracked turn reaches a terminal outcome, not on every poll cycle.
stateDiagram-v2
[*] --> none : initial state
none --> success : settle timer fires —<br/>success candidate held<br/>through settle window
none --> interrupted : interruption signal<br/>detected by detector
none --> known_failure : failure signal<br/>detected by detector
success --> success : new turn also<br/>succeeds
success --> interrupted : new turn<br/>interrupted
success --> known_failure : new turn<br/>failed
success --> none : premature success retracted —<br/>surface changed after settle
interrupted --> success : new turn<br/>succeeds
interrupted --> interrupted : new turn also<br/>interrupted
interrupted --> known_failure : new turn<br/>failed
known_failure --> success : new turn<br/>succeeds
known_failure --> interrupted : new turn<br/>interrupted
known_failure --> known_failure : new turn also<br/>failed
note right of success : Sticky — persists<br/>across poll cycles<br/>until next terminal outcome
note left of none : Returns here only<br/>if premature success<br/>is retracted
Turn Lifecycle Sequence¶
A complete turn lifecycle from the consumer's perspective, showing both the successful and interrupted paths.
sequenceDiagram
participant C as Consumer
participant S as Server API<br/>/houmao/terminals/
participant T as LiveSession<br/>Tracker
participant D as Detector
C->>S: POST .../input<br/>(send prompt)
S->>T: note_prompt_submission()
T->>T: arm explicit_input anchor
Note over T: turn.phase → active
loop Poll cycles
T->>D: detect(output_text,<br/>parsed_surface)
D-->>T: DetectedTurnSignals
T->>D: derive_temporal_hints(recent_frames)
D-->>T: TemporalHintSignals
end
alt Successful turn
D-->>T: active_evidence=true
Note over T: turn.phase stays active
D-->>T: success_candidate=true
T->>T: arm settle timer
Note over T: waiting for<br/>settle window...
T->>T: settle timer fires
Note over T: last_turn.result → success<br/>last_turn.source → explicit_input<br/>turn.phase → ready
else Interrupted turn
D-->>T: interrupted=true
Note over T: cancel settle timer<br/>last_turn.result → interrupted<br/>last_turn.source → explicit_input<br/>turn.phase → ready
end
C->>S: GET .../state
S-->>C: HoumaoTerminal<br/>StateResponse
State Composition¶
How low-level observations compose upward through the pipeline into public state groups. This reflects the shared module extraction — detectors and mapping helpers live in shared_tui_tracking/, not inside the server module.
flowchart TD
Probe["tmux probe<br/><i>pane capture + liveness</i>"]
Proc["process inspection<br/><i>PaneProcessInspector</i>"]
Parse["official parser<br/><i>OfficialTuiParserAdapter</i>"]
Surf["HoumaoParsedSurface"]
Detect["tracked-TUI profile<br/><i>shared_tui_tracking/apps/<app_id>/</i>"]
Shared["TuiTrackerSession<br/><i>shared_tui_tracking/session.py</i>"]
MapDiag["diagnostics_availability()<br/><i>shared_tui_tracking/public_state.py</i>"]
Anchor{"turn anchor<br/>lifecycle"}
Settle["settle timer<br/><i>ReactiveX kernel</i>"]
PubDiag["diagnostics<br/>.availability"]
PubSurf["surface<br/>.accepting_input<br/>.editing_input<br/>.ready_posture"]
PubTurn["turn<br/>.phase"]
PubLast["last_turn<br/>.result / .source"]
Stable["stability<br/>.stable_for_seconds"]
Hist["recent_transitions"]
Probe --> Proc --> Parse --> Surf
Probe --> MapDiag
Proc --> MapDiag
Parse --> MapDiag
Probe --> Detect
Detect --> Shared
Shared --> PubSurf
Shared --> PubTurn
Shared --> PubLast
MapDiag --> PubDiag
Surf --> Anchor
Anchor --> Settle
PubDiag --> Stable
PubSurf --> Stable
PubTurn --> Stable
PubLast --> Stable
Stable --> Hist
Operation Acceptability¶
What operations are acceptable for major state combinations, using Houmao-native routes.
diagnostics.availability |
turn.phase |
Send input (POST /houmao/terminals/{id}/input) |
Poll state (GET /houmao/terminals/{id}/state) |
Expect meaningful results |
|---|---|---|---|---|
available |
ready |
Yes — terminal is idle and ready | Yes | Yes — all state groups are meaningful |
available |
active |
No — agent is working | Yes — poll for turn completion | Yes — watch for last_turn.result to change |
available |
unknown |
Wait — posture is ambiguous | Yes — may resolve shortly | Partial — surface values may be approximate |
unavailable |
any | No — no terminal to receive input | Yes — monitor for recovery | No — values are stale or default |
tui_down |
any | No — TUI process is not running | Yes — monitor for TUI restart | No — values reflect last observation before exit |
error |
any | No — current sample is untrustworthy | Yes — may be transient | No — wait for recovery |
unknown |
any | Wait — state is not classifiable | Yes — may resolve shortly | No — insufficient data |
Stability and Timing¶
stable_for_seconds¶
The stability.stable_for_seconds field indicates how long the current public state has remained unchanged. Consumers should use this to avoid acting on transient states.
Practical guidance:
- A last_turn.result=success with stable_for_seconds above the settle window threshold indicates a truly settled outcome
- Acting on an unstable success (low stable_for_seconds) risks seeing the result retracted — the settle window may not have elapsed yet
- For turn.phase=ready, stability confirms the TUI has been idle, not just momentarily between outputs
Premature Success Retraction¶
The reducer records the surface signature when a success settles. If a subsequent observation under the same anchor shows a different surface but the detector still reports success_candidate, the previous success is retracted (last_turn.result reverts to none) before the new candidate is evaluated. This prevents a premature success from persisting when the TUI was actually still producing output.
Settle Window¶
The settle window is a configurable duration in the standalone TuiTrackerSession (TrackerConfig.settle_seconds) during which a success_candidate surface must remain stable before being promoted to success. If any of the following occur during the window, the pending success is cancelled:
- Diagnostics degrade (availability leaves
available) - An
interruptedsignal is detected - A
known_failuresignal is detected active_evidencereappears- The surface signature changes (surface is still evolving)
Turn Anchor Effects¶
explicit_input vs surface_inference¶
The two anchor sources have different timing characteristics:
explicit_input — armed immediately when the server receives POST /houmao/terminals/{terminal_id}/input and calls note_prompt_submission():
- The exact submission timestamp is known
- turn.phase transitions to active immediately
- Settle timing starts from a precise anchor point
- This is the preferred path for automation
surface_inference — armed by the tracker when it detects that a direct tmux interaction has produced a new turn:
- The tracker uses raw tmux snapshots plus tool/version detector rules; parser-derived surface growth is not part of this authority path
- The submission moment is estimated from surface changes, not known precisely
- Settle times may be slightly longer due to the estimation uncertainty
- This path keeps repaint churn, cursor motion, and small prompt edits from manufacturing turn semantics
Reducer Transition Rules¶
The standalone TuiTrackerSession in shared_tui_tracking/session.py applies a strict priority chain when processing each raw snapshot. The compatibility StreamStateReducer wrapper and the live server host adapter both reuse that same core behavior. Server-owned diagnostics, parser sidecars, and lifecycle enrichment are published alongside this reducer output; they do not become reducer inputs.
Priority Chain¶
Each observation is processed through these checks in order. The first matching condition determines the output:
-
Interrupted signal — If the detector reports
interrupted=true, emitturn.phase=readyandlast_turn.result=interrupted. Cancel any pending success timer. Clear the armed turn source. -
Known failure signal — If the detector reports
known_failure=true, emitturn.phase=readyandlast_turn.result=known_failure. Cancel any pending success timer. Clear the armed turn source. -
Active evidence — If the detector reports
active_evidence=true, emitturn.phase=active. Cancel any pending success timer. If no turn source is armed yet, armsurface_inference. -
Success candidate — If the detector reports
success_candidate=true, emitturn.phase=readyand arm or retain the settle timer. If a previous settled success exists with a different surface signature, retract it first. -
Default — No strong signal matched. Cancel any pending success timer. Emit
turn.phase=readyifready_posture=yes, otherwiseturn.phase=unknown.
Host-Side Stale-Active Recovery¶
After the shared tracker publishes its surface / turn / last_turn fields, the live host adapter may still apply one corrective step for unanchored sessions.
That recovery exists to handle stale active evidence that survived in tmux history after the visible surface has already become submit-ready again. The current Codex tracker is the main case that benefits from it.
The host adapter recovers turn.phase=active to turn.phase=ready only when all of the following remain true through the configured recovery window:
- diagnostics remain
available - parsed surface is submit-ready (
availability=supported,business_state=idle,input_mode=freeform) - public surface remains
accepting_input=yesandediting_input=no - the session is unanchored (
lifecycle_authority.turn_anchor_state=absent) - remaining shared-tracker active reasons are limited to stale-like
status_rowevidence
This correction does not set last_turn.result=success. It only fixes the public readiness posture.
Success Timer¶
When a success_candidate is detected, the reducer arms a settle timer for the configured settle window duration. The timer fires only if:
- The latest signals still report success_candidate
- No current_error_present
- The surface signature has not changed since arming
On fire, the reducer emits last_turn.result=success with the appropriate turn source and clears the armed tracker source.
Cancellation¶
The success timer is cancelled whenever the priority chain reaches a step before the success_candidate check — i.e., diagnostics degradation, interrupted, known_failure, or active_evidence all cancel a pending success.