A capability adds a reusable feature to your agent, such as WebSocket connections, scheduled tasks, or an agent harness. You construct it as a field on your Durable Object and attach it to the Lifecycle with lifecycle.use(). Install only the features your application needs.
A Durable Object keeps stored data across restarts. To continue interrupted work, a feature also needs to save its progress and arrange for the object to wake again. A Durable Object has one alarm for scheduling that wake-up.
Lifecycle coordinates startup, event dispatch, and a durable job queue. Capabilities share these services, so each feature does not need to manage the object's alarm or startup independently. A capability can:
- Store data in tables. For example,
Schedulerkeeps its schedules in SQLite. Capabilities use distinct table names within the same database. - Schedule work as jobs. A job records work to run later. Lifecycle stores it in SQLite, sets the alarm, and calls the capability's
onJob()hook when it is due. - Handle matching requests. For example,
MCPClientManagerhandles its OAuth callback URL before the request reaches youronRequest().
Each capability implements only the hooks it needs.
This example combines a Pi harness and a scheduler in one object:
flowchart TB
accTitle: Capabilities share a Durable Object's storage and alarm
accDescr: PiHarness and Scheduler submit jobs to Lifecycle, which stores pending jobs in the shared SQLite database and manages the object's single alarm.
subgraph DO["One Durable Object"]
direction TB
P["PiHarness<br/>agent loop and recovery"] -->|"wake-up jobs"| L
S["Scheduler<br/>scheduled callbacks"] -->|"scheduled jobs"| L
L["Lifecycle<br/>startup, dispatch, job queue"] -->|"sets next wake-up"| A["One alarm"]
A -->|"invokes"| L
P -->|"Pi data"| DB[("Shared SQLite database")]
S -->|"Schedules"| DB
L -->|"Pending jobs"| DB
end
Both capabilities schedule through Lifecycle without overwriting each other's alarm. After a restart, Lifecycle starts them again and dispatches due jobs. The harness or capability reads its saved state to decide how to continue. Lifecycle does not automatically save the progress of arbitrary JavaScript code.
Capabilities add features to your agent. Before installing one, check what your harness or base class already provides.
| Your starting point | What it already provides | What you configure |
|---|---|---|
| Pi or OpenCode | The agent loop, conversation storage, and recovery | Use the harness's session and tool APIs. Add capabilities for other application features. |
Agent |
WebSockets, shared state, scheduling, and MCP connections | Use the existing Agent APIs. |
Think or AIChatAgent |
The capabilities provided by Agent, plus conversation storage and streaming |
Use the framework's conversation APIs. |
| Plain Durable Object | The capabilities you explicitly install | Choose the features your application needs. |
For features your starting point does not provide, use these capabilities:
| You want to… | Use |
|---|---|
| Connect browser clients to the agent | WebSockets |
| Keep a shared value synchronized with connected clients | State with WebSockets |
| Run a callback later or on a recurring schedule | Scheduler |
| Manage MCP connections through the runtime | MCPClientManager. Check your harness's MCP integration first, such as OpenCode's MCP plugins. |
If you are writing your own agent loop, consider Sessions for conversation history and Streams for replayable output.
For guidance on conversation storage, refer to Choose a session store.
This object combines MCP connections, scheduled callbacks, shared settings, and WebSocket clients:
import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { MCPClientManager } from "agents/mcp/client";
import { Scheduler } from "agents/schedules";
import { State } from "agents/state";
import { WebSockets } from "agents/websockets";
export class MyAgent extends DurableObject {
mcp = new MCPClientManager("my-agent", "1.0.0");
scheduler = new Scheduler({
callbacks: {
dailySummary: async () => {
for (const connection of this.webSockets.getConnections()) {
connection.send("Time for your daily summary");
}
},
},
});
settings = new State({
initialState: { model: "anthropic/claude-opus-5.5" },
onChanged: (_state, source) => this.webSockets.broadcastState(source),
});
webSockets = new WebSockets({ state: this.settings });
lifecycle = Lifecycle.install(this)
.use(this.mcp)
.use(this.scheduler)
.use(this.settings)
.use(this.webSockets);
}import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { MCPClientManager } from "agents/mcp/client";
import { Scheduler } from "agents/schedules";
import { State } from "agents/state";
import { WebSockets } from "agents/websockets";
type Settings = { model: string };
export class MyAgent extends DurableObject<Env> {
readonly mcp = new MCPClientManager("my-agent", "1.0.0");
readonly scheduler = new Scheduler({
callbacks: {
dailySummary: async () => {
for (const connection of this.webSockets.getConnections()) {
connection.send("Time for your daily summary");
}
},
},
});
readonly settings: State<Settings> = new State<Settings>({
initialState: { model: "anthropic/claude-opus-5.5" },
onChanged: (_state, source) => this.webSockets.broadcastState(source),
});
readonly webSockets: WebSockets = new WebSockets({ state: this.settings });
readonly lifecycle = Lifecycle.install(this)
.use(this.mcp)
.use(this.scheduler)
.use(this.settings)
.use(this.webSockets);
}Pass what a capability needs, such as callbacks or another capability, to its constructor. In this example, WebSockets receives the State capability to synchronize settings with clients.
These capabilities ship with the agents package and are stable:
| Capability | Import | What it does |
|---|---|---|
WebSockets |
agents/websockets |
Accepts WebSocket connections from useAgent and other clients |
State |
agents/state |
Stores one validated value and syncs it to clients through WebSockets |
Scheduler |
agents/schedules |
Runs callbacks after a delay, at a date, on a cron, or on an interval |
MCPClientManager |
agents/mcp/client |
Connects to MCP servers and keeps the connections across restarts |
These capabilities are experimental. Their APIs may change between releases:
| Capability | Import | What it does |
|---|---|---|
Streams |
agents/streams |
Stores incremental output that readers replay and follow |
Sessions |
agents/sessions |
Stores conversation history as a tree of messages |
Queue |
agents/queue |
Runs background callbacks one at a time, in push order |
Tasks |
agents/tasks |
Runs replayable background work with journaled steps |
RoutedAgents |
agents/routing |
Keeps a catalog of independent agents and forwards requests to them |
Browser |
agents/browser |
Keeps one persistent Browser Run session open for the agent |
A harness, such as PiHarness, is a capability that runs a whole agent loop.
To write a capability of your own, refer to Build a capability.
- Install before startup. Call
use()in a field initializer or in the constructor. The Lifecycle starts on the first request, alarm, or WebSocket message, anduse()throws after that. - Declare fields before the Lifecycle. Class fields initialize in order. Declare each capability field before the
lifecyclefield that installs it. - Install each capability once. Each capability has a stable ID, such as
"scheduler"or"mcp". The ID namespaces its jobs and events, anduse()throws on a duplicate. - Startup order. At startup, the Lifecycle runs each capability's
onStart()in install order, with a catch-all capability last. Then it runs your ownonStart(). - Request order. The Lifecycle offers each request to the capabilities in install order before your
onRequest().WebSocketsaccepts every WebSocket upgrade, so it always runs after the others. For more information, refer to Request dispatch. - Shared alarm. Capabilities that run work later, such as
Scheduler, store it as Lifecycle jobs, so they never overwrite each other's alarm. - Host context. Callbacks you register, such as
Schedulercallbacks,WebSocketshandlers, and callables, run inside your object's context, wheregetCurrentAgent()returns your object. - RPC. Native Durable Object RPC bypasses
fetch(), so it does not start the Lifecycle. Callawait this.lifecycle.start()at the top of an RPC method that uses a capability.
The Agent class is a preset: a Durable Object with the Lifecycle and several capabilities already installed, including Scheduler, State, WebSockets, and MCPClientManager. Its methods, such as this.schedule(), this.setState(), and this.addMcpServer(), call those capabilities.
To add another capability to an Agent, call this.lifecycle.use() in its constructor. Do not install a second copy of a capability the Agent already has, because use() throws on a duplicate ID.