Routing
OpenRTC resolves the active agent for each incoming session through a priority chain. The chain runs before ctx.connect(), so it must work entirely from pre-connect metadata.
Priority chain
Strategies run in order. The first one that returns a match wins.
| Priority | Source | How it works |
|---|
| 1 | ctx.job.metadata["agent"] | Agent name from the job assignment metadata (set by your dispatch caller). |
| 2 | ctx.job.metadata["demo"] | Legacy fallback key, same format. |
| 3 | ctx.job.room.metadata["agent"] | Room metadata from the job’s room assignment (read before connect; authoritative). |
| 4 | ctx.job.room.metadata["demo"] | Legacy fallback key, same format. |
| 5 | Room name prefix | Room name begins with <agent-name>-, e.g. support-call-123 routes to support. |
| 6 | First registered agent | Default fallback: the agent registered first with pool.add(). |
Strategies 3 and 4 read ctx.job.room.metadata, not ctx.room.metadata. The rtc.Room (ctx.room) is empty until ctx.connect() is called. The job assignment carries the authoritative room metadata from LiveKit’s dispatch system before the room connects.
Pass metadata as a JSON object with an "agent" key:
OpenRTC accepts:
- A JSON string:
'{"agent": "support"}' (common from LiveKit CreateRoom)
- A Python dict:
{"agent": "support"} (common from test fixtures)
The "demo" key is an alias for "agent" with lower priority. You can use it for showcase scenarios where the primary "agent" key is absent.
Non-JSON strings, blank strings, and JSON scalars (e.g. "42") are ignored — the strategy defers to the next one. An absent metadata field is also a no-op (no error).
When dispatching a job with the LiveKit SDK, set job.metadata:
from livekit import api
await lk_api.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
agent_name="my-worker",
room="my-room",
metadata='{"agent": "dental"}',
)
)
OpenRTC reads ctx.job.metadata first (priority 1), which resolves before the room connects.
Set the room’s metadata when creating it:
await lk_api.room.create_room(
api.CreateRoomRequest(
name="dental-room-42",
metadata='{"agent": "dental"}',
)
)
OpenRTC reads ctx.job.room.metadata (priority 3) — the LiveKit dispatch system copies the room metadata onto the job assignment, so routing works pre-connect.
Routing via room name prefix
If neither job nor room metadata contains an "agent" key, OpenRTC checks whether the room name starts with a registered agent name followed by -:
dental-call-1234 → dental
restaurant-room-42 → restaurant
general-chat → (no match, falls through to default)
This is convenient for low-config deployments where the room naming convention is enough to route.
Default fallback
If no strategy matches, OpenRTC routes to the first registered agent (the first call to pool.add()). This means a single-agent pool always resolves, and a multi-agent pool has a sensible default for sessions that carry no routing signal.
Error behavior
| Condition | Behavior |
|---|
| Metadata specifies an agent name that is not registered | Raises ValueError("Unknown agent '...' requested via ...") — no silent fallback |
| Pool has no registered agents | Raises RuntimeError("No agents are registered in the pool.") |
| Valid agent resolved | Logs Resolved agent '<name>' via <source>. at INFO level |
The deliberate error-on-unknown keeps routing failures loud. A typo in a metadata value surfaces immediately rather than silently falling through to the wrong agent.
Checking routing in the CLI
openrtc list --agents-dir ./agents
This prints each registered agent and its configured providers. Registration order determines default fallback order.