--- name: cloudflare-agents description: | 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. license: MIT metadata: keywords: - Cloudflare Agents - agents sdk - Agent class - Durable Objects agents - stateful agents - WebSocket agents - this.setState - this.sql - this.schedule - cron agents - agent workflows - browser rendering - rag agents - vectorize agents - mcp server - McpAgent - model context protocol - routeAgentRequest - AIChatAgent - streaming chat - human in the loop - multi-agent - autonomous agents - AI SDK vs Agents SDK - Workers AI - new_sqlite_classes - migrations required - binding not found --- # 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 { 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..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 { 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 { 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 { 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
...
; } ``` **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` 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 { 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 { // 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; } export class MyAgent extends Agent { async onRequest(request: Request): Promise { 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 { 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.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 { async processData(data: any) { const agentB = getAgentByName(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 { 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 '@ai-sdk/cloudflare'; const cloudflare = createCloudflare(); const result = streamText({ model: cloudflare('@cf/meta/llama-3-8b-instruct', { binding: env.AI }), messages }); // No manual parsing needed ✅ ``` **When to accept manual parsing**: - Cost is critical (Workers AI is cheaper) - No external dependencies allowed - Willing to maintain SSE parsing code **When to use AI SDK instead**: - Value developer time over compute cost - Want automatic streaming - Need multi-provider support --- ## Dependencies ### Required - **cloudflare-worker-base** - Foundation (Hono, Vite, Workers setup) ### Optional (by feature) - **cloudflare-workers-ai** - For Workers AI model calls - **cloudflare-vectorize** - For RAG with Vectorize - **cloudflare-d1** - For additional persistent storage beyond agent state - **cloudflare-r2** - For file storage - **cloudflare-queues** - For message queues ### NPM Packages - `agents` - Agents SDK (required) - `@modelcontextprotocol/sdk` - For building MCP servers - `@cloudflare/puppeteer` - For web browsing - `ai` - AI SDK for model calls - `@ai-sdk/openai` - OpenAI models - `@ai-sdk/anthropic` - Anthropic models --- ## Official Documentation - **Agents SDK**: https://developers.cloudflare.com/agents/ - **API Reference**: https://developers.cloudflare.com/agents/api-reference/ - **Durable Objects**: https://developers.cloudflare.com/durable-objects/ - **Workflows**: https://developers.cloudflare.com/workflows/ - **Vectorize**: https://developers.cloudflare.com/vectorize/ - **Browser Rendering**: https://developers.cloudflare.com/browser-rendering/ - **Model Context Protocol**: https://modelcontextprotocol.io/ - **Cloudflare MCP Servers**: https://github.com/cloudflare/mcp-server-cloudflare --- ## Bundled Resources ### Templates (templates/) - `wrangler-agents-config.jsonc` - Complete configuration example - `basic-agent.ts` - Minimal HTTP agent - `websocket-agent.ts` - WebSocket handlers - `state-sync-agent.ts` - State management patterns - `scheduled-agent.ts` - Task scheduling - `workflow-agent.ts` - Workflow integration - `browser-agent.ts` - Web browsing - `rag-agent.ts` - RAG implementation - `chat-agent-streaming.ts` - Streaming chat - `calling-agents-worker.ts` - Agent routing - `react-useagent-client.tsx` - React client - `mcp-server-basic.ts` - MCP server - `hitl-agent.ts` - Human-in-the-loop ### References (references/) - `agent-class-api.md` - Complete Agent class reference - `client-api-reference.md` - Browser client APIs - `state-management-guide.md` - State and SQL deep dive - `websockets-sse.md` - WebSocket vs SSE comparison - `scheduling-api.md` - Task scheduling details - `workflows-integration.md` - Workflows guide - `browser-rendering.md` - Web browsing patterns - `rag-patterns.md` - RAG best practices - `mcp-server-guide.md` - MCP server development - `mcp-tools-reference.md` - MCP tools API - `hitl-patterns.md` - Human-in-the-loop workflows - `best-practices.md` - Production patterns ### Examples (examples/) - `chat-bot-complete.md` - Full chat agent - `multi-agent-workflow.md` - Agent orchestration - `scheduled-reports.md` - Recurring tasks - `browser-scraper-agent.md` - Web scraping - `rag-knowledge-base.md` - RAG system - `mcp-remote-server.md` - Production MCP server --- **Last Verified**: 2025-10-21 **Package Versions**: agents@latest **Compliance**: Cloudflare Agents SDK official documentation