Subject Agent Frameworks
Date Feb 2026
Section 06 / 07
06

Agent architectures by example

It's possible to cross the service boundary without rebuilding everything OpenCode provides. Depending on the use case, you may need to implement only some of the layers.

The single biggest design decision is whether you are building a stateful or stateless agent. Statefulness can be achieved with an agent being "always on", being hosted on a VPS for example. But that's not scalable: you end up paying even when the agent is idle.

Alternatively relying on ephemeral environments comes with a persistence challenge: how do you persist the state when the environment is torn down?

Part 5 walks through real projects to illustrate how agents are assembled from different technical bricks, reviewing a variety of architectural choices.

Claude in the Box: the job agent

Agent Framework: Claude Agent SDK
Cloud services: Cloudflare Worker + Cloudflare Sandbox
Layers: transport + artifacts persistence
Link: github.com/craigsdennis/claude-in-the-box

Description:

User journey: the client sends a POST request with a prompt and stays connected. The agent's raw output streams back in real time: progress messages, tool calls, intermediate results. When the agent finishes, the Worker collects the final output files (the artifacts) and stores them in KV and returns it to the client.

Technical flow:

Browser → HTTP POST
  → Cloudflare Worker (~100 lines)
    → Cloudflare Sandbox
      → Claude Agent SDK query()
    ← streams stdout back
    → reads artifacts → stores in KV
    → destroys sandbox

Highlight: Why Cloudflare requires two layers: Worker + Sandbox?

Cloudflare Workers are like application "valets":

The Sandbox is the opposite:

Neither can do the whole job alone. The Worker provides the service boundary (HTTP endpoint, streaming, artifact storage). The Sandbox provides the execution environment (bash, filesystem, long-running agent). The ~100 lines of glue between them wire up the HTTP endpoint, bridge the stream, and collect artifacts.

Server layers implementation

Layer Status Implementation
Authentication Skipped Anyone can call the endpoint.
Network resilience Skipped If the connection drops, the work is lost.
Transport Implemented (minimal) Chunked HTTP streaming — the user watches progress in real time, but cannot send anything back.
Routing Skipped No session IDs, no conversations to switch between. Each request is independent.
Persistence Partial Final artifacts only (stored in KV). No conversation history, no ability to resume.
Lifecycle Skipped The agent dies with the request. Close the tab and the work stops.

sandbox-agent: the adapter

Agent Framework: Agent-agnostic (supports Claude Code, Codex, OpenCode, Amp)
Cloud services: None — runs inside any sandbox (designed to be embedded)
Layers: transport + partial routing
Link: github.com/rivet-dev/sandbox-agent

Description:

Technical flow:

Your App (anywhere)
    |  HTTP + SSE
    v
+--[sandbox boundary]-------------------+
|  sandbox-agent (Rust daemon)           |
|    claude  |  codex  |  opencode       |
|  [filesystem, bash, git, tools...]     |
+----------------------------------------+

Highlight: the Transport layer

Transport is how a client and a server exchange data over a network. There is a spectrum of transport modes, from simplest to most capable:

Mode What the user experiences Interaction Reconnection
HTTP request/response Submit a task, wait, get the full result when done. No progress updates while the agent works. One-shot. N/A.
Chunked HTTP streaming Submit a task, watch the agent's output stream in real time — like a terminal in the browser. Watch only — the user cannot send input mid-stream. None. Connection drops = work lost.
Server-Sent Events (SSE) Same real-time streaming, but the connection survives drops. The browser reconnects automatically and resumes from the last event. Watch + interact via separate requests (e.g. approve a command via a button click). Built-in (automatic).
WebSocket Full interaction while the agent works — approve commands, provide context, cancel tasks. Multiple users can watch the same session. Bidirectional, real-time. Application must implement.

Claude-in-the-Box uses chunked HTTP streaming. sandbox-agent outputs SSE. Ramp Inspect uses WebSocket. Each step up adds capability and complexity.

Now, the agents that sandbox-agent supports speak different native protocols — none of which are network transports:

Server layers implementation

Layer Status Implementation
Authentication Skipped Runs inside a sandbox — assumes the sandbox boundary provides isolation.
Network resilience Partial SSE sequence numbers allow clients to reconnect and resume from last-seen event.
Transport Implemented HTTP + SSE — structured event stream with sequence numbers for reconnection. REST endpoints for approvals/cancellation.
Routing Partial In-memory session management — multiple sessions per daemon, but no persistent session registry.
Persistence None If the daemon crashes or the sandbox is destroyed, there is no way to recover or reconnect to a conversation.
Lifecycle Minimal Agent process managed by the daemon, but no background continuation beyond the sandbox's lifetime.

Ramp Inspect — the full production stack

Agent Framework: OpenCode
Cloud services: Modal Sandbox VMs + Cloudflare Durable Objects + Cloudflare Workers
Layers: transport + routing + persistence + lifecycle + authentication + network resilience (all layers)
Link: builders.ramp.com/post/why-we-built-our-background-agent

Description: Ramp's internal background coding agent that creates pull requests from task descriptions. Reached ~30% of all merged PRs within months.

