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
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
RawEntryshape - How to install and remove ox hooks in the agent's config
- Whether the agent is installed on the current machine
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:
{
"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.
{"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
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
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, andRawEntrytypes, plus the capability constants.
A minimal Go adapter is a main that hands a Config of handlers to the runtime:
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
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:
Once installed, ox can run the same suite for you:
During development, symlink a locally-built binary instead of installing a release:
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-offsetis the hot path — every tool call waits on it. Keep the file handle open fromfind-sessionand never re-open from zero.
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).
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
- Supported coding agents — the agents ox already bundles
- SageOx + Claude Code — the reference adapter in action
- CodeDB: smarter code navigation — what your agent gains once recording works

