Skip to content
Cloudflare Docs

Quick start

Build AI agents that persist, think, and act. Agents run on Cloudflare's global network, maintain state across requests, and connect to clients in real-time via WebSockets.

What you will build: A counter agent with persistent state that syncs to a React frontend in real-time.

Time: ~10 minutes

Create a new project

Terminal window
npm create cloudflare@latest -- -- --template cloudflare/agents-starter

Then install dependencies and start the dev server:

Terminal window
cd my-agent
npm install
npm run dev

This creates a project with:

  • src/server.ts — Your agent code
  • src/client.tsx — React frontend
  • wrangler.jsonc — Cloudflare configuration

Open http://localhost:5173 to see your agent in action.

Your first agent

Build a simple counter agent from scratch. Replace src/server.ts:

JavaScript
import { Agent, routeAgentRequest, callable } from "agents";
// Define the state shape
// Create the agent
export class Counter extends Agent {
// Initial state for new instances
initialState = { count: 0 };
// Methods marked with @callable can be called from the client
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
@callable()
reset() {
this.setState({ count: 0 });
}
}
// Route requests to agents
export default {
async fetch(request, env, ctx) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};

Update wrangler.jsonc to register the agent:

{
"name": "my-agent",
"main": "src/server.ts",
"compatibility_date": "2025-01-01",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "Counter",
"class_name": "Counter",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter"],
},
],
}

Connect from React

Replace src/client.tsx:

src/client.tsx
import { useState } from "react";
import { useAgent } from "agents/react";
import type { Counter } from "./server";
// Match your agent's state type
type CounterState = {
count: number;
};
export default function App() {
const [count, setCount] = useState(0);
// Connect to the Counter agent
const agent = useAgent<Counter, CounterState>({
agent: "Counter",
onStateUpdate: (state) => setCount(state.count),
});
return (
<div style={{ padding: "2rem", fontFamily: "system-ui" }}>
<h1>Counter Agent</h1>
<p style={{ fontSize: "3rem" }}>{count}</p>
<div style={{ display: "flex", gap: "1rem" }}>
<button onClick={() => agent.stub.decrement()}>-</button>
<button onClick={() => agent.stub.reset()}>Reset</button>
<button onClick={() => agent.stub.increment()}>+</button>
</div>
</div>
);
}

Key points:

  • useAgent connects to your agent via WebSocket
  • onStateUpdate fires whenever the agent's state changes
  • agent.stub.methodName() calls methods marked with @callable() on your agent

What just happened?

When you clicked the button:

  1. Client called agent.stub.increment() over WebSocket
  2. Agent ran increment(), updated state with setState()
  3. State persisted to SQLite automatically
  4. Broadcast sent to all connected clients
  5. React updated via onStateUpdate
flowchart LR
    A["Browser<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
    B --> C["SQLite<br/>(State)"]

Key concepts

ConceptWhat it means
Agent instanceEach unique name gets its own agent. Counter:user-123 is separate from Counter:user-456
Persistent stateState survives restarts, deploys, and hibernation. It is stored in SQLite
Real-time syncAll clients connected to the same agent receive state updates instantly
HibernationWhen no clients are connected, the agent hibernates (no cost). It wakes on the next request

Connect from vanilla JavaScript

If you are not using React:

JavaScript
import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "Counter",
name: "my-counter", // optional, defaults to "default"
onStateUpdate: (state) => {
console.log("New count:", state.count);
},
});
// Call methods
await agent.call("increment");
await agent.call("reset");

Deploy to Cloudflare

Terminal window
npm run deploy

Your agent is now live on Cloudflare's global network, running close to your users.

Troubleshooting

"Agent not found" or 404 errors

Make sure:

  1. Agent class is exported from your server file
  2. wrangler.jsonc has the binding and migration
  3. Agent name in client matches the class name (case-insensitive)

State not syncing

Check that:

  1. You are calling this.setState(), not mutating this.state directly
  2. The onStateUpdate callback is wired up in your client
  3. WebSocket connection is established (check browser dev tools)

"Method X is not callable" errors

Make sure your methods are decorated with @callable():

JavaScript
import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}

Type errors with agent.stub

Add the agent and state type parameters:

JavaScript
import { useAgent } from "agents/react";
// Pass the agent and state types to useAgent
const agent = useAgent({
agent: "Counter",
onStateUpdate: (state) => setCount(state.count),
});
// Now agent.stub is fully typed
agent.stub.increment();

Next steps

Now that you have a working agent, explore these topics:

Common patterns

Learn how toRefer to
Add AI/LLM capabilitiesUsing AI models
Expose tools via MCPMCP servers
Run background tasksSchedule tasks
Handle emailsEmail routing
Use Cloudflare WorkflowsRun Workflows

Explore more