references/browse-the-web.md
# Browse the Web (Experimental)
Fetch https://developers.cloudflare.com/agents/api-reference/browse-the-web/ for complete documentation.
CDP-powered browser tools that let agents scrape, screenshot, and interact with web pages.
## Setup
```jsonc
// wrangler.jsonc
{
"browser": { "binding": "BROWSER" },
"worker_loaders": [{ "binding": "LOADER" }],
"compatibility_flags": ["nodejs_compat"]
}
```
## Usage with AI SDK
```typescript
import { createBrowserTools } from "agents/browser/ai";
export class MyAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
const browserTools = createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER
});
const result = streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(this.messages),
tools: { ...myTools, ...browserTools },
onFinish
});
return result.toUIMessageStreamResponse();
}
}
```
## Available Tools
| Tool | Purpose |
|------|---------|
| `browser_search` | Search the web and return results |
| `browser_execute` | Navigate to URL, execute JS, return results |
The LLM writes async JavaScript IIFEs that run in a fresh browser session.
## When to Use
- Need a real browser (JS rendering, screenshots, interaction) → browser tools
- Just need HTML/API data → use `fetch()` instead (faster, cheaper)
## Low-Level API
```typescript
import { connectBrowser, CdpSession } from "agents/browser";
const browser = await connectBrowser(this.env.BROWSER);
const cdp = new CdpSession(browser);
await cdp.send("Page.navigate", { url: "https://example.com" });
```
references/callable.md
# Callable Methods
Fetch https://developers.cloudflare.com/agents/api-reference/callable-methods/ for complete documentation.
## Overview
`@callable()` exposes agent methods to clients via WebSocket RPC.
```typescript
import { Agent, callable } from "agents";
export class MyAgent extends Agent<Env, State> {
@callable()
async greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
@callable()
async processData(data: unknown): Promise<Result> {
// Long-running work
return result;
}
}
```
## Client Usage
```typescript
// Basic call
const greeting = await agent.call("greet", ["World"]);
// With timeout
const result = await agent.call("processData", [data], {
timeout: 5000 // 5 second timeout
});
```
## Streaming Responses
```typescript
import { Agent, callable, StreamingResponse } from "agents";
export class MyAgent extends Agent<Env, State> {
@callable({ streaming: true })
async streamResults(stream: StreamingResponse, query: string) {
for await (const item of fetchResults(query)) {
stream.send(JSON.stringify(item));
}
stream.close();
}
@callable({ streaming: true })
async streamWithError(stream: StreamingResponse) {
try {
// ... work
} catch (error) {
stream.error(error.message); // Signal error to client
return;
}
stream.close();
}
}
```
Client with streaming:
```typescript
await agent.call("streamResults", ["search term"], {
stream: {
onChunk: (data) => console.log("Chunk:", data),
onDone: () => console.log("Complete"),
onError: (error) => console.error("Error:", error)
}
});
```
## Introspection
```typescript
// Get list of callable methods on an agent
const methods = await agent.call("getCallableMethods", []);
// Returns: ["greet", "processData", "streamResults", ...]
```
## When to Use
| Scenario | Use |
|----------|-----|
| Browser/mobile calling agent | `@callable()` |
| External service calling agent | `@callable()` |
| Worker calling agent (same codebase) | DO RPC directly |
| Agent calling another agent | `getAgentByName()` + DO RPC |
references/client-sdk.md
# Client SDK
Choose `useAgent` for React state/RPC, `AgentClient` for other WebSocket clients, and `agentFetch` for one-off HTTP requests. Add `useAgentChat` when the UI needs chat messages and streaming. Check installed package versions before adapting current examples.
| Task | Documentation |
|------|---------------|
| Connect, sync state, call RPC, or send HTTP requests | [Client SDK](https://developers.cloudflare.com/agents/communication-channels/chat/client-sdk/) — hooks, vanilla JS, typed calls, streaming callbacks, and connection options |
| Build chat UI | [Chat agents](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/) — `useAgentChat`, message rendering, status, and tool interactions |
| Authenticate across origins | [Cross-domain authentication](https://developers.cloudflare.com/agents/runtime/operations/cross-domain-authentication/) — token validation and WebSocket authentication |
Keep client instance selection consistent with server routing. For authentication, account for token refresh on reconnect and query caching. Close manually created `AgentClient` connections when finished; React hooks manage their own cleanup.
references/codemode.md
# Codemode (Experimental)
Fetch https://developers.cloudflare.com/agents/api-reference/codemode/ for complete documentation.
Codemode lets LLMs write and execute code that orchestrates your tools, instead of calling them one at a time. The LLM gets a single "write code" tool; generated JavaScript runs in an isolated Worker sandbox.
## When to Use
| Scenario | Use Codemode? |
|----------|---------------|
| Single tool call | No — standard tool calling is simpler |
| Chained tool calls with logic | Yes |
| Conditional logic across tools | Yes |
| MCP multi-server workflows | Yes |
| Simple Q&A chat | No |
## Setup
### Wrangler Config
```jsonc
{
"worker_loaders": [{ "binding": "LOADER" }],
"compatibility_flags": ["nodejs_compat"]
}
```
### Install
```bash
npm install @cloudflare/codemode ai zod
```
## Usage
```typescript
import { createCodeTool } from "@cloudflare/codemode/ai";
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
import { streamText, tool, convertToModelMessages } from "ai";
import { z } from "zod";
const tools = {
getWeather: tool({
description: "Get weather for a location",
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => `Weather: ${location} 72°F`
}),
sendEmail: tool({
description: "Send an email",
inputSchema: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
execute: async ({ to, subject, body }) => `Email sent to ${to}`
})
};
export class MyAgent extends Agent<Env, State> {
async onChatMessage() {
const executor = new DynamicWorkerExecutor({
loader: this.env.LOADER
});
const codemode = createCodeTool({ tools, executor });
const result = streamText({
model,
system: "You are a helpful assistant.",
messages: await convertToModelMessages(this.messages),
tools: { codemode }
});
return result.toUIMessageStreamResponse();
}
}
```
## With MCP Tools
```typescript
const codemode = createCodeTool({
tools: {
...myTools,
...this.mcp.getAITools()
},
executor
});
```
## How It Works
1. `createCodeTool` generates TypeScript type definitions from your tools
2. The LLM writes an async arrow function calling `codemode.toolName(args)`
3. Code runs in an isolated Worker sandbox via `DynamicWorkerExecutor`
4. Tool calls route back to the host via Workers RPC
5. External `fetch()` is blocked by default — sandbox can only call your tools
## Network Isolation
```typescript
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null // default — fully isolated
// globalOutbound: env.MY_SERVICE // route through a Fetcher
});
```
## Limitations
- Experimental — API may change
- `needsApproval` tools execute immediately in sandbox (no approval pause yet)
- JavaScript execution only
- Requires `worker_loaders` binding
references/configuration.md
# Configuration
Fetch https://developers.cloudflare.com/agents/api-reference/configuration/ for complete documentation.
## Wrangler Config (`wrangler.jsonc`)
```jsonc
{
"name": "my-agent",
"main": "src/index.ts",
"compatibility_date": "2025-01-28",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{ "name": "MyAgent", "class_name": "MyAgent" },
{ "name": "ChatAgent", "class_name": "ChatAgent" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyAgent", "ChatAgent"] }
],
"ai": { "binding": "AI" },
"assets": {
"directory": "./dist/client",
"binding": "ASSETS",
"not_found_handling": "single-page-application",
"run_worker_first": true
}
}
```
## Key Rules
- Every agent class needs a DO binding AND a `new_sqlite_classes` migration entry
- `nodejs_compat` is required
- Never edit old migrations — add a new tag (e.g. `v2`) for new classes
- Do NOT enable `experimentalDecorators` in tsconfig — it breaks `@callable`
- For Workers AI locally, set `"ai": { "binding": "AI", "remote": true }` in `.dev.vars` or config
- Use `wrangler secret put` for secrets, never hardcode them
## Vite Setup
```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { cloudflare } from "@cloudflare/vite-plugin";
import { agents } from "agents/vite";
export default defineConfig({
plugins: [react(), cloudflare(), agents()]
});
```
## Type Generation
```bash
npx wrangler types
```
This generates `env.d.ts` with typed bindings. Regenerate after changing `wrangler.jsonc`.
## tsconfig
Extend the agents tsconfig for correct settings:
```jsonc
{
"extends": ["agents/tsconfig"],
"include": ["src/**/*.ts", "src/**/*.tsx"],
"compilerOptions": { "paths": { "~/*": ["./src/*"] } }
}
```
references/durable-execution.md
# Durable Execution
Fetch https://developers.cloudflare.com/agents/api-reference/durable-execution/ for complete documentation.
Fibers let agent work survive Durable Object eviction. Progress is checkpointed to SQLite; on recovery, you decide what to do.
## `runFiber`
```typescript
export class MyAgent extends Agent<Env, State> {
async onRequest(request: Request) {
await this.runFiber("process-data", async (ctx) => {
const step1 = await fetchData();
ctx.stash({ step: 1, data: step1 });
const step2 = await transform(step1);
ctx.stash({ step: 2, result: step2 });
this.setState({ result: step2 });
});
return new Response("Started");
}
async onFiberRecovered(ctx) {
const checkpoint = ctx.stash;
if (checkpoint.step === 1) {
const step2 = await transform(checkpoint.data);
this.setState({ result: step2 });
}
}
}
```
## Key APIs
| API | Purpose |
|-----|---------|
| `this.runFiber(name, fn)` | Start a named fiber |
| `ctx.stash` / `this.stash` | Read latest checkpoint |
| `ctx.stash = data` | Write checkpoint (JSON-serializable) |
| `onFiberRecovered(ctx)` | Called on DO restart if fiber was in-flight |
| `keepAlive()` | Prevent hibernation while fiber runs |
| `keepAliveWhile(fn)` | Keep alive for duration of async function |
## Important
- `stash` replaces the entire checkpoint — not a merge
- The lambda is NOT restored on recovery — only the stash data is. You must re-derive what to do in `onFiberRecovered`
- No auto-retry on throw — handle errors yourself
- For long-running pipelines with automatic retries, use Workflows instead
- Filter concurrent fibers by `ctx.name` in `onFiberRecovered`
references/email.md
# Email Handling
Fetch https://developers.cloudflare.com/agents/api-reference/email/ for complete documentation.
## Overview
Agents receive and reply to emails via Cloudflare Email Routing.
## Wrangler Configuration
```jsonc
{
"durable_objects": {
"bindings": [{ "name": "EmailAgent", "class_name": "EmailAgent" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["EmailAgent"] }],
"send_email": [
{ "name": "SEB", "destination_address": "reply@yourdomain.com" }
]
}
```
## Basic Email Handler
```typescript
import { Agent } from "agents";
import { type AgentEmail } from "agents/email";
import PostalMime from "postal-mime";
export class EmailAgent extends Agent<Env, State> {
async onEmail(email: AgentEmail) {
const raw = await email.getRaw();
const parsed = await PostalMime.parse(raw);
console.log("From:", email.from);
console.log("Subject:", parsed.subject);
await this.replyToEmail(email, {
fromName: "My Agent",
subject: `Re: ${parsed.subject}`,
body: "Thanks for your email!"
});
}
}
```
## Routing Emails
```typescript
import { routeAgentRequest, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver } from "agents/email";
export default {
async email(message, env) {
await routeAgentEmail(message, env, {
resolver: createAddressBasedEmailResolver("EmailAgent")
});
},
async fetch(request, env) {
return routeAgentRequest(request, env) ?? new Response("Not found", { status: 404 });
}
};
```
## Resolvers
### Address-Based (Inbound Mail)
Routes based on recipient address:
```typescript
import { createAddressBasedEmailResolver } from "agents/email";
const resolver = createAddressBasedEmailResolver("EmailAgent");
// support@example.com → EmailAgent, instance "support"
// NotificationAgent+user123@example.com → NotificationAgent, instance "user123"
```
### Secure Reply (Reply Flows)
Verifies replies are authentic using HMAC-SHA256 signatures:
```typescript
import { createSecureReplyEmailResolver } from "agents/email";
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
maxAge: 7 * 24 * 60 * 60, // 7 days (default: 30 days)
onInvalidSignature: (email, reason) => {
console.warn(`Invalid signature from ${email.from}: ${reason}`);
}
});
```
Sign outbound emails to enable secure reply routing:
```typescript
await this.replyToEmail(email, {
fromName: "My Agent",
body: "Thanks!",
secret: this.env.EMAIL_SECRET // Signs headers for secure reply routing
});
```
### Catch-All (Single Instance)
Routes all emails to one agent instance:
```typescript
import { createCatchAllEmailResolver } from "agents/email";
const resolver = createCatchAllEmailResolver("EmailAgent", "default");
```
### Combining Resolvers
```typescript
async email(message, env) {
const secureReply = createSecureReplyEmailResolver(env.EMAIL_SECRET);
const addressBased = createAddressBasedEmailResolver("EmailAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
// Try secure reply first
const result = await secureReply(email, env);
if (result) return result;
// Fall back to address-based
return addressBased(email, env);
}
});
}
```
## Utilities
```typescript
import { isAutoReplyEmail } from "agents/email";
async onEmail(email: AgentEmail) {
if (isAutoReplyEmail(email.headers)) {
// Skip auto-replies (vacation, out-of-office, etc.)
return;
}
// Process email...
}
```
references/human-in-the-loop.md
# Human-in-the-Loop
Choose the approval layer based on where execution must pause, then fetch its current documentation:
| Need | Documentation |
|------|---------------|
| Approve chat tool execution or run a browser-side tool | [Chat agents](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/) — `needsApproval`, approval responses, client tools, and custom denial messages |
| Pause a durable background task or collect MCP input | [Human-in-the-loop patterns](https://developers.cloudflare.com/agents/concepts/agentic-patterns/human-in-the-loop/) — workflow approval, timeout handling, and elicitation |
Distinguish approval responses from client tool outputs. When returning a custom tool error, check whether an explicit continuation is needed. Handle workflow approval timeouts before executing the gated action. Check installed SDK versions before adapting examples.
references/mcp.md
# MCP Integration
For new servers, prefer `createMcpHandler` over the deprecated `McpAgent`. For existing servers, check the installed SDK version and state/session requirements before choosing a migration path.
Read the relevant current documentation for implementation details and supported dependency versions:
| Task | Documentation |
|------|---------------|
| Build a server | [Handler API](https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/) — server factories, Worker entrypoint, dependencies, and examples |
| Migrate an existing server | [MCP SDK v2 migration](https://developers.cloudflare.com/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) — stateless migration and temporary legacy paths |
| Connect to servers and use their tools | [Client API](https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/) — connections, OAuth, tools, resources, and retries |
| Choose a transport | [Transports](https://developers.cloudflare.com/agents/model-context-protocol/protocol/transport/) — remote HTTP and existing RPC integrations |
| Secure a server | [Securing MCP servers](https://developers.cloudflare.com/agents/model-context-protocol/guides/securing-mcp-server/) — OAuth and proxy security |
references/observability.md
# Observability
Fetch https://developers.cloudflare.com/agents/api-reference/observability/ for complete documentation.
Agents emit structured events via Node.js `diagnostics_channel`. Subscribe in development or forward via Tail Workers in production.
## Subscribe to Events
```typescript
import { subscribe } from "agents/observability";
subscribe("agents:rpc", (event) => {
console.log(`RPC call: ${event.payload.method}`);
});
subscribe("agents:state", (event) => {
console.log(`State change on ${event.agent}`);
});
```
## Available Channels
| Channel | Events |
|---------|--------|
| `agents:state` | State changes |
| `agents:rpc` | `@callable` invocations |
| `agents:message` | WebSocket messages |
| `agents:schedule` | Schedule triggers |
| `agents:lifecycle` | Agent start, connect, disconnect |
| `agents:workflow` | Workflow progress, completion, errors |
| `agents:mcp` | MCP server connections, tool calls |
| `agents:email` | Email received |
## Per-Agent Override
```typescript
export class MyAgent extends Agent<Env, State> {
observability = undefined; // disable for this agent
}
```
## Production: Tail Workers
In production, events appear as `diagnosticsChannelEvents` on the Tail Worker `event` object. Attach a Tail Worker to your agent's Worker to forward events to your observability platform.
references/queue-retries.md
# Queue & Retries
Read the current Cloudflare documentation for queue management, retry options, defaults, and callback examples.
| Task | Documentation |
|------|---------------|
| Enqueue, inspect, and remove background work; understand sequential processing and failure handling | [Queue tasks](https://developers.cloudflare.com/agents/runtime/execution/queue-tasks/) |
| Retry an operation or configure retries for queued and scheduled callbacks | [Retries](https://developers.cloudflare.com/agents/runtime/execution/retries/) |
| Delay recovery or run work on a recurring schedule | [Schedule tasks](https://developers.cloudflare.com/agents/runtime/execution/schedule-tasks/) |
Keep these execution choices in mind when using the linked guides:
- Use the built-in queue for sequential background work. Retries block later queue items; use scheduling for long recovery waits.
- Retry delays keep the Durable Object active. Choose retry budgets with execution cost and latency in mind.
- Queued items are removed after their retry budget is exhausted; there is no built-in dead-letter queue. Record failures explicitly when the application needs recovery or auditing.
- The selective retry predicate is available on `this.retry()`, not serialized queue or schedule options. Handle non-retryable errors in those callbacks.
See [state-scheduling.md](state-scheduling.md) for choosing schedule modes and persisting application state.
references/routing.md
# Routing
Fetch https://developers.cloudflare.com/agents/api-reference/routing/ for complete documentation.
## Default URL Pattern
`/agents/{kebab-class-name}/{instance-name}`
```typescript
import { routeAgentRequest } from "agents";
export default {
fetch: (req, env) =>
routeAgentRequest(req, env) ?? new Response("Not found", { status: 404 })
};
```
| Class | URL |
|-------|-----|
| `Counter` | `/agents/counter/user-123` |
| `ChatRoom` | `/agents/chat-room/lobby` |
| `MyAgent` | `/agents/my-agent/default` |
Subpaths after the instance name (e.g. `/agents/my-agent/default/api/data`) route to `onRequest`.
## Custom Routing with `getAgentByName`
```typescript
import { getAgentByName } from "agents";
export default {
async fetch(req, env) {
const url = new URL(req.url);
if (url.pathname.startsWith("/api/")) {
const agent = getAgentByName(env.MyAgent, "singleton");
return agent.fetch(req);
}
return routeAgentRequest(req, env);
}
};
```
## Options
```typescript
routeAgentRequest(req, env, {
cors: true,
prefix: "/api/agents",
locationHint: "enam",
jurisdiction: "eu",
props: { userId: "123" },
onBeforeConnect: async (req) => { /* auth check */ },
onBeforeRequest: async (req) => { /* auth check */ }
});
```
`props` are delivered to `onStart(props)` on first access.
## Client Side
```tsx
useAgent({
agent: "MyAgent",
name: "instance-1",
host: "https://my-worker.workers.dev",
basePath: "/api/agents",
path: "/custom-subpath"
});
```
## Common Mistakes
- Class name `MyAgent` becomes kebab `my-agent` in URLs — match exactly
- "Namespace not found" error = the `class_name` in wrangler doesn't match your exported class
- If `sendIdentityOnConnect: false`, the `ready` promise on the client may never resolve — use state sync instead
references/server-driven-messages.md
# Server-Driven Messages
Read [Autonomous responses](https://developers.cloudflare.com/agents/communication-channels/chat/autonomous-responses/) for scheduled, webhook, email, and agent-triggered turns, message schemas, response hooks, and client streaming status.
Choose `saveMessages` to persist messages and request a model response, or `persistMessages` to update context without starting a turn. Use `onChatResponse` to react to turns regardless of their trigger. For webhooks that need a quick acknowledgement, consult the documented `submitMessages` path.
Before reading conversation history or calling `saveMessages` from non-chat entry points, await `waitUntilStable` and handle a timeout without proceeding as if the conversation were stable. Prefer the functional `saveMessages` form when calls can queue, so each update uses the latest history.
references/state-scheduling.md
# State & Scheduling
Read the current Cloudflare documentation before implementing state, SQL, or scheduling; check installed SDK versions when adapting an existing agent.
| Task | Documentation |
|------|---------------|
| Define state, validate updates, choose state versus SQL, and query SQLite | [Store and sync state](https://developers.cloudflare.com/agents/runtime/lifecycle/state/) |
| Synchronize state with React or vanilla JavaScript clients | [Client SDK](https://developers.cloudflare.com/agents/communication-channels/chat/client-sdk/) |
| Select delayed, date-based, cron, or interval execution; manage schedules and callbacks | [Schedule tasks](https://developers.cloudflare.com/agents/runtime/execution/schedule-tasks/) |
| Configure schedule retry behavior | [Retries](https://developers.cloudflare.com/agents/runtime/execution/retries/) |
| Handle lifecycle events, connections, and hibernation | [WebSockets](https://developers.cloudflare.com/agents/runtime/communication/websockets/) |
Use synchronized state for data clients need immediately, and SQL for larger collections, history, or queries. Reject invalid updates in the validation hook; state-change notifications are for reacting to accepted updates. Use the state documentation for current hook names and behavior.
Choose a one-time delay or date for work that runs once, cron for calendar recurrence, and an interval for a fixed cadence. For queued work and retry tradeoffs, see [queue-retries.md](queue-retries.md).
references/streaming-chat.md
# Streaming Chat with AIChatAgent
Use `AIChatAgent` for persisted conversations with streaming and tools; use callable streaming RPC for non-chat output. Before adapting an existing app, check its installed `agents`, `@cloudflare/ai-chat`, and AI SDK versions against the current docs.
Read the relevant documentation before implementing:
| Task | Documentation |
|------|---------------|
| Build a chat agent | [Chat agent example](https://developers.cloudflare.com/agents/examples/chat-agent/) — setup, provider, server, and UI |
| Implement or customize chat | [Chat agents](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/) — message format, tools, custom streams, persistence, concurrency, cancellation, and recovery |
| Connect a client | [Client guidance](client-sdk.md) — React, vanilla JS, HTTP, and authentication |
| Stream non-chat results | [Callable methods](https://developers.cloudflare.com/agents/runtime/lifecycle/callable-methods/) — server and client streaming RPC |
| Trigger background turns | [Server-driven messages](server-driven-messages.md) |
| Add approvals | [Human-in-the-loop](human-in-the-loop.md) |
Forward the request abort signal to the model call so cancellation stops generation. When customizing streams, verify persistence and completion behavior for the installed version. Treat client reconnection and Durable Object eviction as separate recovery cases.
references/think.md
# Think (Experimental)
Fetch https://developers.cloudflare.com/agents/api-reference/think/ for complete documentation.
`@cloudflare/think` — a higher-level chat agent class that handles the `streamText` loop, tool execution, and message persistence for you. You provide `getModel()` and `getSystemPrompt()`; Think handles the rest.
```bash
npm install @cloudflare/think
```
## Minimal Agent
```typescript
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
export class MyAgent extends Think<Env> {
getModel() {
return createWorkersAI({ binding: this.env.AI })("@cf/meta/llama-4-scout-17b-16e-instruct");
}
getSystemPrompt() {
return "You are a helpful assistant.";
}
}
export default {
fetch: (req, env) => routeAgentRequest(req, env)
};
```
## Wrangler Config
```jsonc
{
"compatibility_flags": ["nodejs_compat", "experimental"],
"durable_objects": {
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }],
"ai": { "binding": "AI" }
}
```
**Note:** Think requires the `experimental` compatibility flag.
## Custom Tools
```typescript
import { tool } from "ai";
import { z } from "zod";
export class MyAgent extends Think<Env> {
getTools() {
return {
getWeather: tool({
description: "Get weather",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => `72°F in ${city}`
})
};
}
}
```
## Lifecycle Hooks
| Hook | When | Use for |
|------|------|---------|
| `configureSession()` | Agent starts | Set up memory, context providers |
| `beforeTurn(ctx)` | Before each LLM call | Per-turn model/tools/system prompt; return `TurnConfig` |
| `onChunk(chunk)` | Each streaming chunk | Progress tracking |
| `onChatResponse(result)` | After LLM turn completes | Chaining, follow-up `saveMessages` |
| `onChatError(error)` | On LLM error | Error handling |
```typescript
async beforeTurn(ctx: TurnContext): Promise<TurnConfig> {
if (ctx.continuation) {
return { model: cheaperModel };
}
return {};
}
```
## Sub-Agents
```typescript
const child = this.subAgent(SpecialistAgent, "specialist-1");
await child.chat("Analyze this data...", (chunk) => {
// stream callback
});
```
## Client
Same React hooks as `AIChatAgent`:
```tsx
const agent = useAgent({ agent: "MyAgent", name: "session-1" });
const { messages, input, handleInputChange, handleSubmit } = useAgentChat({ agent });
```
## Think vs AIChatAgent
| | Think | AIChatAgent |
|-|-------|-------------|
| `streamText` loop | Built-in | You write it |
| Tool execution | Automatic | You wire it |
| Customization | Override hooks | Full control in `onChatMessage` |
| Built-in tools | Workspace, execute, browser | None |
| Compatibility flag | Requires `experimental` | Standard |
references/voice.md
# Voice (Experimental)
Fetch https://developers.cloudflare.com/agents/api-reference/voice/ for complete documentation.
`@cloudflare/voice` — real-time speech-to-text and text-to-speech for agents. Audio streams over WebSocket.
```bash
npm install @cloudflare/voice
```
## Server
```typescript
import { Agent } from "agents";
import { withVoice, WorkersAITTS, WorkersAINova3STT } from "@cloudflare/voice";
export class VoiceAgent extends withVoice(Agent)<Env> {
transcriber = new WorkersAINova3STT(this);
tts = new WorkersAITTS(this);
async onTurn(transcript: string, context: VoiceTurnContext) {
const result = streamText({
model: createWorkersAI({ binding: this.env.AI })("@cf/meta/llama-4-scout-17b-16e-instruct"),
messages: [
{ role: "system", content: "You are a voice assistant." },
...context.conversationHistory,
{ role: "user", content: transcript }
]
});
for await (const chunk of result.textStream) {
if (context.signal.aborted) break;
context.speak(chunk);
}
}
}
```
## Lifecycle Hooks
| Hook | Purpose |
|------|---------|
| `onTurn(transcript, ctx)` | Handle transcribed speech (required) |
| `beforeCallStart(conn)` | Auth/validation before call starts |
| `onCallStart(conn)` | Call connected |
| `onCallEnd(conn)` | Call disconnected |
| `onInterrupt()` | User interrupted agent speech |
## Client (React)
```tsx
import { useVoiceAgent } from "@cloudflare/voice/react";
function VoiceUI() {
const { isConnected, isSpeaking, connect, disconnect } = useVoiceAgent({
agent: "VoiceAgent",
name: "session-1"
});
return <button onClick={isConnected ? disconnect : connect}>
{isConnected ? "End Call" : "Start Call"}
</button>;
}
```
## STT/TTS Providers
Workers AI (default), Deepgram, ElevenLabs — install the provider package and swap the `transcriber`/`tts` properties.
references/webhooks-push.md
# Webhooks & Push Notifications
## Webhooks
Fetch https://developers.cloudflare.com/agents/communication-channels/webhooks/ for complete documentation.
Route external webhooks to agent instances via `onRequest`:
```typescript
export default {
async fetch(req: Request, env: Env) {
const url = new URL(req.url);
if (url.pathname.startsWith("/webhooks/")) {
const entityId = url.pathname.split("/")[2];
const agent = getAgentByName(env.MyAgent, entityId);
return agent.fetch(req);
}
return routeAgentRequest(req, env);
}
};
```
In the agent:
```typescript
export class MyAgent extends Agent<Env, State> {
async onRequest(request: Request) {
const signature = request.headers.get("X-Signature");
if (!verifySignature(signature, await request.text(), this.env.WEBHOOK_SECRET)) {
return new Response("Unauthorized", { status: 401 });
}
const payload = JSON.parse(await request.text());
this.queue("processWebhook", payload);
return new Response("OK", { status: 202 });
}
}
```
**Tips:** Respond quickly (200/202), verify signatures, deduplicate with stored event IDs, use `queue()` for async processing.
## Push Notifications
Fetch https://developers.cloudflare.com/agents/communication-channels/webhooks/push-notifications/ for complete documentation.
Web Push via VAPID from agents. Store subscriptions in agent state, send via `web-push`.
```bash
npm install web-push
```
```typescript
import webpush from "web-push";
export class NotifyAgent extends Agent<Env, State> {
@callable()
async subscribe(subscription: PushSubscription) {
this.setState({
...this.state,
subscriptions: [...this.state.subscriptions, subscription]
});
}
async sendReminder(payload: { message: string }, schedule: Schedule) {
for (const sub of this.state.subscriptions) {
try {
await webpush.sendNotification(sub, JSON.stringify({
title: "Reminder",
body: payload.message
}), {
vapidDetails: {
subject: "mailto:you@example.com",
publicKey: this.env.VAPID_PUBLIC_KEY,
privateKey: this.env.VAPID_PRIVATE_KEY
}
});
} catch (err) {
if (err.statusCode === 404 || err.statusCode === 410) {
// Remove expired subscription
}
}
}
}
}
```
VAPID keys: generate with `npx web-push generate-vapid-keys`, store as secrets.
references/workflows.md
# Workflows Integration
Use Agents for interactive communication and state management. Add a Workflow when a task needs durable multi-step execution, independent retries, or waits for external approval. Choose based on recovery needs; consult [Run Workflows](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/) before implementing the integration.
## Read for the task
| Task | Documentation |
| --- | --- |
| Define a typed `AgentWorkflow`, start it from an Agent, and configure bindings | [Quick start](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#quick-start) |
| Call back into the originating Agent and understand durable versus non-durable helpers | [AgentWorkflow class](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#agentworkflow-class) |
| Send events, query instances, pause, resume, terminate, or delete tracked workflows | [Agent workflow methods](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#agent-workflow-methods) |
| Receive progress, completion, errors, and custom events | [Lifecycle callbacks](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#lifecycle-callbacks) |
| Approve or reject a waiting task | [Human-in-the-loop approval](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#human-in-the-loop-approval) |
| Persist Workflow results into Agent state | [State synchronization](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#state-synchronization) |
| Define steps, parameters, retries, and returned values | [Workers API](https://developers.cloudflare.com/workflows/build/workers-api/) |
## Design checks
Keep external side effects within durable steps, make retried operations idempotent, and persist the values needed after recovery through step results. Use the [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) to choose step boundaries.
Progress reports and client broadcasts may repeat on retry. Use the documented durable step helpers for persistent Agent state changes and completion reporting; see [bidirectional communication](https://developers.cloudflare.com/agents/runtime/execution/run-workflows/#bidirectional-communication).
SKILL.md
---
name: agents-sdk
description: Build, debug, or review Cloudflare Agents SDK applications using the agents package.
---
# Cloudflare Agents SDK
Your knowledge of the Agents SDK may be outdated. **Prefer retrieval over pre-training** for any Agents SDK task.
## Retrieval Sources
Cloudflare docs: https://developers.cloudflare.com/agents/
| Topic | Docs URL | Use for |
|-------|----------|---------|
| Getting started | [Quick start](https://developers.cloudflare.com/agents/getting-started/quick-start/) | First agent, project setup |
| Adding to existing project | [Add to existing project](https://developers.cloudflare.com/agents/getting-started/add-to-existing-project/) | Install into existing Workers app |
| Configuration | [Configuration](https://developers.cloudflare.com/agents/api-reference/configuration/) | `wrangler.jsonc`, bindings, assets, deployment |
| Agent class | [Agents API](https://developers.cloudflare.com/agents/api-reference/agents-api/) | Agent lifecycle, patterns, pitfalls |
| State | [Store and sync state](https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/) | `setState`, `validateStateChange`, persistence |
| Routing | [Routing](https://developers.cloudflare.com/agents/api-reference/routing/) | URL patterns, `routeAgentRequest` |
| Callable methods | [Callable methods](https://developers.cloudflare.com/agents/api-reference/callable-methods/) | `@callable`, RPC, streaming, timeouts |
| Scheduling | [Schedule tasks](https://developers.cloudflare.com/agents/api-reference/schedule-tasks/) | `schedule()`, `scheduleEvery()`, cron |
| Workflows | [Run workflows](https://developers.cloudflare.com/agents/api-reference/run-workflows/) | `AgentWorkflow`, durable multi-step tasks |
| HTTP/WebSockets | [WebSockets](https://developers.cloudflare.com/agents/api-reference/websockets/) | Lifecycle hooks, hibernation |
| Chat agents | [Chat agents](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/) | `AIChatAgent`, streaming, tools, persistence |
| Client SDK | [Client SDK](https://developers.cloudflare.com/agents/communication-channels/chat/client-sdk/) | `useAgent`, `AgentClient`, state, RPC, HTTP |
| Client tools | [Client tools](https://developers.cloudflare.com/agents/harnesses/think/client-tools/) | Client-side tools, `autoContinueAfterToolResult` |
| Server-driven messages | [Autonomous responses](https://developers.cloudflare.com/agents/communication-channels/chat/autonomous-responses/) | `saveMessages`, `waitUntilStable`, server-initiated turns |
| Resumable streaming | [Chat agents](https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/#resumable-streaming) | Stream recovery on disconnect |
| Email | [Email](https://developers.cloudflare.com/agents/api-reference/email/) | Email routing, secure reply resolver |
| MCP client | [MCP client](https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/) | Connecting to MCP servers |
| MCP server | [MCP server](https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/) | Building MCP servers with `createMcpHandler` |
| MCP transports | [MCP transports](https://developers.cloudflare.com/agents/model-context-protocol/protocol/transport/) | Streamable HTTP, SSE, RPC transport options |
| Securing MCP servers | [Securing MCP](https://developers.cloudflare.com/agents/model-context-protocol/guides/securing-mcp-server/) | OAuth, proxy MCP, hardening |
| Human-in-the-loop | [Human-in-the-loop](https://developers.cloudflare.com/agents/concepts/agentic-patterns/human-in-the-loop/) | Workflow approvals, elicitation, timeout handling |
| Durable execution | [Durable execution](https://developers.cloudflare.com/agents/api-reference/durable-execution/) | `runFiber()`, `stash()`, surviving DO eviction |
| Queue | [Queue](https://developers.cloudflare.com/agents/api-reference/queue-tasks/) | Built-in FIFO queue, `queue()` |
| Retries | [Retries](https://developers.cloudflare.com/agents/api-reference/retries/) | `this.retry()`, backoff/jitter |
| Observability | [Observability](https://developers.cloudflare.com/agents/api-reference/observability/) | Diagnostics-channel events |
| Push notifications | [Push notifications](https://developers.cloudflare.com/agents/communication-channels/webhooks/push-notifications/) | Web Push + VAPID from agents |
| Webhooks | [Webhooks](https://developers.cloudflare.com/agents/communication-channels/webhooks/) | Receiving external webhooks |
| Cross-domain auth | [Cross-domain auth](https://developers.cloudflare.com/agents/runtime/operations/cross-domain-authentication/) | WebSocket auth, tokens, CORS |
| Readonly connections | [Readonly](https://developers.cloudflare.com/agents/api-reference/readonly-connections/) | `shouldConnectionBeReadonly` |
| Voice | [Voice](https://developers.cloudflare.com/agents/api-reference/voice/) | Experimental STT/TTS, `withVoice` |
| Browse the web | [Browser tools](https://developers.cloudflare.com/agents/api-reference/browse-the-web/) | Experimental CDP browser automation |
| Think | [Think](https://developers.cloudflare.com/agents/api-reference/think/) | Experimental higher-level chat agent class |
| Migrations | [AI SDK v5](https://github.com/cloudflare/agents/blob/main/docs/agents/migration-to-ai-sdk-v5.md), [AI SDK v6](https://github.com/cloudflare/agents/blob/main/docs/agents/migration-to-ai-sdk-v6.md) | Upgrading `@cloudflare/ai-chat` |
## Capabilities
The Agents SDK provides:
- **Persistent state** — SQLite-backed, auto-synced to clients via `setState`
- **Callable RPC** — `@callable()` methods invoked over WebSocket
- **Scheduling** — One-time, recurring (`scheduleEvery`), and cron tasks
- **Workflows** — Durable multi-step background processing via `AgentWorkflow`
- **Durable execution** — `runFiber()` / `stash()` for work that survives DO eviction
- **Queue** — Built-in FIFO queue with retries via `queue()`
- **Retries** — `this.retry()` with exponential backoff and jitter
- **MCP integration** — Connect to MCP servers or build your own with `createMcpHandler`
- **Email handling** — Receive and reply to emails with secure routing
- **Streaming chat** — `AIChatAgent` with resumable streams, message persistence, tools
- **Server-driven messages** — `saveMessages`, `waitUntilStable` for proactive agent turns
- **React hooks** — `useAgent`, `useAgentChat` for client apps
- **Observability** — `diagnostics_channel` events for state, RPC, schedule, lifecycle
- **Push notifications** — Web Push + VAPID delivery from agents
- **Webhooks** — Receive and verify external webhooks
- **Voice** (experimental) — STT/TTS via `@cloudflare/voice`
- **Browser tools** (experimental) — CDP-powered browsing via `agents/browser`
- **Think** (experimental) — Higher-level chat agent via `@cloudflare/think`
## FIRST: Verify Installation
```bash
npm ls agents # Should show agents package
```
If not installed:
```bash
npm install agents
```
For chat agents:
```bash
npm install agents @cloudflare/ai-chat ai @ai-sdk/react
```
## Wrangler Configuration
```jsonc
{
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]
}
```
**Gotchas:**
- Do NOT enable `experimentalDecorators` in tsconfig (breaks `@callable`)
- Never edit old migrations — always add new tags
- Each agent class needs its own DO binding + migration entry
- Add `"ai": { "binding": "AI" }` for Workers AI
## Agent Class
```typescript
import { Agent, routeAgentRequest, callable } from "agents";
type State = { count: number };
export class Counter extends Agent<Env, State> {
initialState = { count: 0 };
validateStateChange(nextState: State, source: Connection | "server") {
if (nextState.count < 0) throw new Error("Count cannot be negative");
}
onStateUpdate(state: State, source: Connection | "server") {
console.log("State updated:", state);
}
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
}
export default {
fetch: (req, env) => routeAgentRequest(req, env) ?? new Response("Not found", { status: 404 })
};
```
## Routing
Requests route to `/agents/{agent-name}/{instance-name}`:
| Class | URL |
|-------|-----|
| `Counter` | `/agents/counter/user-123` |
| `ChatRoom` | `/agents/chat-room/lobby` |
Client: `useAgent({ agent: "Counter", name: "user-123" })`
Custom routing: use `getAgentByName(env.MyAgent, "instance-id")` then `agent.fetch(request)`.
## Core APIs
| Task | API |
|------|-----|
| Read state | `this.state.count` |
| Write state | `this.setState({ count: 1 })` |
| SQL query | `` this.sql`SELECT * FROM users WHERE id = ${id}` `` |
| Schedule (delay) | `await this.schedule(60, "task", payload)` |
| Schedule (cron) | `await this.schedule("0 * * * *", "task", payload)` |
| Schedule (interval) | `await this.scheduleEvery(30, "poll")` |
| RPC method | `@callable() myMethod() { ... }` |
| Streaming RPC | `@callable({ streaming: true }) stream(res) { ... }` |
| Start workflow | `await this.runWorkflow("ProcessingWorkflow", params)` |
| Durable fiber | `await this.runFiber("name", async (ctx) => { ... })` |
| Enqueue work | `this.queue("handler", payload)` |
| Retry with backoff | `await this.retry(fn, { maxAttempts: 5 })` |
| Broadcast to clients | `this.broadcast(message)` |
| Get connections | `this.getConnections(tag?)` |
## React Client
Read [client-sdk.md](references/client-sdk.md) for client selection and current connection examples. For chat UI and tools, also read [streaming-chat.md](references/streaming-chat.md).
## References
### Core
- **[references/state-scheduling.md](references/state-scheduling.md)** — State persistence, scheduling, SQL
- **[references/callable.md](references/callable.md)** — RPC methods, streaming, timeouts
- **[references/routing.md](references/routing.md)** — URL patterns, custom routing, `getAgentByName`
- **[references/configuration.md](references/configuration.md)** — Wrangler config, bindings, Vite setup
### Chat & Streaming
- **[references/streaming-chat.md](references/streaming-chat.md)** — AIChatAgent, resumable streams, tools
- **[references/client-sdk.md](references/client-sdk.md)** — `useAgent`, `useAgentChat`, `AgentClient`
- **[references/server-driven-messages.md](references/server-driven-messages.md)** — Trigger patterns, `saveMessages`
- **[references/human-in-the-loop.md](references/human-in-the-loop.md)** — Approval flows, `needsApproval`
### Background Processing
- **[references/workflows.md](references/workflows.md)** — Durable Workflows integration
- **[references/durable-execution.md](references/durable-execution.md)** — `runFiber`, `stash`, surviving eviction
- **[references/queue-retries.md](references/queue-retries.md)** — Built-in queue, retry with backoff
### Integrations
- **[references/mcp.md](references/mcp.md)** — MCP client and server, transports, securing
- **[references/email.md](references/email.md)** — Email routing and handling
- **[references/webhooks-push.md](references/webhooks-push.md)** — Webhooks, push notifications
- **[references/observability.md](references/observability.md)** — Diagnostics-channel events
### Experimental
- **[references/think.md](references/think.md)** — `@cloudflare/think` higher-level chat agent
- **[references/voice.md](references/voice.md)** — `@cloudflare/voice` STT/TTS
- **[references/codemode.md](references/codemode.md)** — Code Mode for tool orchestration
- **[references/browse-the-web.md](references/browse-the-web.md)** — CDP browser tools