Fix dead-air: stop VAD interruption broadcasts under half-duplex
Live call diagnosis (recording + log): replies were generated in <1s but a false VAD trigger (background noise, no transcript) fired 0.7s later, and the aggregator's broadcast_interruption silently discarded the queued TTS audio. Caller heard 20-35s of silence, said "Hello?", repeated themselves. The HalfDuplexGate only closes while the bot is audibly speaking, so the window between generation start and first wire audio was unprotected. SilenceWatchdog never fired because the cancelled reply never emitted BotStoppedSpeaking. With HALF_DUPLEX on, build the user aggregator with enable_interruptions=False on both turn-start strategies: strict turn-taking, nothing is ever cancelled. UserStartedSpeakingFrame still flows, so watchdog resets keep working. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
13
CLAUDE.md
13
CLAUDE.md
@@ -83,6 +83,19 @@ audio while the bot is speaking (+`ECHO_TAIL_SECS`, default 0.5s) so echo never
|
||||
Trade-off: half-duplex — the caller can't barge in mid-utterance (fine for short replies).
|
||||
`HALF_DUPLEX=false` restores barge-in. Keep it on for telephony.
|
||||
|
||||
**Interruption broadcasts OFF under half-duplex (2026-07-04).** The gate left one window
|
||||
open: between "LLM starts generating" and "first audio on the wire" the bot isn't speaking
|
||||
yet, so caller-side audio still reaches the VAD — and a false VAD blip (breath/background
|
||||
noise, no transcript ever produced) made the user aggregator `broadcast_interruption`,
|
||||
silently discarding the queued reply. Live call showed 20–35s of dead air, the caller saying
|
||||
"Hello?" and repeating themselves; SilenceWatchdog never fired because the cancelled reply
|
||||
never produced a `BotStoppedSpeakingFrame` to arm it. Fix: when `HALF_DUPLEX` is on, the
|
||||
aggregator is built with `VADUserTurnStartStrategy(enable_interruptions=False)` +
|
||||
`TranscriptionUserTurnStartStrategy(enable_interruptions=False)` — strict turn-taking, no
|
||||
interruption broadcasts at all (there's nothing legitimate for them to do in a no-barge-in
|
||||
bot). `UserStartedSpeakingFrame` is still emitted, so the watchdog reset keeps working. If
|
||||
the caller talks over generation, both replies play in order instead of one being dropped.
|
||||
|
||||
**`CallStateGroomer` (`callstate.py`) — deterministic slot memory (2026-07-03).** Fixes the
|
||||
8B re-asking for things the caller already gave (name, reason, phone — seen repeatedly in the
|
||||
historical call logs: "Didn't you say you had my phone number?", "I already gave you my full
|
||||
|
||||
Reference in New Issue
Block a user