Chapter 315m

Agent dispatch

Agent dispatch

Your token endpoint works, but there is a gap: how does LiveKit know which agent to put in the room? Right now it uses automatic dispatch — every new room gets the same agent. That is fine for a single-agent project, but the moment you have two agents (say a receptionist and a billing specialist), you need explicit control. This chapter covers how agent dispatch works and how to configure it from both the agent server and the frontend.

Automatic dispatchExplicit dispatchagent_nameRoomAgentDispatchJob metadata

What you'll learn

  • The difference between automatic and explicit agent dispatch
  • How to register an agent with a name on the server side
  • How to dispatch a specific agent from your token endpoint
  • How to pass metadata to the agent through dispatch
  • How to dispatch multiple agents into a single room
  • When to use API dispatch vs. token-based dispatch

How dispatch works

When a participant connects to a room, LiveKit server needs to decide which agent server should handle the session. This process is called dispatch. LiveKit optimizes it for high concurrency — hundreds of thousands of connections per second with dispatch latency under 150 ms.

There are two modes:

ModeHow it worksBest for
AutomaticEvery new room gets the agent. No configuration needed.Single-agent projects, prototyping
ExplicitYou specify which agent(s) to dispatch by name.Multi-agent projects, production apps
What's happening

Think of automatic dispatch like a restaurant with one chef — every order goes to the same person. Explicit dispatch is like a restaurant with a sushi chef and a pastry chef. You need to tell the kitchen which chef should handle which order. The dispatch system is that routing layer.

Automatic dispatch: the default

If you followed Course 1.1, your agent already uses automatic dispatch. When you define an entrypoint without an agent_name, LiveKit dispatches it to every new room:

agent.pypython
from livekit.agents import AgentSession, Agent, RtcSession

@server.rtc_session()
async def entrypoint(ctx: RtcSession):
  agent = Agent(instructions="You are a helpful dental receptionist.")
  session = AgentSession()
  await session.start(agent=agent, room=ctx.room)

This is zero-configuration and convenient for getting started. But the moment you deploy a second agent in the same LiveKit project, both agents would try to join every room. That is not what you want.

Explicit dispatch: naming your agent

To switch to explicit dispatch, give your agent a name. This single change tells LiveKit: "do not auto-dispatch this agent — wait until someone asks for it by name."

agent.pypython
@server.rtc_session(agent_name="dental-receptionist")
async def entrypoint(ctx: RtcSession):
  agent = Agent(instructions="You are a helpful dental receptionist.")
  session = AgentSession()
  await session.start(agent=agent, room=ctx.room)
agent.tstypescript
const server = new AgentServer({
agentName: "dental-receptionist",
});

Setting agent_name disables automatic dispatch

Once you set agent_name, the agent will not be dispatched to any room unless explicitly requested. If you deploy this change without updating your token endpoint, your frontend will connect to an empty room with no agent.

Dispatching from your token endpoint

The most common way to dispatch an agent is through the participant's access token. When the token includes a RoomConfiguration with agent dispatch information, LiveKit dispatches the named agent as soon as the participant connects.

Update the token endpoint you built in the previous chapter:

app/api/token/route.tstypescript
import { AccessToken } from "livekit-server-sdk";
import { RoomAgentDispatch, RoomConfiguration } from "@livekit/protocol";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
const roomName = request.nextUrl.searchParams.get("roomName");
const participantName = request.nextUrl.searchParams.get("participantName");

if (!roomName || !participantName) {
  return NextResponse.json(
    { error: "Missing roomName or participantName" },
    { status: 400 }
  );
}

const at = new AccessToken(
  process.env.LIVEKIT_API_KEY,
  process.env.LIVEKIT_API_SECRET,
  {
    identity: participantName,
    ttl: "10m",
  }
);

at.addGrant({
  roomJoin: true,
  room: roomName,
  canPublish: true,
  canSubscribe: true,
});

// Dispatch the dental receptionist agent when the participant connects
at.roomConfig = new RoomConfiguration({
  agents: [
    new RoomAgentDispatch({
      agentName: "dental-receptionist",
    }),
  ],
});

const token = await at.toJwt();
return NextResponse.json({ token });
}

The Python equivalent for a FastAPI backend:

token_server.pypython
from livekit.api import AccessToken, VideoGrants, RoomAgentDispatch, RoomConfiguration

def create_token(room_name: str, participant_name: str) -> str:
  token = (
      AccessToken()
      .with_identity(participant_name)
      .with_grants(VideoGrants(room_join=True, room=room_name))
      .with_room_config(
          RoomConfiguration(
              agents=[
                  RoomAgentDispatch(agent_name="dental-receptionist")
              ],
          ),
      )
      .to_jwt()
  )
  return token
What's happening

Token-based dispatch is the recommended approach for frontend applications. The dispatch configuration travels with the token, so the agent is assigned the moment the participant connects — no extra API calls needed. The Session API on the frontend does not need any changes; it just works.

Passing metadata to the agent