User journey: an engineer describes a task in Slack, the web UI, or a Chrome extension. The agent works in the background — the engineer can close the tab, switch clients, come back later from a different device. When done, the agent posts a PR or a Slack notification. Multiple engineers can watch the same session simultaneously.

Technical flow:

Clients (Slack, Web UI, Chrome Extension, VS Code)
  → Cloudflare Workers
    → Durable Object (per-session: SQLite, WebSocket Hub, Event Stream)
      → Modal Sandbox VM (OpenCode agent, full dev environment)

Highlight: Durable Objects as the coordination layer

In Part 4, we saw that OpenCode is a single-server agent — it has session management, persistence, and transport, but all scoped to one machine. To make it globally accessible, you need global routing, persistent state that survives restarts, and WebSocket management across clients. This is the gap Ramp filled with Durable Objects.

A Durable Object is a stateful micro-server with a globally unique ID (while Workers are stateless). Any request from anywhere in the world can reach a specific DO by its ID — Cloudflare routes it automatically. Each DO has its own embedded SQLite database (up to 10 GB), and it can hold WebSocket connections. It runs single-threaded, which matches the agent pattern: one session = one sequential execution context.

What makes DOs useful for agents specifically:

Why a Modal VM is required on top of the DO:
A DO is a lightweight JavaScript runtime — it cannot run bash, access a filesystem, or execute agent tools. It is the coordination layer (routing, state, WebSocket), not the execution layer. Code execution happens in a separate VM or container. This is why Ramp pairs DOs with Modal VMs: the DO routes and remembers, the VM computes.

Server layers implementation

Layer Status Implementation
Authentication Internal only Restricted to Ramp employees — no public access.
Network resilience Implemented WebSocket with DO hibernation — connections survive idle periods, clients reconnect seamlessly.
Transport Implemented WebSocket — bidirectional, real-time, multiple clients connect to the same session simultaneously.
Routing Implemented Cloudflare Durable Objects — per-session, globally routed, guaranteed affinity by session ID.
Persistence Implemented (two layers) DO SQLite for conversation state + Modal snapshots for full VM state (code, deps, environment).
Lifecycle Implemented (full) Agent survives client disconnection — background continuation is the core design principle.

Cloudflare Moltworker — the platform provides the layers

Agent engine: Pi SDK (LLM abstraction + core agent loop)
Agent product: OpenClaw (personal AI assistant built on Pi SDK — multi-channel gateway, session management, skills platform)
Cloud services: Cloudflare Worker + Durable Objects + Sandbox + R2 + AI Gateway
Layers: ALL (transport, routing, persistence, lifecycle, authentication, network resilience)
Link: github.com/cloudflare/moltworker — blog: blog.cloudflare.com/moltworker-self-hosted-ai-agent

Description:

User journey: the user accesses their agent via a browser, protected by Cloudflare Access (Zero Trust). They chat with the agent, which can browse the web, execute code, and remember context across sessions. They can close the browser and come back — conversations persist. The agent can also run autonomously on a cron schedule with no client connected at all.

Technical flow:

Internet → Cloudflare Access (Zero Trust)
  → Worker (V8 isolate, API router)
    → Durable Object (routing, state, WebSocket)
      → Container (Linux VM, managed via Sandbox)
        → /data/moltbot → R2 Bucket (via s3fs)
        → OpenClaw (Pi SDK agent)

Highlight: how persistence works with ephemeral compute

Both Ramp and Moltworker face the same problem: the agent runs in an ephemeral machine (Modal VM or Cloudflare Container) that will eventually be destroyed. How do you keep state across restarts?

The 2 projects made different design decisions:

Ramp (Modal) Moltworker (Cloudflare)
What dies VM is terminated after 24-hour TTL Container filesystem is wiped on sleep
Conversation state Stored in Durable Object (SQLite) — survives VM restarts Stored in Durable Object (SQLite) — survives container restarts
Code, deps, environment Modal snapshot API — full point-in-time capture of the VM filesystem. Taken before termination, restored into a fresh VM later. R2 bucket mounted at /data/moltbot via s3fs — everything written there survives. No snapshot, just continuous persistence.
What survives Everything (full VM state frozen and restored) Only what's explicitly written to /data/moltbot
What's lost Nothing (if snapshotted before termination) Anything on the container filesystem outside the R2 mount
Trade-off Full fidelity but requires snapshot orchestration Simpler but selective — you must design for it

Server layers implementation

Layer Status Implementation
Authentication Implemented Cloudflare Access (Zero Trust) — identity-based access control before any request reaches the application.
Network resilience Implemented DO hibernation keeps WebSocket alive during idle periods. Container wakes on next message.
Transport Implemented WebSocket (via Durable Objects) + HTTP API for the entrypoint Worker.
Routing Implemented Durable Object instance IDs — globally routable, all requests for same ID reach the same location.
Persistence Implemented Multi-layer: DO SQLite for conversation, R2 bucket mounted via s3fs for artifacts and session memory.
Lifecycle Implemented Agent survives client disconnection. DO hibernates. Containers sleep/wake. Cron enables autonomous runs.

What to keep in mind