0057 — Clara's voice promises must be backed by real, verified delivery (unknown-caller path)

Context

When an inbound voice caller can't be matched to a resident, prospect, or vendor on file, Triage routes them to the unknown_caller voice sibling (ADR-context: PR #1993). That sibling has exactly two webhook tools, and both are bookkeeping that perform no real-world action:

Net: on this path, every "someone will follow up" / "connecting you now" is a promise the system cannot keep. Verified across four independent layers:

  1. Code/config — the two-tool surface above; PM_PAGE_URGENCY_TIERS = {gas_emergency, high}.
  2. Production data (real, test-excluded) — e.g. a utility's water-shutoff notice and a billing complaint, same property, same day (2026-06-15), each captured/escalated with zero delivery signals; a vendor's two attempts to deliver a resident's rent checks (2026-04-21 transferred to an empty office; 2026-05-20 the message disappeared with escalatedAt/transferredTo/workOrderId/unknownCaller all null).
  3. Telephony (other session, Twilio) — a medium escalate returned transferred:true with 0 child legs dialed. This disproved an earlier read of ours: transferredTo being set is bookkeeping, not proof of a transfer. (The April re-greet loop is separately fixed by #2113/#2341 — that is a different bug from "the caller never reaches a human.")
  4. Human/operator — the community manager emailed a resident's vendor a ledger with "Sorry if we've missed your calls… [the AI] does have an option to connect with a person. Next time, if we don't answer, please send us an email" — the PM is absorbing the trust cost and routing people off the phone line because the "connect to a person" option does not actually connect.

A wider audit of 1,377 voice calls found the same promise-without-delivery class leaking into other flows (lost work orders, unsent renewal texts, a tour_inquiry vs billing misclassification). This ADR scopes only the unknown_caller path; the others get their own ADRs/tickets so this one stays shippable.

Decision

Adopt one invariant and rebuild the unknown_caller action surface to satisfy it.

Invariant (binding): Clara may not speak a promise of human follow-up, notification, or transfer unless the corresponding action has actually been performed and the tool result confirms it. Her spoken wording is derived from the tool's delivery result, never hardcoded ahead of it.

Concretely:

  1. Taking a message delivers it. capture_unknown_caller_note must, as part of taking the message, send a real notification to the property team's escalation inbox (reusing the sendEmail/pagePropertyManager leg with a calm, non-life-safety template). Its return value carries delivered: true|false. Clara only says "I've let the team know / someone will follow up" when delivered === true. On false (no inbox configured, send failed) she does not claim a human was reached, and the failure is logged loud (Sentry) — never a silent no-op.

  2. A transfer must actually transfer. When Clara hands a caller to a person it must be a real telephony transfer. transfer_to_number is now declared on the unknown_caller agent (unknown-caller.config.json inline_tools[] — the conference transfer to {{pm_phone_number}}) and pushed to the live agent by a dedicated, gated cutover script (scripts/cutover-unknown-caller-transfers.ts, run with --apply only after a Test Property clone + 3-channel pass). That script is deliberately not in sync-specialists.yml, so merging this branch can't ship the live transfer by itself. escalate_to_human must never emit "Transferring call…" on a path that places no call. Known limitation: EL's native conference transfer gives the app no no-answer signal, so a transfer that rings out is not yet auto-detected. The guarantee "a transfer that can't connect degrades to a delivered message + PM page" is the target; the implementation is tracked in docs/planning/unknown-caller-transfer-no-answer-detection.md (recommended Phase 1: page the PM at transfer-time in call-ended, a one-file change shippable separately). Until that lands, the prompt closes the gap behaviorally — transfer is reserved for callers who insist, the bias is to a delivered message, and escalate_to_human already pages the PM on every tier. The transfer tools were verified against a real ElevenLabs agent on 2026-06-16 (clone + Simulate-Conversation, scripts/voice-harness/verify-unknown-caller-transfers.ts): EL accepts the config, and Clara fires transfer_to_number on insist, transfer_to_agent→leasing on revealed intent, and a delivered message otherwise. That harness also caught a residual wording bug — escalate_to_human returned "Transferring call…" on voice (Clara echoed "connecting you now") while only paging, never placing a call. Two complementary fixes: the handler no longer narrates a transfer (delivery-conditional, honest on every channel — landed independently via ADR-0058 / PR #2411), and the unknown_caller prompt adds a hard honesty rule: "connecting/transferring you" is true only in the same turn a real transfer tool fires; paging and message-taking speak in future tense (re-verified on the clone — an insister with no transfer tool now hears "someone will follow up shortly", not "connecting you now"). The no-answer follow-up is decided (notify the PM only on a no-answer, and forward the caller's voicemail to them — see docs/planning/unknown-caller-transfer-no-answer-detection.md), not "page on every transfer."

  3. Bias to taking a message; transfer is the exception. Because a captured message now actually reaches the PM, it is the default for anything non-urgent. Reserve a live transfer for the narrow cases that need a person now — a safety/urgent situation, or a caller who explicitly insists on a human. Routing: can Clara handle it herself? → handle it; is it urgent, or does the caller insist on a person? → transfer (for real); otherwise → take a message that is actually delivered to the PM. Minimizing transfers also shrinks the surface where a "connecting you" promise can break — fewer transfer attempts, fewer ways to fail one.

  4. Revealed intent routes to the right specialist. The unknown_caller sibling now carries a transfer_to_agent system tool, so when a caller's real need becomes clear mid-call Clara hands them to the specialist who can actually help instead of parking a message: a brand-new leasing prospect → leasing (agent_8601…); a current resident with a maintenance issue → maintenance intake (agent_8301…). Routing is high-precision and narrow — when identity or intent is unclear, the default (a delivered message) wins over a wrong transfer, and a failed handoff falls back to a delivered message rather than a dead end. Same gated cutover as Decision 2 (declared in unknown-caller.config.json, applied by the cutover script after the sandbox pass).

  5. A guard test makes regression impossible. A unit test asserts that no unknown_caller tool may return follow-up/notification/transfer wording without a corresponding delivery side-effect; a second test (unknown-caller-transfer-wiring.test.ts) fences the declared transfer-tool shape and the cutover merge. The reproduction specs (below) are the first guard in its initial, RED state.

Entity classification

No new entities. This ADR changes the behavior of existing handlers and the unknown_caller prompt/tooling; the Conversation.unknownCaller field is unchanged in shape.

Consequences

Alternatives considered

Steps to reproduce

In production (observed, real callers, test data excluded): an inbound voice call that Triage can't match routes to unknown_caller; the caller leaves a routine message or asks for a person; the conversation row ends with unknownCaller.status:'new' and/or transferredTo stamped while escalatedAt/workOrderId are null and no email/SMS is sent (Twilio shows 0 child legs for the "transfer"). No human is notified, though Clara's spoken close promised one.

In the test suite (deterministic, ships with this ADR):

npx vitest run src/__tests__/unknown-caller-promise-delivery.repro.test.ts \
               src/__tests__/unknown-caller-escalate-no-transfer.repro.test.ts

These specs encoded the broken invariant in their initial it.fails (green-while-bug-exists) form; with the fix landed they are now plain it() assertions that lock it in — a human is notified before Clara promises follow-up, and escalate_to_human pages the PM on every tier. The transfer wiring itself is fenced by src/__tests__/unknown-caller-transfer-wiring.test.ts (config shape + the cutover merge function).

Multi-turn, prompt level (coverage hole filled):

npx tsx evals/voice-simulation/run-scenarios.ts uc-adv-RI-01 --probe   # revealed-intent → leasing
npx tsx evals/voice-simulation/run-scenarios.ts uc-adv-IH-01 --probe   # insists on a person → transfer
# live (cutover only — hits ElevenLabs, tests the DEPLOYED agent): drop --probe

Out of scope here (separate tickets): the parallel promise-without-delivery gaps the same audit surfaced — lost work orders (workOrderId null after "your work order is in"), unsent renewal texts ("I'll text you the details"), and the issueType: tour_inquiry vs topics: billing misclassification.