VoiceTurnCoordinator and its pure VoiceTurnReducer. Capture, provider, tool, and playback components remain I/O adapters. They report typed events and execute reducer effects; they do not independently decide whether a turn is listening, waiting, playing, or finished.
Turn lifecycle
Every turn has a stableVoiceTurnID. Async boundaries add the narrowest identity needed to reject late work: VoiceCaptureID, VoiceSessionID, VoiceResponseID, VoiceToolCallID, and VoiceLeaseID.
Realtime provider callbacks are delivered in transport order inside a RealtimeHubEventIdentity envelope. The controller rejects the envelope before mutating transcript, playback, tools, persistence, or terminal state unless both its turn and response identities still own the active reducer turn.
Ownership boundaries
| Component | Owns | Must not own |
|---|---|---|
VoiceTurnReducer | Legal transitions, route, deadlines, terminal reason, semantic UI projection | I/O, singletons, network, audio buffers |
VoiceTurnCoordinator | Reducer execution, timers, effects, diagnostics, bounded timeline | Provider protocol parsing or audio processing |
PushToTalkManager | Shortcut and capture adapters, bounded audio sidecar, existing STT dispatch | A second route or phase state machine |
RealtimeHubController | Warm session transport, normalized provider callbacks, tool adapter | Floating-bar presentation or terminal policy |
VoiceOutputCoordinator | One turn-scoped audible lease across every PTT lane | Audio synthesis or playback |
FloatingBarVoicePlaybackService | Synthesis and playback | PTT ownership or direct bar mutation |
PTTBarPresenter | Applying the reducer projection to floating-bar state | Turn policy |
Audible output invariant
Native realtime audio, selected-voice fallback, deterministic agent acknowledgement, filler, and system-voice fallback use oneVoiceOutputLease. A stale turn cannot acquire a lease, a competing real lane is denied, and only the exact lease identity can release playback. Filler is provisional: the playback engine stops it before releasing its lease, then the real answer acquires the lane. Provider completion and local playback drain are separate events: a turn remains active until both the provider is done and queued audio has drained.
Gemini audio is also gated by its manual activity window. Audio and commit requests that arrive before activityStart remain buffered inside RealtimeHubSession; they flush in order only after the input window opens. Abandoning a turn clears that buffer so pre-window audio cannot leak into the next turn.
Provider-specific barge-in is an atomic handoff. Reducer terminalization preserves an active hub runtime long enough for OpenAI to cancel its in-session response or Gemini to create a fresh replacement session. Preparation, seed refresh, permission, authentication, transcription, and persistence work capture immutable turn ownership and revalidate it after every suspension.
Continuity persistence resolves the user transcript at the write boundary. A provider transcript is preferred when it is present and compatible with configured voice languages; otherwise the already-running local full-buffer decode gets a bounded wait. Barge-in replacement awaits that immutable resolution before recording the interrupted exchange, refreshing the kernel seed, and opening the new provider session. Agent delegation follows the same rule: resolution must still own the tool turn immediately before the canonical spawn side effect, and only a successful spawn may produce the success acknowledgement and durable handoff.
UI and error signals
VoiceTurnUIProjection is the semantic source for listening, lock, transcript, thinking, response-waiting, response-active, and terminal hints. PTTBarPresenter is the only writer for those floating-bar fields. User-facing failures stay visible briefly as actionable hints; developer signals remain available through typed terminal diagnostics, stale-event and invalid-transition counts, and the bounded event timeline.
Testing contract
Reducer tests use deterministic events and fake-clock deadlines. Cover the happy path plus races that can change ownership: late capture completion, old session or response callbacks, warm-wait fallback, deferred and replacement commits, pending tools, provider-finish versus playback-drain ordering, competing leases, barge-in, cleanup from every phase, duplicate terminal events, and randomized stale-event sequences. Integration tests verify the coordinator’s real effects and presentation adapter. Provider and playback tests verify normalized callbacks and lease enforcement. Keep tests hermetic: no live provider, network, sleep, or ordering dependency in CI. The tier-2ptt-lifecycle harness flow drives the real ptt_start and ptt_stop bridge actions, then checks the typed phase and anomaly counters. A file belongs in that flow’s covers list only when those steps exercise its behavior; typed chat-only flows do not claim PTT reducer coverage.