Repository navigation
berd-call: stream the user's words while they are transcribed - #428
Closed
johnmatthewtennant wants to merge 2 commits into
Closed
johnmatthewtennant wants to merge 2 commits into
johnmatthewtennant wants to merge 2 commits into
Conversation
johnmatthewtennant
force-pushed
the
jtennant/henry/call-interim-caption
branch
from
October 23, 2026 12:37
d4d7e28 to
e347e12
Compare
Hosts could only show what the user said once Apple finalized it, often seconds after they stopped talking. Conventional sessions on macOS now emit `user_interim` with Apple's interim text, replaced by each update and cleared when its final arrives, on mute, input reset or restart. `--stream` carries it as an ephemeral `0\tuser_interim` row whose JSON gives the text and the cursor the next `user` row will take. A client can show the words as a caption, or let an agent start on them and keep its reply current when the final lands. The row never advances a cursor or enters transcript history, and Codex tasks never see it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Berd's native voice handler matches every voice input event, so the new interim transcript event broke its build. Berd doesn't show interim words yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
johnmatthewtennant
force-pushed
the
jtennant/henry/call-interim-caption
branch
from
October 23, 2026 13:39
e347e12 to
6174276
Compare
johnmatthewtennant
added a commit
that referenced
this pull request
Oct 23, 2026
…cel fixes (#429) ## Summary Berd Call kept holding the agent's reply after the user had finished, and showed the user's words only once Apple finalized them. This collects the release-safe fixes from the call learning work, without its capture, replay or tuning infrastructure. **Stuck speech holds.** The hold that keeps an agent from speaking over the user is released by Apple's finals, but Apple emits one final for a sentence that pauses split into several VAD turns. Resolving one turn per final left the rest outstanding, so in recent calls 55 of 57 holds ran the full 5 s timeout even though Apple had finished 0 to 4 s after the user stopped. Expiry also cancelled the recognizer, throwing away a transcript still on its way, and the hold lifted at the timeout while Apple was still transcribing, so a reply started and was then cut off by the late transcript. - Expiry releases only the hold. Recognition, its queued audio and the recognizer generation continue, so a late final still reaches the conversation. - Each final carries the end of its audio through the Swift bridge and resolves exactly the ended turns it covers. A final that arrives during the silence window, before its turn ends, resolves that turn when it ends. A final for an earlier phrase of a turn still being spoken resolves nothing. - The bridge reports Apple's interim results. While Apple's latest result is interim words, the hold lasts up to 10 s beyond the 5 s timeout, until a final follows. Noise with no interim words keeps 5 s. **Interim words.** Conventional macOS sessions emit `user_interim` (`--stream` carries it as an ephemeral `0\tuser_interim` row) with Apple's interim text, replaced by each update and cleared on its final, mute, input reset or restart. It is never stored, never takes a token and never interrupts output. **One VAD threshold.** Listening and interruption during agent playback use the same speech probability, 0.9, instead of separate thresholds. **Playback and cancel fixes.** A cancel wake could be lost between a playback worker's flag check and its wait, idle live playback did not wake on cancellation, and handoff terminal failures lost their order. ### What came from where | Commit | Source | | --- | --- | | wake idle live playback on cancellation | `80b97e4c2` in #426 (cherry-pick) | | cover a cancel wake racing the flag check (test) | `f6097e517` in #426 (cherry-pick) | | order cancel wakes after the flag check | `14943c91e` in #426 (cherry-pick) | | preserve ordered handoff terminal failures | `6b5afdf00` in #426 (cherry-pick) | | tolerate audio replies after child shutdown (test) | `46fdf603b` in #426 (cherry-pick) | | validate partial audio EOF after intentional disconnect (test) | `71214694e` in #426 (cherry-pick, conflict resolved by dropping the capture-only replay test) | | expire speech holds without cancelling recognition | `2eda9918c` in #426 (ported) | | release the speech hold when a final covers its turns | `43efb0e00` in #426 (ported) | | keep the speech hold while Apple still has words to finalize | `09ee43fd9`, `e173655ac`, `4d2f03488` in #426 (ported, final combined behavior) | | stream the user's words while they are transcribed | `7b28c3ecd` in #428 (ported) | | ignore interim words in native voice conversations | `e347e12a6` in #428 (cherry-pick) | The ports differ from #426 only where #426 depended on infrastructure this branch leaves out: - The pending-utterance timeout is a fixed 5 s constant and the interim grace a fixed 10 s, not `DetectionSettings` fields, CLI flags or a protocol bump. - Capture hooks and the capture-only `heard` and `settled` records are gone, along with the detection-evidence handling in finals (`result_is_current` still gates hold accounting). - A turn's end is the pushed audio minus main's 1.2 s silence flush window, where #426 subtracted its configurable `release_ms`. ### Left in #426 Capture and replay, VAD debug cues, failure fixtures, speech-profile evaluation, and the learning and tuning work (dynamic timeouts, thresholds and release). ### Related issue Follows #426 and #428. None found otherwise. ### Testing - Real Apple recognition: the ignored on-device test `mac_speech::tests::final_end_is_measured_on_the_pushed_audio_timeline` passes, so a final's end is on the same pushed-audio timeline the VAD turns use. That is what makes the coverage rule sound. - Hold release, red/green: `mac_final_before_its_turn_ends_releases_the_hold_when_it_does` fails before the coverage fix (the turn stays pending until the 5 s timeout) and passes after it. Companion tests cover a final resolving every turn its audio covers, an earlier-phrase final keeping the hold, the hold waiting for interim words and returning to 5 s once a final follows, and late finals surviving hold expiry. - Interim words: tests cover the stream through mute, reset and restart, and the interim token matching the next final. - Playback cancellation: the cherry-picked `session_protocol` regressions run the real `berd-call` child against a scripted audio host. Not yet exercised on a live call: the end-to-end hold timing and interim captions with a real microphone. --------- Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz> Co-authored-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> Co-authored-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
johnmatthewtennant
pushed a commit
that referenced
this pull request
Oct 23, 2026
Main now carries the speech-hold, interim-word and playback-cancel fixes from #429, which this branch and #428 already had in their own form, plus the Swift link-path fix from #430. Conflicts take this branch's versions, with #428's interim words and the final_end coverage fix layered on, so the configurable and learned thresholds stay in place of #429's single fixed VAD threshold. The swiftbuild link-path change from #430 is kept as is. The merged tree matches the combined build that passed the full berd-call suite, plus the two build.rs changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
johnmatthewtennant
pushed a commit
that referenced
this pull request
Oct 23, 2026
Main now carries the speech-hold, interim-word and playback-cancel fixes from #429, which this branch and #428 already had in their own form, plus the Swift link-path fix from #430. Conflicts take this branch's versions, with #428's interim words and the final_end coverage fix layered on, so the configurable and learned thresholds stay in place of #429's single fixed VAD threshold. The swiftbuild link-path change from #430 is kept as is. The merged tree matches the combined build that passed the full berd-call suite, plus the two build.rs changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
During a Berd Call, what you say shows up only once Apple finalizes it, often a few seconds after you stop talking. This streams Apple's interim words to hosts so they can show them as you speak, or let agents start on them early.
Conventional macOS sessions now emit a
user_interimsession message with Apple's interim text. Each update replaces the previous one, and empty text clears it. Itstokenis the token the next final will take, so a reply composed against the interim words is still current once they finalize. It's never stored, never allocates a token, and never interrupts output.start --streamcarries it as an ephemeral row likeinput_activity:0\tuser_interim\t{"cursor":12,"text":"you should"}. The row never advances a speech cursor or enters transcript history, and Codex tasks never see it. It clears on the final, mute, input reset and session restart. Berd's own native voice conversations ignore the event for now.The Buzz plugin already handles these rows (squareup/jtennant-agent-config
6885f90,6c94934). It shows a caption above the composer and has an experimental setting that wakes agents with the interim words when you pause. A reply to that wake is spoken with the interim cursor, so it plays once the words are final instead of being refused as stale.Stacked on #426.
Testing
cargo test -p berd-callpasses on macOS, including new tests for the interim token matching its final, and for interim rows that show, clear, stay hidden while muted, and stay off for silent and Expert–Spokesperson transcripts.just clippypasses.🤖 Generated with Claude Code