building-ai-agent-on-cloudflare

Stateful AI agents on Cloudflare Workers with WebSocket communication, persistent state, and scheduled tasks. Build agents using the Agents SDK that maintain state across client reconnections and scale via Durable Objects Support real-time WebSocket communication, HTTP endpoints, and automatic message history for chat-focused agents Schedule background tasks with delay, specific dates, or cron expressions; query state with embedded SQLite Extend Agent or AIChatAgent base classes, deploy with Wrangler, and integrate clients via React hooks or vanilla JavaScript

INSTALLATION
npx skills add https://github.com/cloudflare/skills --skill building-ai-agent-on-cloudflare
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2c

Prerequisites

  • Cloudflare account with Workers enabled
  • Node.js 18+ and npm/pnpm/yarn
  • Wrangler CLI (npm install -g wrangler)

Quick Start

npm create cloudflare@latest -- my-agent --template=cloudflare/agents-starter

cd my-agent

npm start

Agent runs at http://localhost:8787

Core Concepts

What is an Agent?

An Agent is a stateful, persistent AI service that:

  • Maintains state across requests and reconnections
  • Communicates via WebSockets or HTTP
  • Runs on Cloudflare's edge via Durable Objects
  • Can schedule tasks and call tools
  • Scales horizontally (each user/session gets own instance)

Agent Lifecycle

Client connects → Agent.onConnect() → Agent processes messages

                                    → Agent.onMessage()

                                    → Agent.setState() (persists + syncs)

Client disconnects → State persists → Client reconnects → State restored

Basic Agent Structure

import { Agent, Connection } from "agents";

interface Env {

  AI: Ai;  // Workers AI binding

}

interface State {

  messages: Array<{ role: string; content: string }>;

  preferences: Record<string, string>;

}

export class MyAgent extends Agent<Env, State> {

  // Initial state for new instances

  initialState: State = {

    messages: [],

    preferences: {},

  };

  // Called when agent starts or resumes

  async onStart() {

    console.log("Agent started with state:", this.state);

  }

  // Handle WebSocket connections

  async onConnect(connection: Connection) {

    connection.send(JSON.stringify({

      type: "welcome",

      history: this.state.messages,

    }));

  }

  // Handle incoming messages

  async onMessage(connection: Connection, message: string) {

    const data = JSON.parse(message);

    if (data.type === "chat") {

      await this.handleChat(connection, data.content);

    }

  }

  // Handle disconnections

  async onClose(connection: Connection) {

    console.log("Client disconnected");

  }

  // React to state changes

  onStateUpdate(state: State, source: string) {

    console.log("State updated by:", source);

  }

  private async handleChat(connection: Connection, userMessage: string) {

    // Add user message to history

    const messages = [

      ...this.state.messages,

      { role: "user", content: userMessage },

    ];

    // Call AI

    const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", {

      messages,

    });

    // Update state (persists and syncs to all clients)

    this.setState({

      ...this.state,

      messages: [

        ...messages,

        { role: "assistant", content: response.response },

      ],

    });

    // Send response

    connection.send(JSON.stringify({

      type: "response",

      content: response.response,

    }));

  }

}

Entry Point Configuration

// src/index.ts

import { routeAgentRequest } from "agents";

import { MyAgent } from "./agent";

export default {

  async fetch(request: Request, env: Env) {

    // routeAgentRequest handles routing to /agents/:class/:name

    return (

      (await routeAgentRequest(request, env)) ||

      new Response("Not found", { status: 404 })

    );

  },

};

export { MyAgent };

Clients connect via: wss://my-agent.workers.dev/agents/MyAgent/session-id

Wrangler Configuration

{

  "name": "my-agent",

  "main": "src/index.ts",

  "compatibility_date": "2024-12-01",

  "ai": { "binding": "AI" },

  "durable_objects": {

    "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]

  },

  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]

}

State Management

Reading State

// Current state is always available

const currentMessages = this.state.messages;

const userPrefs = this.state.preferences;

Updating State

// setState persists AND syncs to all connected clients

this.setState({

  ...this.state,

  messages: [...this.state.messages, newMessage],

});

