# Cloudflare Agents

> Build AI agents with Cloudflare Agents SDK on Workers + Durable Objects. Includes critical guidance on choosing between Agents SDK (infrastructure/state) vs AI SDK (simpler flows). Use when: deciding SDK choice, building WebSocket agents with state, RAG with Vectorize, MCP servers, multi-agent orchestration, or troubleshooting "Agent class must extend", "new_sqlite_classes", binding errors.

- Skill: `ovachiever/cloudflare-agents` (Agent Skill, multi-file: 17 files)
- Install (CLI): `npx skillmds@latest add ovachiever/cloudflare-agents`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ovachiever/cloudflare-agents/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: ovachiever (https://skillmd.com/u/ovachiever)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/ovachiever/cloudflare-agents

---


# Cloudflare Agents SDK

**Status**: Production Ready ✅
**Last Updated**: 2025-11-23
**Dependencies**: cloudflare-worker-base (recommended)
**Latest Versions**: agents@0.2.23 (Nov 13, 2025), @modelcontextprotocol/sdk@latest
**Production Tested**: Cloudflare's own MCP servers (https://github.com/cloudflare/mcp-server-cloudflare)

**Recent Updates (2025)**:
- **Sept 2025**: AI SDK v5 compatibility, automatic message migration
- **April 2025**: MCP support (MCPAgent class), `import { context }` from agents
- **March 2025**: Package rename (agents-sdk → agents)

---

## What is Cloudflare Agents?

The Cloudflare Agents SDK enables building AI-powered autonomous agents that run on Cloudflare Workers + Durable Objects. Agents can:

- **Communicate in real-time** via WebSockets and Server-Sent Events
- **Persist state** with built-in SQLite database (up to 1GB per agent)
- **Schedule tasks** using delays, specific dates, or cron expressions
- **Run workflows** by triggering asynchronous Cloudflare Workflows
- **Browse the web** using Browser Rendering API + Puppeteer
- **Implement RAG** with Vectorize vector database + Workers AI embeddings
- **Build MCP servers** implementing the Model Context Protocol
- **Support human-in-the-loop** patterns for review and approval
- **Scale to millions** of independent agent instances globally

Each agent instance is a **globally unique, stateful micro-server** that can run for seconds, minutes, or hours.

---

## Do You Need Agents SDK?

**STOP**: Before using Agents SDK, ask yourself if you actually need it.

### Use JUST Vercel AI SDK (Simpler) When:

- ✅ Building a basic chat interface
- ✅ Server-Sent Events (SSE) streaming is sufficient (one-way: server → client)
- ✅ No persistent agent state needed (or you manage it separately with D1/KV)
- ✅ Single-user, single-conversation scenarios
- ✅ Just need AI responses, no complex workflows or scheduling

**This covers 80% of chat applications.** For these cases, use [Vercel AI SDK](https://sdk.vercel.ai/) directly on Workers - it's simpler, requires less infrastructure, and handles streaming automatically.

**Example** (no Agents SDK needed):
```typescript
// worker.ts - Simple chat with AI SDK only
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

export default {
  async fetch(request: Request, env: Env) {
    const { messages } = await request.json();

    const result = streamText({
      model: openai('gpt-4o-mini'),
      messages
    });

    return result.toTextStreamResponse(); // Automatic SSE streaming
  }
}

// client.tsx - React with built-in hooks
import { useChat } from 'ai/react';

function ChatPage() {
  const { messages, input, handleSubmit } = useChat({ api: '/api/chat' });
  // Done. No Agents SDK needed.
}
```

**Result**: 100 lines of code instead of 500. No Durable Objects setup, no WebSocket complexity, no migrations.

---

### Use Agents SDK When You Need:

- ✅ **WebSocket connections** (true bidirectional real-time communication)
- ✅ **Durable Objects** (globally unique, stateful agent instances)
- ✅ **Built-in state persistence** (SQLite storage up to 1GB per agent)
- ✅ **Multi-agent coordination** (agents calling and communicating with each other)
- ✅ **Scheduled tasks** (delays, cron expressions, recurring jobs)
- ✅ **Human-in-the-loop workflows** (approval gates, review processes)
- ✅ **Long-running agents** (background processing, autonomous workflows)
- ✅ **MCP servers** with stateful tool execution

**This is ~20% of applications** - when you need the infrastructure that Agents SDK provides.

---

### Key Understanding: What Agents SDK IS vs IS NOT

**Agents SDK IS**:
- 🏗️ **Infrastructure layer** for WebSocket connections, Durable Objects, and state management
- 🔧 **Framework** for building stateful, autonomous agents
- 📦 **Wrapper** around Durable Objects with lifecycle methods

**Agents SDK IS NOT**:
- ❌ **AI inference provider** (you bring your own: AI SDK, Workers AI, OpenAI, etc.)
- ❌ **Streaming response handler** (use AI SDK for automatic parsing)
- ❌ **LLM integration** (that's a separate concern)

**Think of it this way**:
- **Agents SDK** = The building (WebSockets, state, rooms)
- **AI SDK / Workers AI** = The AI brain (inference, reasoning, responses)

You can use them together (recommended for most cases), or use Workers AI directly (if you're willing to handle manual SSE parsing).

---

### Decision Flowchart

```
Building an AI application?
│
├─ Need WebSocket bidirectional communication? ───────┐
│  (Client sends while server streams, agent-initiated messages)
│
├─ Need Durable Objects stateful instances? ──────────┤
│  (Globally unique agents with persistent memory)
│
├─ Need multi-agent coordination? ────────────────────┤
│  (Agents calling/messaging other agents)
│
├─ Need scheduled tasks or cron jobs? ────────────────┤
│  (Delayed execution, recurring tasks)
│
├─ Need human-in-the-loop workflows? ─────────────────┤
│  (Approval gates, review processes)
│
└─ If ALL above are NO ─────────────────────────────→ Use AI SDK directly
                                                       (Much simpler approach)

   If ANY above are YES ────────────────────────────→ Use Agents SDK + AI SDK
                                                       (More infrastructure, more power)
```

---

### Architecture Comparison

| Feature | AI SDK Only | Agents SDK + AI SDK |
|---------|-------------|---------------------|
| **Setup Complexity** | 🟢 Low (npm install, done) | 🔴 Higher (Durable Objects, migrations, bindings) |
| **Code Volume** | 🟢 ~100 lines | 🟡 ~500+ lines |
| **Streaming** | ✅ Automatic (SSE) | ✅ Automatic (AI SDK) or manual (Workers AI) |
| **State Management** | ⚠️ Manual (D1/KV) | ✅ Built-in (SQLite) |
| **WebSockets** | ❌ Manual setup | ✅ Built-in |
| **React Hooks** | ✅ useChat, useCompletion | ⚠️ Custom hooks needed |
| **Multi-agent** | ❌ Not supported | ✅ Built-in (routeAgentRequest) |
| **Scheduling** | ❌ External (Queue/Workflow) | ✅ Built-in (this.schedule) |
| **Use Case** | Simple chat, completions | Complex stateful workflows |

---

### Still Not Sure?

**Start with AI SDK.** You can always migrate to Agents SDK later if you discover you need WebSockets or Durable Objects. It's easier to add infrastructure later than to remove it.

**For most developers**: If you're building a chat interface and don't have specific requirements for WebSockets, multi-agent coordination, or scheduled tasks, use AI SDK directly. You'll ship faster and with less complexity.

**Proceed with Agents SDK only if** you've identified a specific need for its infrastructure capabilities.

---

## Quick Start (10 Minutes)

### 1. Scaffold Project with Template

```bash
npm create cloudflare@latest my-agent -- \
  --template=cloudflare/agents-starter \
  --ts \
  --git \
  --deploy false
```

**What this creates:**
- Complete Agent project structure
- TypeScript configuration
- wrangler.jsonc with Durable Objects bindings
- Example chat agent implementation
- React client with useAgent hook

### 2. Or Add to Existing Worker

```bash
cd my-existing-worker
npm install agents
```

**Then create an Agent class:**

```typescript
// src/index.ts
import { Agent, AgentNamespace } from "agents";

export class MyAgent extends Agent {
  async onRequest(request: Request): Promise<Response> {
    return new Response("Hello from Agent!");
  }
}

export default MyAgent;
```

### 3. Configure Durable Objects Binding

Create or update `wrangler.jsonc`:

```jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-agent",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-21",
  "compatibility_flags": ["nodejs_compat"],
  "durable_objects": {
    "bindings": [
      {
        "name": "MyAgent",        // MUST match class name
        "class_name": "MyAgent"   // MUST match exported class
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyAgent"]  // CRITICAL: Enables SQLite storage
    }
  ]
}
```

**CRITICAL Configuration Rules:**
- ✅ `name` and `class_name` **MUST be identical**
- ✅ `new_sqlite_classes` **MUST be in first migration** (cannot add later)
- ✅ Agent class **MUST be exported** (or binding will fail)
- ✅ Migration tags **CANNOT be reused** (each migration needs unique tag)

### 4. Deploy

```bash
npx wrangler@latest deploy
```

Your agent is now running at: `https://my-agent.<subdomain>.workers.dev`

---

## Architecture Overview: How the Pieces Fit Together

Understanding what each tool does prevents confusion and helps you choose the right combination.

### The Stack

```
┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
│                                                          │
│  ┌────────────────┐         ┌──────────────────────┐   │
│  │  Agents SDK    │         │   AI Inference       │   │
│  │  (Infra Layer) │   +     │   (Brain Layer)      │   │
│  │                │         │                      │   │
│  │ • WebSockets   │         │  Choose ONE:         │   │
│  │ • Durable Objs │         │  • Vercel AI SDK ✅   │   │
│  │ • State (SQL)  │         │  • Workers AI ⚠️      │   │
│  │ • Scheduling   │         │  • OpenAI Direct     │   │
│  │ • Multi-agent  │         │  • Anthropic Direct  │   │
│  └────────────────┘         └──────────────────────┘   │
│         ↓                             ↓                │
│  Manages connections          Generates responses      │
│  and state                    and handles streaming    │
└─────────────────────────────────────────────────────────┘
                          ↓
              Cloudflare Workers + Durable Objects
```

### What Each Tool Provides

#### 1. Agents SDK (This Skill)

**Purpose**: Infrastructure for stateful, real-time agents

**Provides**:
- ✅ WebSocket connection management (bidirectional real-time)
- ✅ Durable Objects wrapper (globally unique agent instances)
- ✅ Built-in state persistence (SQLite up to 1GB)
- ✅ Lifecycle methods (`onStart`, `onConnect`, `onMessage`, `onClose`)
- ✅ Task scheduling (`this.schedule()` with cron/delays)
- ✅ Multi-agent coordination (`routeAgentRequest()`)
- ✅ Client libraries (`useAgent`, `AgentClient`, `agentFetch`)

**Does NOT Provide**:
- ❌ AI inference (no LLM calls)
- ❌ Streaming response parsing (bring your own)
- ❌ Provider integrations (OpenAI, Anthropic, etc.)

**Think of it as**: The building and infrastructure (rooms, doors, plumbing) but NOT the residents (AI).

---

#### 2. Vercel AI SDK (Recommended for AI)

**Purpose**: AI inference with automatic streaming

**Provides**:
- ✅ Automatic streaming response handling (SSE parsing done for you)
- ✅ Multi-provider support (OpenAI, Anthropic, Google, etc.)
- ✅ React hooks (`useChat`, `useCompletion`, `useAssistant`)
- ✅ Unified API across providers
- ✅ Tool calling / function calling
- ✅ Works on Cloudflare Workers ✅

**Example**:
```typescript
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = streamText({
  model: openai('gpt-4o-mini'),
  messages: [...]
});

// Returns SSE stream - no manual parsing needed
return result.toTextStreamResponse();
```

**When to use with Agents SDK**:
- ✅ Most chat applications
- ✅ When you want React hooks
- ✅ When you use multiple AI providers
- ✅ When you want clean, abstracted AI calls

**Combine with Agents SDK**:
```typescript
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";

export class MyAgent extends AIChatAgent<Env> {
  async onChatMessage(onFinish) {
    // Agents SDK provides: WebSocket, state, this.messages
    // AI SDK provides: Automatic streaming, provider abstraction

    return streamText({
      model: openai('gpt-4o-mini'),
      messages: this.messages  // Managed by Agents SDK
    }).toTextStreamResponse();
  }
}
```

---

#### 3. Workers AI (Alternative for AI)

**Purpose**: Cloudflare's on-platform AI inference

**Provides**:
- ✅ Cost-effective inference (included in Workers subscription)
- ✅ No external API keys needed
- ✅ Models: LLaMA 3, Qwen, Mistral, embeddings, etc.
- ✅ Runs on Cloudflare's network (low latency)

**Does NOT Provide**:
- ❌ Automatic streaming parsing (returns raw SSE format)
- ❌ React hooks
- ❌ Multi-provider abstraction

**Manual parsing required**:
```typescript
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
  messages: [...],
  stream: true
});

// Returns raw SSE format - YOU must parse
for await (const chunk of response) {
  const text = new TextDecoder().decode(chunk);  // Uint8Array → string
  if (text.startsWith('data: ')) {              // Check SSE format
    const data = JSON.parse(text.slice(6));     // Parse JSON
    if (data.response) {                        // Extract .response field
      fullResponse += data.response;
    }
  }
}
```

**When to use**:
- ✅ Cost is critical (embeddings, high-volume)
- ✅ Need Cloudflare-specific models
- ✅ Willing to handle manual SSE parsing
- ✅ No external dependencies allowed

**Trade-off**: Save money, spend time on manual parsing.

---

### Recommended Combinations

#### Option A: Agents SDK + Vercel AI SDK (Recommended ⭐)

**Use when**: You need WebSockets/state AND want clean AI integration

```typescript
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

export class ChatAgent extends AIChatAgent<Env> {
  async onChatMessage(onFinish) {
    return streamText({
      model: openai('gpt-4o-mini'),
      messages: this.messages,  // Agents SDK manages history
      onFinish
    }).toTextStreamResponse();
  }
}
```

**Pros**:
- ✅ Best developer experience
- ✅ Automatic streaming
- ✅ WebSockets + state from Agents SDK
- ✅ Clean, maintainable code

**Cons**:
- ⚠️ Requires external API keys
- ⚠️ Additional cost for AI provider

---

#### Option B: Agents SDK + Workers AI

**Use when**: You need WebSockets/state AND cost is critical

```typescript
import { Agent } from "agents";

export class BudgetAgent extends Agent<Env> {
  async onMessage(connection, message) {
    const response = await this.env.AI.run('@cf/meta/llama-3-8b-instruct', {
      messages: [...],
      stream: true
    });

    // Manual SSE parsing required (see Workers AI section above)
    for await (const chunk of response) {
      // ... manual parsing ...
    }
  }
}
```

**Pros**:
- ✅ Cost-effective
- ✅ No external dependencies
- ✅ WebSockets + state from Agents SDK

**Cons**:
- ❌ Manual SSE parsing complexity
- ❌ Limited model selection
- ❌ More code to maintain

---

#### Option C: Just Vercel AI SDK (No Agents)

**Use when**: You DON'T need WebSockets or Durable Objects

```typescript
// worker.ts - Simple Workers route
export default {
  async fetch(request: Request, env: Env) {
    const { messages } = await request.json();

    const result = streamText({
      model: openai('gpt-4o-mini'),
      messages
    });

    return result.toTextStreamResponse();
  }
}

// client.tsx - Built-in React hooks
import { useChat } from 'ai/react';

function Chat() {
  const { messages, input, handleSubmit } = useChat({ api: '/api/chat' });
  return <form onSubmit={handleSubmit}>...</form>;
}
```

**Pros**:
- ✅ Simplest approach
- ✅ Least code
- ✅ Fast to implement
- ✅ Built-in React hooks

**Cons**:
- ❌ No WebSockets (only SSE)
- ❌ No Durable Objects state
- ❌ No multi-agent coordination

**Best for**: 80% of chat applications

---

### Decision Matrix

| Your Needs | Recommended Stack | Complexity | Cost |
|-----------|------------------|-----------|------|
| Simple chat, no state | AI SDK only | 🟢 Low | $$ (AI provider) |
| Chat + WebSockets + state | Agents SDK + AI SDK | 🟡 Medium | $$$ (infra + AI) |
| Chat + WebSockets + budget | Agents SDK + Workers AI | 🔴 High | $ (infra only) |
| Multi-agent workflows | Agents SDK + AI SDK | 🔴 High | $$$ (infra + AI) |
| MCP server with tools | Agents SDK (McpAgent) | 🟡 Medium | $ (infra only) |

---

### Key Takeaway

**Agents SDK is infrastructure, not AI.** You combine it with AI inference tools:

- **For best DX**: Agents SDK + Vercel AI SDK ⭐
- **For cost savings**: Agents SDK + Workers AI (accept manual parsing)
- **For simplicity**: Just AI SDK (if you don't need WebSockets/state)

The rest of this skill focuses on Agents SDK (the infrastructure layer). For AI inference patterns, see the `ai-sdk-core` or `cloudflare-workers-ai` skills.

---

## Configuration (wrangler.jsonc)

**Critical Required Configuration**:
```jsonc
{
  "durable_objects": {
    "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyAgent"] }  // MUST be in first migration
  ]
}
```

**Common Optional Bindings**: `ai`, `vectorize`, `browser`, `workflows`, `d1_databases`, `r2_buckets`

**CRITICAL Migration Rules**:
- ✅ `new_sqlite_classes` MUST be in tag "v1" (cannot add SQLite to existing deployed class)
- ✅ `name` and `class_name` MUST match exactly
- ✅ Migrations are atomic (all instances updated simultaneously)
- ✅ Each tag must be unique, cannot edit/remove previous tags

**See**: https://developers.cloudflare.com/agents/api-reference/configuration/

---

## Core Agent Patterns

**Agent Class Basics** - Extend `Agent<Env, State>` with lifecycle methods:
- `onStart()` - Agent initialization
- `onRequest()` - Handle HTTP requests
- `onConnect/onMessage/onClose()` - WebSocket handling
- `onStateUpdate()` - React to state changes

**Key Properties**:
- `this.env` - Environment bindings (AI, DB, etc.)
- `this.state` - Current agent state (read-only)
- `this.setState()` - Update persisted state
- `this.sql` - Built-in SQLite database
- `this.name` - Agent instance identifier
- `this.schedule()` - Schedule future tasks

**See**: Official Agent API docs at https://developers.cloudflare.com/agents/api-reference/agents-api/

---

## WebSockets & Real-Time Communication

Agents support WebSockets for bidirectional real-time communication. Use when you need:
- Client can send messages while server streams
- Agent-initiated messages (notifications, updates)
- Long-lived connections with state

**Basic Pattern**:
```typescript
export class ChatAgent extends Agent<Env, State> {
  async onConnect(connection: Connection, ctx: ConnectionContext) {
    // Auth check, add to participants, send welcome
  }

  async onMessage(connection: Connection, message: WSMessage) {
    // Process message, update state, broadcast response
  }
}
```

**SSE Alternative**: For one-way server → client streaming (simpler, HTTP-based), use Server-Sent Events instead of WebSockets.

**See**: https://developers.cloudflare.com/agents/api-reference/websockets/

---

## State Management

**Two State Mechanisms**:

1. **`this.setState(newState)`** - JSON-serializable state (up to 1GB)
   - Automatically persisted, syncs to WebSocket clients
   - Use for: User preferences, session data, small datasets

2. **`this.sql`** - Built-in SQLite database (up to 1GB)
   - Tagged template literals prevent SQL injection
   - Use for: Relational data, large datasets, complex queries

**State Rules**:
- ✅ JSON-serializable only (objects, arrays, primitives, null)
- ✅ Persists across restarts, immediately consistent
- ❌ No functions or circular references
- ❌ 1GB total limit (state + SQL combined)

**SQL Pattern**:
```typescript
await this.sql`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)`
await this.sql`INSERT INTO users (email) VALUES (${userEmail})`  // ← Prepared statement
const users = await this.sql`SELECT * FROM users WHERE email = ${email}`  // ← Returns array
```

**See**: https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/

---

## Schedule Tasks

Agents can schedule tasks to run in the future using `this.schedule()`.

### Delay (Seconds)

```typescript
export class MyAgent extends Agent {
  async onRequest(request: Request): Promise<Response> {
    // Schedule task to run in 60 seconds
    const { id } = await this.schedule(60, "checkStatus", { requestId: "123" });

    return Response.json({ scheduledTaskId: id });
  }

  // This method will be called in 60 seconds
  async checkStatus(data: { requestId: string }) {
    console.log('Checking status for request:', data.requestId);
    // Perform check, update state, send notification, etc.
  }
}
```

### Specific Date

```typescript
export class MyAgent extends Agent {
  async scheduleReminder(reminderDate: string) {
    const date = new Date(reminderDate);

    const { id } = await this.schedule(date, "sendReminder", {
      message: "Time for your appointment!"
    });

    return id;
  }

  async sendReminder(data: { message: string }) {
    console.log('Sending reminder:', data.message);
    // Send email, push notification, etc.
  }
}
```

### Cron Expressions

```typescript
export class MyAgent extends Agent {
  async setupRecurringTasks() {
    // Every 10 minutes
    await this.schedule("*/10 * * * *", "checkUpdates", {});

    // Every day at 8 AM
    await this.schedule("0 8 * * *", "dailyReport", {});

    // Every Monday at 9 AM
    await this.schedule("0 9 * * 1", "weeklyReport", {});

    // Every hour on the hour
    await this.schedule("0 * * * *", "hourlyCheck", {});
  }

  async checkUpdates(data: any) {
    console.log('Checking for updates...');
  }

  async dailyReport(data: any) {
    console.log('Generating daily report...');
  }

  async weeklyReport(data: any) {
    console.log('Generating weekly report...');
  }

  async hourlyCheck(data: any) {
    console.log('Running hourly check...');
  }
}
```

### Managing Scheduled Tasks

```typescript
export class MyAgent extends Agent {
  async manageSchedules() {
    // Get all scheduled tasks
    const allTasks = this.getSchedules();
    console.log('Total tasks:', allTasks.length);

    // Get specific task by ID
    const taskId = "some-task-id";
    const task = await this.getSchedule(taskId);

    if (task) {
      console.log('Task:', task.callback, 'at', new Date(task.time));
      console.log('Payload:', task.payload);
      console.log('Type:', task.type);  // "scheduled" | "delayed" | "cron"

      // Cancel the task
      const cancelled = await this.cancelSchedule(taskId);
      console.log('Cancelled:', cancelled);
    }

    // Get tasks in time range
    const upcomingTasks = this.getSchedules({
      timeRange: {
        start: new Date(),
        end: new Date(Date.now() + 24 * 60 * 60 * 1000)  // Next 24 hours
      }
    });

    console.log('Upcoming tasks:', upcomingTasks.length);

    // Filter by type
    const cronTasks = this.getSchedules({ type: "cron" });
    const delayedTasks = this.getSchedules({ type: "delayed" });
  }
}
```

**Scheduling Constraints:**
- Each task maps to a SQL database row (max 2 MB per task)
- Total tasks limited by: `(task_size * count) + other_state < 1GB`
- Cron tasks continue running until explicitly cancelled
- Callback method MUST exist on Agent class (throws error if missing)

**CRITICAL ERROR**: If callback method doesn't exist:
```typescript
// ❌ BAD: Method doesn't exist
await this.schedule(60, "nonExistentMethod", {});

// ✅ GOOD: Method exists
await this.schedule(60, "existingMethod", {});

async existingMethod(data: any) {
  // Implementation
}
```

---

## Run Workflows

Agents can trigger asynchronous [Cloudflare Workflows](https://developers.cloudflare.com/workflows/).

### Workflow Binding Configuration

`wrangler.jsonc`:

```jsonc
{
  "workflows": [
    {
      "name": "MY_WORKFLOW",
      "class_name": "MyWorkflow"
    }
  ]
}
```

If Workflow is in a different script:

```jsonc
{
  "workflows": [
    {
      "name": "EMAIL_WORKFLOW",
      "class_name": "EmailWorkflow",
      "script_name": "email-workflows"  // Different project
    }
  ]
}
```

### Triggering a Workflow

```typescript
import { Agent } from "agents";
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers";

interface Env {
  MY_WORKFLOW: Workflow;
  MyAgent: AgentNamespace<MyAgent>;
}

export class MyAgent extends Agent<Env> {
  async onRequest(request: Request): Promise<Response> {
    const userId = new URL(request.url).searchParams.get('userId');

    // Trigger a workflow immediately
    const instance = await this.env.MY_WORKFLOW.create({
      id: `user-${userId}`,
      params: { userId, action: "process" }
    });

    // Or schedule a delayed workflow trigger
    await this.schedule(300, "runWorkflow", { userId });

    return Response.json({ workflowId: instance.id });
  }

  async runWorkflow(data: { userId: string }) {
    const instance = await this.env.MY_WORKFLOW.create({
      id: `delayed-${data.userId}`,
      params: data
    });

    // Monitor workflow status periodically
    await this.schedule("*/5 * * * *", "checkWorkflowStatus", { id: instance.id });
  }

  async checkWorkflowStatus(data: { id: string }) {
    // Check workflow status (see Workflows docs for details)
    console.log('Checking workflow:', data.id);
  }
}

// Workflow definition (can be in same or different file/project)
export class MyWorkflow extends WorkflowEntrypoint<Env> {
  async run(event: WorkflowEvent<{ userId: string }>, step: WorkflowStep) {
    // Workflow implementation
    const result = await step.do('process-data', async () => {
      return { processed: true };
    });

    return result;
  }
}
```

### Agents vs Workflows

| Feature | Agents | Workflows |
|---------|--------|-----------|
| **Purpose** | Interactive, user-facing | Background processing |
| **Duration** | Seconds to hours | Minutes to hours |
| **State** | SQLite database | Step-based checkpoints |
| **Interaction** | WebSockets, HTTP | No direct interaction |
| **Retry** | Manual | Automatic per step |
| **Use Case** | Chat, real-time UI | ETL, batch processing |

**Best Practice**: Use Agents to **coordinate** multiple Workflows. Agents can trigger, monitor, and respond to Workflow results while maintaining user interaction.

---

## Browse the Web

Agents can use Browser Rendering for web scraping and automation:

**Binding**: Add `"browser": { "binding": "BROWSER" }` to wrangler.jsonc
**Package**: `@cloudflare/puppeteer`
**Use Case**: Web scraping, screenshots, automated browsing within agent workflows

**See**: `cloudflare-browser-rendering` skill for complete Puppeteer + Workers integration guide.

---

## Retrieval Augmented Generation (RAG)

Agents can implement RAG using Vectorize (vector database) + Workers AI (embeddings):

**Pattern**: Ingest docs → generate embeddings → store in Vectorize → query → retrieve context → pass to AI

**Bindings**:
- `"ai": { "binding": "AI" }` - Workers AI for embeddings
- `"vectorize": { "bindings": [{ "binding": "VECTORIZE", "index_name": "my-vectors" }] }` - Vector search

**Typical Workflow**:
1. Generate embeddings with Workers AI (`@cf/baai/bge-base-en-v1.5`)
2. Upsert vectors to Vectorize (`this.env.VECTORIZE.upsert(vectors)`)
3. Query similar vectors (`this.env.VECTORIZE.query(queryVector, { topK: 5 })`)
4. Use retrieved context in AI prompt

**See**: `cloudflare-vectorize` skill for complete RAG implementation guide.

---

## Using AI Models

Agents can call AI models using:
- **Vercel AI SDK** (recommended): Multi-provider, automatic streaming, tool calling
- **Workers AI**: Cloudflare's on-platform inference (cost-effective, manual parsing)

**Architecture Note**: Agents SDK provides infrastructure (WebSockets, state, scheduling). AI inference is a separate layer - use AI SDK for the "brain".

**See**:
- `ai-sdk-core` skill for complete AI SDK integration patterns
- `cloudflare-workers-ai` skill for Workers AI streaming parsing

---

## Calling Agents

**Two Main Patterns**:

1. **`routeAgentRequest(request, env)`** - Auto-route via URL pattern `/agents/:agent/:name`
   - Example: `/agents/my-agent/user-123` routes to MyAgent instance "user-123"

2. **`getAgentByName<Env, T>(env.AgentBinding, instanceName)`** - Custom routing
   - Returns agent stub for calling methods or passing requests
   - Example: `const agent = getAgentByName(env.MyAgent, 'user-${userId}')`

**Multi-Agent Communication**:
```typescript
export class AgentA extends Agent<Env> {
  async processData(data: any) {
    const agentB = getAgentByName<Env, AgentB>(this.env.AgentB, 'processor-1');
    return await (await agentB).analyze(data);
  }
}
```

**CRITICAL Security**: Always authenticate in Worker BEFORE creating/accessing agents. Agents should assume the caller is authorized.

**See**: https://developers.cloudflare.com/agents/api-reference/calling-agents/

---

## Client APIs

**Browser/React Integration**:
- **`AgentClient`** (from `agents/client`) - WebSocket client for browser
- **`agentFetch`** (from `agents/client`) - HTTP requests to agents
- **`useAgent`** (from `agents/react`) - React hook for WebSocket connections + state sync
- **`useAgentChat`** (from `agents/ai-react`) - Pre-built chat UI hook

**All client libraries automatically handle**: WebSocket connections, state synchronization, reconnection logic.

**See**: https://developers.cloudflare.com/agents/api-reference/client-apis/

---

## Model Context Protocol (MCP)

Build MCP servers using the Agents SDK.

### MCP Server Setup

```bash
npm install @modelcontextprotocol/sdk agents
```

### Basic MCP Server

```typescript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
  server = new McpServer({ name: "Demo", version: "1.0.0" });

  async init() {
    // Define a tool
    this.server.tool(
      "add",
      "Add two numbers together",
      {
        a: z.number().describe("First number"),
        b: z.number().describe("Second number")
      },
      async ({ a, b }) => ({
        content: [{ type: "text", text: String(a + b) }]
      })
    );
  }
}
```

### Stateful MCP Server

```typescript
type State = { counter: number };

export class StatefulMCP extends McpAgent<Env, State> {
  server = new McpServer({ name: "Counter", version: "1.0.0" });

  initialState: State = { counter: 0 };

  async init() {
    // Resource
    this.server.resource(
      "counter",
      "mcp://resource/counter",
      (uri) => ({
        contents: [{ uri: uri.href, text: String(this.state.counter) }]
      })
    );

    // Tool
    this.server.tool(
      "increment",
      "Increment the counter",
      { amount: z.number() },
      async ({ amount }) => {
        this.setState({
          ...this.state,
          counter: this.state.counter + amount
        });

        return {
          content: [{
            type: "text",
            text: `Counter is now ${this.state.counter}`
          }]
        };
      }
    );
  }
}
```

### MCP Transport Configuration

```typescript
import { Hono } from 'hono';

const app = new Hono();

// Modern streamable HTTP transport (recommended)
app.mount('/mcp', MyMCP.serve('/mcp').fetch, { replaceRequest: false });

// Legacy SSE transport (deprecated)
app.mount('/sse', MyMCP.serveSSE('/sse').fetch, { replaceRequest: false });

export default app;
```

**Transport Comparison:**
- **/mcp**: Streamable HTTP (modern, recommended)
- **/sse**: Server-Sent Events (legacy, deprecated)

### MCP with OAuth

```typescript
import { OAuthProvider } from '@cloudflare/workers-oauth-provider';

export default new OAuthProvider({
  apiHandlers: {
    '/sse': MyMCP.serveSSE('/sse'),
    '/mcp': MyMCP.serve('/mcp')
  },
  // OAuth configuration
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  // ... other OAuth settings
});
```

### Testing MCP Server

```bash
# Run MCP inspector
npx @modelcontextprotocol/inspector@latest

# Connect to: http://localhost:8788/mcp
```

**Cloudflare's MCP Servers**: See [reference](https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/) for production examples.

---


## Critical Rules

### Always Do ✅

1. **Export Agent class** - Must be exported for binding to work
2. **Include new_sqlite_classes in v1 migration** - Cannot add SQLite later
3. **Match binding name to class name** - Prevents "binding not found" errors
4. **Authenticate in Worker, not Agent** - Security best practice
5. **Use tagged template literals for SQL** - Prevents SQL injection
6. **Handle WebSocket disconnections** - State persists, connections don't
7. **Verify scheduled task callback exists** - Throws error if method missing
8. **Use global unique instance names** - Same name = same agent globally
9. **Check state size limits** - Max 1GB total per agent
10. **Monitor task payload size** - Max 2MB per scheduled task
11. **Use workflow bindings correctly** - Must be configured in wrangler.jsonc
12. **Create Vectorize indexes before inserting** - Required for metadata filtering
13. **Close browser instances** - Prevent resource leaks
14. **Use setState() for persistence** - Don't just modify this.state
15. **Test migrations locally first** - Migrations are atomic, can't rollback

### Never Do ❌

1. **Don't add SQLite to existing deployed class** - Must be in first migration
2. **Don't gradually deploy migrations** - Atomic only
3. **Don't skip authentication in Worker** - Always auth before agent access
4. **Don't construct SQL strings manually** - Use tagged templates
5. **Don't exceed 1GB state per agent** - Hard limit
6. **Don't schedule tasks with non-existent callbacks** - Runtime error
7. **Don't assume same name = different agent** - Global uniqueness
8. **Don't use SSE for MCP** - Deprecated, use /mcp transport
9. **Don't forget browser binding** - Required for web browsing
10. **Don't modify this.state directly** - Use setState() instead

---

## Known Issues Prevention

This skill prevents **16+** documented issues:

### Issue 1: Migrations Not Atomic
**Error**: "Cannot gradually deploy migration"
**Source**: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
**Why**: Migrations apply to all instances simultaneously
**Prevention**: Deploy migrations independently of code changes, use `npx wrangler versions deploy`

### Issue 2: Missing new_sqlite_classes
**Error**: "Cannot enable SQLite on existing class"
**Source**: https://developers.cloudflare.com/agents/api-reference/configuration/
**Why**: SQLite must be enabled in first migration
**Prevention**: Include `new_sqlite_classes` in tag "v1" migration

### Issue 3: Agent Class Not Exported
**Error**: "Binding not found" or "Cannot access undefined"
**Source**: https://developers.cloudflare.com/agents/api-reference/agents-api/
**Why**: Durable Objects require exported class
**Prevention**: `export class MyAgent extends Agent` (with export keyword)

### Issue 4: Binding Name Mismatch
**Error**: "Binding 'X' not found"
**Source**: https://developers.cloudflare.com/agents/api-reference/configuration/
**Why**: Binding name must match class name exactly
**Prevention**: Ensure `name` and `class_name` are identical in wrangler.jsonc

### Issue 5: Global Uniqueness Not Understood
**Error**: Unexpected behavior with agent instances
**Source**: https://developers.cloudflare.com/agents/api-reference/agents-api/
**Why**: Same name always returns same agent instance globally
**Prevention**: Use unique identifiers (userId, sessionId) for instance names

### Issue 6: WebSocket State Not Persisted
**Error**: Connection state lost after disconnect
**Source**: https://developers.cloudflare.com/agents/api-reference/websockets/
**Why**: WebSocket connections don't persist, but agent state does
**Prevention**: Store important data in agent state via setState(), not connection state

### Issue 7: Scheduled Task Callback Doesn't Exist
**Error**: "Method X does not exist on Agent"
**Source**: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/
**Why**: this.schedule() calls method that isn't defined
**Prevention**: Ensure callback method exists before scheduling

### Issue 8: State Size Limit Exceeded
**Error**: "Maximum database size exceeded"
**Source**: https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/
**Why**: Agent state + scheduled tasks exceed 1GB
**Prevention**: Monitor state size, use external storage (D1, R2) for large data

### Issue 9: Scheduled Task Too Large
**Error**: "Task payload exceeds 2MB"
**Source**: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/
**Why**: Each task maps to database row with 2MB limit
**Prevention**: Keep task payloads minimal, store large data in agent state/SQL

### Issue 10: Workflow Binding Missing
**Error**: "Cannot read property 'create' of undefined"
**Source**: https://developers.cloudflare.com/agents/api-reference/run-workflows/
**Why**: Workflow binding not configured in wrangler.jsonc
**Prevention**: Add workflow binding before using this.env.WORKFLOW

### Issue 11: Browser Binding Required
**Error**: "BROWSER binding undefined"
**Source**: https://developers.cloudflare.com/agents/api-reference/browse-the-web/
**Why**: Browser Rendering requires explicit binding
**Prevention**: Add `"browser": { "binding": "BROWSER" }` to wrangler.jsonc

### Issue 12: Vectorize Index Not Found
**Error**: "Index does not exist"
**Source**: https://developers.cloudflare.com/agents/api-reference/rag/
**Why**: Vectorize index must be created before use
**Prevention**: Run `wrangler vectorize create` before deploying agent

### Issue 13: MCP Transport Confusion
**Error**: "SSE transport deprecated"
**Source**: https://developers.cloudflare.com/agents/model-context-protocol/transport/
**Why**: SSE transport is legacy, streamable HTTP is recommended
**Prevention**: Use `/mcp` endpoint with `MyMCP.serve('/mcp')`, not `/sse`

### Issue 14: Authentication Bypass
**Error**: Security vulnerability
**Source**: https://developers.cloudflare.com/agents/api-reference/calling-agents/
**Why**: Authentication done in Agent instead of Worker
**Prevention**: Always authenticate in Worker before calling getAgentByName()

### Issue 15: Instance Naming Errors
**Error**: Cross-user data leakage
**Source**: https://developers.cloudflare.com/agents/api-reference/calling-agents/
**Why**: Poor instance naming allows access to wrong agent
**Prevention**: Use namespaced names like `user-${userId}`, validate ownership

### Issue 16: Workers AI Streaming Requires Manual Parsing
**Error**: "Cannot read property 'response' of undefined" or empty AI responses
**Source**: https://developers.cloudflare.com/workers-ai/platform/streaming/
**Why**: Workers AI returns streaming responses as `Uint8Array` in Server-Sent Events (SSE) format, not plain objects
**Prevention**: Use `TextDecoder` + SSE parsing pattern (see "Workers AI (Alternative for AI)" section above)

**The problem** - Attempting to access stream chunks directly fails:
```typescript
const response = await env.AI.run(model, { stream: true });
for await (const chunk of response) {
  console.log(chunk.response);  // ❌ undefined - chunk is Uint8Array, not object
}
```

**The solution** - Parse SSE format manually:
```typescript
const response = await env.AI.run(model, { stream: true });
for await (const chunk of response) {
  const text = new TextDecoder().decode(chunk);  // Step 1: Uint8Array → string
  if (text.startsWith('data: ')) {              // Step 2: Check SSE format
    const jsonStr = text.slice(6).trim();       // Step 3: Extract JSON from "data: {...}"
    if (jsonStr === '[DONE]') break;            // Step 4: Handle termination
    const data = JSON.parse(jsonStr);           // Step 5: Parse JSON
    if (data.response) {                        // Step 6: Extract .response field
      fullResponse += data.response;
    }
  }
}
```

**Better alternative**: Use Vercel AI SDK which handles this automatically:
```typescript
import { streamText } from 'ai';
import { createCloudflare } from '@a

…(truncated)
