dry-baker-25622
04/08/2026, 6:06 PM# apps/livekit-agent/agent.py
""""
Basic inbound call to LiveKit with webRTC gateway
Clarus CIVR — Milestone 1: Basic Voice Conversation
=====================================================
STT : Deepgram Nova-3
LLM : Claude Haiku 4.5 (claude-haiku-4-5-20251001)
TTS : Deepgram Aura-2 (aura-2-andromeda-en)
VAD : Silero (local, CPU-based)
Run modes:
python agent.py dev # connects to LiveKit Cloud, processes one job then exits
python agent.py start # production worker, stays alive for multiple jobs
"""
# import structlog
import logging
import os
from dotenv import load_dotenv
from livekit import agents
from livekit.agents import (
Agent,
AgentServer,
AgentSession,
JobContext,
JobProcess,
cli,
)
from livekit.plugins import anthropic, deepgram, silero
load_dotenv()
logger = logging.getLogger("xxx.civr")
# ---------------------------------------------------------------------------
# System prompt — intentionally minimal in Phase 1
# This will be replaced by a dynamically built governing prompt in Phase 2
# once the Service Model and PPP context are flowing through.
# ---------------------------------------------------------------------------
SYSTEM_PROMPT = """\
You are Graham, a helpful medical office assistant for a xxx Healthcare.
You are speaking with a patient over the phone.
Rules:
- Keep responses SHORT — 1 to 3 sentences. You are speaking, not writing.
- Speak in a warm, professional tone.
- Never mention that you are an AI unless directly asked.
- If you don't know something, say you will have someone follow up.
"""
# ---------------------------------------------------------------------------
# Prewarm — runs once per worker process when the process initializes,
# BEFORE any job is dispatched to it. For local dev, this means the Silero model is
# already loaded in memory when the first call arrives — no cold-start
# delay on the first job. Subsequent jobs in the same process reuse it.
# ---------------------------------------------------------------------------
def prewarm(proc: JobProcess) -> None:
logging.getLogger("clarus.civr").info("prewarm: loading Silero VAD model")
proc.userdata["vad"] = silero.VAD.load(min_silence_duration=0.55)
logging.getLogger("clarus.civr").info("prewarm: Silero VAD ready")
# ---------------------------------------------------------------------------
# xxxIVRAgent
# In livekit-agents v1.x, Agent is the unit of conversation logic.
# AgentSession (below) wraps it and manages the NLP and Inference pipeline.
# on_enter() fires once when the agent first becomes active in the session —
# this is where the opening greeting goes.
# --------------------------------------------------------------------------
class xxxIVRAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions=SYSTEM_PROMPT,
)
async def on_enter(self) -> None:
"""Speaks the opening greeting as soon as the agent joins the room."""
await self.session.say(
"Hello, thank you for calling. This is Graham. How can I help you today?"
)
<http://logger.info|logger.info>("greeting fired")
server = AgentServer()
@server.rtc_session(agent_name="xxx-civr-agent")
async def session_handler(ctx: JobContext):
print(">>> JOB RECEIVED <<<", flush=True)
await ctx.connect()
session = AgentSession(
stt=deepgram.STT(model="nova-3", language="en-US"),
llm=anthropic.LLM(model="claude-haiku-4-5-20251001", temperature=0.1),
tts=deepgram.TTS(model="aura-2-andromeda-en"),
vad=silero.VAD.load(),
)
await session.start(agent=xxxIVRAgent(), room=ctx.room)
if __name__ == "__main__":
cli.run_app(server)
The Agent is connecting to the PSTN via a Telnyx SIP Trunk, and what I can see is that LiveKit is not returning a 200OK back to Telnyx after the room is created. I have the pcap files if needed. Even though in the Telnyx console it shows the 200OK is being generated, there is no evidence of this in the LiveKit pcap.
When I run the app locally and then use a separate terminal window to manually run the dispatch:
lk dispatch create \
--agent-name xxx-civr-agent \
--room civr_poc_RM_3GSRPFmpG7Vt
Using default project [civr]
Dispatch created: id:"AD_nmHDCdwhCSb8" agent_name:"xxx-civr-agent" room:"civr_poc_RM_3GSRPFmpG7Vt" state:{created_at:1775670724438439169}
I can see that the agent DOES get created:
k dispatch create \
--agent-name xxx-civr-agent \
--room civr_poc_RM_3GSRPFmpG7Vt
Using default project [civr]
Dispatch created: id:"AD_nmHDCdwhCSb8" agent_name:"xxx-civr-agent" room:"civr_poc_RM_3GSRPFmpG7Vt" state:{created_at:1775670724438439169}tall-airline-16756
04/08/2026, 6:06 PMtall-airline-16756
04/08/2026, 6:07 PMlk dispatch create works).
Root cause to check first
• You’re using explicit dispatch by setting agent_name="xxx-civr-agent" in @server.rtc_session. Explicit dispatch only receives jobs that are dispatched to that exact agent name. Inbound SIP calls won’t dispatch that agent unless your SIP dispatch rule is configured to dispatch that named agent.
Two immediate fixes to try
• Configure your SIP dispatch rule to include your agent (recommended if you want this agent for incoming calls). Add the agent under room_config.agents for the rule that handles your Telnyx inbound destinations.
• Or remove agent_name (use @server.rtc_session()), which enables automatic dispatch so an agent is created for each new room.
Quick verification steps
1. Confirm your dispatch rule (Dashboard or API) for the SIP trunk includes:
◦ room_config.agents containing agentName: "xxx-civr-agent".
1. After updating, place a test inbound call and watch the agent dispatch events (or the agent worker logs) — you should see the dispatch created automatically.
2. If still failing, capture:
◦ LiveKit server logs around the room creation time
◦ The LiveKit pcap for that call
◦ The active dispatch rules (JSON) for the SIP route
If you want, share the dispatch rule JSON (redact secrets) or the relevant LiveKit logs and I’ll point out exactly what to change.
Sources: Agent dispatch | LiveKit Documentation | Avoiding crosstalk when multiple developers share a LiveKit project | LiveK... | Agents Framework | livekit/livekit_composite | DeepWiki, with interactive elementsdry-baker-25622
04/08/2026, 6:24 PMtall-airline-16756
04/08/2026, 6:24 PMclarus-civr-agent under Agents, so that part looks correct.
Next checks to narrow the problem
• Confirm the SIP route that Telnyx hits matches this dispatch rule (same SipTrunks condition or any covers your trunk).
• Verify the actual room name created by the inbound call matches the rule’s RoomName pattern call-_<caller>_<random> (pattern must match exactly).
• Check the LiveKit server logs at the time of the inbound call for a dispatch creation event or any errors when evaluating the rule.
• Confirm your agent worker is running and connected to the same LiveKit project when the inbound call arrives (same project, env, and agent name).
• Ensure there are no extra whitespace/case differences in the agent name (exact match required).
If those all look okay, capture these and share them (redact secrets):
• The exact room name created for the inbound call.
• The LiveKit server log lines around room creation (±30s).
• The dispatch rule JSON (not the console table) to confirm pattern matching.
Share any of the above and I’ll point out the exact mismatch to fix.
, with interactive elementsdry-baker-25622
04/08/2026, 6:26 PMtall-airline-16756
04/08/2026, 6:27 PM<any>, any inbound trunk will match.
What to check next
• Confirm you’re viewing the same LiveKit project/organization in the dashboard as where you created the rule.
• Confirm the create call returned success (no error) and that the rule ID matches what you expect.
• If you created the rule “online” via API/CLI but it doesn’t show:
◦ Refresh the dashboard and retry selecting the project.
◦ Re-run the list API/CLI command (e.g., lk sip-dispatch list) to confirm the rule exists server-side.
• Verify the rule’s SipTrunks field:
◦ If it’s <any> — any trunk (including your Telnyx trunk) should match.
◦ If it lists specific trunks — the inbound trunk ID used by Telnyx must be one of those listed.
If it still doesn’t appear, gather and share:
• The create/list CLI output (redact secrets).
• The project name/ID you’re viewing in the dashboard.
• The inbound call’s trunk ID (as seen in Telnyx or LiveKit logs).
Share those and I’ll help pinpoint whether the rule was created in the wrong project or why it isn’t matching inbound calls.
, with interactive elementsdry-baker-25622
04/08/2026, 6:29 PMtall-airline-16756
04/08/2026, 6:30 PMlk cloud auth
◦ Complete the browser sign-in and select the desired project. The CLI will set that project as the default.
1. Confirm the active project:
◦ Run: lk project list or any list command like lk agent list to confirm resources show from the intended project.
1. Target a project temporarily (optional):
◦ Use flags: lk ... --api-key <KEY> --api-secret <SECRET> --url <URL>
◦ Or set env vars for the session:
▪︎ export LIVEKIT_API_KEY=<KEY>
▪︎ export LIVEKIT_API_SECRET=<SECRET>
▪︎ export LIVEKIT_URL=<URL>
1. Re-run your create/list commands:
◦ Example: lk sip-dispatch list to verify the dispatch rule exists in the selected project.
If you want, tell me the output of lk project list or lk sip-dispatch list and I’ll confirm you’re in the right project.
Sources: Avoiding crosstalk when multiple developers share a LiveKit project | LiveK... | Server-side | livekit/livekit_composite | DeepWiki, with interactive elementsdry-baker-25622
04/08/2026, 6:36 PMtall-airline-16756
04/08/2026, 6:36 PMclarus-civr-agent. No extra whitespace or truncation.
1. Confirm room name created by the inbound call:
◦ Share the exact room name (from LiveKit logs or pcap). It must match the rule pattern call-_<caller>_<random>.
1. Verify dispatch creation on inbound call:
◦ Immediately after an inbound call, run: lk dispatch list and lk dispatch list --room <room-name> to see if a dispatch was created for that room.
1. Check LiveKit server logs for these timestamps (±30s around the call):
◦ Room created
◦ Dispatch evaluated/created
◦ Any errors about agent matching or worker connection
1. Confirm the agent worker is connected to the same project at call time:
◦ Ensure the worker’s env (LIVEKIT_API_KEY/SECRET/URL or lk cloud auth selection) is the same project as the dispatch rule.
◦ Confirm your agent process shows it’s connected (startup logs from AgentServer/cli) before the call.
1. Trunk matching behavior (already covered):
◦ Your trunk config sipTrunkId: ST_kbnLZs5QqzwV is fine because the dispatch rule uses <any>. If you later list specific trunks, the inbound trunk ID must be included.
If you still don’t see dispatches or agent logs, please provide:
• The exact room name created for a recent inbound call.
• LiveKit server log lines around that call (±30s).
• Output of lk dispatch list and lk dispatch list --room <room-name> after the call.
With those I’ll pinpoint whether the rule matched, whether a dispatch was created, or whether the worker failed to accept the job.
, with interactive elementsdry-baker-25622
04/08/2026, 6:40 PM{
"sipDispatchRuleId": "SDR_z2d2JgByahxy",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "clarus-civr-inbound",
"roomConfig": {
"agents": [
{
"agentName": "clarus-civr-agent"
}
]
}
}
• The new error is: "call was not authorized by trunk configuration"tall-airline-16756
04/08/2026, 6:40 PMsipTrunks field shown. If you want to restrict to specific trunks, add the trunk ID ST_kbnLZs5QqzwV to the rule’s sipTrunks. If you want any trunk to match, explicitly set sipTrunks: ["<any>"] (or the equivalent in the API/CLI).
1. Verify the trunk’s allowed numbers/origination
◦ Confirm the Telnyx call arrives from a number or SIP peer that the trunk config allows (the numbers list or trunk-level ACLs). If the inbound caller number is not permitted, LiveKit will block the call.
1. Confirm trunk credentials and gateway settings
◦ Make sure the Telnyx SIP trunk is configured exactly as LiveKit expects (SIP URI, auth, media encryption policy). Any mismatch can cause authorization failures.
1. Recreate a minimal test rule
◦ Temporarily create a dispatch rule with:
▪︎ roomPrefix: "call-"
▪︎ sipTrunks: ["<any>"]
▪︎ agents: [{ "agentName": "clarus-civr-agent" }]
◦ Place a test inbound call and check if the call is accepted and a dispatch is created.
What to gather if it still fails
• LiveKit server logs at the call time (authorization failure lines).
• The exact inbound trunk ID and the Telnyx SIP INVITE details (From/To/Request-URI).
• The dispatch rule JSON after your change and output of lk sip-dispatch list.
If you share the logs or the INVITE summary, I’ll point to the exact field causing the authorization failure.
, with interactive elementsdry-baker-25622
04/08/2026, 6:44 PM{
"sipTrunkId": "ST_kbnLZs5QqzwV",
"name": "clarus-civr-agent",
"numbers": [
"+19724728758"
],
"krispEnabled": true,
"mediaEncryption": "SIP_MEDIA_ENCRYPT_ALLOW",
"createdAt": "2026-04-06T17:44:06.161528Z",
"updatedAt": "2026-04-08T13:53:33.925129Z"
}
The 972 number is where the call is originating from Telnyx. My current inbound routing is set according to your dashboard to accept ALL inbound numbers and trunks.tall-airline-16756
04/08/2026, 6:44 PM"sipTrunks": ["ST_kbnLZs5QqzwV"] to the dispatch rule JSON.
◦ To allow any trunk (test only):
▪︎ Add "sipTrunks": ["<any>"] to the dispatch rule JSON.
1. Re-create or patch the rule via CLI/API and verify it’s present:
◦ Run lk sip-dispatch list to confirm the rule shows the sipTrunks value you set.
1. Verify the inbound INVITE fields match the trunk config:
◦ The call must arrive on trunk ST_kbnLZs5QqzwV (Telnyx should be sending to that trunk).
◦ Confirm the Telnyx outbound number and SIP peer match the trunk’s `numbers`/allowed peers if you have trunk-level restrictions.
Diagnostics to collect if it still fails
• LiveKit server log lines for the rejected INVITE (authorization failure) — include timestamp.
• The INVITE summary: Request-URI, To, From, and the trunk ID Telnyx reports the call was sent to.
• Output of lk sip-dispatch list and the full dispatch rule JSON after your change.
If you want, paste the updated dispatch JSON (after adding sipTrunks) and one example INVITE summary and I’ll confirm the exact fields to match.
, with interactive elementsdry-baker-25622
04/08/2026, 6:45 PM{
"sipDispatchRuleId": "SDR_z2d2JgByahxy",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "clarus-civr-inbound",
"roomConfig": {
"agents": [
{
"agentName": "clarus-civr-agent"
}
]
}
}tall-airline-16756
04/08/2026, 6:46 PM"sipTrunks": ["ST_kbnLZs5QqzwV"] in your dispatch rule.
Put sipTrunks at the top level of the rule (alongside rule, name, and roomConfig). Example:
{
"sipTrunks": [
"ST_kbnLZs5QqzwV"
],
"sipDispatchRuleId": "SDR_z2d2JgByahxy",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "clarus-civr-inbound",
"roomConfig": {
"agents": [
{
"agentName": "clarus-civr-agent"
}
]
}
}
After updating, verify the rule server-side:
• lk sip-dispatch list (or lk sip-dispatch get SDR_z2d2JgByahxy)
Then test an inbound call. If it still fails, collect:
• LiveKit server log lines for the INVITE (timestamped),
• Telnyx INVITE summary (Request-URI, To, From, and trunk ID Telnyx reports),
• Output of lk sip-dispatch get SDR_z2d2JgByahxy.
Share those and I’ll help pinpoint the remaining authorization mismatch.
Sources: sip | livekit/livekit_composite | DeepWiki, with interactive elementsdry-baker-25622
04/08/2026, 6:48 PMtall-airline-16756
04/08/2026, 6:48 PMsipTrunks and how to proceed.
Why the UI is blocking this
• The dashboard may not expose editing that top-level sipTrunks field for an existing rule. You can still set it via the CLI or API.
Two safe options (pick one)
1) Recreate the dispatch rule via CLI (recommended)
• Delete the existing rule:
◦ lk sip-dispatch delete SDR_z2d2JgByahxy
• Create a new rule with the trunk allowed (replace project context if needed):
◦ Create JSON file `dispatch.json`:
{
"sipTrunks": [
"ST_kbnLZs5QqzwV"
],
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "clarus-civr-inbound",
"roomConfig": {
"agents": [
{
"agentName": "clarus-civr-agent"
}
]
}
}
• Create rule:
◦ lk sip-dispatch create --file dispatch.json
2) Patch the existing rule via API
• Use the API/CLI update endpoint to patch sipTrunks (if CLI supports lk sip-dispatch update --file ... use that) with the same JSON body (omit sipDispatchRuleId if required).
• Or call the REST API PATCH/PUT for the dispatch rule with the JSON above.
Testing tips
• For quick testing, allow any trunk: "sipTrunks": ["<any>"] then place an inbound call to confirm dispatch works, then tighten to the specific trunk ID.
• After create/update, verify server-side:
◦ lk sip-dispatch list
◦ lk sip-dispatch get SDR_z2d2JgByahxy
If you want, tell me which option you prefer and your CLI output after running it (or paste the lk sip-dispatch get result) and I’ll verify the rule JSON and next test steps.
, with interactive elementsdry-baker-25622
04/08/2026, 6:50 PMtall-airline-16756
04/08/2026, 6:50 PMunknown field "sipTrunks".
Use the API/CLI field name trunk_ids (not sipTrunks). Add it at the top level of the dispatch rule. Example payload:
{
"trunk_ids": [
"ST_kbnLZs5QqzwV"
],
"sipDispatchRuleId": "SDR_z2d2JgByahxy",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "clarus-civr-inbound",
"roomConfig": {
"agents": [
{
"agentName": "clarus-civr-agent"
}
]
}
}
What to do next
• If the UI won’t accept it, recreate or patch the rule via CLI/API:
◦ Delete existing: lk sip-dispatch delete SDR_z2d2JgByahxy
◦ Create with JSON file: lk sip-dispatch create --file dispatch.json
◦ Or use the API CreateSIPDispatchRuleRequest with trunk_ids=[...].
• Verify with: lk sip-dispatch get SDR_z2d2JgByahxy (or lk sip-dispatch list).
After you update, test an inbound call and tell me the result or paste the lk sip-dispatch get output and any server log lines if it still fails.
Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | Python SDKs | livekit/livekit_composite | DeepWiki | examples/bank-ivr | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit Documentation | Creating SIP Inbound Trunks and Dispatch Rules with Python SDK, with interactive elementsdry-baker-25622
04/08/2026, 6:56 PMtall-airline-16756
04/08/2026, 6:56 PMlk sip-dispatch get SDR_z2d2JgByahxy
◦ lk sip-dispatch list
◦ lk sip trunk list
• One timestamped LiveKit server log block (±30s) showing the authorization failure when an inbound call arrives.
• A short summary of the Telnyx INVITE as seen in Telnyx (Request-URI, To, From, and trunk ID).
With those I’ll pinpoint the exact mismatch and give the precise CLI/API payload and commands to fix it.
, with interactive elementsdry-baker-25622
04/08/2026, 7:01 PMtall-airline-16756
04/08/2026, 7:02 PMST_kbnLZs5QqzwV and the trunk exists, but the agent still isn’t invoked.
What’s confirmed
• Dispatch rule SDR_kQSWkRM5ZXAn includes trunk ST_kbnLZs5QqzwV.
• Trunk ST_kbnLZs5QqzwV exists and lists number +19724728758.
• The rule maps agent clarus-civr-agent.
Next targeted checks (please run and paste results or confirm)
1) Confirm agent name exact match in code
• In your agent code, ensure the decorator uses exactly: @server.rtc_session(agent_name="clarus-civr-agent") (no extra whitespace/case difference).
2) Confirm room naming on inbound call
• Immediately after an inbound call, run:
◦ lk sip-dispatch list --verbose
◦ lk dispatch list --room the-room-name
• Share the exact room name created (or confirm if none created).
3) Check dispatch creation for that call
• Right after a test inbound call run:
◦ lk dispatch list
◦ lk dispatch list --room room-name
• If no dispatch entry for the room exists, the rule didn’t match or the call was blocked.
4) Check LiveKit server logs at call time (copy exact lines ±30s)
• Look for: room create, dispatch rule evaluation, dispatch create, or authorization failure messages.
• Paste the log block here (redact secrets).
5) Confirm agent worker/process connection
• On the machine running your agent, confirm the worker shows connected to the same project before the call:
◦ Share the agent start logs (first 10–20 lines) that show project or connection info.
• Confirm the agent process is running and listening when you place the inbound call.
6) Verify Telnyx INVITE -> trunk mapping
• From Telnyx, get the INVITE summary for the failed call: Request-URI, To, From, and the Telnyx trunk destination ID. Share those values.
If these checks all look correct and you still see no dispatch or logs, paste:
• The LiveKit server log lines around the call,
• The lk dispatch list and lk sip-dispatch get SDR_kQSWkRM5ZXAn outputs after the call,
• One example INVITE summary from Telnyx.
With those I’ll pinpoint whether the rule matched, whether LiveKit rejected the call, or whether the worker failed to accept the job.
, with interactive elementsdry-baker-25622
04/08/2026, 7:09 PMtall-airline-16756
04/08/2026, 7:09 PM