Bug Description
After a confirmed false interruption resumes agent speech, LiveKit Agents keeps the discarded user recognition turn alive. The next real user transcript inherits the false turn's original speech-start anchor, so ChatMessage.metrics.started_speaking_at can predate the actual utterance and be ordered before agent speech that really occurred first.
This reproduces on livekit-agents==1.7.1. The ordering guard from #6093 / #6098 does not cover it because the resulting user metrics remain internally ordered (stopped_speaking_at >= started_speaking_at); the start anchor is stale but not mathematically impossible.
Deterministic timeline
Observed production timeline, UTC:
19:20:06.481 — VAD opens a user turn; no STT is committed.
19:20:10.114 — LiveKit confirms a false interruption and resumes the paused agent speech.
19:20:18.632 — the user actually begins the later utterance.
- The committed user
ChatMessage reports started_speaking_at = 19:20:06.481, 12.151 seconds before the real utterance.
This incorrectly places the user line before the resumed agent response in timestamp-sorted transcripts.
Deterministic code-path reproduction
On 1.7.1:
- Create an
AgentActivity with resume_false_interruption=True, paused speech whose agent_state == "speaking", and an active AudioRecognition turn with _speech_start_time set by VAD but no committed transcript.
- Trigger
_start_false_interruption_timer(0) and let _on_false_interruption(resumed=True) complete.
- Observe that agent speech starts/resumes, but
AudioRecognition._clear_user_turn() is never called.
- Emit a later real user utterance and commit its transcript.
- The resulting
ChatMessage.metrics.started_speaking_at is the step-1 VAD timestamp, not the later utterance's start.
A deterministic spy around step 2 shows the current resume path lacks recognition cleanup. The local patch changes the event order to:
clear_user_turn -> start_agent_speech -> audio_resume
Expected Behavior
Once a false interruption is confirmed and paused agent audio will resume, the uncommitted recognition turn should be discarded before agent speech resumes. Its logical-turn/span anchor, speech timing anchors, buffered transcript, and related turn state must not survive into the next real user utterance.
Actual Behavior
_on_false_interruption resumes the agent state/audio without clearing the active recognition turn. The next committed user message can inherit the abandoned turn's started_speaking_at.
Proposed Solution
Call the recognition turn's existing full clear/reset path before _on_start_of_agent_speech and audio resume when agent_false_interruption(resumed=True) is accepted. Resetting only _speech_start_time would leave other discarded-turn state behind.
PeakSend has a version- and checksum-pinned 1.7.1 patch implementing and build-verifying this ordering:
Those links are in a private repository, but the complete reproduction timeline and patch boundary are included above.
Package Versions
The original incident was observed on 1.6.9 and confirmed against the 1.7.1 source.
Bug Description
After a confirmed false interruption resumes agent speech, LiveKit Agents keeps the discarded user recognition turn alive. The next real user transcript inherits the false turn's original speech-start anchor, so
ChatMessage.metrics.started_speaking_atcan predate the actual utterance and be ordered before agent speech that really occurred first.This reproduces on
livekit-agents==1.7.1. The ordering guard from #6093 / #6098 does not cover it because the resulting user metrics remain internally ordered (stopped_speaking_at >= started_speaking_at); the start anchor is stale but not mathematically impossible.Deterministic timeline
Observed production timeline, UTC:
19:20:06.481— VAD opens a user turn; no STT is committed.19:20:10.114— LiveKit confirms a false interruption and resumes the paused agent speech.19:20:18.632— the user actually begins the later utterance.ChatMessagereportsstarted_speaking_at = 19:20:06.481, 12.151 seconds before the real utterance.This incorrectly places the user line before the resumed agent response in timestamp-sorted transcripts.
Deterministic code-path reproduction
On 1.7.1:
AgentActivitywithresume_false_interruption=True, paused speech whoseagent_state == "speaking", and an activeAudioRecognitionturn with_speech_start_timeset by VAD but no committed transcript._start_false_interruption_timer(0)and let_on_false_interruption(resumed=True)complete.AudioRecognition._clear_user_turn()is never called.ChatMessage.metrics.started_speaking_atis the step-1 VAD timestamp, not the later utterance's start.A deterministic spy around step 2 shows the current resume path lacks recognition cleanup. The local patch changes the event order to:
Expected Behavior
Once a false interruption is confirmed and paused agent audio will resume, the uncommitted recognition turn should be discarded before agent speech resumes. Its logical-turn/span anchor, speech timing anchors, buffered transcript, and related turn state must not survive into the next real user utterance.
Actual Behavior
_on_false_interruptionresumes the agent state/audio without clearing the active recognition turn. The next committed user message can inherit the abandoned turn'sstarted_speaking_at.Proposed Solution
Call the recognition turn's existing full clear/reset path before
_on_start_of_agent_speechand audio resume whenagent_false_interruption(resumed=True)is accepted. Resetting only_speech_start_timewould leave other discarded-turn state behind.PeakSend has a version- and checksum-pinned 1.7.1 patch implementing and build-verifying this ordering:
Those links are in a private repository, but the complete reproduction timeline and patch boundary are included above.
Package Versions
The original incident was observed on 1.6.9 and confirmed against the 1.7.1 source.