Dispatch rules: routing calls to agents
Dispatch rules: routing calls to agents
You have a phone number and an inbound SIP trunk. Calls can now reach LiveKit's SIP bridge. But what happens next? Without dispatch rules, LiveKit accepts the SIP connection and then has nowhere to send it. Dispatch rules are the routing layer that connects incoming calls to rooms -- and rooms are where your agent lives.
What you'll learn
- What dispatch rules are and why they exist
- The difference between individual room and room prefix dispatch
- How to create dispatch rules via the CLI and the API
- How room naming connects dispatch rules to agent registration
What is a dispatch rule?
A dispatch rule answers a simple question: "When a call arrives on this trunk, which room should the caller join?"
Without a dispatch rule, incoming SIP calls are rejected. The trunk tells LiveKit to accept the SIP signaling, but the dispatch rule tells it what to do with the call. Think of the trunk as the front door and the dispatch rule as the receptionist who directs visitors to the right meeting room.
In LiveKit's architecture, agents register to handle specific room patterns. When a room is created that matches an agent's pattern, LiveKit dispatches that agent to join the room. Dispatch rules create the rooms that trigger agent dispatch. The chain is: incoming call -> dispatch rule creates/joins room -> agent is dispatched to room -> conversation begins.
Dispatch rule types
LiveKit supports two main dispatch strategies, and each serves a different use case.
Individual room (one room per call)
Each incoming call gets its own room with a unique name. This is the most common pattern for AI agents because each caller gets a private conversation.
With a room prefix of dental-, an incoming call creates a room like dental-abc123def456. The next call creates dental-xyz789ghi012. Each room has exactly one phone caller and one agent -- a private conversation.
Use individual rooms for AI agents
For your dental receptionist, individual room dispatch is almost certainly what you want. Each patient gets a private room with their own agent instance. The agent can maintain conversation context without interference from other callers.
Specific room (all calls to one room)
All incoming calls on the trunk are routed to the same named room. This is useful when you want multiple callers in a single conference-style room, but it is rarely the right choice for AI agent scenarios.
Creating dispatch rules via CLI
Here is the CLI command to create a dispatch rule that gives each caller their own room:
lk sip dispatch create --trunk-id "ST_xxxxxxxxxxxx" --rule-type "individual" --room-prefix "dental-"Replace ST_xxxxxxxxxxxx with the trunk ID from the previous chapter. Every call arriving on that trunk will now create a new room with a name starting with dental-.
You can also create a rule that routes all calls to a single room:
lk sip dispatch create --trunk-id "ST_xxxxxxxxxxxx" --rule-type "direct" --room-name "dental-office"List your existing dispatch rules:
lk sip dispatch listCreating dispatch rules via the API
Python
import asyncio
from livekit.api import (
LiveKitAPI,
CreateSIPDispatchRuleRequest,
SIPDispatchRule,
SIPDispatchRuleIndividual,
)
async def main():
api = LiveKitAPI()
rule = await api.sip.create_sip_dispatch_rule(
CreateSIPDispatchRuleRequest(
rule=SIPDispatchRule(
dispatch_rule_individual=SIPDispatchRuleIndividual(
room_prefix="dental-"
),
trunk_ids=["ST_xxxxxxxxxxxx"],
)
)
)
print(f"Created dispatch rule: {rule.sip_dispatch_rule_id}")
print(f"Room prefix: dental-")
await api.aclose()
asyncio.run(main())TypeScript
import { SipClient } from "livekit-server-sdk";
const sipClient = new SipClient(
process.env.LIVEKIT_URL!,
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
const rule = await sipClient.createSipDispatchRule({
rule: {
dispatchRuleIndividual: {
roomPrefix: "dental-",
},
},
trunkIds: ["ST_xxxxxxxxxxxx"],
});
console.log(`Created dispatch rule: ${rule.sipDispatchRuleId}`);Connecting dispatch rules to agent registration
The final piece of the puzzle is making sure your agent is registered to handle rooms that match the dispatch rule's naming pattern. When you registered your agent in Course 1.1, you specified which rooms it handles. Now that pattern must match the dispatch rule's room prefix.
Here is the connection:
Dispatch rule creates rooms with prefix
Your dispatch rule uses room_prefix="dental-". Every incoming call creates a room like dental-abc123.
Agent registers for matching rooms
Your agent's AgentServer is configured to handle rooms that start with dental-. When LiveKit creates a dental-abc123 room, it dispatches your agent to join.
Agent joins and conversation starts
Your dental receptionist agent joins the room, subscribes to the phone caller's audio track, and begins the conversation.
Your agent registration in agent.py ties everything together:
from livekit.agents import AgentServer, Agent, AgentSession
from livekit.plugins import openai, deepgram, cartesia
server = AgentServer()
@server.rtc_session
async def entrypoint(session: AgentSession):
await session.start(
agent=Agent(
instructions="""You are a friendly receptionist at Bright Smile Dental clinic.
Keep responses brief and conversational. Never use markdown or emojis.
Help callers with appointment inquiries, clinic hours, and general questions.""",
),
room=session.room,
stt=deepgram.STT(model="nova-3"),
llm=openai.LLM(model="gpt-4o-mini"),
tts=cartesia.TTS(voice="<voice-id>"),
)
if __name__ == "__main__":
server.run()No telephony code in your agent
Notice there is zero telephony-specific code in the agent. The agent handles rooms and participants. The dispatch rule creates the right room name. The SIP bridge handles the phone-to-WebRTC translation. Each layer does its job independently.
The complete flow
Let's trace a call from start to finish with all the pieces in place:
Patient dials +15551234567
The call enters the PSTN and reaches your SIP trunk provider.
SIP trunk provider sends INVITE to LiveKit
The provider routes the call to LiveKit's SIP bridge.
SIP bridge matches the inbound trunk
LiveKit sees an incoming call to +15551234567 and matches it to your inbound trunk ST_xxxxxxxxxxxx.
Dispatch rule creates a room
The dispatch rule on that trunk says "individual rooms with prefix dental-", so LiveKit creates room dental-f7a2b9c3.
Phone caller joins as SIP participant
The caller enters the room as a ParticipantKind.SIP participant with their phone audio published as a Track.
Agent is dispatched to the room
LiveKit sees a new dental-* room and dispatches your registered agent. The agent joins, subscribes to the caller's audio, and begins the dental receptionist conversation.
The beauty of this architecture is separation of concerns. The SIP bridge handles telephony. Dispatch rules handle routing. Your agent handles conversation. You can change the phone number, switch trunk providers, or modify routing logic -- all without touching a single line of agent code.
Test your knowledge
Question 1 of 3
Why does an incoming SIP call fail even after configuring an inbound trunk without a dispatch rule?
What you learned
- Dispatch rules determine what happens when a call arrives on a trunk
- Individual room dispatch creates a unique private room per call (best for AI agents)
- Direct room dispatch routes all calls to one room (conference style)
- Room naming from dispatch rules must match your agent's registration pattern
- The complete chain is: phone call -> trunk -> dispatch rule -> room -> agent
Next up
Your agent can now receive phone calls. But before diving into the conversation, there is often a legal requirement: recording consent. In the next chapter, you will build an AgentTask that collects consent before handing off to your main dental receptionist agent.