Routing
OpenRTC resolves the active agent for each incoming session through a priority chain. The chain runs beforectx.connect(), so it must work entirely from pre-connect metadata.
Priority chain
Strategies run in order. The first one that returns a match wins.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.Metadata format
Pass metadata as a JSON object with an"agent" key:
- A JSON string:
'{"agent": "support"}'(common from LiveKit CreateRoom) - A Python dict:
{"agent": "support"}(common from test fixtures)
"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).
Routing a session via job metadata
When dispatching a job with the LiveKit SDK, setjob.metadata:
ctx.job.metadata first (priority 1), which resolves before the room connects.
Routing a session via room metadata
Set the room’s metadata when creating it: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 -:
Default fallback
If no strategy matches, OpenRTC routes to the first registered agent (the first call topool.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.
Scoping which rooms a worker accepts
The priority chain runs after a worker has accepted a job, and thanks to the default fallback it always resolves some agent. That is the right behavior for a worker that owns its LiveKit project, but the wrong behavior when workers share one. Under automatic dispatch, LiveKit offers every room to every registered worker. If two OpenRTC workers (or an OpenRTC worker beside a non-OpenRTC agent) share a project, each worker would accept rooms meant for the other and default-route them onto its first agent. Filter jobs one layer earlier, at acceptance time, with LiveKit’s per-jobon_request hook:
A request filter decides whether to take a job; the priority chain decides which agent handles the jobs you took. They are independent: a job that passes the filter still runs through the full chain.
Convenience: accept only your own rooms
- job or room metadata names a registered agent (
{"agent": "support"}), or - the room name is prefixed with a registered agent name (
support-call-123).
req.reject(). This mirrors the priority chain minus the default fallback, so foreign rooms (which no registered agent claims) are declined instead of grabbed. Metadata naming an unregistered agent is treated as “not mine” and rejected, never raised.
Full control: a custom filter
Pass any asyncon_request handler as request_fnc:
request_fnc defaults to None (accept every job, LiveKit’s default). It is mutually exclusive with accept_only_registered_rooms. RequestFilter is exported for typing your own filters.
Error behavior
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.

