Skip to content

refactor(desktop): coordinate TTS playback - #6341

Merged
tlongwell-block merged 2 commits into
mainfrom
wren/vad-playback-coordinator
Aug 20, 2026
Merged

refactor(desktop): coordinate TTS playback#6341
tlongwell-block merged 2 commits into
mainfrom
wren/vad-playback-coordinator

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

Summary

  • introduce a PlaybackCoordinator as the exclusive owner of the rodio Player
  • serialize append, cancellation, synthesis-flight, and drain-release state
  • cancel without blocking the coordinator by replacing the player queue
  • preserve first-append/onset bookkeeping across cancellation
  • increase the existing playback lead-in from 20 ms (480 samples) to a derived 22 ms (528 samples), covering rodio 0.22.2's 512-source-sample add-time bootstrap span

This is the behavior-preserving coordinator prerequisite for the follow-up floor-authorization policy. The only intentional playout difference is +2 ms silence at playback-chunk starts to prevent the measured fresh-player bootstrap chirp.

Verification (all at this exact head, 8bfa4c0531834dce39f5ad0266f2674505e659bf)

  • cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warnings — clean
  • cargo test --manifest-path desktop/src-tauri/Cargo.toml --package buzz-desktop — 2,596 passed / 17 ignored / 0 failed
  • coordinator concurrency tests: stalled cancellation, append-vs-cancel serialization, cancel election, first-append rearming, and the post-swap starts_playback_chunk invariant
  • headless known-answer sweep: 528-sample lead-in clean across 7/7 device configurations (incl. falsifier arm at 192k/96k/32k/16k/8k and 1/2/4/6 ch); current 480 dirty across 5/7
  • live CoreAudio gate at production buffer shape (24 kHz mono, real build_sentence_append_buffer path): bootstrap chirp eliminated (660.1 Hz head vs 660 Hz control), no false drain, cancel-to-silence ~1 ms vs 12 ms baseline
  • independent line review + mutation testing on the shipping coordinator bytes: 4/6 mutants killed, 2 shown equivalent under rodio 0.22.2 Player::drop semantics

Playout cost

  • +2 ms lead-in at each playback-chunk start versus main
  • first post-cancel utterance receives 22 ms total intentional silence (not stacked cushions)

Provenance

Authored and reviewed by Buzz agents (Wren: implementation; Dawn: line review, known-answer control, mutation testing; Max: live CoreAudio gates; Eva: coordination and final verification), with review records on the Buzz relay. This PR was briefly merged to main directly by mistake and reverted via ref reset; this is the same commit going through the front door.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
@tlongwell-block
tlongwell-block requested a review from a team as a code owner August 19, 2026 18:49
…tions

The playback coordinator queued audio and replaced players under its own
lock, but the mic-gate flag those transitions imply (`tts_active`) was
published outside it. Append and cancellation could therefore interleave
in either direction and leave the gate disagreeing with what is audibly
playing:

- a cancellation winning after an append committed left a stale `true`,
  so the mic stayed gated with nothing playing;
- an append winning the lock handoff after a replacement had a late
  `false` land on top of it, ungating the mic for the whole utterance and
  feeding our own TTS back to VAD as a barge-in.

`append_if` and `cancel_if_live` now each take a `commit` callback that
runs under the coordinator, after the queue mutation and before release.
Both directions are one transition again, matching the pre-refactor
behavior where `_ops` was held across the flag write. The old player is
still dropped outside the lock, so rodio teardown does not extend the
critical section.

`release_playback` keeps the unconditional clear on the not-live path:
publishing only inside the replacement would drop the flag write when
there is nothing live to replace.

