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.
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.
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.
partywhen provides a Scheduler class that persists tasks in SQLite and uses Durable Object alarms for reliable execution. It supports three task types:
Type
When it runs
scheduled
At a specific Date
delayed
After delayInSeconds seconds
cron
On a cron expression (e.g. "0 9 * * 1")
no-schedule
Never automatically — useful for manually triggered tasks
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; }}
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); }}
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.