> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opencomputer.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom runtimes

> Package your own agent implementation as a runtime

<Note>**Labs preview.** Custom runtimes are not part of the stable Durable Sessions API. Production sessions use the built-in [runtimes](/agent-sessions/runtimes): `claude`, `codex`, and `pi`.</Note>

Building on the [Flue framework](https://flueframework.com)? Use the supported [Flue runtime](/agent-sessions/flue) instead. It deploys Flue's Cloudflare target as an agent Worker with one Durable Object per session, so it does not use the custom brain-box contract described here. Custom runtimes remain the general escape hatch for other agent implementations.

A custom runtime lets you run your own agent implementation on Durable Agent Sessions. Once registered, select it on an agent with `runtime: "<name>"`; sessions, events, steering, webhooks, recovery, and sandbox tools use the same public API as built-in runtimes.

The runtime image contains a **brain server**: a resident process around your agent SDK. OpenComputer supplies the platform adapter that reads the session log, owns fencing and idempotency, exposes tools, calls your brain over localhost, and commits the brain's stream as durable events.

You implement the brain. You do **not** implement OpenComputer's event API, session cursors, turn-token auth, sandbox HTTP calls, or webhook delivery.

See [example runtimes](https://github.com/diggerhq/oc-runtime-examples) for minimal `claude` and `codex` brain servers.

## Runtime contract

Your image starts an HTTP server on `127.0.0.1:$OC_BRAIN_PORT` (`8080` by default). The server stays warm across turns and handles one turn at a time.

```http theme={null}
GET /healthz
```

```json theme={null}
{
  "status": "ready",
  "contract_version": "1",
  "busy": false
}
```

Return `busy: true` while a turn is running. A second turn should return `409`; OpenComputer already serializes real turns, so this is a safety check.

<Note>`contract_version` (`"1"`) is the runtime↔adapter contract version. Treat this Labs contract as unstable until it is promoted out of Labs.</Note>

## Turn request

For each turn, the adapter sends the new input and run config:

```http theme={null}
POST /turn
Content-Type: application/json
```

```json theme={null}
{
  "contract_version": "1",
  "turn_id": "turn_...",
  "input": [
    { "role": "user", "content": "Review this repository." }
  ],
  "config": {
    "model": "anthropic/claude-opus-4-8",
    "system_prompt": "Run tests and explain risks.",
    "mcp_endpoint": "http://127.0.0.1:8765/mcp",
    "state_dir": "/home/sandbox/.oc/runtime-state",
    "resume": false,
    "max_turns": 24
  }
}
```

The adapter has already read the event log. Your brain receives the new turn input, not a session cursor.

Everything in `config` is turn-invariant for the life of the box, so a resident brain may cache values like `mcp_endpoint`, `state_dir`, and the ports across turns. When something has to change — for example the session's skills — the platform restarts the brain rather than handing it a changed `config` mid-life.

## Step stream

`POST /turn` responds with newline-delimited JSON. The brain streams its SDK's **native** events verbatim, one per line, then a terminal `done`:

```jsonl theme={null}
{"seq":0,"kind":"assistant","msg":{ "...native SDK message..." }}
{"seq":1,"kind":"user","msg":{ "...native SDK message, e.g. a tool result..." }}
{"kind":"done","reason":"quiescent"}
```

* `kind` is the SDK's message/event type; `msg` is the native object, unchanged.
* `done.reason` is `quiescent` (nothing left to do), `awaiting_input` (the agent called `ask` and is paused for a reply), or `error` (with an `error`).

The platform ships a **per-SDK adapter** that maps native SDK events to [session events](/agent-sessions/events) with stable idempotency keys, and flushes committed output before ending the turn. Custom runtimes target an SDK the platform has an adapter for.

Tool calls, including user-visible `say` and `ask`, are made through MCP, not by emitting HTTP requests to OpenComputer.

## Deadlines

A turn runs under nested deadlines. Each inner clock expires before the one outside it, and the layer that enforces a deadline also cleans up the one below it.

| Clock                | Value                               | Enforced by                                                       |
| -------------------- | ----------------------------------- | ----------------------------------------------------------------- |
| Health check         | 2s per probe                        | adapter → your `/healthz`                                         |
| Brain start          | 30s to first `ready`                | adapter (fails the turn if exceeded)                              |
| Step cap             | `config.max_turns` (24) per `/turn` | your SDK — end the stream cleanly when hit                        |
| Model call           | provider-dependent                  | your SDK                                                          |
| Turn deadline        | `min(agent turnSeconds, 1800s)`     | platform — the turn is killed at the deadline                     |
| Fence / cancel grace | 5s after the request is aborted     | adapter — a brain still busy is killed; the next turn restarts it |

If your brain runs past the turn deadline the turn ends `deadline_exceeded`; the session can be steered to continue. Aim to yield often — fresh config and input arrive on the next turn.

## Tools

The adapter exposes tools at `config.mcp_endpoint`. Your brain connects through your SDK's MCP support.

| Tool    | What it does                                     |
| ------- | ------------------------------------------------ |
| `bash`  | Run a shell command in the hands sandbox.        |
| `read`  | Read a file from the hands sandbox.              |
| `write` | Write a file in the hands sandbox.               |
| `ls`    | List a directory in the hands sandbox.           |
| `say`   | Emit a user-visible message.                     |
| `ask`   | Ask for input and end the turn awaiting a reply. |

The brain sandbox is for the model loop. The hands sandbox is where files and commands run.

The MCP endpoint is live only during an in-flight `/turn`. Don't call tools from a background timer or a deferred flush between turns — the endpoint is gone once the turn ends. The adapter always provides `config.mcp_endpoint`; connect to it — a brain that skips tools silently degrades to a chat-only agent.

## State and recovery

Store resumable state under `config.state_dir`. OpenComputer checkpoints that directory at turn boundaries and restores it after recovery.

Examples:

* Claude writes a local journal and continues from it.
* Codex persists a server-side thread id and resumes that thread.

Design your brain so replay is safe. If a crash happens after a step is emitted but before the turn finishes, the adapter may replay from durable state and dedupe already committed events.

## Register runtimes

Registration command:

```bash theme={null}
oc runtime push
```

Runtimes are versioned. Each session pins the runtime build it starts with, so publishing a new build affects new sessions only.
