Skip to content
brain.GitHub
Developer docs OPEN SOURCE
Adapters

Harness adapters

Implement a harness runner over Brain's Go interface or stdio JSON protocol.

A harness adapter runs the model loop, manages conversation context and compaction, and writes native session state inside the session's native/ directory while Brain coordinates requests, tool dispatch, event logs, and checkpoints.

Go lifecycle (harness.Factory and harness.Runner)

Factory.Open(ctx, opts) receives a harness.Options struct containing the session ID, the native/ working directory (Dir), the immutable session Config, enabled Tools schemas, resolved Provider credentials (APIKey, BaseURL, API), and two callbacks: Event (for emitting assistant.delta and assistant.message) and Call (for executing bound tools).

The application package exports these contracts as brain.HarnessFactory, brain.Harness, and brain.HarnessOptions. Open returns a runner with five methods (see Go interfaces):

MethodPurpose
Prompt(ctx, text)Runs one user turn to completion (including any model-initiated tool calls and follow-up generations) and returns when the turn settles.
Steer(ctx, text)Steers an active turn if Factory.SupportsSteering() is enabled.
Interrupt(ctx)Aborts the active turn cleanly when the caller sends user.interrupt or closes the session.
Checkpoint(ctx)Returns the list of core.NativeFile entries (path, size, append_only) relative to Dir required to restore the conversation.
Close()Terminates the runner and releases its resources.

Core runner invariants

  • Passive open: Opening an existing session directory must load saved native state without calling the model, re-running tools, or starting background cache-warming timers.
  • Settled return: Prompt blocks until the turn finishes or is interrupted.
  • Clean checkpoints: Checkpoint is called only after Prompt settles and must return regular, append-only relative file paths and exact byte sizes in Dir, excluding any provider credentials.

Child process stdio protocol (harness.ProcessFactory)

You do not need to write Go code to add a harness runner. Every entry in the "harnesses" configuration block uses harness.ProcessFactory (internal/harness/process.go) to spawn a child process (such as node runners/pi/dist/main.js) and communicate over newline-delimited JSON frames (RunnerFrame) on stdin and stdout.

  • Frame limit: Each line is capped at 4 MiB (MaxFrame = 4 << 20) with strict JSON decoding (DisallowUnknownFields).
  • Standard streams: stdout is reserved strictly for RunnerFrame JSON lines. Brain suppresses runner diagnostic payloads from logs.
  • Startup timeout: The initial open request during Factory.Open has a 30-second timeout.

Brain → Runner requests (stdin)

Brain sends RPC requests with attachment, id, method, and params. An attachment identifies one native session within a shared host. prompt, steer, interrupt, events, and tool callbacks also carry the active turn identity; replies echo the request's attachment and turn. Open, checkpoint, and detach have no active turn.

{"attachment":"ATTACHMENT_ID","id":"REQUEST_ID","method":"open","params":{"session_id":"SESSION_ID","dir":"/path/to/native","config":{"model":"openai/gpt-4.1-mini","tools":["echo"]},"tools":[{"name":"echo","description":"Echo text","input_schema":{"type":"object"}}],"provider":{"api_key":"PROVIDER_TOKEN"}}}
{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","id":"REQUEST_ID","method":"prompt","params":{"text":"Hello"}}
{"attachment":"ATTACHMENT_ID","id":"REQUEST_ID","method":"checkpoint","params":null}

The runner replies on stdout with a matching id and either result or error (code: "provider_error" or "brain_error"):

{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","id":"REQUEST_ID","result":{}}
{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","id":"REQUEST_ID","error":{"code":"provider_error","message":"upstream rate limit exceeded"}}
Methodparamsresult
openharness.Options (session_id, dir, config, tools, provider){} (or diagnostic metadata such as {"tools": [...]})
prompt{"text": "..."} (core.Text){} (or {"interrupted": true})
steer{"text": "..."} (core.Text){}
interruptnull{}
checkpointnull[{"path": "session/file.jsonl", "size": 1024, "append_only": true}] ([]core.NativeFile)
detachnull{}; release this settled attachment without stopping peers.

Runner → Brain events and tool calls (stdout)

While prompt is running, the runner can emit streaming events and reverse tool.call requests (up to 32 concurrent tool calls per runner):

{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","event":"assistant.delta","data":{"text":"Hello"}}
{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","id":"CALL_FRAME_ID","method":"tool.call","params":{"id":"TOOL_CALL_ID","name":"echo","arguments":{"value":"hi"}}}
{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","event":"assistant.message","data":{"text":"Hello!","usage":{"input":12,"output":4},"stop_reason":"stop"}}

For each tool.call, Brain validates that the tool is bound to the session, appends a tool.call event, dispatches the call to the external tool service, appends a tool.result event, and writes the correlated response frame back to the runner's stdin:

{"attachment":"ATTACHMENT_ID","turn":"TURN_ID","id":"CALL_FRAME_ID","result":{"content":[{"type":"text","text":"hi"}],"is_error":false}}

Wire types for this protocol are generated in internal/harness and runners/pi/src/generated.ts via go run ./cmd/generate.

Isolation and verification

A harness adapter must disable all built-in local tools (such as local shell execution or file editors), ambient plugins, and background cache warmers so the model can only invoke tools explicitly bound in config.tools. harness.ProcessFactory passes only minimal OS environment variables (PATH, SystemRoot, WINDIR, COMSPEC, PATHEXT, TMP, TEMP, TMPDIR, LANG, LC_ALL, and NO_COLOR=1) and supplies provider credentials exclusively through the open frame.

When testing a new harness adapter, verify:

  1. Passive open: Reopening a saved directory makes zero model or tool calls until the next Prompt.
  2. Checkpoint fidelity: Restoring a checkpoint into a fresh directory produces the exact same prompt context on the next turn.
  3. Failure separation: Tool errors (is_error: true) return to the model as tool results, whereas broken stdio framing, unexpected exits, or disk failures abort the turn with brain_error.
  4. Clean interruption: Interrupting a turn while a tool call is waiting stops execution cleanly and leaves prior checkpoint files intact.

On this page