Knowledge > Processes > Inbound Call Flow
Inbound Call Flow
What happens from the moment a phone rings to the moment the call ends and all data is captured. The system is multi-tenant: ONE deployed LiveKit Agents worker serves ALL churches. Per-church behavior is loaded dynamically at call time.
1. Phone Rings — Twilio Receives the Call
- A caller dials a Twilio phone number (toll-free, demo line, or church-specific number).
- Twilio forwards the call via a SIP trunk to the LiveKit Cloud SIP gateway (project:
cwa-voice-9x077mph). - LiveKit Cloud dispatches a job to the Railway agent worker. The worker's entry point in
main.pyreceives aJobContextcontaining:room.sip.trunkPhoneNumber— the number that was dialedroom.sip.from_— the caller's phone numberroom.name— unique LiveKit room identifier for this call
2. Route Resolution — Which Agent Handles This Call?
-
resolve_route(to_number)inmain.pyreadssip.trunkPhoneNumberand maps the dialed number to an agent type:- Toll-free number (+18886030316) -->
("sales", None)--> Sales Agent - Demo lines (3 known numbers) -->
("demo_router", None)--> Demo Router Agent - Known church number (in PHONE_REGISTRY) -->
("church", church_id)--> Church Agent - Unknown number (not in registry) -->
("church", None)--> needs DB lookup
- Toll-free number (+18886030316) -->
-
If the route is "church" but
church_idis None: a. Querychurch_voice_agentstable wheretwilio_phone_number= dialed number (cached 5 min). b. If found, proceed with that church_id. c. If not found, fall back to Sales Agent.
3. Sales Agent Path
- If agent_type is "sales":
a. Initialize Supabase client (singleton, lazy).
b. Insert a call log row into
voice_call_logswith status "in_progress". c. Load in parallel:- Product knowledge from
product_knowledgetable (cached 15 min) - Demo church data for Grace Community (protestant demo)
- Demo church data for St. Joseph Catholic Parish (catholic demo) d. Build the Sales Agent with product knowledge and demo church data. e. Initialize session state (abuse_count=0, crisis_detected=false). f. Return the agent (moderation + noise filtering run via on_user_turn_completed callback).
- Product knowledge from
4. Demo Router Path
- If agent_type is "demo_router": a. Insert call log row. b. Load both demo churches in parallel. c. Build Demo Router Agent (offers caller a choice of demo churches to experience). d. Return the agent.
5. Church Agent Path
-
If agent_type is "church": a. Load church data from
church_voice_agentsviaload_church_data().- This includes call-limit enforcement — if the church has exceeded their monthly limit,
load_church_data()returns None. b. If church data is None (over limit or load failure), fall back to Sales Agent. c. Insert call log row intovoice_call_logs. d. Incrementcalls_this_monthonchurch_voice_agents(non-fatal if fails).
- This includes call-limit enforcement — if the church has exceeded their monthly limit,
-
Load contextual data in parallel (4 concurrent queries):
- RAG context — denomination-specific theological content from
unified_rag_content - Product knowledge — from
product_knowledgetable (cached 15 min) - Inline FAQs — church-specific Q&A pairs from
church_knowledge_base(cached 5 min) - Repeat caller history — last 5 calls from this phone+church pair within 90 days, from
voice_call_logs(privacy-gated: agent must NOT proactively mention previous topics)
- RAG context — denomination-specific theological content from
-
Build datetime context:
- Current date/time in the church's timezone
- "This Sunday" = next Sunday's date
- "Next week" = the Monday after this Sunday
- Used so the agent can correctly interpret relative time expressions from callers
-
Combine all context blocks into one string (RAG + product knowledge + FAQs + repeat history + datetime).
-
Build the Coordinator Agent with church data and combined RAG context.
- The Coordinator handles general inquiries, directions, service times, visitor info, giving/tithing.
- It can hand off to the Care Agent for pastoral/emotional topics (prayer, grief, crisis).
- LLM: Gemini 2.5 Flash (Coordinator), Claude Haiku 4.5 (Care Agent — better empathy).
- See products/voice-agent/fallback-strategy.md for fallback plans.
-
Attach call context to agent (
_call_contextdict):call_id,caller_phone,dialed_numberchurch_data: the loaded church data dictsupabase: async Supabase clientcare_voice_id: opposite gender voice for Care Agent handoff
-
Return the Coordinator Agent. Moderation, noise filtering, RAG, and farewell detection run per-turn via
on_user_turn_completedcallbacks (see voice-turn-processing.md).
6. Per-Turn Processing Loop
For every utterance the caller speaks, the TurnProcessor pipeline runs:
-
Session injection — caller phone, church data, Supabase client, PCO keys, Cal.com keys, and timezone are injected into the turn environment so tools can access them.
-
Duration tracking — elapsed time since call start is updated on every turn.
-
"Are you there?" reassurance — If the agent is currently processing a prior turn and the caller says something like "are you there?" or "hello?", immediately respond "Yes, I'm here! Just one more moment." without interrupting the pending work.
-
Moderation checks (run BEFORE noise filtering — threats and crises must never be silently dropped):
a. Threat detection (
check_threat) — pattern-matched against violence-toward-others phrases. Has negation guard ("I'm NOT going to kill") and self-harm exclusion (redirects to crisis, not threat).- If threat detected:
- Log moderation violation to DB
- Send threat alert email to church notification_email
- Send threat alert SMS to church notification_phone
- Send threat alert to support team
- Speak: "I need to stop you right there. This call is being recorded and logged. I'm ending this call now."
- Wait 4 seconds for audio to finish, then hang up
b. Crisis detection (
check_crisis) — pattern-matched against self-harm, suicidal ideation, domestic violence phrases.- If crisis detected:
- Log moderation violation
- Send crisis alerts (email + SMS + support)
- Set
crisis_detected = trueon session - Inject directive into LLM context: "Provide 988 Lifeline immediately. Do NOT ask clarifying questions. Do NOT end the call."
c. Abuse detection (
check_abuse) — profanity, slurs, sexual content.- Tracks
abuse_countacross turns - First offense: inject "redirect calmly" context into LLM
- After threshold: speak "I'm going to end this call now. Have a good day." then hang up
- If threat detected:
-
Noise filtering (runs AFTER moderation — only if moderation did not flag the utterance):
- Silently drop pure noise sounds ("um", "uh", "hmm")
- Silently drop pure backchannels ("uh huh", "mhm", "i see")
- Drop context-dependent words ("okay", "yeah", "sure") ONLY if the agent did not ask a question
- Never filter floor-takes ("wait", "stop", "actually") or farewells ("thanks", "bye")
-
Per-turn RAG — if moderation did not fire, do a quick semantic search against the church's knowledge base with a 500ms timeout. Adds relevant context to this specific turn.
-
Combine contexts — merge any moderation directive + per-turn RAG into a single context string.
-
LLM processing — delegate to the wrapped LlmAgent with the combined context:
- Mark session as
is_processing = true - If the LLM wants to call a tool, inject a random filler phrase first ("One moment.", "Let me check on that.", etc.) so the caller hears something while the tool executes. Tools that skip the filler:
end_call,demo_agent. - Track the agent's last response text for farewell detection.
- Track whether the agent's response contained a question mark (for noise filtering on the next turn).
- Set
is_processing = falsewhen done.
- Mark session as
-
Tool execution — during LLM processing, the agent may invoke tools:
submit_prayer_request— writes tovoice_prayer_requeststable, sends notificationrequest_callback— writes tovoice_callback_requeststable, sends notificationcapture_visitor_contact— writes tovoice_visitor_contactstableregister_for_event— captures event interestsend_giving_link— sends giving URL via SMSsend_sms_link— sends a link via SMS to the callersend_directions_link— sends Google Maps link via SMScheck_availability/book_appointment— Cal.com integrationget_service_times_tool/get_upcoming_events_tool/get_staff_directory_tool— Planning Center integrationend_call— terminates the call
-
Auto-hangup on mutual farewell — after the LLM generates its response:
- If crisis_detected is true, skip farewell detection entirely (never hang up on someone in crisis).
- If both the caller's message AND the agent's response are farewells, set a 4-second grace period.
- If the caller speaks again during the grace period, cancel the hangup.
- If the grace period expires without new speech, hang up.
7. Call Ends — Data Capture
-
When a
CallEndedevent fires (caller hung up, agent hung up, or network drop):a. Write call log — update the
voice_call_logsrow:- Set status = "completed"
- Set
duration_seconds= elapsed time since call start - Save full transcript (all user + agent messages as JSONB array)
- Summary starts as empty string (will be filled by classification)
b. Generate call classification (fire-and-forget via Gemini 2.5 Flash):
- Only runs if transcript has >50 characters of content
- Produces 7 structured fields:
summary— 1-2 sentence factual summarycaller_sentiment— -1.0 to 1.0 scalecall_topics— array of topic tags (prayer, visitor, giving, callback, etc.)category— single primary category (prayer_request, visitor, callback_request, pastoral_care, crisis, etc.)urgency— low / normal / urgent / pastoral_emergencyfollow_up_needed— true/falsesuggested_assignee— pastor / office_admin / prayer_team / care_team / etc.
- Updates the
voice_call_logsrow with these fields
c. The call is now fully captured. Church admin can view the call in the admin dashboard under the Calls tab, including transcript, summary, classification, and any tool results (prayer requests, callbacks, visitor contacts).