Skip to main content

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.
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:
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).

Routing a session via job metadata

When dispatching a job with the LiveKit SDK, set job.metadata:
OpenRTC reads 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:
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 -:
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.

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-job on_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

The worker accepts a job only when an explicit routing signal maps it to one of this pool’s agents:
  • job or room metadata names a registered agent ({"agent": "support"}), or
  • the room name is prefixed with a registered agent name (support-call-123).
Everything else is rejected via 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 async on_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.

Checking routing in the CLI

This prints each registered agent and its configured providers. Registration order determines default fallback order.