FuneralWiseAI — Live Director Transfer
Why now (pre-cold-email)
Founder's strategic frame: to compete with ASD Answering Service (~33% of US funeral homes, $150-300/mo, human-pickup) on the day-side, FuneralWiseAI needs real-time human handoff, not just async callback. "Funeral homes would want to get contacted, day or night or else they would lose business." Async callback is a fine secondary path; live transfer is the primary path.
We were about to send the cold-email batch with async-only escalation. Founder decided to delay the batch ~1 week and ship live transfer first. Better demo, stronger product, fewer "but what if I'm not at my desk" objections.
The killer demo (founder's idea — 2026-04-28)
"Could you imagine if we let the client put in their phone number and use browser call in and see the handoff to themselves personally and get a phone call?"
Mechanic: prospect lands on funeralwiseai.com/s/<their-funeral-home-slug>, types their own cell number into a "configure your director phone" field, clicks "Call from your browser." They have a normal at-need-style conversation with the AI as if they're a grieving family. At some point they say "can I speak with a director?" — the AI fires the transfer_to_director tool. Their own phone rings. They answer. They are NOW in a 3-way call: themselves-as-family + AI agent + themselves-as-director. They experience the handoff first-hand AND see how it would work for their own customers.
This is unforgettable. Sales-cycle compressor. Build the demo flip into /s/[slug] as part of the v1 ship.
Current state (what we already have)
✅ Async escalation works: request_callback tool fires on at-need death, writes to voice_callback_requests with urgency, sends SMS + email to notification_phone/notification_email. Verified working today (call_sid web-browser-walker-mortuary-daughenb-e6c1-moitecum, callback badc4909...).
✅ Per-tenant config exists: tenant_voice_agents has notification_phone, notification_email, escalation_contact_name, pastor_availability_text (or funeral equivalent).
✅ Prompt scaffolding for HUMAN ESCALATION already lives in verticals/funeral/prompts.py:_build_human_escalation(). Defines WHEN the agent should escalate.
✅ LiveKit SIP outbound supported at the SDK level — lk_api.SIP.create_sip_participant() initiates outbound dial, when target answers they're added to the room.
What we DON'T have (to build)
❌ Outbound SIP trunk — current ST_Xa3Bp9aixRFP is INBOUND-locked per CLAUDE.md. Need a separate outbound trunk via Telnyx/Twilio. Per-minute cost ~$0.005-0.01 US.
❌ transfer_to_director tool — agent-callable function that initiates the dial-out + bridges into the room.
❌ Fallback logic — if director doesn't answer within N seconds, gracefully degrade to request_callback + verbal "we'll call you back within {response_time_promise}."
❌ response_time_promise per-tenant config — agent currently says generic "very soon"; we want "within 15 minutes" / "within the hour" depending on the funeral home's actual SLA.
❌ Demo mechanic UI on /s/[slug] — input field for "use my phone as the director phone for this demo" + token-stamp it onto the LiveKit room metadata so the agent knows which number to dial.
Architecture sketch
New tool: transfer_to_director(reason: str, urgency: str)
1. Agent decides to fire (at-need death, demand for human, etc.)
2. Tool reads tenant_voice_agents.director_phone (validated E.164)
3. Tool calls lk_api.SIP.create_sip_participant():
- sip_trunk_id = TENANT_OUTBOUND_TRUNK_ID (env var)
- sip_call_to = director_phone
- room_name = ctx.room.name (current caller's room)
- participant_identity = f"director-{director_phone}"
4. Wait up to 25s for participant to publish audio (= director answered)
5. ON SUCCESS:
- Agent says one bridge line: "I have John on the line — his grandmother just passed at hospice in Woodstock, Ontario. I'll let you take it from here."
- Agent stops generating turns (room_input_options.close_on_disconnect or
a "step back" signal)
- Caller + director continue 1-on-1; LiveKit handles audio mixing
6. ON TIMEOUT (no answer / busy):
- Fall through to existing request_callback tool (writes DB row,
SMS+email to director)
- Agent tells caller: "I couldn't reach a director directly, but I've
made sure they get your message. Someone will call you back within
{response_time_promise}."
7. ON SIP ERROR (trunk down, invalid number, etc.):
- Same fallback as timeout. Loud structured log [TRANSFER] FAILED
reason=<exc> for diagnostics.
Schema additions (migration on tenant_voice_agents)
| Column | Type | Default | Notes |
|---|---|---|---|
director_phone | text | NULL | E.164 phone for live transfer. NULL = transfer disabled. |
transfer_enabled | boolean | false | Master toggle. False = always async-only. |
transfer_business_hours_only | boolean | false | If true, only attempt transfer during configured hours; outside → straight to async. |
transfer_business_hours | jsonb | NULL | Per-day windows (Mon-Sat 8am-6pm pattern, etc.) — same shape as existing pastor_availability |
response_time_promise_text | text | "shortly" | Spoken to caller when async fallback fires. e.g. "within 15 minutes" / "within the hour". |
transfer_ring_seconds | int | 25 | How long to wait for director to answer before fallback |
transfer_bridge_intro_template | text | NULL | Optional override for the agent's bridge line. Falls back to a default template if NULL. |
Demo mechanic (/s/[slug] UI)
For the prospect-self-handoff demo, expose a small input on the demo page:
[Test the live director transfer with your own phone number:]
[__________________] [Save and call]
When prospect saves a number:
- POST to
/api/livekit/tokenwith{ prospectSlug, demoOverrides: { directorPhone: "+1xxxxxxxxxx" } } - Token mint stamps
demo_director_phone_overrideinto the LiveKit room metadata (separate fromprospect_slug) - Voice agent reads override at session start, uses it instead of
tenant_voice_agents.director_phone - Limited to one number per session (no re-targeting mid-call)
- Number is NOT persisted to
tenant_voice_agents— it's session-scoped only
Server-side guards:
- Rate-limit by IP (3 numbers/day to avoid abuse)
- E.164 validation
- Block known disposable / VoIP test numbers? Probably not — false positives. Just rate-limit.
- Session TTL: ≤30 min to dial out (no calling 2 days later)
Implementation timeline (target: 3 working days)
Day 1 — outbound trunk + base tool
- Telnyx outbound SIP trunk (or Twilio elastic SIP) provisioned + creds in LiveKit Cloud secrets as
TENANT_OUTBOUND_TRUNK_ID - Schema migration in
churchwiseai-web/migrations/for the 7 new columns ontenant_voice_agents - New
transfer_to_directortool inverticals/<future-shared>/tools.py(or temporarily on the church Coordinator class as a hot-patch — Phase 2 generalizes) - Tool happy path: dial director, on answer agent says bridge line and stops generating
- Smoke test via synthetic dispatch with a personal phone number
Day 2 — fallback logic + prompt updates
- Timeout fallback to
request_callback+ verbal "within {response_time_promise}" - Funeral coordinator prompt updates:
- Forbid "I'm going to connect you right now" without firing
transfer_to_directorfirst - Explicit USE-CASE-WHEN-TO-FIRE language for
transfer_to_director(at-need death, demand for human, urgent legal/cert questions) - Read response_time_promise from home dict and use it verbatim in fallback language
- Forbid "I'm going to connect you right now" without firing
- Church coordinator prompt: same updates so it benefits too (church demos with director phone configured)
- Structured logs:
[TRANSFER] dialing,[TRANSFER] answered,[TRANSFER] timeout fallback=callback,[TRANSFER] error reason=… - Voice deploy + verification
Day 3 — demo mechanic + provisioning UI minimum
/s/[slug]demo page input: "test transfer with your own number"/api/livekit/tokenextension to acceptdemoOverrides.directorPhone- Token stamps
demo_director_phone_overrideinto room metadata - Voice agent reads override at session start
- Provisioning UI minimum (founder-only, will fold into Phase 2 Master Config later):
/founder/[token]/voice-agents/[id]/transferadmin page- Set director_phone, transfer_enabled, response_time_promise_text, business_hours
- "Send me a test transfer" button that calls the agent's transfer_to_director against the founder's number
- End-to-end test: prospect → browser call → ask for director → prospect's phone rings → 3-way call works
Risks + mitigations
| Risk | Severity | Mitigation |
|---|---|---|
| Outbound SIP costs spiral if abused (especially demo mechanic) | M | Rate-limit demo dial-outs to 3/IP/day. Per-tenant daily transfer cap (e.g. 50/day). Alert on cost > $5/day. |
| Director doesn't answer (common case) | H | Fallback to async request_callback works today, already tested. The promise becomes "we'll call you back within {response_time}." User experience degrades gracefully. |
| Spam to director phones via demo mechanic | M | Server-side validation: E.164 only, rate limit, session TTL ≤30min. Optionally require email verification for the demo flip in v2. |
| LiveKit SIP outbound fails due to trunk misconfig | H | Smoke test on day 1 with our own phone numbers. Alarm + auto-fallback in tool. Document the trunk config in knowledge/runbooks/. |
| Director hears "AI bridge line" and is confused | L | Bridge line template is per-tenant override-able. Default: warm + concise context. Worst case the director just hears "Hello, this is Walker Mortuary's AI assistant — I have John on the line, his grandmother passed at hospice in Woodstock. Connecting you now." |
| Caller gets confused when AI fades out and a human appears | M | Bridge line speaks AT the director-side; caller hears nothing different until director starts talking. Smooth from caller perspective. |
| Live customer phone lines accidentally enabled for transfer | H | transfer_enabled defaults FALSE. Existing 4 customer rows stay false until founder opts each one in. |
Open questions (resolve in next session's planning before code starts)
- Outbound SIP provider — Telnyx (already used for inbound on new customers) or Twilio (existing relationship)? Recommendation: Telnyx for cost + already-integrated; founder confirms.
- Trunk ID storage — single shared outbound trunk in LiveKit Cloud secret
OUTBOUND_TRUNK_ID, or per-tenant trunks for billing isolation? Recommendation: single shared, with a per-tenant cap; per-tenant only if a customer demands isolation later. - Director-side experience — does the director hear the AI's bridge line, or do we want them to JUST hear the family with no AI intro? Founder preference?
- What happens to the AI when the director joins — fades silent / leaves the room / stays as a transcript-capture observer? Recommendation: stays in the room as silent observer for transcript continuity, but does not generate turns. Caller + director see a clean experience.
- Demo mechanic abuse mitigation — beyond IP rate limit, do we want the demo flip behind a Cloudflare Turnstile / hCaptcha? Probably yes for the cold-email batch URLs since they're public. Founder approves.
- Recording the bridge call — once a human director joins, do we keep recording? US/Canada two-party consent laws vary. Recommendation: stop recording at bridge moment; caller is told via opening disclosure that "this call may be recorded for quality" but live director-bridged calls drop recording. Confirm legal.
- Voicemail detection — if the director's voicemail picks up, does that count as "answered"? LiveKit/SIP usually treats it as answered (audio detected). Need post-answer voicemail detection (silence + tone heuristic) OR shorter ring timeout. Recommendation: 25s ring timeout, no post-answer detection in v1; if calls land in voicemail too often, add detection in v2.
- Response-time promise variants — what's the canonical list customers can pick? Recommendation: dropdown of {"immediately", "within 5 minutes", "within 15 minutes", "within the hour", "within 2 hours", "within 4 hours", "by end of business day"} — covers funeral (urgent) through church (less urgent).
Sales / competitive positioning
This feature is the lever that makes (b) volume-priced premium + (c) hybrid co-existence from the integrations doc actually work against ASD:
- Day-side AI handles 80% of operational calls — info, hours, pricing-ballpark, simple callbacks. Cheap.
- At-need / demand-for-human → live director transfer — the moment of truth. AI doesn't pretend to handle, it bridges to the human in real time. Same UX as ASD's value prop, but at scale.
- After-hours fallback to ASD coexistence (Phase 2 with FuneralSync 3.0 API) — when the director's phone doesn't answer at 2am, the AI hands the case to ASD via API instead of just a callback row.
Pricing position once shipped: $999 setup + $199/mo + outbound SIP costs (pass-through, ~$5-15/mo for typical use). Frame it: "ASD costs $165/mo to answer. We cost $199/mo to answer + transfer + integrate + chatbot — and if the director doesn't pick up, you get the SMS callback for free."
Phase 2 follow-ups (after v1 ships)
- Multi-director routing (round-robin, on-call schedule, role-based — "transfer to whoever's on funeral call this week")
- Voicemail detection (don't fade AI out if the director's voicemail picks up; instead step back into capture mode)
- Conference mode (3-way: family + AI + director, AI continues to take notes / fire follow-up tools mid-call)
- ASD FuneralSync 3.0 integration as failover when director also doesn't answer (true 24/7 coverage)
- Unified TenantConfig.tools whitelist (per the unified architecture doc) —
transfer_to_directorbecomes one tool in the catalog, vertical defaults set it on for funeral / off for transactional - Master Config UI exposes transfer config per-tenant (already in scope for Phase 2 provisioning UI)
Decision
Build live transfer in 3 working days, BEFORE the cold-email batch sends. Founder commits to delaying the batch. Plan-then-execute starts next session.