| name | live-end-of-turn-pause |
| description | Understand and safely change Voice Layer's Live end-of-turn pause, including the persisted setting, native 32 ms VAD silence tracking, Sherpa queued-segment behavior, transcription gating, and countdown rendering. Use when debugging or modifying Live pauses, silence thresholds, countdown bars, VAD timing, early turn endings, or regressions where the pause appears to stop around 0.5 seconds. |
Live End-of-Turn Pause
Protect the user's configured Live pause from being shortened by detector queue
behavior, stale UI timers, or an uncorrelated lifecycle event. Treat the native
frame-counted silence tracker as the turn-boundary authority and the UI bar as
its projection.
Trace the contract
Follow this path before changing code:
- Read
AGENTS.md, inspect git status --short --branch, and preserve
unrelated dirty changes.
- Read
src/settings.ts for liveEndOfTurnMs and
liveEndOfTurnRange. The supported range is 500–3000 ms in 100 ms steps;
the current default is 1200 ms. Settings are snapshotted when Start Live
is pressed, so changing Settings must not alter an active session.
- Read
src/main.ts to confirm the Settings slider and
src/live.ts to confirm the endOfTurnCountdown event, bar text, and
stale-event fallback.
- Read
src-tauri/src/live/vad.rs and src-tauri/src/live/mod.rs. The
snapshotted end_of_turn_ms must reach both the primary detector and
TurnSilenceTracker.
The runtime sequence is:
settings.liveEndOfTurnMs
-> LiveStartConfig.end_of_turn_ms
-> primary Silero detector + zero-hysteresis frame probe
-> TurnSilenceTracker.on_frame(32 ms, probe_is_speech)
-> EndOfTurnCountdown ticks (~100 ms maximum interval)
-> detector queue consumed only after tracker completion
-> Transcribing
Non-negotiable timing rules
- Count silence from the actual VAD windows fed to the native detector. Do not
replace it with a JavaScript timer, wall-clock time since the last event, or
an energy heuristic.
TurnSilenceTracker is authoritative. It becomes complete only after the
configured duration of consecutive non-speech windows and becomes
incomplete immediately when the frame probe detects speech.
- Sherpa may expose a segment in
detector.front() as soon as it observes a
non-speech frame. Its queue availability is not proof that the configured
end-of-turn pause elapsed. Keep the segment pending until
TurnSilenceTracker::is_complete() is true.
- Never call
Transcribing, pop the queued segment, or reset the tracker early.
A resumed speech frame must revoke the pending completion gate.
- End-of-turn countdown events are progress observations, not a second timing
authority. Emit them while trailing silence is active and stop/reset them
when speech resumes, the turn is consumed, Live is muted, or Live stops.
- The frontend countdown must not hide before the reported remaining duration
has elapsed. A stale-event watchdog may provide a bounded grace period, but
it must never be a fixed short timeout such as 300 or 500 ms for every pause.
- Keep session, utterance, and event correlation intact. A late countdown or
transcription event from an older Live session must not affect the current
bar or state.
Safe change locations
- Change persisted range/default/migration behavior only in
src/settings.ts
and its settings tests.
- Change the native silence contract in
src-tauri/src/live/vad.rs; keep the
tracker small, frame-based, resettable, and directly unit tested.
- Change detector queue admission and
Transcribing ownership in
src-tauri/src/live/mod.rs. Do not solve an early stop only in TypeScript.
- Change countdown rendering and stale-event handling in
src/live.ts and
tests/live.test.ts. The bar mirrors native events; it does not decide when
a turn ends.
- Keep model pins, microphone capture, ASR, assistant generation, TTS, and
playback behavior unchanged unless the observed failure crosses one of
those boundaries.
Debugging workflow
- Record the configured value, the value sent in
start_live_session, the
native end_of_turn_ms, the first countdown tick, the final countdown tick,
speechStart, Transcribing, and any stopped event. Compare timestamps
and request/session IDs.
- Determine whether the bar disappeared early or whether native
transcription/turn completion happened early. These are separate bugs.
- Inspect whether
detector.front() was consumed before
TurnSilenceTracker reached completion. If so, fix native admission first.
- Add or update a deterministic regression test for the configured duration
and for speech resuming before completion. Do not assert only that a UI
element was briefly visible.
- Recheck settings snapshot behavior so a mid-session slider change cannot
shorten the active turn.
Validation
Run focused checks before broad checks:
cargo test --manifest-path src-tauri/Cargo.toml --lib live::vad::tests
cargo test --manifest-path src-tauri/Cargo.toml --lib live::tests
bun test tests/live.test.ts tests/settings.test.ts
git diff --check
just check
just bundle
Use $test-voice-layer-app for packaged Live UI checks. Distinguish these
claims:
- Unit tests prove frame accounting, reset behavior, queue-gate logic, and
event/UI state transitions.
just check and just bundle prove the current source builds and packages;
they do not prove the pause is audible or correct on a microphone.
- Packaged UI inspection proves the saved slider value and rendered labels.
- Real microphone acceptance is required to prove the countdown remains for
the configured duration, resumed speech resets it, and the resulting turn
does not end at the detector's earlier queue point. Do not claim device
acceptance without exercising an actual microphone.
If a broader check is blocked by unrelated dirty work, report the exact file,
error, and independently passing focused checks. Do not overwrite that work
just to make this skill's validation green.
Completion bar
Finish only when the configured value is carried unchanged into the active Live
session, native queue consumption cannot bypass the full pause, resumed speech
resets the pending completion, the UI cannot hide the bar early, regression
tests cover both completion and reset, and validation evidence clearly labels
the boundary between automated, packaged, and real-device acceptance.