Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/partykit/llms.txt

Use this file to discover all available pages before exploring further.

Durable Objects are an unusually good fit for AI agents. An agent needs to be long-lived (it may wait days between tasks), stateful (it maintains conversation history and tool state), and cost-effective (it should not burn CPU while idle). PartyServer’s hibernation support, transactional SQLite storage, and alarm-based scheduling give you all three. The partyagent package extends this foundation with autonomous agent abstractions, while partywhen provides a full task scheduler for cron, delayed, and one-off work.

Why Durable Objects for agents

Long-running

A DO instance persists indefinitely. An agent can wait for an external event, an LLM response, or a scheduled alarm without holding a Worker slot open the entire time.

Stateful

Each DO has its own embedded SQLite database. Conversation history, tool outputs, and task queues live in the same process as the agent’s logic.

Hibernatable

Set static options = { hibernate: true } to let the agent sleep between messages. onStart rehydrates state on wake, keeping costs near zero at rest.

Globally unique

Each named DO is a single, authoritative process. No two requests to agent-42 race — they queue naturally, eliminating coordination overhead.

The partyagent package

partyagent provides autonomous agent classes built on top of PartyServer’s Server. Agents extend PartySync for state synchronization across connected clients and support natural language processing, customizable personalities, tool usage with input/output schemas, task hand-off between agents, and built-in observability.
npm install partyagent partyserver
The design of partyagent is informed by Anthropic’s Building Effective Agents research, which describes patterns for tool use, multi-agent orchestration, and reliable task delegation.

Scheduling agent tasks with partywhen

partywhen provides a Scheduler class that persists tasks in SQLite and uses Durable Object alarms for reliable execution. It supports three task types:
TypeWhen it runs
scheduledAt a specific Date
delayedAfter delayInSeconds seconds
cronOn a cron expression (e.g. "0 9 * * 1")
no-scheduleNever automatically — useful for manually triggered tasks
npm install partywhen partyserver
// wrangler.jsonc binding:
// { "name": "SCHEDULER", "class_name": "Scheduler" }

import { Scheduler } from "partywhen";
export { Scheduler };

export default {
  async fetch(request: Request, env: Env) {
    const id = env.SCHEDULER.idFromName("my-agent-scheduler");
    const scheduler = env.SCHEDULER.get(id);

    // Run a webhook every Friday at 6 PM
    await scheduler.scheduleTask({
      id: "weekly-report",
      type: "cron",
      cron: "0 18 * * 5",
      payload: { report: "weekly-summary" },
      callback: {
        type: "webhook",
        url: "https://my-app.workers.dev/webhooks/report"
      }
    });

    // Delay a one-off task by 60 seconds
    await scheduler.scheduleTask({
      id: "followup-email",
      type: "delayed",
      delayInSeconds: 60,
      payload: { userId: "user-123" },
      callback: {
        type: "durable-object",
        namespace: "AgentServer",
        name: "user-123",
        function: "sendFollowUp"
      }
    });

    return new Response("Scheduled");
  }
};

Pattern: an agent that receives instructions over WebSocket

The most direct pattern is a Server subclass that accepts instructions from a client, calls an LLM, and streams the response back. Because the DO is hibernatable, it only uses CPU while actively processing:
import { Server } from "partyserver";
import type { Connection, ConnectionContext, WSMessage } from "partyserver";

type AgentState = { sessionId: string };

export class AgentServer extends Server {
  static options = { hibernate: true };

  // Chat history persisted in SQLite
  async onStart() {
    this.ctx.storage.sql.exec(`
      CREATE TABLE IF NOT EXISTS history (
        role TEXT NOT NULL,
        content TEXT NOT NULL,
        ts INTEGER DEFAULT (unixepoch())
      )
    `);
  }

  onConnect(connection: Connection<AgentState>, ctx: ConnectionContext) {
    const sessionId = new URL(ctx.request.url).searchParams.get("session") ?? connection.id;
    connection.setState({ sessionId });
    connection.send(JSON.stringify({ type: "ready", sessionId }));
  }

  async onMessage(connection: Connection<AgentState>, message: WSMessage) {
    const { content } = JSON.parse(message as string) as { content: string };

    // Persist the user's message
    this.ctx.storage.sql.exec(
      "INSERT INTO history (role, content) VALUES (?, ?)",
      "user",
      content
    );

    // Load recent history for context window
    const history = this.ctx.storage.sql
      .exec("SELECT role, content FROM history ORDER BY ts DESC LIMIT 20")
      .toArray() as { role: string; content: string }[];

    // Call the LLM (replace with your preferred client)
    const reply = await this.callLLM(history.reverse());

    // Persist and broadcast the assistant reply
    this.ctx.storage.sql.exec(
      "INSERT INTO history (role, content) VALUES (?, ?)",
      "assistant",
      reply
    );

    connection.send(JSON.stringify({ type: "reply", content: reply }));
  }

