# Build a custom adapter (/docs/developers/custom-adapter)



If ox doesn't bundle your coding agent, write an adapter for it. An adapter is a standalone binary that bridges ox and one agent — once it exists, `ox adapter install <name>` makes recording work without any change to ox itself.

The adapter model [#the-adapter-model]

An adapter is an external binary named `ox-adapter-<name>` that speaks the **ox adapter protocol** over stdin/stdout as newline-delimited JSON (NDJSON). ox never touches agent session files directly — every agent-specific detail (where sessions live, the transcript format, how to install hooks) lives in the adapter.

Because the contract is a wire protocol, any language works: Go, Rust, Python, TypeScript, C++. The adapter knows four things about its agent:

* Where the agent writes session files
* How to parse the agent's transcript into ox's `RawEntry` shape
* How to install and remove ox hooks in the agent's config
* Whether the agent is installed on the current machine

Two operational modes [#two-operational-modes]

An adapter runs in one of two modes depending on what ox asks for.

| Mode         | Used for                                                                          | Process lifetime                                       |
| ------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------ |
| **One-shot** | Low-frequency calls: `info`, `detect`, `install-hooks`, `check-hooks`, `diagnose` | Spawned, responds with one JSON line, exits            |
| **Serve**    | High-frequency calls during a live session: `find-session`, `read-from-offset`    | Spawned at session start, lives until the session ends |

One-shot subcommands are invoked as `ox-adapter-<name> <subcommand> [flags]`. They read flags and environment, write exactly one compact JSON object to stdout, and exit. Here's a one-shot `info` response — the call that declares the adapter's identity and capabilities:

```json
{
  "protocol_version": 1,
  "name": "myagent",
  "display_name": "My Agent",
  "version": "1.0.0",
  "type": "session",
  "capabilities": ["session_reader", "hook_installer", "incremental_reader", "serve_mode"],
  "hook_env_values": ["myagent"],
  "serve_mode": true
}
```

Serve mode keeps one process alive across many hook calls. The daemon spawns a single `ox-adapter-<name> --serve` and routes every active session of that agent type through it, so each request carries an `agent_id` you use to look up per-session state.

```json
{"id": 1, "method": "find-session", "params": {"agent_id": "r7f3a2-OxA1b2", "repo_root": "/tmp/repo", "since": "2026-01-01T00:00:00Z"}}
{"id": 1, "result": {"session_file": "/path/session.jsonl", "offset": 512}}
```

Unknown methods must return a `method_not_found` error rather than crashing — ox treats that as "capability absent" and degrades gracefully.

Declare your capabilities [#declare-your-capabilities]

The `capabilities` array in your `info` response tells ox exactly what your adapter implements. Declare only what you build; ox asserts required capabilities at session registration, so a missing one fails fast with a clear error instead of breaking mid-recording.

| Capability           | What it grants                                                                 |
| -------------------- | ------------------------------------------------------------------------------ |
| `session_reader`     | `find-session`, `read`, `read-metadata` — the baseline for any session adapter |
| `hook_installer`     | `install-hooks`, `check-hooks`, `uninstall-hooks`                              |
| `incremental_reader` | `read-from-offset` — required for serve-mode recording                         |
| `file_watcher`       | Push entry events automatically after `find-session`, no polling               |
| `serve_mode`         | Support the `--serve` flag                                                     |
| `rules_installer`    | Install and remove ox rules in the agent's config                              |
| `session_importer`   | Import a prior session by ID after the fact                                    |

The Go SDK [#the-go-sdk]

Go authors should use the SDK rather than implementing the wire protocol by hand. Two public, versioned packages do the heavy lifting:

* `pkg/adapterruntime` — the serve loop, subcommand dispatch, JSON framing, and graceful shutdown. You register typed handler functions; the SDK makes the protocol invisible.
* `pkg/adapterprotocol` — the shared request, response, and `RawEntry` types, plus the capability constants.

A minimal Go adapter is a `main` that hands a `Config` of handlers to the runtime:

```go
func main() {
    adapterruntime.Run(adapterruntime.Config{
        Info:           handleInfo,
        Detect:         handleDetect,
        InstallHooks:   handleInstallHooks,
        CheckHooks:     handleCheckHooks,
        UninstallHooks: handleUninstallHooks,
        Read:           handleRead,
        ReadMetadata:   handleReadMetadata,
        Diagnose:       handleDiagnose,
        FindSession:    handleFindSession,
        Serve:          handleServe,
    })
}
```

Non-Go adapters implement the protocol spec directly. The package `pkg/ndjson` provides the framing utilities Go gets for free.

Verify against the compliance suite [#verify-against-the-compliance-suite]

The compliance test suite validates any adapter binary against the protocol, regardless of language. Run it during development to catch contract violations before users hit them:

<Terminal>
  <TerminalComment>
    Run compliance against your binary, from the ox repo
  </TerminalComment>

  <TerminalCommand>
    go test ./pkg/adapterprotocol/compliance/... -adapter ./ox-adapter-myagent
  </TerminalCommand>
</Terminal>

Once installed, ox can run the same suite for you:

<Terminal>
  <TerminalCommand>
    ox adapter verify myagent
  </TerminalCommand>
</Terminal>

During development, symlink a locally-built binary instead of installing a release:

<Terminal>
  <TerminalCommand>
    go build -o ./bin/ox-adapter-myagent ./cmd/ox-adapter-myagent
  </TerminalCommand>

  <TerminalCommand>
    ox adapter link ./bin/ox-adapter-myagent
  </TerminalCommand>
</Terminal>

Key contracts to get right [#key-contracts-to-get-right]

A few behaviors are load-bearing regardless of language. Get these wrong and recordings silently truncate or stall.

* **Compact JSON only.** One object per line, no pretty-printing, no literal newlines inside values. NDJSON uses newlines as delimiters.
* **Line buffer ≥ 1MB.** Default line readers (64KB in Go's `bufio.Scanner`) truncate large tool outputs. Raise the buffer and check for read errors after the scan loop finishes.
* **Respect the timeouts.** `read-from-offset` is the hot path — every tool call waits on it. Keep the file handle open from `find-session` and never re-open from zero.

<Callout type="warn">
  The `fast` timeout for `read-from-offset` defaults to 100ms. Exceeding it on three consecutive calls downgrades the session to one-shot mode — recording continues, only slower. Key all per-session state by `agent_id`, never by `repo_root` (git worktrees produce different paths for the same repo).
</Callout>

This page is the orientation, not the full reference. The in-repo authoring guide walks through every subcommand with examples, and the protocol spec is the complete contract — read both before shipping an adapter for others to install.

What's next [#whats-next]

* [Supported coding agents](/docs/developers/coding-agents) — the agents ox already bundles
* [SageOx + Claude Code](/docs/developers/claude-code) — the reference adapter in action
* [CodeDB: smarter code navigation](/docs/developers/codedb) — what your agent gains once recording works
