Build a Durable AI Agent·续
This is especially important for:
- LLM calls: Expensive and slow, should not repeat unnecessarily
- External APIs: May have rate limits or side effects
- Idempotency: Some tools (like sending emails) should not run twice
</details>
4. Write your Agent
The Agent handles HTTP requests, WebSocket connections, and Workflow lifecycle events. It triggers a workflow instance runWorkflow() and receives progress updates via callbacks.
Create src/agent.ts:
src/agent.tsts
import { Agent } from "agents";
type State = {
currentWorkflow?: string;
status?: string;
};
export class ResearchAgent extends Agent<Env, State> {
initialState: State = {};
// Start a research task - called via HTTP or WebSocket
async startResearch(task: string) {
const instanceId = await this.runWorkflow("RESEARCH_WORKFLOW", { task });
this.setState({
...this.state,
currentWorkflow: instanceId,
status: "running",
});
return { instanceId };
}
// Get status of a workflow
async getResearchStatus(instanceId: string) {
return this.getWorkflow(instanceId);
}
// Called when workflow reports progress
async onWorkflowProgress(
workflowName: string,
instanceId: string,
progress: unknown,
) {
// Broadcast to all connected WebSocket clients
this.broadcast(JSON.stringify({ type: "progress", instanceId, progress }));
}
// Called when workflow completes
async onWorkflowComplete(
workflowName: string,
instanceId: string,
result?: unknown,
) {
this.setState({ ...this.state, status: "complete" });
this.broadcast(JSON.stringify({ type: "complete", instanceId, result }));
}
// Called when workflow errors
async onWorkflowError(
workflowName: string,
instanceId: string,
error: string,
) {
this.setState({ ...this.state, status: "error" });
this.broadcast(JSON.stringify({ type: "error", instanceId, error }));
}
}
5. Configure your project
- Open
wrangler.jsoncand add the Agent and Workflow configuration:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "durable-ai-agent",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"observability": {
"enabled": true
},
"durable_objects": {
"bindings": [
{
"name": "ResearchAgent",
"class_name": "ResearchAgent"
}
]
},
"workflows": [
{
"name": "research-workflow",
"binding": "RESEARCH_WORKFLOW",
"class_name": "ResearchWorkflow"
}
],
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["ResearchAgent"]
}
]
}
```
```toml
"$schema" = "node_modules/wrangler/config-schema.json"
name = "durable-ai-agent"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[observability]
enabled = true
[[durable_objects.bindings]]
name = "ResearchAgent"
class_name = "ResearchAgent"
[[workflows]]
name = "research-workflow"
binding = "RESEARCH_WORKFLOW"
class_name = "ResearchWorkflow"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "ResearchAgent" ]
```
2. Generate types for your bindings:
```sh
npx wrangler types
```
This creates a `worker-configuration.d.ts` file with the `Env` type that includes your bindings.
## 6. Write your API
The Worker routes requests to the Agent, which manages workflow lifecycle. Use `routeAgentRequest()` for WebSocket connections and `getAgentByName()` for server-side RPC calls.
Replace `src/index.ts`:
*src/index.tsts*
```ts
import { getAgentByName, routeAgentRequest } from "agents";
export { ResearchAgent } from "./agent";
export { ResearchWorkflow } from "./workflow";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Route WebSocket connections to /agents/research-agent/{name}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// HTTP API for starting research tasks
if (request.method === "POST" && url.pathname === "/research") {
const { task, agentId } = await request.json<{
task: string;
agentId?: string;
}>();
// Get agent instance by name (creates if doesn't exist)
const agent = await getAgentByName(
env.ResearchAgent,
agentId ?? "default",
);
// Start the research workflow via RPC
const result = await agent.startResearch(task);
return Response.json(result);
}
// Check workflow status
if (url.pathname === "/status") {
const instanceId = url.searchParams.get("instanceId");
const agentId = url.searchParams.get("agentId") ?? "default";
if (!instanceId) {
return Response.json({ error: "instanceId required" }, { status: 400 });
}
const agent = await getAgentByName(env.ResearchAgent, agentId);
const status = await agent.getResearchStatus(instanceId);
return Response.json(status);
}
return new Response("POST /research with { task } to start", {
status: 400,
});
},
} satisfies ExportedHandler<Env>;
7. Develop locally
- Create a
.envfile for local development:
.envsh
ANTHROPIC_API_KEY=your-api-key-here
```
2. Start the dev server:
```sh
npx wrangler dev
```
3. Start a research task:
```sh
curl -X POST http://localhost:8787/research \
-H "Content-Type: application/json" \
-d '{"task": "Compare open-source LLM projects"}'
```
```json
{ "instanceId": "abc-123-def" }
```
4. Check progress (may take a few seconds to complete):
```sh
curl "http://localhost:8787/status?instanceId=abc-123-def"
```
The agent will search for repositories, fetch details, and return a comparison. Progress updates are broadcast to any connected WebSocket clients.
## 8. Deploy
1. Deploy the Worker:
```sh
npx wrangler deploy
```
2. Add your API key as a secret:
```sh
npx wrangler secret put ANTHROPIC_API_KEY
```
3. Start a research task on your deployed Worker:
```sh
curl -X POST https://durable-ai-agent.<your-subdomain>.workers.dev/research \
-H "Content-Type: application/json" \
-d '{"task": "Compare open-source LLM projects"}'
```
4. Inspect workflow runs with the CLI:
```sh
npx wrangler workflows instances describe research-workflow latest
```
This shows every step the agent took, including LLM calls, tool executions, timing, and any retries.
You can also view this in the Cloudflare dashboard under **research-workflow**. [Go to **Workflows** ↗](https://dash.cloudflare.com/?to=/:account/workers/workflows)
## Real-time client integration
Connect to your Agent via WebSocket to receive real-time progress updates. The `useAgent` hook connects to `/agents/{agent-name}/{instance-name}`:
```plaintext
/agents/research-agent/default → ResearchAgent instance "default"
/agents/research-agent/user-123 → ResearchAgent instance "user-123"
import { useState } from "react";
import { useAgent } from "agents/react";
function ResearchUI({ agentId = "default" }) {
const [progress, setProgress] = useState(null);
const { state } = useAgent({
agent: "research-agent", // Maps to ResearchAgent class
name: agentId, // Instance name
onMessage: (message) => {
const data = JSON.parse(message.data);
if (data.type === "progress") {
setProgress(data.progress);
}
},
});
return (
<div>
{progress && (
<p>
{progress.message} ({Math.round(progress.percent * 100)}%)
</p>
)}
</div>
);
}
Agent class names are automatically converted to kebab-case for URLs (ResearchAgent → research-agent).
Learn more
Agents SDK Workflows
Complete API reference for AgentWorkflow, lifecycle callbacks, and bidirectional communication.
Events and parameters
Pass data to Workflows and pause for external events with waitForEvent.
Sleeping and retrying
Configure retry behavior and sleep patterns.
Workers API
Explore the full Workflows API for programmatic control.
Agents SDK
For interactive agents with real-time chat and WebSocket connections.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workflows/get-started/durable-agents/#page","headline":"Build a Durable AI Agent","description":"Build a durable AI agent using Cloudflare Workflows that researches GitHub repositories with automatic retries.","url":"https://developers.cloudflare.com/workflows/get-started/durable-agents/","inLanguage":"en","image":"https://developers.cloudflare.com/workflows/get-started/durable-agents/og.png?v=174bc75a15bd8094","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Create and deploy your first Cloudflare Workflow with durable, multi-step execution on the Workers platform. title: Build your first Workflow image: https://developers.cloudflare.com/workflows/get-started/guide/og.png?v=96c1b93bf642b79e
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workflows/llms.txt
Use this file to discover all available pages before exploring further.
Docs