Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
Use this file to discover all available pages before exploring further.
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 these all work:
useAgent({ agent: "Counter" }) → /agents/counter/...
useAgent({ agent: "counter" }) → /agents/counter/...
Basic Usage
routeAgentRequest()
The main entry point for agent routing. Handles both HTTP requests and WebSocket upgrades:
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 });
}
};
getAgentByName()
Get a specific agent instance for server-side RPC calls or request forwarding:
import { getAgentByName, routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// API endpoint that interacts with an agent
if (url.pathname === "/api/increment") {
const counter = await getAgentByName(env.Counter, "global-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// Regular agent routing
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
}
};
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"
});
/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"
});
/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
});
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()}`
});
Routing Options
Both routeAgentRequest() and getAgentByName() accept options for customizing routing behavior.
CORS
For cross-origin requests (common when your frontend is on a different domain):
const response = await routeAgentRequest(request, env, {
cors: true // Enable default CORS headers
});
Location Hints
For latency-sensitive applications, hint where the agent should run:
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
locationHint: "enam" // Eastern North America
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
locationHint: "enam"
});
Available location hints: wnam, enam, sam, weur, eeur, apac, oc, afr, me
Jurisdiction
For data residency requirements:
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
jurisdiction: "eu" // EU jurisdiction
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
jurisdiction: "eu"
});
Props
Since agents are instantiated by the runtime rather than constructed directly, props provides a way to pass initialization arguments:
const agent = await getAgentByName(env.MyAgent, "instance-name", {
props: {
userId: session.userId,
config: { maxRetries: 3 }
}
});
Props are passed to the agent’s onStart lifecycle method:
class MyAgent extends Agent<Env, State> {
private userId?: string;
private config?: { maxRetries: number };
async onStart(props?: { userId: string; config: { maxRetries: number } }) {
this.userId = props?.userId;
this.config = props?.config;
}
}
For McpAgent, props are automatically stored and accessible via this.props.
Hooks
routeAgentRequest supports hooks for intercepting requests before they reach agents:
const response = await routeAgentRequest(request, env, {
onBeforeConnect: (req, lobby) => {
// Called before WebSocket connections
// Return a Response to reject, Request to modify, or void to continue
},
onBeforeRequest: (req, lobby) => {
// Called before HTTP requests
// Return a Response to reject, Request to modify, or void to continue
}
});
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
});
This is useful when:
- You want clean URLs without the
/agents/ prefix
- The instance name is determined server-side (e.g., from auth/session)
- You’re integrating with an existing URL structure
Server-Side Instance Selection
When using basePath, the server must handle routing:
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Custom routing - server determines instance from session
if (url.pathname === "/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 })
);
}
};
Receiving the Instance Identity (Client-Side)
When using basePath, the client doesn’t know which instance it connected to until the server tells it:
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentity: (name, agentType) => {
console.log(`Connected to ${agentType} instance: ${name}`);
}
});
// Reactive state - re-renders when identity is received
return (
<div>
{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
</div>
);
Multiple Agents
You can have multiple agent classes in one project. Each gets its own namespace:
// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
}
};
{
"durable_objects": {
"bindings": [
{ "name": "Counter", "class_name": "Counter" },
{ "name": "ChatRoom", "class_name": "ChatRoom" },
{ "name": "UserProfile", "class_name": "UserProfile" }
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter", "ChatRoom", "UserProfile"]
}
]
}
Each agent is accessed via its own path:
/agents/counter/...
/agents/chat-room/...
/agents/user-profile/...
Troubleshooting
”Agent namespace not found”
The error message lists available agents. Check:
- Agent class is exported from your entry point
- Class name in code matches
class_name in wrangler.jsonc
- URL uses correct kebab-case name
Request returns 404
- Verify the URL pattern:
/agents/{agent-name}/{instance-name}
- Check that
routeAgentRequest() is called before your 404 handler
- Ensure the response from
routeAgentRequest() is returned (not just called)
WebSocket won’t connect
- Don’t modify the response from
routeAgentRequest() for WebSocket upgrades
- Ensure CORS is enabled if connecting from a different origin
- Check browser dev tools for the actual error
basePath not working
- Ensure your Worker handles the custom path and forwards to the agent
- Use
getAgentByName() + agent.fetch(request) to forward requests
- The
agent parameter is still required but ignored when basePath is set
- Check that the server-side route matches the client’s
basePath
API Reference
routeAgentRequest(request, env, options?)
Routes a request to the appropriate agent.
Environment with agent bindings
Props passed to whichever agent handles the request
Preferred location for agent instances
Data jurisdiction for agent instances
Callback before WebSocket connections
Callback before HTTP requests
Returns: Promise<Response | undefined> - Response if matched, undefined if no agent route
getAgentByName(namespace, name, options?)
Get an agent instance by name for server-side RPC or request forwarding.
namespace
DurableObjectNamespace<T>
required
Agent binding from env
Initialization properties for onStart
Returns: Promise<DurableObjectStub<T>> - Typed stub for calling agent methods or forwarding requests