aloof-account-63566
08/17/2025, 6:05 PMtall-airline-16756
08/17/2025, 6:05 PMtall-airline-16756
08/17/2025, 6:05 PMAgentDispatchService
. Here’s an example: import asyncio
from livekit import api
room_name = "my-room"
agent_name = "test-agent"
async def create_explicit_dispatch():
lkapi = api.LiveKitAPI()
dispatch = await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
agent_name=agent_name,
room=room_name,
metadata='{"user_id": "12345"}'
)
)
print("created dispatch", dispatch)
dispatches = await lkapi.agent_dispatch.list_dispatch(room_name=room_name)
print(f"there are {len(dispatches)} dispatches in {room_name}")
await lkapi.aclose()
asyncio.run(create_explicit_dispatch())
• Using Room Configuration in Access Tokens: Another way is to dispatch an agent by configuring the room in the access token when a participant connects. Check this out: from livekit.api import (
AccessToken,
RoomAgentDispatch,
RoomConfiguration,
VideoGrants,
)
room_name = "my-room"
agent_name = "test-agent"
def create_token_with_agent_dispatch() -> str:
token = (
AccessToken()
.with_identity("my_participant")
.with_grants(VideoGrants(room_join=True, room=room_name))
.with_room_config(
RoomConfiguration(
agents=[RoomAgentDispatch(agent_name=agent_name, metadata='{"mydata": "myvalue"}')],
),
)
.to_jwt()
)
return token
Just a couple of things to keep in mind:
• Make sure to set the agent_name
in Worker Options when registering the agent. This turns off automatic dispatch and lets you take explicit control.
• Agents need to be registered with the LiveKit server and set up to accept explicit dispatches.
Explicit dispatch is great because it lets you decide exactly when agents join rooms—perfect for customizing your workflows. If you want me to walk you through anything else or share more examples, just let me know! Sources: Dispatching agents | LiveKit Docs | An open source stack for real-time multimodal AIaloof-account-63566
08/17/2025, 6:15 PMtall-airline-16756
08/17/2025, 6:16 PMagent_name
in Worker Options for both agents. This helps prevent the second agent from being dispatched automatically, giving you full control.
◦ Also, make sure each agent has a unique agent_name
to keep things running smoothly without conflicts.
• Step 4: Manage Deployments
◦ Use handy LiveKit CLI commands like lk agent create
and lk agent deploy
to manage your agent deployments easily.
With this approach, your first agent gets deployed automatically, and the second agent is programmatically deployed into a new room created by the first agent. If you want me to walk you through any part of this or share more examples, just let me know—I’m here to help! Sources: LiveKit CLI | livekit/livekit_composite | DeepWiki | Dispatching agents | LiveKit Docsaloof-account-63566
08/17/2025, 6:18 PMtall-airline-16756
08/17/2025, 6:19 PMaloof-account-63566
08/17/2025, 6:21 PMtall-airline-16756
08/17/2025, 6:21 PMaloof-account-63566
08/17/2025, 11:52 PMtall-airline-16756
08/17/2025, 11:53 PMaloof-account-63566
08/17/2025, 11:59 PMtall-airline-16756
08/18/2025, 12:00 AMdispatchRuleIndividual
with a unique roomPrefix
helps create unique rooms for each call dynamically: {
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"trunkIds": [
"ST_YYBW4eKVVVVV"
],
"name": "twilio Dispatch Rule"
}
• Trunk Address Configuration: Double-check that your inbound and outbound trunk addresses aren’t causing any loops. Here’s an example: {
"outbound_address": "<http://maqualsdsfdh.pstn.twilio.com|maqualsdsfdh.pstn.twilio.com>",
"inbound_addresses": [],
"outbound_number": "+1628767867876",
"inbound_numbers": [
"+1949771CCC"
]
}
• Room Existence and Naming: Make sure the room mentioned in your dispatch rule actually exists and is valid. If you’re using dispatchRuleDirect
, the room needs to be there already to prevent re-routing issues.
• Authentication Configuration: If your trunks require authentication, double-check that the credentials are spot on to avoid any misrouting.
• Monitor Call Flow: Take a look at your LiveKit logs to spot any routing patterns that might hint at loops. This can really help pinpoint where things are going in circles.
Tackling these areas should help clear up that 482 error caused by routing loops. If you want, I’m here to help with more details or examples tailored to your setup—just let me know! Sources: livekit/livekit_sip.pb.go | SIP overview | LiveKit Docs | README.md | src/setup-livekit.ts | Create and configure SIP trunk | LiveKit Docsaloof-account-63566
08/18/2025, 12:26 AMtall-airline-16756
08/18/2025, 12:27 AMagent_name
in WorkerOptions: When you specify the agent_name
in the WorkerOptions
, it stops the automatic agent dispatch. This way, the default agent won’t automatically show up in new rooms. For example: opts = WorkerOptions(
...
agent_name="test-agent",
)
• Use Explicit Dispatch: Go ahead and explicitly dispatch the agent you want to the new room using the AgentDispatchClient
. Here’s how: lkapi = api.LiveKitAPI()
dispatch = await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
agent_name="outbound-caller", room="new-room", metadata="{\"user_id\": \"12345\"}"
)
)
• Optional: Configure Room in Access Token: If you’d like the agent to be dispatched right when a participant joins, you can set up the room in the access token. For example: token = (
api.AccessToken()
.with_identity("my_participant")
.with_grants(api.VideoGrants(room_join=True, room="new-room"))
.with_room_config(
api.RoomConfiguration(
agents=[
api.RoomAgentDispatch(agent_name="outbound-caller", metadata="{\"user_id\": \"12345\"}")
],
),
)
.to_jwt()
)
So, by setting agent_name for your second agent and dispatching explicitly, you’re all set to have just the agent you want in the new room—no surprises from the default agent. If you want to chat more or need a hand with anything else, just let me know!aloof-account-63566
08/18/2025, 1:48 AMtall-airline-16756
08/18/2025, 1:49 AMAgentDispatchService
to dispatch an agent to a room. Here’s a quick example: async def create_explicit_dispatch():
lkapi = api.LiveKitAPI()
dispatch = await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
agent_name="outbound-caller", room="new-room", metadata="{\"user_id\": \"12345\"}"
)
)
print("created dispatch", dispatch)
• Room Configuration in Access Token: When creating a room, you can include the agent configuration right in the access token. Check this out: def create_token_with_agent_dispatch() -> str:
token = (
api.AccessToken()
.with_identity("my_participant")
.with_grants(api.VideoGrants(room_join=True, room="new-room"))
.with_room_config(
api.RoomConfiguration(
agents=[api.RoomAgentDispatch(agent_name="outbound-caller", metadata="{\"user_id\": \"12345\"}")],
),
)
.to_jwt()
)
return token
Just a few things to keep in mind:
• Set agent_name
in Worker Options if you want to turn off automatic dispatch and take explicit control.
• Explicit dispatch makes sure that only the agent you specify gets assigned to the room.
• You can pass metadata during dispatch to share extra context — super handy!
If you want me to walk you through anything else or need more examples, just let me know. I’m here to help! Sources: Dispatching agents | LiveKit Docs | Agent dispatch | LiveKit Docsaloof-account-63566
08/18/2025, 1:55 AMtall-airline-16756
08/18/2025, 1:56 AMaloof-account-63566
08/18/2025, 2:03 AMtall-airline-16756
08/18/2025, 2:03 AMconst { AgentDispatchClient, WebhookReceiver } = require('livekit-server-sdk');
const http = require('http');
// Webhook server setup
const host = 'localhost';
const port = 3000;
// Initialize WebhookReceiver with your API credentials
const receiver = new WebhookReceiver(
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET
);
// Agent dispatch client
const agentDispatchClient = new AgentDispatchClient(process.env.LIVEKIT_URL);
// Create HTTP server
const server = http.createServer(async (req, res) => {
let data = '';
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', async () => {
try {
// Parse the webhook event
const event = receiver.receive(data, req.headers.authorization);
// Handle room_created event
if (event.event === 'room_created') {
const roomName = event.data.room.id;
// Explicitly dispatch agent to the newly created room
const agentName = 'your-agent-name'; // Replace with your agent name
const dispatch = await agentDispatchClient.createDispatch(
roomName,
agentName,
{
metadata: '{"room_created": "true"}', // Optional metadata
}
);
console.log(`Dispatched agent ${agentName} to room ${roomName}`);
console.log('Dispatch response:', dispatch);
}
res.writeHead(200);
res.end();
} catch (error) {
console.error('Error processing webhook:', error);
res.writeHead(500);
res.end();
}
});
});
server.listen(port, host, () => {
console.log(`Webhook server running on <http://localhost:${port}`>);
});
Steps to Set Up:
• Set the following environment variables in your .env
file: LIVEKIT_URL=<YOUR_LIVEKIT_SERVER_URL>
LIVEKIT_API_KEY=<YOUR_API_KEY>
LIVEKIT_API_SECRET=<YOUR_API_SECRET>
• Install the required packages: npm install livekit-server-sdk dotenv
• Make sure you have an agent worker running with the agent_name
matching the agentName
variable in the code.
Notes:
• Don't forget to replace 'your-agent-name'
with your actual agent's name.
• For production, it's a good idea to secure your webhook server with authentication and HTTPS.
• You can use the metadata
field to send extra info to the agent if needed.
If you have any questions or want me to help tweak this further, just let me know! I'm here to help.aloof-account-63566
08/18/2025, 2:34 AMtall-airline-16756
08/18/2025, 2:35 AMROOM_CREATED
event and save your settings.
• For Self-Hosted LiveKit:
◦ Update the webhook
section in your livekit.yaml
file like this: webhook:
api_key: 'your-api-key'
urls:
- <YOUR_AZURE_FUNCTION_URL>/api/webhook-endpoint
◦ Don’t forget to restart your LiveKit server to apply these changes.
Step 2: Create an Azure Function
Here’s a simple example of an Azure Function that listens for the webhook and dispatches an agent when a room is created:
const { WebhookReceiver, AgentDispatchClient } = require('livekit-server-sdk');
module.exports = async function (context, req) {
try {
const receiver = new WebhookReceiver(
process.env.LIVEKIT_WEBHOOK_API_KEY,
process.env.LIVEKIT_WEBHOOK_API_SECRET
);
// Validate the webhook event
const event = receiver.receive(req.body, req.headers.authorization);
// Handle room_created event
if (event.event === 'room_created') {
const roomName = event.data.room.id;
// Initialize AgentDispatchClient
const agentDispatchClient = new AgentDispatchClient(
process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET
);
// Dispatch an agent to the newly created room
const metadata = '{"context": "room_created"}';
const dispatch = await agentDispatchClient.createDispatch(roomName, 'my-agent', { metadata });
context.log(`Dispatched agent to room: ${roomName}`);
context.res = {
status: 200,
body: `Agent dispatched to room ${roomName}`
};
} else {
context.res = {
status: 200,
body: 'Event not handled'
};
}
} catch (error) {
context.log.error('Error processing webhook:', error);
context.res = {
status: 500,
body: `Error: ${error.message}`
};
}
};
Step 3: Set Environment Variables
• Make sure to set these environment variables in your Azure Function’s configuration:
LIVEKIT_URL=<YOUR_LIVEKIT_SERVER_URL>
LIVEKIT_API_KEY=<YOUR_API_KEY>
LIVEKIT_API_SECRET=<YOUR_API_SECRET>
LIVEKIT_WEBHOOK_API_KEY=<YOUR_WEBHOOK_API_KEY>
LIVEKIT_WEBHOOK_API_SECRET=<YOUR_WEBHOOK_API_SECRET>
Step 4: Deploy and Test
• Deploy your Azure Function and set the webhook URL in LiveKit.
• Create a room to trigger the ROOM_CREATED
event and check that your agent gets dispatched smoothly.
Quick Tips:
• Swap out 'my-agent'
with your agent’s actual name.
• Ensure your webhook server uses HTTPS and proper authentication for security.
• You can use the metadata
field to send extra context to your agent.
If you have any questions or want to tweak this setup, just let me know — I’m here to help!
Sources: Webhooks | LiveKit Docs | livekit/livekit_analytics.pb.go | Dispatching agents | LiveKit Docsaloof-account-63566
08/29/2025, 2:59 AMtall-airline-16756
08/29/2025, 2:59 AMasync def entrypoint(ctx: JobContext):
# Perform agent work
...
# Disconnect the agent from the room
ctx.shutdown(reason="Agent task completed")
Key Points:
• Room Continuity: Using ctx.shutdown()
disconnects the agent but keeps the room alive until the last human participant leaves.
• Alternative: You could also use the RemoveParticipant
API to kick out the agent, but ctx.shutdown()
is usually the nicer, more graceful way to go.
• Egress Consideration: If you’re recording the session, just double-check that egress is handled properly when everything wraps up.
Feel free to reach out if you want more examples or have any questions — I’m here to help! Sources: Inside a session | LiveKit Docs | Job lifecycle | LiveKit Docs | Anatomy of an Agent | LiveKit Docs | Managing participants | LiveKit Docsaloof-account-63566
08/29/2025, 3:15 AMtall-airline-16756
08/29/2025, 3:15 AMctx.shutdown
just disconnects the agent and runs any shutdown hooks you’ve set up. It won’t close the room unless no one else is there.
• Room Closure: If you want to close a room on purpose, you’ll need to use the server API deleteRoom
. This isn’t done by ctx.shutdown
.
• Automatic Room Closure: LiveKit closes a room automatically when the last non-agent participant leaves.
If the room is closing when you didn’t expect it to, it’s a good idea to double-check that there are still active non-agent participants in the room when you call ctx.shutdown. And of course, feel free to reach out if you want a hand with anything else! Sources: Inside a session | LiveKit Docs | Managing rooms | LiveKit Docs | Anatomy of an Agent | LiveKit Docs | Worker lifecycle | LiveKit Docs | Job lifecycle | LiveKit Docsaloof-account-63566
08/29/2025, 4:09 AMtall-airline-16756
08/29/2025, 4:10 AMagent_name
is correctly set both when creating the agent (using WorkerOptions
) and in the dispatch rule (RoomAgentDispatch
).
• Check Agent Registration: Make sure the agent worker is properly registered with the LiveKit server and is in standby mode, ready to take on job requests.
• Review Initialization Logic: Confirm that the agent moves from the "connecting" state to an active state (like "listening") within the timeout. The agent is only considered available if it’s in "listening," "thinking," or "speaking" state.
If you’re still running into trouble, just let me know—I’m here to help you troubleshoot your setup! Sources: SIP troubleshooting guide | LiveKit Docs | Worker lifecycle | LiveKit Docs | complex-agents/role-playing/role_playing_frontend/components/session-view.t... | components/session-view.tsxaloof-account-63566
08/29/2025, 4:11 AMtall-airline-16756
08/29/2025, 4:11 AMimport asyncio
from livekit import agents, rtc, JobContext, AutoSubscribe
async def entrypoint(ctx: JobContext):
room = ctx.room
agent_shutdown_initiated = False # To make sure we don't trigger shutdown multiple times
async def safe_shutdown(reason: str):
nonlocal agent_shutdown_initiated
if agent_shutdown_initiated:
return
agent_shutdown_initiated = True
ctx.shutdown(reason=reason)
@room.on("participant_connected")
def on_participant_connected(participant: rtc.Participant):
# If we're already shutting down, no need to do anything
if agent_shutdown_initiated:
return
# Ignore non-human participants (like ingress, egress, or agents)
if participant.kind != rtc.ParticipantKind.PARTICIPANT_KIND_STANDARD:
return
# Skip if participant disconnected due to duplicate identity
if participant.disconnect_reason == rtc.DisconnectReason.DUPLICATE_IDENTITY:
return
# When a human joins, disconnect the agent gracefully
print(f"Human participant {participant.identity} joined. Agent exiting.")
asyncio.create_task(safe_shutdown("Human participant joined"))
# Connect with some error handling to keep things smooth
try:
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
except Exception as e:
print(f"Oops, failed to connect agent to room: {e}")
ctx.shutdown(reason="Connection failed")
What’s going on here:
• Smart Human Detection: We check for PARTICIPANT_KIND_STANDARD
to spot real human participants and skip system-related disconnects like DUPLICATE_IDENTITY
.
• One-Time Shutdown: A simple flag keeps ctx.shutdown()
from running more than once, even if multiple people join quickly.
• Non-Blocking Shutdown: Using asyncio.create_task()
keeps the event loop happy while shutting down.
Just a couple of friendly reminders:
• The room stays open as long as there’s at least one STANDARD
or INGRESS
participant around.
• You might want to add some cleanup steps with ctx.add_shutdown_callback()
to tidy things up after disconnecting.
If you want me to walk you through anything else or tweak this example, just let me know! I'm here to help. Sources: Connecting to LiveKit | LiveKit Docs | Inside a session | LiveKit Docs | agent/game_host.py | Managing participants | LiveKit Docs | Job lifecycle | LiveKit Docsaloof-account-63566
08/29/2025, 4:14 AMtall-airline-16756
08/29/2025, 4:14 AMctx.shutdown(reason="Session ended")
to disconnect the agent but keep the room active for others.
• Room Continuity: Make sure there are still active non-agent participants in the room when the agent leaves, since LiveKit closes the room automatically once the last non-agent participant exits.
• Sandbox Behavior: The Sandbox might be set up to require an agent to stay connected. If that’s the case, you might want to tweak your workflow or try testing in an environment closer to production.
If you’re still running into issues, just let me know—I’m here to help you debug your setup! Sources: Worker lifecycle | LiveKit Docs | examples/voice_agents/realtime_joke_teller.py