PuppyIP Resource Center
Developer Integration 6 min Published 2026-10-13

OpenCode 2 on Cloudflare: durable sessions and local-tool limits

OpenCode 2 can now run in a Durable Object through Cloudflare’s experimental OpenCodeHarness, with stored sessions and recovery for interrupted work. To embed it in your application, configure SQLite, Lifecycle and the model binding first, then choose where repository files and shell commands will run. The integration does not automatically provide a complete cloud development machine.

OpenCode 2 Cloudflare Agents Durable Objects Session recovery Developer guide

Service eligibility and regional restrictions

PuppyIP serves only compliant overseas businesses and their authorized personnel. Proxy services are not available in mainland China. The service may only be used for lawful business activities outside mainland China. Use of this service within mainland China is prohibited.

Hosting a proxy IP or server overseas does not change these restrictions. The service must not be provided to end users in mainland China through relaying, forwarding, sharing or resale. Before use, read the Terms of Service.

Key Takeaways

  • Use Agents SDK 0.28.0 or later. This integration supports the optional OpenCode peer packages in the ^2.0.24 range.
  • SQLite stores sessions, messages and durable events. Lifecycle jobs wake work after a crash or eviction.
  • Local read, write, edit, patch, glob, grep and shell tools need replacement; configure plugins and any external execution environment explicitly.
  • Use operationId for deduplication within a session. Reconnect through history and durable events; uncommitted live text is not replayed automatically.

What does the integration provide?

Cloudflare announced OpenCode 2.0 support on October 12 and explicitly labels OpenCodeHarness experimental. Developers can embed model calls, tools and sessions in their own Worker applications. A Durable Object provides application state and persistent storage, rather than a complete development machine with a filesystem and terminal.

OpenCode handles the agent loop, transcript, tools and session recovery. OpenCodeHarness supplies SQLite storage and wake-up after interruption. The example connects Workers AI through an AI binding. The reviewed pages do not establish access for every account, regional availability or comprehensive pricing; check your plan, model and usage limits before adopting it.

Configure the packages, storage and model first

First, check that your development project uses agents 0.28.0 or later, with @opencode/sdk and @opencode/plugin. They are optional peer dependencies but required for this integration, with a documented supported range of ^2.0.24. The announcement’s @2 installation example does not replace checking the resolved versions.

Next, follow the Wrangler example for a Workers AI binding, a SQLite-backed Durable Object and nodejs_compat. Match the class, binding and new_sqlite_classes migration declaration. The example asks for today’s compatibility_date; October 12 is an example date, rather than a permanent required value.

Create OpenCodeHarness from agents/harness/opencode. Its factory passes the supplied storage to OpenCodeWorkerd.create(), while Lifecycle.install(this).use(this.harness) manages its lifecycle. Register this.ai.plugin in plugins and connect createAI from agents/models/opencode to your AI binding.

models: { fetch: false } skips the external model-catalog refresh; the provider registers models named through this.ai(). The Kimi model shown is an example, without making it mandatory or free. The host closes after 30 idle seconds when no work or event subscriptions remain, then reopens on the next call. Its factory must tolerate repeated calls.

Why do local files and shell commands need another route?

The official workerTools example removes read, write, edit, patch, glob, grep and shell, then adds a current_time tool that runs inside the Worker. The removed tools cannot use desktop filesystem and process capabilities unchanged. Provide your own implementations or connect an explicit external execution environment.

Suppose your agent must modify a repository. A durable conversation does not establish that the repository is mounted or that shell commands can run. This is a design example. Decide where files live, which operations are authorized and how execution results return, then expose those capabilities through plugins.

Bundle plugins, remote MCP configuration and skill content with the Worker and register them in the factory’s plugins array. Do not depend on loading local plugin files or npm packages at runtime. The skill example supplies content directly instead of reading a local SKILL.md in the cloud.

Submit background work without queuing it twice

harness.prompt() submits to the root session and waits. Check status: done means answered, while unanswered can mean failed, interrupted, withdrawn or not found. A text fragment is not a substitute for completion status.

For background work, submit() returns a receipt. Retain operationId and session, then call wait() in a later request. operationId deduplicates only within the same session. Reusing it returns accepted: false; it is not a guarantee of uniqueness across sessions or the whole application.

Input arriving while a session runs normally queues as a follow-up. whenBusy: "steer" or session.steer() joins running work at its next step. harness.pending() lists queued and running operations. Check the original operation before deciding to resubmit it.

Recover the object and the client separately

Sessions, messages and durable events live in the object’s SQLite database under opencode_ table and index prefixes. Active sessions have Lifecycle jobs with heartbeats. After a crash or eviction, an alarm wakes the object and OpenCode resumes interrupted work. This describes documented behavior, rather than an uptime guarantee tested by PuppyIP.

Client reconnection is a separate layer. session.events() supplies live events without a transcript snapshot. Read messages() for the active context or history() for the full transcript, including compacted messages, then resume events. For durable replay, use session.log(), save event.durable.seq and pass it as after.

The durable log does not replay uncommitted live text deltas. Recreate subscriptions after an object restart and stop unwanted subscriptions with an AbortSignal; subscriptions keep the host open. Session recovery, restoring the client display and retrying tools with side effects need separate decisions.

Check these boundaries before adapting the demo

The official example uses getByName("demo") to address one object. In an application, choose the object name from an authenticated user or conversation; a shared demo name cannot provide user isolation. Sessions inside one object can run independently, and the default root session ID is ses_root.

In your own isolated test environment, check three paths: normal work returns done; interrupted work resumes in the same session without queuing a repeated operationId again; and a reconnecting client restores history and committed events. Also review tool permissions, model charges, storage usage and access controls. The sources contain complete configuration and plugin examples.

OpenCode’s Cloudflare SDK warns that eviction may happen without cleanup, so correctness must not depend on close() running. PuppyIP reviewed public announcements and documentation; it did not install packages, run a Worker, call models or verify deployment results for an individual account.

Sources

Frequently Asked Questions

Does cancelling wait() stop the background task?

No. The session API says aborting the wait() signal stops only the wait. Use session.abort(operationId) to withdraw the specified queued operation or interrupt the run it joined. With no ID, it aborts all work in that session.

Should the client use messages() or history()?

messages() returns the active context after the latest compaction, useful for the current view and ongoing work. history() returns the full transcript, including compacted messages. To restore the complete conversation, map the latter to your application’s UI format.