  private async callLLM(
    messages: { role: string; content: string }[]
  ): Promise<string> {
    // Use this.env.AI (Workers AI), Anthropic, OpenAI, etc.
    const result = await this.env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
      messages
    });
    return (result as { response: string }).response;
  }
}

Pattern: periodic agent tasks with onAlarm

Use this.ctx.storage.setAlarm inside onStart or onConnect to schedule recurring work, then handle it in onAlarm. This runs even when no clients are connected:
export class PollingAgent extends Server {
  static options = { hibernate: true };

  async onStart() {
    // Schedule initial poll if none is pending
    const existing = await this.ctx.storage.getAlarm();
    if (!existing) {
      await this.ctx.storage.setAlarm(Date.now() + 5 * 60_000); // 5 min
    }
  }

  async onAlarm() {
    // Fetch external data and broadcast to any live clients
    const data = await fetch("https://api.example.com/updates").then((r) =>
      r.json()
    );
    this.broadcast(JSON.stringify({ type: "update", data }));

    // Reschedule
    await this.ctx.storage.setAlarm(Date.now() + 5 * 60_000);
  }
}

Pattern: delegating tasks to other agents

Use getServerByName to get a stub for a peer agent and invoke work on it. This is the PartyServer equivalent of agent task hand-off:
import { getServerByName, Server } from "partyserver";

export class OrchestratorAgent extends Server {
  async onMessage(connection, message: string) {
    const { task, assignee } = JSON.parse(message) as {
      task: string;
      assignee: string;
    };

    // Get (or create) a worker agent by name
    const workerStub = await getServerByName(
      this.env.WorkerAgent,
      assignee,
      { locationHint: "weur" }
    );

    // Delegate the task via HTTP RPC
    await workerStub.fetch(
      new Request("https://internal/task", {
        method: "POST",
        body: JSON.stringify({ task }),
        headers: { "Content-Type": "application/json" }
      })
    );

    connection.send(
      JSON.stringify({ type: "delegated", task, assignee })
    );
  }
}

export class WorkerAgent extends Server {
  async onRequest(request: Request): Promise<Response> {
    const { task } = await request.json<{ task: string }>();
    // Process the delegated task...
    console.log(`${this.name} processing task: ${task}`);
    return new Response("accepted");
  }
}

Conceptual agent class overview

import { Server } from "partyserver";
import type { Connection, ConnectionContext, WSMessage } from "partyserver";

/**
 * A minimal but complete agent skeleton:
 *
 *   - Hibernates to save cost while idle
 *   - Loads conversation history from SQLite on wake
 *   - Accepts instructions over WebSocket
 *   - Runs periodic tasks via onAlarm
 *   - Can delegate to peer agents via getServerByName
 */
export class MyAgent extends Server {
  static options = { hibernate: true };

  // ── Initialization ────────────────────────────────────────────────
  async onStart() {
    this.ctx.storage.sql.exec(`
      CREATE TABLE IF NOT EXISTS memory (
        key   TEXT PRIMARY KEY,
        value TEXT NOT NULL
      )
    `);
    await this.ctx.storage.setAlarm(Date.now() + 60_000);
  }

  // ── Client instructions ───────────────────────────────────────────
  onConnect(conn: Connection, ctx: ConnectionContext) {
    conn.send(JSON.stringify({ type: "ready", agent: this.name }));
  }

  async onMessage(conn: Connection, msg: WSMessage) {
    const { instruction } = JSON.parse(msg as string) as {
      instruction: string;
    };
    // … call LLM, use tools, update memory …
    conn.send(JSON.stringify({ type: "done", instruction }));
  }

  // ── Periodic work ─────────────────────────────────────────────────
  async onAlarm() {
    // … check external APIs, evaluate pending tasks …
    await this.ctx.storage.setAlarm(Date.now() + 60_000);
  }
}
Keep agent classes small and focused on a single responsibility. Use getServerByName to delegate to specialized peer agents rather than cramming orchestration, tool execution, and memory management into one class. This mirrors the multi-agent orchestration patterns described in Anthropic’s research.

Build docs developers (and LLMs) love