Harness Server
Overview
The server consists of three layers:
Browser ←─ WebSocket ─→ HarnessGateway ─→ HarnessSession ←─ RunnerProtocol ─→ SandboxRunner
(CF Worker) (Durable Object) (in container)- HarnessGateway — Cloudflare Worker. Edge router that resolves the user/session and forwards to the right Durable Object.
- HarnessSession — Durable Object. Owns the
HarnessState, manages the WebSocket (with hibernation), and orchestrates runs. - SandboxRunner — Thin client inside the sandbox container. Receives instructions from the DO, runs claude-code, streams results back.
The RunnerProtocol is the internal protocol between the DO and the sandbox runner.
HarnessGateway
The gateway is a stateless Cloudflare Worker. It handles incoming WebSocket upgrade requests, resolves which HarnessSession DO to route to, and forwards the connection.
GET /ws?userId=abc → upgrade to WebSocket → forward to HarnessSession DOThe gateway does not hold state. It maps a user/session identifier to a Durable Object ID and hands off the connection.
HarnessSession
The Durable Object is the core of the server. It:
- Accepts WebSocket connections from the client (via the gateway)
- Hibernates when idle — the DO sleeps and the WebSocket is held by the Cloudflare runtime, consuming no compute
- Wakes on incoming message — when the client sends a command, the DO wakes up
- Owns HarnessState — the single source of truth for messages, status, errors
- Translates between protocols — converts
HarnessCommands intoRunnerProtocolcalls and runner events into state deltas
Lifecycle
Client connects
→ DO wakes, sends state snapshot
→ DO hibernates
Client sends submit command
→ DO wakes
→ DO updates state (status: running, add user message)
→ DO sends deltas to client
→ DO starts sandbox runner (if not running)
→ DO forwards prompt via RunnerProtocol
→ Runner streams events back
→ DO translates events into deltas, sends to client
→ Run completes, DO updates state (status: idle)
→ DO hibernates
Client disconnects
→ DO cleans up WebSocket
→ DO hibernates (sandbox may stay warm)State Management
The DO maintains HarnessState in memory (and optionally persists to SQLite for resume). When state changes, it computes deltas and sends them to connected clients.
// Pseudocode
onCommand(command: HarnessCommand) {
if (command.type === "submit") {
this.state.status = "running";
this.state.messages.push({ id, role: "user", content: command.prompt, status: "complete" });
this.sendDelta(/* ... */);
this.startRun(command.prompt);
}
if (command.type === "cancel") {
this.abortCurrentRun();
this.state.status = "idle";
this.sendDelta(/* ... */);
}
}WebSocket Hibernation
The DO uses Cloudflare's WebSocket Hibernation API. Between messages, the DO consumes zero compute. The runtime holds the WebSocket open and wakes the DO when a message arrives or the connection closes.
Key handlers:
webSocketMessage(ws, message)— DO wakes, processes commandwebSocketClose(ws)— DO wakes, cleans upwebSocketError(ws)— DO wakes, handles error
SandboxRunner
A lightweight HTTP server running inside the sandbox container. It exposes a simple API for the DO to call.
The runner is the only process that interacts with claude-code / the agent SDK. It:
- Receives a prompt
- Runs the agent
- Streams events back to the caller
The runner is stateless between runs. It does not know about users, sessions, or WebSockets.
RunnerProtocol
The protocol between HarnessSession (DO) and SandboxRunner (container). Uses HTTP with SSE responses.
Start a Run
POST /query
Content-Type: application/json
{ "prompt": "..." }Response: SSE stream
event: run.started
data: {"type":"run.started","requestId":"..."}
event: assistant.delta
data: {"type":"assistant.delta","text":"Hello"}
event: tool.started
data: {"type":"tool.started","toolName":"Read","toolUseId":"..."}
event: assistant.delta
data: {"type":"assistant.delta","text":" world"}
event: run.completed
data: {"type":"run.completed","result":"..."}Health Check
GET /healthResponse:
{ "ok": true, "busy": false, "hasAnthropicKey": true }Event Types
| Event | Fields | Meaning |
|---|---|---|
run.started | requestId | Run has begun |
assistant.delta | text | Incremental text from the assistant |
tool.started | toolName, toolUseId | A tool is being invoked |
run.completed | result?, sessionId? | Run finished successfully |
run.error | message | Run failed |
Event → Delta Translation
The HarnessSession translates runner events into deltas for the client:
| Runner Event | State Delta |
|---|---|
run.started | Set status to "running", add user message |
assistant.delta | append-text on current assistant message content |
tool.started | Add tool call to current assistant message |
run.completed | Set message status to "complete", set status to "idle" |
run.error | Set error, set status to "error" |
Sandbox Lifecycle
The DO manages the sandbox container via the Cloudflare Containers API:
- First request — container starts, runner process is launched inside it
- Subsequent requests — container is reused if healthy (health check via
/health) - Idle timeout — container is stopped by the platform after inactivity
- Environment —
ANTHROPIC_API_KEYand config are injected viasetEnvVarsbefore each run