Explicit dispatch lets you pass a metadata string to the agent. This metadata is available in the agent's RtcSession context, making it a clean way to send user-specific information without an extra API call.

app/api/token/route.ts (metadata example)typescript
at.roomConfig = new RoomConfiguration({
agents: [
  new RoomAgentDispatch({
    agentName: "dental-receptionist",
    metadata: JSON.stringify({
      userId: "user-123",
      userName: "Jane Doe",
      appointmentId: "apt-456",
    }),
  }),
],
});

On the agent side, read the metadata from the job context:

agent.pypython
import json

@server.rtc_session(agent_name="dental-receptionist")
async def entrypoint(ctx: RtcSession):
  metadata = json.loads(ctx.job.metadata or "{}")
  user_name = metadata.get("userName", "there")

  agent = Agent(
      instructions=f"You are a dental receptionist. The caller's name is {user_name}."
  )
  session = AgentSession()
  await session.start(agent=agent, room=ctx.room)

Use JSON for structured metadata

The metadata field is a plain string. LiveKit recommends JSON so both sides can serialize and deserialize structured data reliably. Keep it small — this is for routing context, not large payloads.

Dispatching multiple agents

A single token can dispatch multiple agents into the same room. This is useful when you want a primary conversational agent plus a background agent for tasks like note-taking or real-time translation.

app/api/token/route.ts (multi-agent)typescript
at.roomConfig = new RoomConfiguration({
agents: [
  new RoomAgentDispatch({
    agentName: "dental-receptionist",
    metadata: JSON.stringify({ role: "primary" }),
  }),
  new RoomAgentDispatch({
    agentName: "note-taker",
    metadata: JSON.stringify({ role: "observer" }),
  }),
],
});

Each agent receives its own job with its own metadata. They join the same room as separate participants and can interact with the user and with each other through tracks and data channels.

Dispatch via API

Sometimes you need to dispatch an agent without a participant connecting first — for example, when making an outbound phone call or pre-warming a room. The AgentDispatchService API handles this:

dispatch_api.pypython
from livekit import api

async def dispatch_agent_to_room():
  lkapi = api.LiveKitAPI()

  dispatch = await lkapi.agent_dispatch.create_dispatch(
      api.CreateAgentDispatchRequest(
          agent_name="dental-receptionist",
          room="outbound-call-room-123",
          metadata='{"phone": "+15551234567"}',
      )
  )
  print(f"Dispatched agent: {dispatch.agent_name} to {dispatch.room}")
  await lkapi.aclose()
dispatch_api.tstypescript
import { AgentDispatchClient } from "livekit-server-sdk";

async function dispatchAgentToRoom() {
const client = new AgentDispatchClient(
  process.env.LIVEKIT_URL!,
  process.env.LIVEKIT_API_KEY!,
  process.env.LIVEKIT_API_SECRET!
);

const dispatch = await client.createDispatch(
  "outbound-call-room-123",
  "dental-receptionist",
  { metadata: JSON.stringify({ phone: "+15551234567" }) }
);
console.log("Dispatched agent:", dispatch.agentName);
}

The room is automatically created if it does not already exist. This is the pattern used for outbound calling — dispatch the agent first, then dial the phone number into the same room.

What's happening

API dispatch and token dispatch are not mutually exclusive. You might use token dispatch for web users (the agent joins when they connect) and API dispatch for outbound calls (the agent joins before the callee picks up). Both use the same agent server infrastructure — the only difference is who triggers the dispatch.

Choosing a dispatch strategy

ScenarioStrategyWhy
Single agent, all roomsAutomaticZero configuration, simplest setup
Web frontend, 1:1 sessionsToken dispatchAgent arrives with the user, no extra API calls
Multiple agents per roomToken dispatch (multiple entries)All agents dispatched in one token
Outbound phone callsAPI dispatchAgent must be in the room before the call connects
Background job / no participantAPI dispatchNo participant token to carry dispatch info
Dynamic agent selection at runtimeAPI dispatchServer logic decides which agent based on business rules

Test your knowledge

Question 1 of 3

What happens if you set agent_name on your agent server but do not include dispatch configuration in the participant's token?

What you learned

  • Automatic dispatch sends the same agent to every room — simple but inflexible
  • Explicit dispatch requires setting agent_name on the agent server, which disables automatic dispatch
  • Token-based dispatch embeds RoomAgentDispatch in the access token's RoomConfiguration — the recommended approach for frontends
  • Dispatch metadata passes user context (IDs, names, preferences) to the agent without extra API calls
  • Multiple agents can be dispatched to the same room from a single token
  • API dispatch via AgentDispatchService is used when no participant triggers the connection (outbound calls, background jobs)

Next up

Your agent is dispatched and in the room. In the next chapter, you will track its lifecycle — connecting, listening, thinking, speaking — and build UI that responds to every state change.

Concepts covered
Automatic dispatchExplicit dispatchagent_nameRoomAgentDispatchJob metadata