Skip to content

fix(stt): send the turn commit when a flush yields no buffered audio - #6598

Open
chenghao-mou wants to merge 3 commits into
mainfrom
chenghao/fix/AGT-2264-elevenlabs-stt-commit-on-flush
Open

fix(stt): send the turn commit when a flush yields no buffered audio#6598
chenghao-mou wants to merge 3 commits into
mainfrom
chenghao/fix/AGT-2264-elevenlabs-stt-commit-on-flush

Conversation

@chenghao-mou

@chenghao-mou chenghao-mou commented Jul 29, 2026

Copy link
Copy Markdown
Member

AudioByteStream.flush() returns [] when its buffer happens to be empty, roughly 1 in 5 flushes with 10ms input frames repacked into 50ms chunks. Any finalize gated on for frame in frames: is then skipped, and has_ended leaks into the next utterance so the message fires mid-way through the following turn.

elevenlabs: Scribe v2 realtime never finalized at all without a commit, so send an empty commit: true chunk, hoisted out of the frame loop.
deepgram: hoist Finalize out of the frame loop (stt_v2 gets the same dedent for its metrics flush).

Tests: both plugins get coverage that drives the real SpeechStream._run loop against a fake socket, asserting the commit lands after a flush with an empty repack buffer (the regression) and after the leftover audio when a partial chunk is pending (ordering). Both fail with the fix reverted.

Scope: the default stt_node only calls push_frame, and the pipeline commits a turn by pushing silence, so nothing flushes a streaming STT stream in a normal AgentSession. Verified by patching RecognizeStream.flush/end_input across the unit suite: 0 calls from the voice pipeline. This hardens direct stt.stream() + flush() use; it does not on its own fix AGT-2264.

Gladia has the same shape and is deliberately left alone: stop_recording is session-terminating there (see gladia/stt.py:388), so making it fire reliably per turn would be worse than the current miss.

Related to AGT-2264 (#4087)

🤖 Generated with Claude Code

Scribe v2 realtime only finalizes a transcript once it receives a commit,
so a VAD-driven end of turn left the agent waiting on the server's own
(very late) commit. Send an empty commit=True chunk on flush.

Fixes AGT-2264

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chenghao-mou
chenghao-mou requested a review from a team as a code owner July 29, 2026 08:16

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

AudioByteStream.flush() returns no frames when the buffer is empty, so a
Finalize gated on the per-frame loop is skipped and has_ended leaks into
the next utterance. Hoist it out of the loop, matching rtzr and the
elevenlabs fix. stt_v2 gets the same dedent for its metrics flush.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chenghao-mou chenghao-mou changed the title fix(elevenlabs stt): commit the turn when the input stream flushes fix(stt): send the turn commit when a flush yields no buffered audio Jul 29, 2026
Drives the real SpeechStream._run loop against a fake socket and asserts the
commit/Finalize goes out after a flush, both when the repack buffer is empty
(the regression) and when a partial chunk is still pending (ordering).

Verified both fail with the fix reverted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant