feat(voice): guardian-wait heartbeat, impatience handling & silence suppression#10897
Conversation
…lence suppression Improve phone-call UX during guardian approval wait states: - Add proactive spoken heartbeat updates while waiting for guardian response (~5s initially, then jittered 7-10s steady state) - Classify caller utterances during wait (patience check, impatient, callback opt-in/decline, neutral) and respond appropriately - Offer callback path when caller sounds impatient - Suppress 'Are you still there?' silence prompt during guardian wait - Use guardian display name/username in wait messages instead of generic 'your guardian' - Add cooldown to prevent TTS spam from repeated caller interjections - Add 4 new config fields for heartbeat cadence tuning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@codex review |
|
@devin review |
… reset, cross-channel guardian label - Exempt callback_opt_in and callback_decline from cooldown guard so quick callback decisions are never dropped - Reset callbackOptIn to false on decline so timeout handler respects the caller's latest decision - Fall back to listActiveBindingsByAssistant when no voice-channel guardian binding exists, matching the pattern in access-request-helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Superseding my previous comment (formatting got mangled by shell escaping). Clean review below. Review Findings (against agreed PR1+PR2 scope)I reviewed this against the plan at 1) [P0] Typecheck is currently broken (build-blocking)
Impact: PR cannot pass strict typecheck as-is. 2) [P1] Explicit callback opt-in can be dropped if caller responds quickly
Impact: violates the requirement that explicit caller opt-in should be captured when offered. 3) [P1] Declining callback does not clear prior opt-in state
Impact: stale state can produce incorrect user-facing messaging. 4) [P2] Heartbeat jitter math can produce invalid intervals when misconfigured
Impact: fragile runtime behavior under bad config; should clamp or add cross-field validation. Validation Notes
OverallThe UX direction is aligned with the agreed scope (heartbeat + impatience handling + no auto-callback), but I would not merge until the P0/P1 items above are fixed. |
…amp jitter math - Add 5 new voice_guardian_wait_* event types to CallEventType union - Add 4 new guardianWaitUpdate* config defaults to config-schema test - Clamp heartbeat jitter to Math.max(0, steadyMax - steadyMin) to handle misconfigured steadyMax < steadyMin gracefully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the thorough review! All 4 findings have been addressed across 2 fix commits: Fix commit 1 (
Fix commit 2 (
|
…double TTS Clear and reschedule the heartbeat timer in impatient, neutral, callback_opt_in, and callback_decline branches of handleWaitStatePrompt, matching the existing pattern in patience_check. Prevents a queued heartbeat from firing immediately after a wait-state response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6886e1fd64
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Second-Pass Review (after
|
Replace the constant import SILENCE_TIMEOUT_MS with a getSilenceTimeoutMs() getter function in call-controller.ts so the test mock can effectively override the timeout value at runtime. The constant was bound at import time, making the Object.defineProperty getter in the test ineffective. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Fixed the silence timeout test regression in
|
|
@codex review |
Final Review PassReviewed after commit ResultNo remaining blockers from my prior review. This looks merge-ready from the issues I previously raised. Validation Runcd assistant && bunx tsc --noEmit✅ Passed cd assistant && bun test src/__tests__/call-controller.test.ts -t "silence timeout fires normally when not in guardian wait|silence timeout suppressed during guardian wait: does not say \"Are you still there\?\""✅ Passed cd assistant && bun test src/__tests__/relay-server.test.ts -t "guardian wait: heartbeat timer emits periodic updates|guardian wait: heartbeat stops on approval|guardian wait: heartbeat stops on destroy|guardian wait: impatience utterance triggers callback offer|guardian wait: explicit callback opt-in after offer is acknowledged|guardian wait: neutral utterance gets acknowledgment|guardian wait: empty utterance is ignored without response|guardian wait: cooldown prevents rapid-fire responses"✅ Passed |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96774831f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
All review feedback has been verified as already addressed in this PR's subsequent commits or in PR #10930 (callback handoff notification). |
…uppression (#10897) * feat(voice): add guardian-wait heartbeat, impatience handling, and silence suppression Improve phone-call UX during guardian approval wait states: - Add proactive spoken heartbeat updates while waiting for guardian response (~5s initially, then jittered 7-10s steady state) - Classify caller utterances during wait (patience check, impatient, callback opt-in/decline, neutral) and respond appropriately - Offer callback path when caller sounds impatient - Suppress 'Are you still there?' silence prompt during guardian wait - Use guardian display name/username in wait messages instead of generic 'your guardian' - Add cooldown to prevent TTS spam from repeated caller interjections - Add 4 new config fields for heartbeat cadence tuning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address review feedback — cooldown bypass for callbacks, decline reset, cross-channel guardian label - Exempt callback_opt_in and callback_decline from cooldown guard so quick callback decisions are never dropped - Reset callbackOptIn to false on decline so timeout handler respects the caller's latest decision - Fall back to listActiveBindingsByAssistant when no voice-channel guardian binding exists, matching the pattern in access-request-helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing CallEventType literals, config test defaults, and clamp jitter math - Add 5 new voice_guardian_wait_* event types to CallEventType union - Add 4 new guardianWaitUpdate* config defaults to config-schema test - Clamp heartbeat jitter to Math.max(0, steadyMax - steadyMin) to handle misconfigured steadyMax < steadyMin gracefully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: reset heartbeat timer after all wait-state responses to prevent double TTS Clear and reschedule the heartbeat timer in impatient, neutral, callback_opt_in, and callback_decline branches of handleWaitStatePrompt, matching the existing pattern in patience_check. Prevents a queued heartbeat from firing immediately after a wait-state response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: convert SILENCE_TIMEOUT_MS to getter function for test mockability Replace the constant import SILENCE_TIMEOUT_MS with a getSilenceTimeoutMs() getter function in call-controller.ts so the test mock can effectively override the timeout value at runtime. The constant was bound at import time, making the Object.defineProperty getter in the test ineffective. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Files changed
assistant/src/config/calls-schema.ts— 4 new Zod config fields for heartbeat cadenceassistant/src/calls/call-constants.ts— 4 new getter functions for config valuesassistant/src/calls/relay-server.ts— Heartbeat timer, utterance classifier, wait-state prompt handler, guardian label resolutionassistant/src/calls/call-controller.ts— Silence timer suppression during guardian waitassistant/src/__tests__/relay-server.test.ts— 8 new tests + updated existing assertionsassistant/src/__tests__/call-controller.test.ts— 2 new silence suppression tests + mock relay enhancementsTest plan
Original prompt
/Users/noaflaherty/Repos/vellum-ai/vellum-assistant/.private/plans/voice-guardian-wait-heartbeat-and-impatience-one-pr-plan-2026-02-28.md
🤖 Generated with Claude Code