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 FeatureAgent Equivalent
fetch() handleronRequest() method
webSocketMessage()onMessage()
WebSocket hibernationBuilt-in, automatic
SQLite storagethis.sql template tag
Key-value storagethis.state (managed)
Alarmsthis.schedule()
Input gatesSingle-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 agents on npm (pre-1.0). The API may change between minor versions. Pin your version in package.json and test after upgrades.

See Agents SDK quickstart for a complete working example.

What’s Next