// Partial updates work too

this.setState({

  preferences: { ...this.state.preferences, theme: "dark" },

});

SQL Storage

For complex queries, use the embedded SQLite database:

// Create tables

await this.sql`

  CREATE TABLE IF NOT EXISTS documents (

    id INTEGER PRIMARY KEY AUTOINCREMENT,

    title TEXT NOT NULL,

    content TEXT,

    created_at DATETIME DEFAULT CURRENT_TIMESTAMP

  )

`;

// Insert

await this.sql`

  INSERT INTO documents (title, content)

  VALUES (${title}, ${content})

`;

// Query

const docs = await this.sql`

  SELECT * FROM documents WHERE title LIKE ${`%${search}%`}

`;

Scheduled Tasks

Agents can schedule future work:

async onMessage(connection: Connection, message: string) {

  const data = JSON.parse(message);

  if (data.type === "schedule_reminder") {

    // Schedule task for 1 hour from now

    const { id } = await this.schedule(3600, "sendReminder", {

      message: data.reminderText,

      userId: data.userId,

    });

    connection.send(JSON.stringify({ type: "scheduled", taskId: id }));

  }

}

// Called when scheduled task fires

async sendReminder(data: { message: string; userId: string }) {

  // Send notification, email, etc.

  console.log(`Reminder for ${data.userId}: ${data.message}`);

  // Can also update state

  this.setState({

    ...this.state,

    lastReminder: new Date().toISOString(),

  });

}

Schedule Options

// Delay in seconds

await this.schedule(60, "taskMethod", { data });

// Specific date

await this.schedule(new Date("2025-01-01T00:00:00Z"), "taskMethod", { data });

// Cron expression (recurring)

await this.schedule("0 9 * * *", "dailyTask", {});  // 9 AM daily

await this.schedule("*/5 * * * *", "everyFiveMinutes", {});  // Every 5 min

// Manage schedules

const schedules = await this.getSchedules();

await this.cancelSchedule(taskId);

Chat Agent (AI-Powered)

For chat-focused agents, extend AIChatAgent:

import { AIChatAgent } from "@cloudflare/ai-chat";

export class ChatBot extends AIChatAgent<Env> {

  // Called for each user message

  async onChatMessage(message: string) {

    const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", {

      messages: [

        { role: "system", content: "You are a helpful assistant." },

        ...this.messages,  // Automatic history management

        { role: "user", content: message },

      ],

      stream: true,

    });

    // Stream response back to client

    return response;

  }

}

Features included:

  • Automatic message history
  • Resumable streaming (survives disconnects)
  • Built-in saveMessages() for persistence

Client Integration

React Hook

import { useAgent } from "agents/react";

function Chat() {

  const { state, send, connected } = useAgent({

    agent: "my-agent",

    name: userId,  // Agent instance ID

  });

  const sendMessage = (text: string) => {

    send(JSON.stringify({ type: "chat", content: text }));

  };

  return (

    <div>

      {state.messages.map((msg, i) => (

        <div key={i}>{msg.role}: {msg.content}</div>

      ))}

      <input onKeyDown={(e) => e.key === "Enter" &#x26;&#x26; sendMessage(e.target.value)} />

    </div>

  );

}

Vanilla JavaScript

const ws = new WebSocket("wss://my-agent.workers.dev/agents/MyAgent/user123");

ws.onopen = () => {

  console.log("Connected to agent");

};

ws.onmessage = (event) => {

  const data = JSON.parse(event.data);

  console.log("Received:", data);

};

ws.send(JSON.stringify({ type: "chat", content: "Hello!" }));

Common Patterns

See references/agent-patterns.md for:

  • Tool calling and function execution
  • Multi-agent orchestration
  • RAG (Retrieval Augmented Generation)
  • Human-in-the-loop workflows

Deployment

# Deploy

npx wrangler deploy

# View logs

wrangler tail

# Test endpoint

curl https://my-agent.workers.dev/agents/MyAgent/test-user

Troubleshooting

See references/troubleshooting.md for common issues.

References

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card