Skip to main content

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_addresses on 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). The room_config field is silently ignored. Must use dispatch_rule=SIPDispatchRuleInfo(...) wrapper (field 10).
  • Also: No explicit agent_name — auto-dispatch is unreliable for SIP. Must set agent_name in both @server.rtc_session() decorator AND dispatch rule's RoomAgentDispatch.
  • Fix: Recreate dispatch rules using SIPDispatchRuleInfo wrapper with explicit agent_name="churchwiseai-voice"

Problem 3: "room is not connected" crash (intermittent)

  • Symptom: Agent crashes at wait_for_participant() with RuntimeError: room is not connected
  • Root cause: wait_for_participant() is called before the room is connected. session.start() auto-calls ctx.connect(), but our code calls wait_for_participant() BEFORE session.start().
  • Fix: Call ctx.connect() before wait_for_participant(), remove redundant ctx.connect() at end. Do NOT use AutoSubscribe (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/.envTELNYX_API_KEY
  • FQDN Connection: 2925081061861885519 ("LiveKit ChurchWiseAI")
  • FQDN: cwa-voice-9x077mph.sip.livekit.cloud:5060 (record ID 2925083349418510207)
  • Outbound Voice Profile: 2925086295615079772 (US + CA)
  • Number: +14144007103 assigned to connection 2925081061861885519
  • Current credentials: EMPTY (cleared during debugging — must restore)

LiveKit

  • Project: cwa-voice-9x077mph (ID p_5u9xu5ysoly)
  • CLI: C:\dev\lk.exe
  • Agent: CA_pX3Me4NK6qK8, version v20260327224429 (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_agents row 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

  1. Call +14144007103 — agent should greet as "Thank you for calling Medhanialem Ethiopian Evangelical Church"
  2. Call +14696152221 — demo agent should answer
  3. Call +13658253552 — test agent should answer
  4. Check agent logs: C:\dev\lk.exe agent logs --project cwa-voice
    • Should see "INBOUND CALL" log lines, NO "room is not connected" crashes
  5. Check voice_call_logs table for new entries
  6. Check Telnyx SIP Call Flow Tool — should see 200 OK (not 404)

Research Sources

All findings backed by GitHub issues and official docs: