Deterministic phone confirmation safety net + docs

EndCallProcessor now guarantees the callback number is confirmed on booking
calls: the 8B reads it back only ~half the time, so if a closing is reached on a
booking call (booking keyword seen) without the agent having spoken the number
(phone_marker absent from its replies), the hang-up is suppressed and a scripted
confirmation line (caller-ID spelled out) is injected as a TTSSpeakFrame first.
The agent's own readback satisfies the gate (no double-ask); info-only calls are
never asked for a number. Runtime-tested all four paths (inject / no-inject /
info-only / inject-then-end).

CLAUDE.md: document the safety net, the "never claim a booking" rule, the direct
phone-confirm phrasing, and the insurance "never say we accept" rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tocmo0nlord
2026-06-27 15:52:22 +00:00
parent 1e0472e864
commit d7bfe2dbe8
2 changed files with 79 additions and 24 deletions

View File

@@ -40,6 +40,14 @@ Watches LLM text stream for closing keywords ("goodbye"), waits for TTS to finis
clipped, then pushes `EndTaskFrame` upstream. `TwilioFrameSerializer` with `auto_hang_up`
drops the carrier leg. Verified working in the Phase 1 gate (4/4 clean hang-ups).
It also **deterministically guarantees the callback number is confirmed** on booking calls:
the 8B reads the number back only ~half the time, so if a closing is reached on a booking
call (booking keyword seen) without the agent having spoken the number (`phone_marker` not
seen in its replies), the hang-up is suppressed and a scripted confirmation line
(`phone_confirm_line`, the caller-ID spelled out) is injected as a `TTSSpeakFrame` first.
The agent's own readback satisfies the gate, so there's no double-ask in the common case;
info-only calls (no booking keyword) are never asked for a number.
**Mulaw 8kHz ↔ 16kHz conversion** — handled internally by `TwilioFrameSerializer`.
`PIPELINE_SAMPLE_RATE = 16000`, `WIRE_SAMPLE_RATE = 8000` are already set correctly.
No custom audio module needed.
@@ -250,18 +258,25 @@ time, leading the call rather than waiting on the caller. Fixed order:
2. **Location** — ask city/area, confirm the matching office (don't offer others — see office rule).
3. **Caller info** — full name (ask last name if only a first is given), then **address the caller
by name** from there on; insurance (log only); preferred day/time in their words.
4. **Verify phone** — near the end, read the caller-ID back digit-by-digit and ask if it's best;
if not, use the number they give. Never raised earlier in the call.
5. **Wrap up** — recap the booking by name, then ask **"Is there anything else I can help you
with?"**
4. **Verify phone** — near the end, state the caller-ID back in one line ("I have your number
as <number> — is that the best number?"), no asking permission first; if not, use the number
they give. Never raised earlier. **Backed by a deterministic safety net** — if the agent
skips it, `EndCallProcessor` injects the confirmation before hang-up (see "already solved").
5. **Wrap up** — recap the booking **as a REQUEST** by name ("I've noted your request to come
in…"), make clear staff will call to confirm, then ask **"Is there anything else I can help
you with?"**
**Never claims a booking:** AVA must never say an appointment is "booked / scheduled / set /
confirmed" — everything is a request staff confirm on callback. **Insurance:** never say "we
accept/take" a plan (or invent one) — just note what the caller said; staff verify.
**Closing is gated:** the word "Goodbye" ends the call (triggers `EndCallProcessor` → hang-up),
so it is never said in the same turn as confirming details and never before the anything-else
question — only after the caller says they need nothing more.
> Reliability: this is prompt-driven on the local 8B, so order is followed well but not
> perfectly — the phone-readback step in particular varies (sometimes reads back, sometimes
> asks for the number), and it can re-ask a last name. Same model ceiling noted elsewhere.
> Reliability: the script is prompt-driven on the local 8B (order followed well, not perfectly;
> it can re-ask a last name). The phone-confirmation step is the exception — it's now
> **guaranteed** by the deterministic `EndCallProcessor` safety net.
## Call Data Capture
@@ -277,7 +292,7 @@ Replies are kept to one short sentence.
| Phone | Confirmed **near the end** (not led with); reads back the caller-ID — injected pre-spelled so it's said digit-by-digit — and if the caller declines, uses the number they give | `callback_number` (+ `phone_confirmed`) |
| Office / city | Asks city/area; when the caller names a place that matches an office, **confirms that office and moves on** — never offers/compares other offices or asks them to choose; names the nearest only if nothing matches | folded into `reason` prefix |
| Reason | Captured from the conversation | `reason` |
| Insurance | **Log only, never suggest or guess** — asks open-endedly (no plan names read out), captures only what the caller says, never fills in/completes/guesses the plan (asks them to repeat if unclear), never promises/confirms/denies coverage or treatment even for a listed plan; staff verify on callback | `insurance` (note: "log only — staff to verify") |
| Insurance | **Log only, never suggest or guess** — asks open-endedly (no plan names read out), captures only what the caller says, never fills in/completes/guesses the plan (asks to repeat if unclear), never says "we accept/take" a plan, never promises/confirms/denies coverage or treatment even for a listed plan; staff verify on callback | `insurance` (note: "log only — staff to verify") |
| Preferred day & time | **Capture & defer** — taken in the caller's own words; AVA does not compute or correct the date | `preferred_time` + best-effort resolved `YYYY-MM-DD` |
### Dates — capture & defer (do NOT compute in-call)