Repository navigation
Berd Call: clear stuck speech holds, show interim words, playback cancel fixes - #429
Merged
Merged
Conversation
Cancellation revoked output authority and notified output waiters, but a live playback worker could already be waiting for its next provider message. Queue Finish as well so the existing cancellation path can serialize Cancel without another provider event. A full input queue already gives the worker work that observes the revoked authority. The regression synchronizes after one chunk and checks cancellation with no tail, one buffered sample and a nearly full partial chunk. Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> (cherry picked from commit 80b97e4)
The playback worker checks the cancellation flag under the state lock and then waits on the condvar. A cancel request that stores the flag and notifies between those two steps is lost, so the worker sleeps until its operation timeout. This reproduces the CI timeout in autonomous_spokesperson_cancel_is_correlated_before_audio_cancellation_and_then_stale. Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit f6097e5)
notify_cancel_requested stored the flag and notified without the state lock, so a worker that had checked the flag but not yet waited missed the wake and slept until its operation timeout. Taking the lock before notifying orders the wake after any such check. Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 14943c9)
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> (cherry picked from commit 6b5afdf)
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> (cherry picked from commit 46fdf60)
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> (cherry picked from commit 7121469)
The idle timeout after local speech ended used to cancel the native recognizer and start a new generation, discarding Apple's queued audio and any final still on its way. A slow recognition therefore lost its transcript, and a final the recognizer had already produced could not release a newer hold. Expiry now releases only the agent speech admission hold. Recognition, its queued audio and the recognizer generation continue, so a late final is still forwarded. Native finals have no utterance identity, so hold credits are tracked separately from unresolved recognitions: an expired credit is consumed before a newer blocking one, and a final for an old utterance cannot release a newer hold. Credit accounting is capped at 1024 and stays ambiguous until the recognizer resets. The pending-utterance timeout is the existing fixed five seconds. Ported from 2eda991 in #426 without the detection settings, CLI flag and protocol field it added there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
Each VAD turn adds a pending recognition credit, but SpeechTranscriber emits one final for a sentence that pauses split into several turns. Resolving one credit per final left the rest outstanding, so in John's recent calls 55 of 57 holds ran the full pending-utterance timeout even though Apple had finished 0 to 4 seconds after he stopped. Pass each final's audio end through the native bridge and resolve exactly the ended turns whose speech it covers. A final for an earlier phrase of a still-pending turn resolves nothing, so the rest of the sentence keeps the hold. Finals without an end keep the old behaviour. Ported from 43efb0e in #426 without its capture hooks. A turn's end is the pushed audio minus the host's silence flush window, since the host declares speech over after that much silence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
…inalize The hold lifted at the pending-utterance timeout even when Apple was still transcribing. On a live call a second sentence took about seven seconds to finalize, so an agent's reply to the first sentence started playing and was then cut off by the late transcript. The bridge now reports Apple's interim results. While Apple's latest result is interim words, the hold lasts up to ten seconds beyond the timeout, until a final follows them; noise with no interim words keeps the timeout. Apple re-reports words a final leaves out as a fresh interim result, and interim results end where its audio does rather than at the last word, so the hold follows result order, not sample positions. Empty finals now reach the host so they settle their audio. Ported from 09ee43f, e173655 and 4d2f034 in #426 as their final combined behavior. The ten-second grace is a fixed constant, and the capture-only `heard` and `settled` records are left out with the capture infrastructure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
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. Ported from 7b28c3e in #428, re-applied on the speech-hold commits without the capture trace calls that branch carried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@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> (cherry picked from commit e347e12)
The call detected the user's speech at 0.5 while listening and 0.65 during agent playback. One threshold is simpler to reason about and to tune, so both now use 0.9, the value John converged on in live calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
Apple can finalize a complete utterance during the 1.2-second silence window, before the detector records the turn's end. That final could not cover a turn still in progress, and its coverage was then lost, so the hold waited the full five-second timeout. Remember the furthest final and resolve the turns it covers when a turn ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
johnmatthewtennant
marked this pull request as ready for review
October 23, 2026 18:22
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
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.
Interim words. Conventional macOS sessions emit
user_interim(--streamcarries it as an ephemeral0\tuser_interimrow) 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
80b97e4c2in #426 (cherry-pick)f6097e517in #426 (cherry-pick)14943c91ein #426 (cherry-pick)6b5afdf00in #426 (cherry-pick)46fdf603bin #426 (cherry-pick)71214694ein #426 (cherry-pick, conflict resolved by dropping the capture-only replay test)2eda9918cin #426 (ported)43efb0e00in #426 (ported)09ee43fd9,e173655ac,4d2f03488in #426 (ported, final combined behavior)7b28c3ecdin #428 (ported)e347e12a6in #428 (cherry-pick)The ports differ from #426 only where #426 depended on infrastructure this branch leaves out:
DetectionSettingsfields, CLI flags or a protocol bump.heardandsettledrecords are gone, along with the detection-evidence handling in finals (result_is_currentstill gates hold accounting).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
mac_speech::tests::final_end_is_measured_on_the_pushed_audio_timelinepasses, so a final's end is on the same pushed-audio timeline the VAD turns use. That is what makes the coverage rule sound.mac_final_before_its_turn_ends_releases_the_hold_when_it_doesfails 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.session_protocolregressions run the realberd-callchild 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.