Developers

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 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

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

ModeUsed forProcess lifetime
One-shotLow-frequency calls: info, detect, install-hooks, check-hooks, diagnoseSpawned, responds with one JSON line, exits
ServeHigh-frequency calls during a live session: find-session, read-from-offsetSpawned 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.

CapabilityWhat it grants
session_readerfind-session, read, read-metadata — the baseline for any session adapter
hook_installerinstall-hooks, check-hooks, uninstall-hooks
incremental_readerread-from-offset — required for serve-mode recording
file_watcherPush entry events automatically after find-session, no polling
serve_modeSupport the --serve flag
rules_installerInstall and remove ox rules in the agent's config
session_importerImport 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, and RawEntry types, 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:

terminal
# Run compliance against your binary, from the ox repo
$

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

terminal
$

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

terminal
$
$

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.

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