fix(desktop): end an on-device transcription window when the speaker pauses - #12181
Conversation
…pauses The wake word's latency was not the cloud ambient lane. On Apple Silicon `STTSessionState.resolveMode` picks the on-device Parakeet path by default, and `LocalTranscriptionService` only closed a window on a fixed 10s boundary — so a spoken command waited for wherever it happened to land in that window. Measured live on a MacBook Air, real microphone, identical utterances: before 1.08s / 6.18s / 7.65s (~5s expected, ~10s worst case) after 0.66s / 0.76s / 0.82s / 0.89s / 0.89s / 1.04s Three changes, all in the on-device path: - A window closes early once the buffer holds a second of voiced audio followed by 0.6s of quiet. The minimum is measured in voiced samples, not buffer length: a mostly-quiet window with one blip in it is what Parakeet answers with a hallucinated word (a 1.1s window at rms 0.0067 decoded to "Yeah."). - Silence ahead of the first speech is dropped, advancing the emitted-seconds cursor over it so absolute timestamps stay exact. Otherwise quiet counted against the 10s cap and the window filled partway through the next sentence — observed cutting "Omi, what time is it now" down to "Now". - The pump ticks at 0.5s rather than 1s, since it is now the floor on how soon a finished utterance can be transcribed rather than a poll for a full window. The 10s cap is unchanged and still bounds continuous speech. Verification: - xcrun swift test --package-path Desktop --filter 'LocalTranscriptionEndpointTests|LocalTranscriptionDuplicatePolicyTests|WakeWord' -> 42 tests, 0 failures - Eight timed utterances through the real microphone on a named dev bundle, wake word enabled, on-device Parakeet: every one dispatched, full text, in 0.66-1.04s from the end of speech. Failure-Class: none Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The four red checks here are not this diff. This PR is
Reproduces on a pristine checkout of I have the one-line fix but am deliberately not sending it: on its own it turns a broken-manifest error into a red credential check on every PR, which is worse. #12182 has the detail and the two honest resolutions, both of which need someone who owns that area. This PR's own content is verified independently of CI: |
…12183) Reverts #11125. The ratchet shipped with two defects that compound, and the first hid the second (see #12182). It declared backend/agent-proxy/main.py and backend/agent-proxy/Dockerfile as trigger paths and neither exists. run_checks.py:187 existence-checks any trigger without glob magic and fails the whole manifest resolution when one is missing, before any check runs -- so this broke every consumer of the manifest, not the ratchet alone. #12181 touches three desktop Swift files and no backend, and lost Detect Desktop Swift Changes, Desktop Swift Build & Tests, Hygiene and PR Metadata Preflight to it. Making resolution succeed only moves the failure: the ratchet then runs for the first time and does not pass its own baseline, on pristine main, with 13 findings. Twelve are stale-shrink entries -- several for backend/agent-proxy/*, the same paths that do not exist. One is not: backend/database/google_credentials.py:service-account-json-env reports found 6 against a cap of 1, on a file main has not modified, so the cap was miscounted when written rather than something having regressed. --write-baseline never raises caps by design, so that one cannot be corrected with the tool; it needs either real key-path removal or a baseline edit under security review. Reverting costs nothing that currently works: the check has never executed, so no key path is being protected by it today. It can be re-landed with a baseline generated against this tree and backend/agent-proxy/** as the trigger. Deliberately kept: the Backend_Setup.mdx change, restored verbatim. It steers local setup to ADC instead of copying application_default_credentials.json into the repo as google-credentials.json. That improvement has no CI dependency, and reverting it would put the copy-a-key-into-the-repo instructions back. Failure-Class: none
|
Verified the premise first:
The four red checks are not this diff. Confirmed independently: Splitting this out of #11801 was also correct — pause endpointing applies to every on-device session, with or without the wake word. What's left for a maintainer is judgment rather than correctness: 0.6 s tail / 1.0 s min-utterance are single-room, single-voice tuned constants on the default transcription path, and the disclosed trade-off — shorter windows surface Parakeet's quiet-window single-word hallucinations more often — is a product-behavior call. Merge will stay blocked until the #12182 manifest fix lands so CI can actually run these tests. by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
kodjima33
left a comment
There was a problem hiding this comment.
Desktop bug-fix fast lane (owner-override) — well-verified pause-endpointing fix for on-device transcription. Holding merge: PR Metadata Preflight fails on a stale frozen merge-ref referencing a since-deleted backend/agent-proxy path (gcp-sa-key-ratchet check) — unrelated to this PR's Swift-only diff, matches the known frozen-merge-ref CI pattern. Reruns won't clear it; needs a branch update to pick up current main.
…on-pause-endpointing
kodjima33
left a comment
There was a problem hiding this comment.
Desktop bug fix: pause-based endpointing cuts on-device transcription latency from ~5s avg/10s worst-case to <1s, with 13 pinned tests. CLEAN, all CI green (PR Metadata Preflight now passes — prior frozen-ref block cleared). Confirmed unfixed on main. Owner-override merge.
Summary
On Apple Silicon,
STTSessionState.resolveModeselects the on-device Parakeet path by default for microphone and system audio — cloud STT is the fallback, not the default. So ambient transcription runs throughLocalTranscriptionService, which only closed a transcription window on a fixed 10-second boundary:An utterance therefore waited for wherever it happened to land inside that window. Uniform arrival means ~5 s expected and ~10 s worst case, for speech that finished seconds earlier.
This closes a window when the speaker pauses instead. The 10 s cap is unchanged and still bounds continuous speech.
What changed
Three changes, all inside the on-device path:
Measured
Real microphone, named dev bundle, on-device Parakeet, MacBook Air. Latency is speech-end wall clock to the transcription log line.
Before — fixed 10 s window, three identical utterances:
[90.0s–100.0s][120.0s–130.0s][140.0s–150.0s]Identical speech, 1.08 s vs 7.65 s — the only variable is where the utterance fell inside the window. Every window is exactly 10.0 s wide and boundary-aligned, which is the tell.
After — 14 utterances across two builds:
The spread collapsing from 6.6 s to 0.5 s is the part that matters: transcription arrives at the same moment every time instead of anywhere in a ten-second range.
Tests
LocalTranscriptionEndpointTests— 13 new cases pinning each half of the rule in both directions: speech-then-pause closes, speech-still-running does not, a pause shorter than the tail does not, pure silence never drains, a blip in a mostly-quiet window does not, voiced audio accumulates across short gaps, room tone under the noise floor reads as a pause, leading silence is trimmed with lead-in kept, a buffer opening with speech is not trimmed, and a pause between two utterances is never trimmed.Verified on this branch, which contains no other change:
xcrun swift build -c debug --package-path Desktop— cleanLocalTranscriptionEndpointTests+LocalTranscriptionDuplicatePolicyTests— 18 tests, 0 failuresWhy this is its own PR
Split out of #11801 (wake word) at the reviewer's request. Pause endpointing applies to every on-device transcription session, whether or not anyone enables the wake word, so it should not wait on a product decision about an opt-in toggle. It stands alone: this branch is
mainplus one commit, and the endpointing tests pass on it with no wake-word code present.Honest gaps
resolveModesends.bleDeviceto the cloud path, untouched here.Failure-Class: none