Repository navigation
berd-call: capture calls and replay speech detection profiles - #426
Draft
johnmatthewtennant wants to merge 45 commits into
Draft
johnmatthewtennant wants to merge 45 commits into
johnmatthewtennant wants to merge 45 commits into
Conversation
Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
Co-authored-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz> Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
This was referenced Oct 22, 2026
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
With --capture and Apple recognition, vad.jsonl now has one row per 16 ms detector frame: its probability, threshold, mode, detection and gate output, placed in mic.wav by mic_sample (the sample just past the frame's audio, corrected for resampler lag), with settings records before the rows that use them. events.jsonl gains recognizer records for each generation and why it began or ended, since a new generation also resets the speech gate. Capture times no longer go backwards: producers on several threads can enqueue slightly out of order, so the writer never writes an earlier us than the last, gap records use the latest written time, and event frame counts come from the frames actually written. Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: John Tennant <jtennant@block.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Nora <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>
Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
…tures Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
…vation Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Add failure_fixtures and `vad-replay extract CAPTURE_DIR OUT_DIR --partition train|held-out`. Captures are cut into short clips around recorded fragments, interruptions, held replies and disagreements with human-checked labels. Findings are review hints, never labels. Clips never touch a gap, limit, lost mic frames, missing detector rows or trace beyond mic.wav, and they start only where a fresh replay gate equals the live one: the capture start, a recognizer or mute reset, or a quiet run whose bounds are recorded so candidates outside them can be rejected. Replay inputs are clip-relative, with a 48-sample-aligned source offset, a recut-stable source session and the partition in provenance. Speech labels are never truncated at onset, and label provenance is preserved. Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: John Tennant <jtennant@block.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Captures now record the default input and output device UIDs at session start, and fixture provenance carries them with an explicit session_start basis. learning_corpus::load reads fixture directories into one corpus per device pair, with index-aligned inputs, source sessions and start constraints for evaluate_grouped. Each fixture's metadata, replay input and decoded mic.wav length must agree with its provenance; captures without UIDs are rejected. A source whose fixtures overlap, mix partitions or mix setups is rejected whole. File sizes, fixture count and total bytes are bounded. Every corpus lists why it cannot authorize a live profile trial: the host can switch profiles mid-call, so a start-time setup does not establish the devices behind each clip. Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: John Tennant <jtennant@block.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
johnmatthewtennant
added a commit
that referenced
this pull request
Oct 22, 2026
…#425) ## Summary Prevent native status audio operations from blocking the microphone PCM loop. Keep cues on their five-second schedule, skip cues due during speech, and let a cue already playing finish. A Working/Waiting transition during speech is announced after speech ends. Status cues follow the existing mute-during-TTS setting, with Auto resolved against the selected output. Cue suppression pauses incoming PCM without discarding active speech or pending recognition. ### Related issue Supersedes #423. Optional VAD debug playback and its controls belong to #426; this PR does not advertise or implement them. ### Testing Retained regressions pass for skipped ticks without catch-up, active sample completion, deferred status announcements, microphone admission while an audio worker is stalled, recognition-epoch preservation and selected-output Auto policy. A compatibility regression loads preferences from a debug-enabled build, saves and reloads them, verifies unrelated nondefault values survive, and verifies the optional debug key is omitted. The rebuilt settings help omits the debug flag while retaining status and input-policy controls. The full package run still reports the previously observed Spokesperson timing/fixture, CLI usage and session-protocol failures. Normal push checks pass. Focused removal review reports no findings. Installed call and plugin files are unchanged. --------- Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz> Signed-off-by: Nora <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz> Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz> Co-authored-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz> Co-authored-by: Nora <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz> Co-authored-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
The host now sends AudioRoute records through the session's ordered input. input_opened names the microphone UID once its stream plays and before any of its frames are forwarded. output observes the default output UID and a CoreAudio change-notification count, before the first frame, on change and every 5 seconds, only when the menu bar's main run loop can deliver notifications. Extraction gives a clip the observed_route basis only within one input stream and between agreeing output observations, with no possible record loss overlapping that span. Other clips in routed captures are excluded. Output stays observational, so corpora remain diagnostic only; VAD_REPLAY.md records what still prevents trial eligibility. Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: John Tennant <jtennant@block.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
johnmatthewtennant
force-pushed
the
jtennant/quinn/call-learning-loop
branch
from
October 22, 2026 18:18
f0f55e5 to
5f7db75
Compare
Signed-off-by: Quinn <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
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>
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>
Buzz plays and records through the system default devices, which can change mid-call without the session hearing about it. A capture now polls the default input and output every 250 ms and writes a route event with their UIDs and names at start and on every change, so each clip can be attributed to the devices in use around it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Automatic tuning needs one entry point that combines replay acceptance with the corpus's trial eligibility, so callers cannot promote settings the CLI would report as diagnostic only. evaluate_corpus returns a CorpusEvaluation whose eligible_candidate is Some only when replay accepted a candidate, the corpus has no trial limitations, both device identities are known and the baseline is static. The CLI now reports eligibility through the same method. Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
…file trials An empty recognition result cannot tell noise from a recognizer failure, yet the live tuner raised the detecting mode's threshold on each one. A run of recognizer failures could therefore teach the detector to ignore speech. No-result outcomes are now counted but never adjust thresholds. detection_profiles gains prepare_trial and finish_trial. A prepared trial holds an eligible candidate in memory with the saved baseline it expects, and only a passed trial on the same devices with the applied settings promotes through the existing compare-and-save. Failed or abandoned trials leave the saved profile untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz>
johnmatthewtennant
force-pushed
the
jtennant/quinn/call-learning-loop
branch
from
October 23, 2026 03:26
d1237d5 to
18d00ed
Compare
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. Captures record each final's end and how many turns it resolved. Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`vad-replay events` now reports how long after detected speech each final transcript and each recognition hold release arrived. Across John's three recent captures, finals came a median 1.8 to 2.6 s and at p90 3.1 to 4.1 s after speech, while holds ran the full 5 s timeout. These distributions bound how short the pending-utterance timeout can safely be for a device. Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
…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 interim words exist past the latest final, the hold lasts up to ten seconds beyond the timeout, until their final arrives; noise with no interim words keeps the timeout. Empty finals now reach the host so they settle their audio. Captures record interim coverage (`heard`) and Apple's finalization point (`settled`). Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaying a 20-minute call held agents for the full fifteen seconds twice after finals that had already arrived. Interim results end where Apple's audio does, not at the last word, so they ran past the final's end by more than the coverage slack and looked unfinalized. Apple re-reports words a final leaves out as a fresh interim result, so the hold now waits only while its latest result is interim words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
Linux and Windows clippy flagged `heard` and `unfinalized` as never read, because only the macOS recognizer reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
… call Make the interim-words extension a setting (`interim_words_wait_ms`, default 10000, replacing the hard-coded 10 s grace) and let four more parameters learn online, each behind its own opt-in that defaults to false so saved profiles load unchanged. A learned value is saved to the detection profile with the thresholds. Every parameter uses one rule: an error steps it by U toward fixing the error, and every Kth success since its last step steps it back by U, so it settles near one error in K + 1 outcomes. Steps stay inside the learning bounds and a value outside them never moves. - pending timeout (+250 ms, 2000..8000, K 19): a final with words arrives after the hold expired on the idle timeout; success is a final that resolves the hold before it expires. - interim words wait (+500 ms, 2000..20000, K 19): a final arrives after the hold expired during the extension; success is a final that resolves it within the extension. - VAD release (+32 ms, 160..1500, K 19): one final that resolves several ended turns has an agent start inside its words, read from the first word start Apple now reports before each final; success is such a split final the agent did not jump into. - thresholds: keep the missed-detection step down, and step 0.01 back up after 99 detected-speech transcripts per mode. Expose the opt-ins as `--dynamic-pending-utterance-timeout`, `--dynamic-interim-words-wait` and `--dynamic-release`, the wait as `--interim-words-wait-ms`, and per-parameter adjustment counts in the detection snapshot. `DetectionTuner::without_drift` keeps errors only, for replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
johnmatthewtennant
force-pushed
the
jtennant/quinn/call-learning-loop
branch
from
October 23, 2026 13:37
6eec741 to
205d410
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>
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>
johnmatthewtennant
force-pushed
the
jtennant/quinn/call-learning-loop
branch
from
October 23, 2026 19:46
d5af7d3 to
47000de
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Henry <a0d9f90f86da5f023099d22b8e3599af162d9423a422d8f385a12815de879cf2@buzz.block.builderlab.xyz>
This branch has not been deployed
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 needs recordings and repeatable measurements to tune speech detection from daily use. This adds device-specific listening and interruption profiles, opt-in local call capture, offline replay and evaluation of detection settings, and fixes to the speech hold that keeps agents from talking over the user.
Capture.
berd-call start --capture DIRrecords microphone audio, sent agent audio, timestamped protocol events, per-frame VAD decisions, recognizer generations, the default input and output devices (including changes mid-call), and each Apple final's audio end with how many speech turns it resolved.Replay and evaluation.
berd-call vad-replay events CAPTURE_DIRmeasures request waiting against VAD and pending recognition, and the delay from the end of detected speech to each final transcript and each hold release.convert,extractandevaluateturn captures into grouped fixtures and compare static candidates against a baseline on held-out sessions.learning_evaluation::evaluate_corpusexposes anEligibleCandidateonly for corpora with exact device evidence; today's captures remain diagnostic (trial_ineligible), so nothing is promoted automatically.detection_profiles::prepare_trialandfinish_trialkeep the saved baseline until a trial passes on the same devices and settings.Speech hold. Agent speech waits while the user speaks and while Apple still owes a final. Previously each VAD turn added a pending credit and each final resolved one, so a sentence split by pauses left credits outstanding and nearly every hold ran the full pending-utterance timeout (55 of 57 holds across two recorded calls). The native bridge now passes each final's audio end, and a final resolves exactly the ended turns it covers; a final for an earlier phrase of a still-pending turn resolves nothing. Apple can take seven seconds or more to finalize a second sentence. So the bridge also reports Apple's interim results, and while its latest result is interim words rather than a final, the hold lasts up to ten seconds past the timeout. Noise Apple never transcribes keeps the timeout. When the hold does expire, recognition keeps running and late finals are still published. Empty recognition results no longer raise detection thresholds.
Related issue
Consolidates #421, #422 and #424. No related issue found.
Testing
cargo test -p berd-callpasses locally on macOS at e173655 (400 library and 284 binary tests plus every integration target), including regressions for multi-turn and split-phrase finals, holds that wait for interim words and holds that keep their timeout once a final follows them, cancellation while live playback waits for provider audio (with buffered tails of 1 and 4095 samples), and capture route recording.cargo test -p berd-call --lib final_end_is_measured_on_the_pushed_audio_timeline -- --ignoredruns real on-device SpeechTranscriber oversayoutput with 1 s and 3 s of leading silence. Final ends shift by the lead within 40 ms and land about 0.3 s after the last sound, which confirms finals report the same timeline the session pushes.vad-replay eventsshows finals arriving a median 1.8 to 2.6 s and at p90 3.1 to 4.1 s after speech ends, while holds ran 5.0 s.Automatic promotion from captured calls is not implemented: captured corpora lack verified playback provenance and labels, so
evaluate_corpusreturns no eligible candidate.