Handoff: Fix Voice Agent — Telnyx SIP + LiveKit Dispatch
Created: 2026-03-27 Priority: P0 — first paying customer ($49.95/mo) has no working voice line Context: We spent 4 hours debugging. 10 research agents identified the root causes. This handoff contains everything needed to fix it properly.
The Problems (3 separate issues)
Problem 1: Telnyx calls don't reach LiveKit
- Symptom: Caller hears "number not assigned" or dead air
- SIP trace shows: Telnyx sends INVITE to LiveKit, LiveKit returns
404 No trunk found - Root cause (confirmed by GitHub livekit/sip#592): In LiveKit Cloud (shared environment), the same IP can be associated with multiple Telnyx connections. Without credential auth, Telnyx may route to the wrong connection. Also, we cleared the username/password on the FQDN connection during debugging — Telnyx treats a credential-based connection with empty credentials as "not registered."
- Fix: Either restore credentials on Telnyx FQDN connection, OR switch to IP-based auth with
allowed_addresseson the LiveKit trunk
Problem 2: LiveKit dispatch rules silently fail (agent doesn't join room)
- Symptom: Call connects (room created) but agent never joins — caller hears silence then "customer unavailable"
- Root cause (confirmed by GitHub livekit/sip#401, livekit/livekit#3690): We use deprecated top-level fields on
CreateSIPDispatchRuleRequest(fields 1-9). Theroom_configfield is silently ignored. Must usedispatch_rule=SIPDispatchRuleInfo(...)wrapper (field 10). - Also: No explicit
agent_name— auto-dispatch is unreliable for SIP. Must setagent_namein both@server.rtc_session()decorator AND dispatch rule'sRoomAgentDispatch. - Fix: Recreate dispatch rules using
SIPDispatchRuleInfowrapper with explicitagent_name="churchwiseai-voice"
Problem 3: "room is not connected" crash (intermittent)
- Symptom: Agent crashes at
wait_for_participant()withRuntimeError: room is not connected - Root cause:
wait_for_participant()is called before the room is connected.session.start()auto-callsctx.connect(), but our code callswait_for_participant()BEFOREsession.start(). - Fix: Call
ctx.connect()beforewait_for_participant(), remove redundantctx.connect()at end. Do NOT useAutoSubscribe(not needed for this pattern).
The Fixes (execute in order)
Fix 1: main.py — Code ordering + agent_name
File: C:\dev\churchwiseai-web\voice-agent-livekit\main.py
# Line 79: Add agent_name
@server.rtc_session(agent_name="churchwiseai-voice")
async def entrypoint(ctx: JobContext):
"""Route inbound SIP call to the correct agent."""
# Line 84: Connect FIRST, then wait for participant
await ctx.connect()
participant = await ctx.wait_for_participant()
# Extract phone numbers from SIP attributes
caller_phone = participant.attributes.get("sip.phoneNumber", "")
dialed_number = participant.attributes.get("sip.trunkPhoneNumber", "")
# IMPORTANT: Normalize number — Telnyx may send without + prefix
if dialed_number and not dialed_number.startswith("+"):
dialed_number = "+" + dialed_number
call_id = participant.attributes.get("sip.callID", "")
# ... rest of routing code unchanged ...
Also: REMOVE the await ctx.connect() at the end of entrypoint (around line 190). It's now at the top.
Fix 2: Dispatch rules — Use SIPDispatchRuleInfo wrapper
Delete ALL existing dispatch rules and recreate with the non-deprecated API:
from livekit.protocol.sip import (
CreateSIPDispatchRuleRequest,
SIPDispatchRuleInfo,
SIPDispatchRule,
SIPDispatchRuleIndividual,
)
from livekit.protocol.room import RoomConfiguration
from livekit.protocol.agent_dispatch import RoomAgentDispatch
# For the main "All Lines" trunk (ST_Xa3Bp9aixRFP):
CreateSIPDispatchRuleRequest(
dispatch_rule=SIPDispatchRuleInfo(
trunk_ids=["ST_Xa3Bp9aixRFP"],
rule=SIPDispatchRule(
dispatch_rule_individual=SIPDispatchRuleIndividual(
room_prefix="call-",
)
),
name="ChurchWiseAI Call Dispatch",
room_config=RoomConfiguration(
agents=[
RoomAgentDispatch(
agent_name="churchwiseai-voice",
metadata='{"source": "phone"}',
)
]
),
)
)
# For the test trunk (ST_hvf5m2RXfEiS):
# Same pattern, trunk_ids=["ST_hvf5m2RXfEiS"], room_prefix="test-call-"
IMPORTANT: Do NOT delete-then-create. Use UpdateSIPDispatchRule if available, or create the new rule BEFORE deleting the old one to avoid a gap.
Fix 3: Telnyx FQDN connection — Restore credentials
curl -X PATCH "https://api.telnyx.com/v2/fqdn_connections/2925081061861885519" \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_name": "churchwiseai",
"password": "CWA-livekit-2026!",
"transport_protocol": "TCP",
"inbound": {
"ani_number_format": "+E.164",
"dnis_number_format": "+e164",
"default_primary_fqdn_id": "2925083349418510207"
}
}'
Fix 4: LiveKit inbound trunk — Add auth credentials to match Telnyx
The main trunk needs auth_username and auth_password matching the Telnyx credentials:
trunk = await lk.sip.create_sip_inbound_trunk(
CreateSIPInboundTrunkRequest(
trunk=SIPInboundTrunkInfo(
name='ChurchWiseAI All Lines',
numbers=['+18886030316', '+14696152221', '+13658254095', '+14144007103'],
krisp_enabled=True,
auth_username='churchwiseai',
auth_password='CWA-livekit-2026!',
),
)
)
NOTE: Update in place if possible. If you must recreate, create new trunk first, update dispatch rule to point to it, THEN delete old trunk.
Fix 5: Deploy
cd C:\dev\churchwiseai-web\voice-agent-livekit
echo "CA_pX3Me4NK6qK8" | C:\dev\lk.exe agent deploy --project cwa-voice --silent .
Current Infrastructure State
Telnyx
- API Key:
knowledge/.env→TELNYX_API_KEY - FQDN Connection:
2925081061861885519("LiveKit ChurchWiseAI") - FQDN:
cwa-voice-9x077mph.sip.livekit.cloud:5060(record ID2925083349418510207) - Outbound Voice Profile:
2925086295615079772(US + CA) - Number:
+14144007103assigned to connection2925081061861885519 - Current credentials: EMPTY (cleared during debugging — must restore)
LiveKit
- Project:
cwa-voice-9x077mph(IDp_5u9xu5ysoly) - CLI:
C:\dev\lk.exe - Agent:
CA_pX3Me4NK6qK8, versionv20260327224429(reverted working code) - Main trunk:
ST_Xa3Bp9aixRFP→+18886030316,+14696152221,+13658254095,+14144007103,14144007103 - Test trunk:
ST_hvf5m2RXfEiS→+13658253552 - Main dispatch:
SDR_cYzx7sAkUTvx(uses DEPRECATED top-level fields — must recreate) - Test dispatch:
SDR_Wpyno7GDNQqg(same issue) - Agent env vars: 16 secrets configured (verified: Gemini, Cartesia, Deepgram, Supabase, Anthropic, OpenAI, Twilio, Resend)
Database
church_voice_agentsrow for Zewdei:twilio_phone_number = '+14144007103',welcome_greeting= fixed,church_timezone= America/Chicago- PHONE_REGISTRY in
session.py:"+14144007103": "96f5b89e-b238-4811-8d76-777f84f4241c"
Verification
- Call
+14144007103— agent should greet as "Thank you for calling Medhanialem Ethiopian Evangelical Church" - Call
+14696152221— demo agent should answer - Call
+13658253552— test agent should answer - Check agent logs:
C:\dev\lk.exe agent logs --project cwa-voice- Should see "INBOUND CALL" log lines, NO "room is not connected" crashes
- Check
voice_call_logstable for new entries - Check Telnyx SIP Call Flow Tool — should see 200 OK (not 404)
Research Sources
All findings backed by GitHub issues and official docs:
- livekit/sip#592 — Telnyx FQDN credential auth causing 407/404
- livekit/sip#401 — Named agent dispatch broken
- livekit/livekit#3690 — room_config silently ignored on deprecated path
- livekit/agents#3202 — Explicit agent_name fixes dispatch reliability
- livekit/agents#4861 — wait_for_participant room not connected
- LiveKit Telnyx Setup Guide
- LiveKit SIP Lifecycle Recipe
- Telnyx SIP Number Formats