Agents Model
The Cloudflare Agents SDK lets you build stateful AI agents that persist state, hold WebSocket connections, define tools, and maintain conversation history across reconnections. Under the hood, each agent is a Durable Object instance.
Prerequisites: Durable Objects, Workers
Agents Are Durable Objects
The Agent base class extends Durable Objects. This means every agent instance:
- Has a globally unique identity (by name or ID)
- Runs as a single instance at a time (no race conditions)
- Has persistent storage (SQLite, key-value)
- Can hold WebSocket connections with hibernation
- Survives restarts and re-deploys (state persists)
The SDK adds agent-specific capabilities on top: state management, tool definitions, conversation handling, and a client library for connecting from browsers.
Architecture
flowchart LR
subgraph clients["Clients"]
B["Browser"]
M["Mobile"]
end
subgraph worker["Worker"]
H["Hono + agentsMiddleware"]
end
subgraph agent["Agent DO Instance"]
direction TB
ST["State"]
TL["Tools"]
AI["AI Model"]
SQL[("SQLite")]
end
B -->|WebSocket| H
M -->|WebSocket| H
H -->|"env.AGENT.get(id)"| agent
ST --- SQL
TL --> AI
The Worker routes requests to the correct agent instance. The agent handles everything else: parsing messages, calling tools, updating state, and pushing state changes back to connected clients.
Agent Lifecycle
sequenceDiagram
participant C as Client
participant W as Worker
participant A as Agent
C->>W: Connect WebSocket
W->>A: Route to agent instance
A->>C: Send current state
C->>A: User message
A->>A: Process with LLM
A->>A: Tool call
A->>A: Update state
A->>C: Stream response
Note over A: Client disconnects
A->>A: Hibernate with state
C->>W: Reconnect
W->>A: Same agent instance
A->>C: Full state sync
Key points:
- Connect: Client connects via WebSocket (or HTTP). The agent sends its current state immediately.
- Message: Client sends a message. The agent processes it (optionally with an LLM), may call tools, updates state.
- State sync: Any state change broadcasts to all connected clients automatically.
- Disconnect/reconnect: The agent hibernates when idle. On reconnect, the client gets the full state, so the conversation resumes exactly where it left off.
Core Concepts
State
Each agent has typed state that persists across connections:
interface MyState {
counter: number;
notes: string[];
}
export class NoteAgent extends Agent<Env, MyState> {
initialState: MyState = {
counter: 0,
notes: [],
};
// Called whenever state changes
async onStateChange(state: MyState): Promise<void> {
console.log("State updated:", state);
}
}
Call this.setState(newState) to update. The SDK persists the state and broadcasts the update to all connected clients.
Connections
The agent receives connection lifecycle events:
export class ChatAgent extends Agent<Env, ChatState> {
async onConnect(connection: Connection, ctx: ConnectionContext): Promise<void> {
// Client connected - send them the current state
connection.send(JSON.stringify({ type: "state", data: this.state }));
}
async onMessage(connection: Connection, message: string): Promise<void> {
const data = JSON.parse(message);
// Handle the message, update state, respond
}
async onClose(connection: Connection): Promise<void> {
// Client disconnected
}
}
Use this.broadcast(message) to send to all connected clients, or connection.send(message) for a single client.
Tools
Tools are functions the agent can call during conversation. Define them as methods, and the LLM decides when to use them:
export class AssistantAgent extends Agent<Env, AssistantState> {
async getWeather(location: string): Promise<string> {
const res = await fetch(`https://api.weather.com/${location}`);
return res.text();
}
async saveNote(content: string): Promise<string> {
this.setState({
...this.state,
notes: [...this.state.notes, content],
});
return `Saved: ${content}`;
}
}
The Agents SDK handles the tool-call loop: LLM decides to call a tool, the agent executes it, the result goes back to the LLM, and the final response goes to the client.
SQL Storage
Each agent has a built-in SQLite database (inherited from Durable Objects):
export class DataAgent extends Agent<Env, DataState> {
async onStart(): Promise<void> {
// Runs once when the agent starts
this.sql`CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
query TEXT NOT NULL,
result TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)`;
}
async logQuery(query: string, result: string): Promise<void> {
this.sql`INSERT INTO history (query, result) VALUES (${query}, ${result})`;
}
async getHistory(): Promise<unknown[]> {
return this.sql`SELECT * FROM history ORDER BY created_at DESC LIMIT 10`;
}
}
Relationship to Durable Objects
Everything you know about DOs applies:
| DO Feature | Agent Equivalent |
|---|---|
fetch() handler | onRequest() method |
webSocketMessage() | onMessage() |
| WebSocket hibernation | Built-in, automatic |
| SQLite storage | this.sql template tag |
| Key-value storage | this.state (managed) |
| Alarms | this.schedule() |
| Input gates | Single-instance guarantee |
The SDK is a convenience layer. If you need raw DO capabilities, they’re all available on this.state (the DurableObjectState).
Client Connection
The Agents SDK provides client libraries for connecting from browsers:
// React hook
import { useAgent } from "agents/react";
function Chat() {
const agent = useAgent({
agent: "ChatAgent",
name: "room-123",
onStateUpdate: (state) => updateUI(state),
});
return <button onClick={() => agent.call("sendMessage", ["hello"])}>Send</button>;
}
// Vanilla JS
import { AgentClient } from "agents/client";
const client = new AgentClient({
agent: "ChatAgent",
name: "room-123",
host: "my-worker.workers.dev",
onStateUpdate: (state, source) => console.log(state),
});
await client.call("sendMessage", ["hello"]);
Both connect via WebSocket and automatically sync state. When the agent calls this.setState(), every connected client’s onStateUpdate fires.
Hono Integration
The hono-agents package provides middleware that handles routing to agents:
import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
const app = new Hono();
app.use("*", agentsMiddleware());
// Your regular Hono routes still work
app.get("/api/health", (c) => c.json({ ok: true }));
export default app;
The middleware intercepts agent connection requests and routes them to the correct DO. Non-agent requests pass through to your Hono routes.
Gotcha: The Agents SDK package is
agentson npm (pre-1.0). The API may change between minor versions. Pin your version inpackage.jsonand test after upgrades.
See Agents SDK quickstart for a complete working example.
What’s Next
- Agents SDK quickstart - Build a working agent
- Agent Patterns - Scheduled agents, MCP, human-in-the-loop, multi-agent
- Durable Objects - The foundation agents build on