Routing (2)
Last updated Sep 15, 2026 This guide explains how requests are routed to agents, how naming works, and patterns for organizing your agents.
How routing works
When a request comes in, routeAgentRequest() examines the URL and routes it to the appropriate agent instance:
https://your-worker.dev/agents/{agent-name}/{instance-name}
└────┬────┘ └─────┬─────┘
Class name Unique instance ID
(kebab-case)
Example URLs:
| URL | Agent Class | Instance |
|---|---|---|
/agents/counter/user-123 |
Counter |
user-123 |
/agents/chat-room/lobby |
ChatRoom |
lobby |
/agents/my-agent/default |
MyAgent |
default |
Name resolution
Agent class names are automatically converted to kebab-case for URLs:
| Class Name | URL Path |
|---|---|
Counter |
/agents/counter/... |
MyAgent |
/agents/my-agent/... |
ChatRoom |
/agents/chat-room/... |
AIAssistant |
/agents/ai-assistant/... |
The router matches both the original name and kebab-case version, so you can use either:
useAgent({ agent: "Counter" })→/agents/counter/...useAgent({ agent: "counter" })→/agents/counter/...
Using routeAgentRequest()
The routeAgentRequest() function is the main entry point for agent routing:
import { routeAgentRequest } from "agents";
export default {
async fetch(request, env, ctx) {
// Route to agents - returns Response or undefined
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) {
return agentResponse;
}
// No agent matched - handle other routes
return new Response("Not found", { status: 404 });
},
};
import { routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Route to agents - returns Response or undefined
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) {
return agentResponse;
}
// No agent matched - handle other routes
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;
Build Agent URLs
Use buildAgentPath() to create a pathname for a known Agent identity. The function handles the root route and each nested /sub/ route.
import { buildAgentPath, buildAgentUrl } from "agents";
const address = [
{ className: "Inbox", name: userId },
{ className: "Chat", name: chatId },
];
buildAgentPath(address, { leafPath: "/callbacks/job" });
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job
buildAgentUrl("https://app.example.com", address, {
leafPath: "/callbacks/job",
});
// URL("https://app.example.com/agents/inbox/...")
import { buildAgentPath, buildAgentUrl } from "agents";
const address = [
{ className: "Inbox", name: userId },
{ className: "Chat", name: chatId },
];
buildAgentPath(address, { leafPath: "/callbacks/job" });
// /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job
buildAgentUrl("https://app.example.com", address, {
leafPath: "/callbacks/job",
});
// URL("https://app.example.com/agents/inbox/...")
Inside an Agent, this.selfPath provides the required root-first identity. If the root Durable Object binding name differs from its class name, pass the binding name as rootBinding. The pathname supports both HTTP requests and WebSocket connections.
For a custom route prefix, pass the same prefix to buildAgentPath() and routeAgentRequest(). Refer to Sub-agents for callback and webhook examples.
Instance naming patterns
The instance name (the last part of the URL) determines which agent instance handles the request. Each unique name gets its own isolated agent with its own state.
Per-user agents
Each user gets their own agent instance:
// Client
const agent = useAgent({
agent: "UserProfile",
name: `user-${userId}`, // e.g., "user-abc123"
});
// Client
const agent = useAgent({
agent: "UserProfile",
name: `user-${userId}`, // e.g., "user-abc123"
});
/agents/user-profile/user-abc123 → User abc123's agent
/agents/user-profile/user-xyz789 → User xyz789's agent (separate instance)
Shared rooms
Multiple users share the same agent instance:
// Client
const agent = useAgent({
agent: "ChatRoom",
name: roomId, // e.g., "general" or "room-42"
});
// Client
const agent = useAgent({
agent: "ChatRoom",
name: roomId, // e.g., "general" or "room-42"
});
/agents/chat-room/general → All users in "general" share this agent
Global singleton
A single instance for the entire application:
// Client
const agent = useAgent({
agent: "AppConfig",
name: "default", // Or any consistent name
});
// Client
const agent = useAgent({
agent: "AppConfig",
name: "default", // Or any consistent name
});
Dynamic naming
Generate instance names based on context:
// Per-session
const agent = useAgent({
agent: "Session",
name: sessionId,
});
// Per-document
const agent = useAgent({
agent: "Document",
name: `doc-${documentId}`,
});
// Per-game
const agent = useAgent({
agent: "Game",
name: `game-${gameId}-${Date.now()}`,
});
// Per-session
const agent = useAgent({
agent: "Session",
name: sessionId,
});
// Per-document
const agent = useAgent({
agent: "Document",
name: `doc-${documentId}`,
});
// Per-game
const agent = useAgent({
agent: "Game",
name: `game-${gameId}-${Date.now()}`,
});
Custom URL routing
For advanced use cases where you need control over the URL structure, you can bypass the default /agents/{agent}/{name} pattern.
Using basePath (client-side)
The basePath option lets clients connect to any URL path:
// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
agent: "UserAgent", // Required but ignored when basePath is set
basePath: "user", // → connects to /user
});
// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
agent: "UserAgent", // Required but ignored when basePath is set
basePath: "user", // → connects to /user
});
This is useful when:
- You want clean URLs without the
/agents/prefix - The instance name is determined server-side (for example, from auth/session)
- You are integrating with an existing URL structure
Server-side instance selection
When using basePath, the server must handle routing. Use getAgentByName() to get the agent instance, then forward the request with fetch():
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Custom routing - server determines instance from session
if (url.pathname.startsWith("/user/")) {
const session = await getSession(request);
const agent = await getAgentByName(env.UserAgent, session.userId);
return agent.fetch(request); // Forward request directly to agent
}
// Default routing for standard /agents/... paths
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Custom routing - server determines instance from session
if (url.pathname.startsWith("/user/")) {
const session = await getSession(request);
const agent = await getAgentByName(env.UserAgent, session.userId);
return agent.fetch(request); // Forward request directly to agent
}
// Default routing for standard /agents/... paths
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Custom path with dynamic instance
Route different paths to different instances:
// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
const roomId = url.pathname.replace("/chat/", "");
const agent = await getAgentByName(env.ChatRoom, roomId);
return agent.fetch(request);
}
// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
const docId = url.pathname.replace("/doc/", "");
const agent = await getAgentByName(env.Document, docId);
return agent.fetch(request);
}
// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
const roomId = url.pathname.replace("/chat/", "");
const agent = await getAgentByName(env.ChatRoom, roomId);
return agent.fetch(request);
}
// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
const docId = url.pathname.replace("/doc/", "");
const agent = await getAgentByName(env.Document, docId);
return agent.fetch(request);
}
Receiving the instance identity (client-side)
When using basePath, the client does not know which instance it connected to until the server returns this information. The agent automatically sends its identity on connection:
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentity: (name, agentType) => {
console.log(`Connected to ${agentType} instance: ${name}`);
// e.g., "Connected to user-agent instance: user-123"
},
});
// Reactive state - re-renders when identity is received
return (
<div>
{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
</div>
);
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentity: (name, agentType) => {
console.log(`Connected to ${agentType} instance: ${name}`);
// e.g., "Connected to user-agent instance: user-123"
},
});
// Reactive state - re-renders when identity is received
return (
<div>
{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
</div>
);
For AgentClient:
const agent = new AgentClient({
agent: "UserAgent",
basePath: "user",
host: "example.com",
onIdentity: (name, agentType) => {
// Update UI with actual instance name
setInstanceName(name);
},
});
// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name
const agent = new AgentClient({
agent: "UserAgent",
basePath: "user",
host: "example.com",
onIdentity: (name, agentType) => {
// Update UI with actual instance name
setInstanceName(name);
},
});
// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name
Handling identity changes on reconnect
If the identity changes on reconnect (for example, session expired and user logs in as someone else), you can handle it with onIdentityChange:
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
console.log(`Session changed: ${oldName} → ${newName}`);
// Refresh state, show notification, etc.
},
});
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
console.log(`Session changed: ${oldName} → ${newName}`);
// Refresh state, show notification, etc.
},
});
If onIdentityChange is not provided and identity changes, a warning is logged to help catch unexpected session changes.
Disabling identity for security
If your instance names contain sensitive data (session IDs, internal user IDs), you can disable identity sending:
class SecureAgent extends Agent {
// Do not expose instance names to clients
static options = { sendIdentityOnConnect: false };
}
class SecureAgent extends Agent {
// Do not expose instance names to clients
static options = { sendIdentityOnConnect: false };
}