Also removes `first_append` from the `prepare_audio` chain. It was
written but never read there, and clearing it during preparation
recorded an append that authorization might still refuse. It is now
coordinator-private: cleared by `append_if` on the accepted path only,
rearmed by `cancel_if_live` and `release_if_drained`.

Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
@tlongwell-block
tlongwell-block merged commit 09718fb into main Aug 20, 2026
44 of 48 checks passed
@tlongwell-block
tlongwell-block deleted the wren/vad-playback-coordinator branch August 20, 2026 01:26
jedwards27 pushed a commit to jedwards27/buzz that referenced this pull request Aug 20, 2026
* origin/main:
  feat(desktop): make the Projects overview follow the selected section (block#6335)
  refactor(desktop): coordinate TTS playback (block#6341)
  fix(desktop): show complete repository trees (block#5102)

Signed-off-by: Carl <5f365698229751c0461f57bb03a4e93134e6e936bd7039ebe7b737282a43c754@buzz.block.builderlab.xyz>
funge added a commit to funge/buzz that referenced this pull request Aug 20, 2026
…-shortcut

* origin/main: (341 commits)
  feat(desktop): make the Projects overview follow the selected section (block#6335)
  refactor(desktop): coordinate TTS playback (block#6341)
  fix(desktop): show complete repository trees (block#5102)
  Add appearance preference previews (block#6193)
  fix(desktop): restore emoji recents (block#6263)
  chore: serialize mobile pre-push checks (block#6322)
  fix(buzz-acp): loosen workspace-scan guardrail to allow named paths (block#6261)
  fix(buzz-dev-mcp): expand leading ~ in read_file/str_replace paths (block#6271)
  perf(desktop): move five hot renderer paths from JS into Rust (block#6024)
  fix(media): accept portrait video resolutions (block#6058)
  fix(desktop): hide archived channels from #/Tab autocomplete (block#6156)
  Unify mobile channel details (block#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (block#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (block#6306)
  feat(desktop): refine repository-aware project workspaces (block#6003)
  Fix mobile Activity thread navigation (block#5850)
  perf(desktop): parallelize relay agent directory rebuild (block#6258)
  Refine the mobile emoji picker (block#5853)
  fix(desktop): exclude archived agents from nest, order regeneration (block#5905)
  Add font size and conversation density preferences (block#5644)
  ...

Signed-off-by: John Funge <funge@squareup.com>

# Conflicts:
#	desktop/src/features/messages/ui/MessageComposer.tsx
#	desktop/src/features/settings/ui/KeyboardShortcutsCard.tsx
wpfleger96 pushed a commit that referenced this pull request Aug 20, 2026
…as-aware-labels

* origin/main:
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
tellaho added a commit that referenced this pull request Aug 20, 2026
…ress

* origin/main:
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  fix(desktop): show complete repository trees (#5102)
  Add appearance preference previews (#6193)
  fix(desktop): restore emoji recents (#6263)
  chore: serialize mobile pre-push checks (#6322)
  fix(buzz-acp): loosen workspace-scan guardrail to allow named paths (#6261)
  fix(buzz-dev-mcp): expand leading ~ in read_file/str_replace paths (#6271)
  perf(desktop): move five hot renderer paths from JS into Rust (#6024)
  fix(media): accept portrait video resolutions (#6058)
  fix(desktop): hide archived channels from #/Tab autocomplete (#6156)
  Unify mobile channel details (#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>

# Conflicts:
#	desktop/src/features/messages/ui/MentionAutocomplete.test.mjs
morgmart added a commit that referenced this pull request Aug 20, 2026
…handoff

* origin/main:
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  fix(desktop): show complete repository trees (#5102)
  Add appearance preference previews (#6193)
  fix(desktop): restore emoji recents (#6263)
  chore: serialize mobile pre-push checks (#6322)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
wpfleger96 pushed a commit that referenced this pull request Aug 20, 2026
…el-directory

* origin/main:
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)

Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
wpfleger96 added a commit that referenced this pull request Aug 20, 2026
…el-directory

* origin/main:
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
atishpatel added a commit that referenced this pull request Aug 20, 2026
…ounting-and-solo

* origin/main: (48 commits)
  fix(hooks): scope pre-push lanes to branch merge-base diff (#6423)
  Enforce a three-day dependency cooldown (#6426)
  perf(desktop): resolve references without directory scans (#6328)
  feat(llm): stamp thinking effort on call-completed log line (#6424)
  Fix cross-owner relay agent mentions in owner-only builds (#6338)
  feat(cli): accept Buzz message links for thread reads (#6359)
  feat(workflows): add workflow editor (#6248)
  fix(desktop): preserve huddle speech boundaries (#6397)
  test(desktop): use a wordlist-safe separator in passphrase word-count test (#6356)
  fix(models): curate Databricks alias-aware labels for 5 missing endpoints (#6360)
  fix(acp): guard against unrequested public relay skills (#6394)
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  ...

Signed-off-by: Atish Patel <atishpatel2012@gmail.com>
brow added a commit that referenced this pull request Aug 20, 2026
…ifications-pr

* origin/main: (33 commits)
  fix(hooks): scope pre-push lanes to branch merge-base diff (#6423)
  Enforce a three-day dependency cooldown (#6426)
  perf(desktop): resolve references without directory scans (#6328)
  feat(llm): stamp thinking effort on call-completed log line (#6424)
  Fix cross-owner relay agent mentions in owner-only builds (#6338)
  feat(cli): accept Buzz message links for thread reads (#6359)
  feat(workflows): add workflow editor (#6248)
  fix(desktop): preserve huddle speech boundaries (#6397)
  test(desktop): use a wordlist-safe separator in passphrase word-count test (#6356)
  fix(models): curate Databricks alias-aware labels for 5 missing endpoints (#6360)
  fix(acp): guard against unrequested public relay skills (#6394)
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  ...

Signed-off-by: Tom Brow <tomb@block.xyz>
tlongwell-block added a commit that referenced this pull request Aug 21, 2026
## Problem

Humans cannot interrupt agents in huddles: agent TTS keeps playing over
a talking human (reported by @tlongwell-block, 2026-08-20). The full
requirement: **any human talking — local or remote, any input mode, any
audio rig — must interrupt any agent on the huddle.**

## This is a restoration, not a new feature

- **`b29c8cdaa` (#4281, 2026-08-04) deleted working local VAD
barge-in.** The pre-image shipped `BARGE_IN_DEBOUNCE_FRAMES = 20` (320
ms sustained speech cancels TTS), live at the production call site
(`pipeline.rs` passed `Some(tts_cancel)` unconditionally). The same day,
`ce3cf3cd2` (#4694) flipped the default input mode from VAD to
push-to-talk, which masked the loss.
- **`068a83b09` (#5671, 2026-08-13) removed the remaining TTS-awareness
plumbing from STT** (deliberately, to keep transcribing over agent audio
— a good change). Consequence: this PR is a re-plumb through the
`PlaybackCoordinator` from #6341, not a revert.
- A gap that **never** worked is also closed: PTT-mode users who open
the mic via the mute button (`manual_mic_unmuted` postdates the
deletion) transcribed fine but could not barge in.

## Design

All floor state lives under the single `PlaybackCoordinator` lock —
onset acceptance, epoch bump, synthesis invalidation, player
replacement, and output lease are one committed transition. No cancel
flag can be observed out of order with the playback state it describes.

- **Human floor**: local + per-peer remote ownership with epoch
invalidation. Onset cancels playback by queue replacement; late
synthesis for a stale epoch cannot append or restart.
- **Local onset (VAD)**: on an **isolated output route** (all CoreAudio
output-stream terminals report headphones), a confirmed short onset
interrupts immediately — no echo path exists. On a **coupled route**
(speakers, unknown, virtual, mixed), the restored **20-frame / 320 ms
sustained-speech debounce** discriminates a real human from speaker
bleed; the deleted code's comment records that 80 ms was tried and
false-triggered on laptop speakers. Route classification is queried
fresh at each onset (never cached — default-device re-routing mid-huddle
would strand a stale verdict).
- **Mic-open gate is per-frame**: barge-in observes on any frame where
the mic is actually open — pure VAD mode, or PTT with the mic manually
unmuted (key-held frames defer to the shortcut's own cancel).
- **Remote onset**: sustained non-DTX frames from a peer enter the same
persistent floor (independent of whether playback is live — a human
speaking while TTS is idle blocks late-arriving synthesis from starting
over them). Release on sustained DTX/absence, peer departure, and
recv-loop exit, with guards so a vanished peer cannot wedge the floor.
- **Output lease**: accepted appends renew an `Active` lease;
drain/cancel/onset start a 100 ms tail hangover (conservative against
measured ~12 ms/~1 ms CoreAudio tails), so speaker-tail bleed in the
just-drained window cannot self-trigger the coupled path.

## Known limitations (phase 2 pointers)

- Coupled-route mid-output barge-in pays the 320 ms debounce; a
playback-reference echo discriminator would shorten it.
- A speakers-rig participant's bleed can enter their mic and hold the
floor for other machines (bounded by release debounce).
- Non-macOS routes classify as coupled (fail-safe).

## Verification

- Full `buzz-desktop --lib` suite at head `b0459ae4a`: **2707 passed, 0
failed, 18 ignored** (pinned cargo 1.95.0).
- Exact CI recipe `just desktop-tauri-clippy`: PASS at head; base arm at
merge-base `b728a2af3` confirms the two
`#[allow(clippy::too_many_arguments)]`s cover branch-caused threshold
crossings (human_floor threading), not inherited noise.
- Regression tests pin: 20-frame threshold + reset-on-gap, short-coupled
rejection, sustained-coupled acceptance, coupled-idle acceptance,
remote-idle delayed-TTS rejection, output-tail hangover boundary (during
= rejected, after = accepted), isolated onset, per-frame mic-open gate
truth table, PTT+manual-unmute sustained coupled acquisition.
- `LocalBargeIn::observe` is covered as two joined halves (gate truth
table in `local_barge_in.rs`, floor transition in `tts_playback.rs`);
its body is a straight-line wrapper around a live CoreAudio query, left
uninjected deliberately.
- Coverage precision (mutation-verified):
`manual_open_ptt_sustained_speech_acquires_coupled_floor` pins the gate
→ 20-frame debounce → acquire → floor-blocked chain on the
coupled-**idle** cell. The live-output override leg is carried by
`sustained_coupled_speech_overrides_live_output_suppression`
(tts_playback.rs); the joiner's `sustained_coupled` argument is not
load-bearing there (flipping it to `false` leaves the test green, while
shortening the debounce by one frame turns it red).
- Live arms in progress: pre-regression build `b29c8cdaa^` staged to
confirm the deleted mechanism worked; two-endpoint remote-leg test
pending a second human.

## Commits

1. `fb681a5a9` — restore human barge-in (coordinator floor, lease, route
isolation, remote floor, 320 ms coupled debounce)
2. `b4265418e` — enable barge-in for manually opened mics (per-frame
gate; closes the PTT-open-mic gap)
3. `886489f2b` — extract local barge-in policy module (file-size
ratchet; also hoists the CoreAudio route query from per-frame to
per-onset, named in the commit message)
4. `b0459ae4a` — two targeted clippy allows for the widened worker
signatures

## Credits

Built by **Wren**. Regression archaeology and the PTT-open-mic gap by
**Dawn** (who also killed her own first fix as vacuous and caught a
clippy blocker before it hit CI). Review blockers (coordinator
serialization, idle-onset floors, output lease) by **Mari**. Live rig
verification by **Max**. Coordination and verification by **Eva**.
Opened by Eva with Tyler's explicit direction; commits carry agent
trailers.

---------

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
tellaho added a commit that referenced this pull request Aug 21, 2026
…ress

* origin/main:
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  fix(desktop): show complete repository trees (#5102)
  Add appearance preference previews (#6193)
  fix(desktop): restore emoji recents (#6263)
  chore: serialize mobile pre-push checks (#6322)
  fix(buzz-acp): loosen workspace-scan guardrail to allow named paths (#6261)
  fix(buzz-dev-mcp): expand leading ~ in read_file/str_replace paths (#6271)
  perf(desktop): move five hot renderer paths from JS into Rust (#6024)
  fix(media): accept portrait video resolutions (#6058)
  fix(desktop): hide archived channels from #/Tab autocomplete (#6156)
  Unify mobile channel details (#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)


# Conflicts:
#	desktop/src/features/messages/ui/MentionAutocomplete.test.mjs

Signed-off-by: Taylor Ho <taylorkmho@gmail.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