diff --git a/docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md b/docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md index dedd2ba..659ca63 100644 --- a/docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md +++ b/docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md @@ -131,12 +131,15 @@ applies the state machine and delegates to `inner.next_pcm_frame()` per the tabl The reflex owns three pieces of state: - `advisory_rx: mpsc::Receiver` — drained sync-non-blocking via `try_recv` - on the 20 ms tick before delegating to `inner`. Fed by the TapEngine task over tokio mpsc. + on the 20 ms tick before delegating to `inner`. Fed by TWO senders on the same channel: + the outer `LocalVadReflex` (local VAD, the primary trigger) and the TapEngine task (the + brain's advisory, secondary/confirmation) — both over tokio mpsc. - `muted: bool` — the kill state. `next_pcm_frame` returns `None` while muted, *unless* the inner returns `Some` (the resume condition — the first fresh `audio_out` clears mute). -- `barge_epoch: u64` — incremented on every `SpeechStarted`. Not strictly required for the - advisory-only MVP (the flush + drain makes the resume race-free), but it's the seam for a - future local-VAD wrapper that could race the advisory. Documented as forward-compatible. +- `barge_epoch: u64` — incremented on every `SpeechStarted`. Load-bearing this slice: the + local VAD (primary) and the brain's advisory (secondary) can both fire on the same barge, + and the epoch distinguishes a genuine re-barge from the slower confirmation landing on the + same event (§3.2). The flush + drain keeps resume race-free regardless. ### 2.2 The dedicated media thread @@ -241,10 +244,12 @@ un-mutes too early, brain's audio overlaps caller's next word). The resume condi "the brain has yielded and started a new response," which is provably signaled by the first `audio_out` frame after the barge — not by the caller's silence. -**Why `epoch`:** not strictly needed for advisory-only (MVP), but it's the seam for the -local-VAD backstop (deferred per §1.2). A future `LocalVadReflex` wrapper racing the -advisory would need to disambiguate "is this barge a re-barge of the same event or a new -one" — the epoch is the disambiguator. Forward-compatible. +**Why `epoch`:** with two concurrent trigger sources this slice — the local VAD (primary) +and the brain's advisory (secondary/confirmation, §6.1) — the epoch disambiguates "is this +barge a re-barge of the same event, or a new one" when both race into the same advisory +channel. The local VAD trips first; the brain's slower ASR-grade advisory lands ~300 ms +later on the *same* event and must not be counted as a fresh barge. The epoch is that +disambiguator — load-bearing now, not a forward-compat seam. ### 3.3 `AudioPipe` trait extension @@ -487,7 +492,7 @@ impl AudioPipe for Reflex

{ } fn barge_in_flush(&mut self) { - // Allow outer wrappers (future local-VadReflex) to barge the inner. + // Allow the outer `LocalVadReflex` (primary trigger) to barge the inner. self.inner.barge_in_flush() } } @@ -710,15 +715,15 @@ from §6.4, which was originally specced as deferred to "when local VAD arrives. ### 6.4 Why `Reflex

` as a wrapper (not inline in `TapAudioPipe`) -- Composition: a future `LocalVadReflex

` composes outside the advisory `Reflex

`, the - same way `Reflex` composes today. The pattern (decorator over `AudioPipe`) is - forward-compatible without restructuring. +- Composition: `LocalVadReflex

` composes outside the advisory `Reflex

` (§6.1), the + same way `Reflex` composes. The pattern (decorator over `AudioPipe`) stacks + the two trigger sources without restructuring either. - The seam: `loop_driver.rs` byte-identical (still calls `pipe.next_pcm_frame()`). If the reflex lived inline in `TapAudioPipe`, the binary-side wiring would still change but the `TapAudioPipe` module itself would grow the reflex state — less isolated. -- YAGNI caveated: the wrapper is the right abstraction for advisory-only because there's - exactly one reflex. When local VAD arrives, the wrapper pattern pays off; the spec does - not pre-empt that by collapsing the wrapper now. +- The payoff is realized this slice, not deferred: two stacked reflexes (local-VAD primary + + advisory secondary) live as independent, separately-testable decorators rather than one + module's commingled state. Keeping them as wrappers is what makes that separation free. ### 6.5 Why `barge_in_flush` on `AudioPipe` (not just `clear_playout_ring`)