OpenCode·续
For background work, submit and wait separately:
const receipt = await this.harness.submit("Summarize the report", {
operationId: "report-summary-42",
});
// In a later request to the same Durable Object:
const result = await this.harness.wait(receipt.operationId, {
session: receipt.session,
});
const receipt = await this.harness.submit("Summarize the report", {
operationId: "report-summary-42",
});
// In a later request to the same Durable Object:
const result = await this.harness.wait(receipt.operationId, {
session: receipt.session,
});
An operationId deduplicates submissions within a session. Reusing it returns a receipt with accepted: false. Use letters, digits, ., _, ~, or - in the ID.
Inputs submitted while a session runs queue as follow-ups. Set whenBusy: "steer" or call session.steer() to join the running work at its next step. harness.pending() lists queued and running operations.
Work with sessions
The harness creates a root session with ID "ses_root". Use harness.session(id) to address another session:
const session = await this.harness.sessions.create();
await session.prompt("Draft a release note");
const fork = await this.harness.sessions.fork(session.id);
const all = await this.harness.sessions.list();
const session = await this.harness.sessions.create();
await session.prompt("Draft a release note");
const fork = await this.harness.sessions.fork(session.id);
const all = await this.harness.sessions.list();
| Session method | Description |
|---|---|
submit(input, options) |
Durably submit input and return a receipt. |
prompt(input, options) |
Submit, wait, and return the result and active transcript. |
steer(input, options) |
Submit with whenBusy: "steer". |
wait(operationId, signal) |
Wait for an operation. Aborting the signal stops only the wait. |
abort(operationId) |
Withdraw a queued operation or interrupt the run it joined. Without an ID, abort all work in the session. |
setModel(model) |
Change the model, using a reference such as this.ai("@cf/moonshotai/kimi-k2.7-code"). |
setAgent(agent) |
Change the OpenCode agent, such as "build" or "plan". |
messages() |
Read the active context after the latest compaction. |
history() |
Read the full transcript, including compacted messages. |
events() |
Open a stream with a snapshot of the session, then its live events. |
log({ after, follow, signal }) |
Replay durable events, optionally following new ones. |
busy() |
Check for running, queued, or interrupted work awaiting recovery. |
sessions.list() includes sessions spawned by OpenCode's subagent tools. Sessions within one object can run independently. Map OpenCode messages to your client's UI format.
Stream events to clients
session.events() returns a stream. Its snapshot holds the session's state when the stream opened: the full transcript, pending operations, whether the session is busy, and permissions OpenCode is waiting on. Call start() to receive every event after the snapshot, in batches:
const stream = await this.harness.session(sessionId).events();
socket.send(JSON.stringify({ type: "snapshot", ...stream.snapshot }));
stream.start((events) => {
for (const event of events) {
socket.send(JSON.stringify({ type: "event", event }));
}
});
// When the client disconnects or the session is idle:
await stream.stop();
const stream = await this.harness.session(sessionId).events();
socket.send(JSON.stringify({ type: "snapshot", ...stream.snapshot }));
stream.start((events) => {
for (const event of events) {
socket.send(JSON.stringify({ type: "event", event }));
}
});
// When the client disconnects or the session is idle:
await stream.stop();
Events use the same kinds as the Pi harness: run_start and run_end, operation_start and operation_end (with the result wait() returns), message_start and message_end, text_delta, reasoning_delta, tool_start and tool_end, plus events for permissions and compaction. An event can repeat what the snapshot already shows, so key messages by ID.
For OpenCode's own events, durable and replayable, use session.log(). Save the event.durable.seq cursor and pass it as after when reconnecting. The durable log does not replay uncommitted live text deltas.
Both hold the host open. Call stream.stop() when you no longer need a stream, and stop log() with an AbortSignal. Open them again after the object restarts. The example's WebSocket adapter ↗︎ shows snapshots, event forwarding, and reattachment after startup.
Add plugins
Bundle plugins with the Worker and pass them in the factory's plugins array alongside this.ai.plugin and workerTools. Do not rely on runtime loading of local plugin files or npm packages.
OpenCode wraps plugin tools in its execute code-mode tool, so tool calls may appear as execute in events. Refer to OpenCode plugin tools ↗︎ for schemas and tool output.
Connect an MCP server
Register a remote MCP server through a plugin. This example connects to the Cloudflare documentation MCP server:
src/cloudflare-docs-mcp.jsjs
import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "cloudflare-docs-mcp",
async setup(ctx) {
await ctx.mcp.transform((servers) => {
servers.set("cloudflare-docs", {
type: "remote",
url: "https://docs.mcp.cloudflare.com/mcp",
});
});
},
});
src/cloudflare-docs-mcp.tsts
import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "cloudflare-docs-mcp",
async setup(ctx) {
await ctx.mcp.transform((servers) => {
servers.set("cloudflare-docs", {
type: "remote",
url: "https://docs.mcp.cloudflare.com/mcp",
});
});
},
});
Import this plugin and add it to plugins. Refer to OpenCode MCP transforms ↗︎ for server configuration.
Add an Agent Skill
Register skill content through a bundled plugin:
src/review-skill.jsjs
import { Plugin, Skill } from "@opencode/plugin";
export default Plugin.define({
id: "review-skill",
async setup(ctx) {
await ctx.skill.transform((skills) => {
skills.add({
id: Skill.ID.make("review"),
name: Skill.Name.make("Review"),
description:
"Review a proposed change for correctness and missing tests.",
path: Skill.Info.fields.path.make("/skills/review/SKILL.md"),
content:
"Review the proposed change. Report actionable findings with evidence.",
});
});
},
});
src/review-skill.tsts
import { Plugin, Skill } from "@opencode/plugin";
export default Plugin.define({
id: "review-skill",
async setup(ctx) {
await ctx.skill.transform((skills) => {
skills.add({
id: Skill.ID.make("review"),
name: Skill.Name.make("Review"),
description:
"Review a proposed change for correctness and missing tests.",
path: Skill.Info.fields.path.make("/skills/review/SKILL.md"),
content:
"Review the proposed change. Report actionable findings with evidence.",
});
});
},
});
Import this plugin and add it to plugins. The content supplies instructions without reading a local file at runtime. Refer to OpenCode skills ↗︎ for registration and Agent Skills for authoring guidance.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/agents/harnesses/opencode/#page","headline":"OpenCode","description":"Run OpenCode 2.0 in a plain Durable Object with the Agents SDK, Workers AI and AI Gateway models, bundled plugins, and durable sessions.","url":"https://developers.cloudflare.com/agents/harnesses/opencode/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/harnesses/opencode/og.png?v=e290a515eca01995","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Run the Pi Durable harness on Cloudflare with the Agents SDK. PiHarness keeps your agent's work durable inside a Durable Object, even if it is interrupted mid-turn. title: Pi image: https://developers.cloudflare.com/agents/harnesses/pi/og.png?v=6a341e286a3bfbc9
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.
Docs