Harness protocol
Startup selection, lifecycle, native state, and journaled callbacks.
Brain reads a name-to-registration map from --agentloops or BRAIN_AGENTLOOPS
before readiness. Each registration has command, optional args, optional
configuration, workers (default 1), and max_concurrency per worker (default 64).
Counts must be positive. Names contain 1–64 ASCII letters, digits, underscores or hyphens.
Sessions require agentloop: { name, configuration?, environments? }. The name selects
a startup registration. Commands and implementation code are rejected in session requests.
The selection, options and Environment grants are immutable after creation.
Brain balances newly opened sessions across healthy workers in that registration. Open sessions retain their worker assignment. An activation waits for worker capacity; cancellation also works while queued. Failure does not reassign an open session or retry effects. Other workers remain available. Readiness succeeds while any worker can serve requests; creating a session for an unavailable registration fails.
Transport and lifecycle
The process exchanges one JSON-RPC 2.0 object per UTF-8 line over stdin/stdout.
Both directions use request ids; diagnostics belong on stderr. The protocol is
brain/harness/v1. Rust types in brain-protocol/src/harness.rs define its payloads.
| Brain request | Parameters | Result |
|---|---|---|
initialize | protocol, state_directory, configuration | protocol, name, state_version |
session/open | session_id | null |
session/run | session_id, sequence, model, input | { result? } |
session/cancel | session_id | null |
session/close | session_id | null |
session/delete | session_id | null |
shutdown | {} | null |
sequence names the activation's journal record. model supplies advisory model
metadata, not credentials. input contains optional user input, transcript, KV,
pending events, immutable tool declarations and current placements, system prompt,
session configuration, and the runtime envelope.
There is one active run per session. Different sessions can run concurrently. Cancellation must be handled while run is pending. Initialization has a 30-second deadline; cancellation has a five-second grace period before Brain kills the harness process. Killing a worker also fails its other active sessions; other workers are unaffected.
Callbacks
A harness requests brain/call with:
{
"session_id": "ses_12345678901234567890",
"sequence": 3,
"method": "model",
"input": {
"messages": [{"role":"user","content":[{"type":"text","text":"Hello"}]}],
"tools": []
}
}| Method | Input / result |
|---|---|
model | ModelRequest / ModelResult |
dispatch | ToolInvocation[] / ToolReturn[] |
set_transcript | Message[] / committed sequence |
kv_read | key string / {} or { value } |
kv_put | { key, value } / committed sequence |
kv_delete | key string / committed sequence |
events | sequence / finite EventPage |
emit | { event_type, data } / committed sequence |
environments | EnvironmentControlRequest / operation result |
telemetry | JSON record / null |
Callbacks work only during the named activation. Await every callback before returning.
An error contains data.code and data.fatal; fatal errors stop the activation even
when upstream catches them. The TypeScript binding aborts ctx.signal and calls
cancel. Brain drains already admitted callbacks before closing their authority.
Model and Tool requests cross durable journal barriers. Brain sends each effect once and records an unknown outcome if a remote result is uncertain. Missing usage counters remain absent. Upstream token estimates used for compaction are private runtime state.
Native state and recovery
The harness identity and state version are bound to the data directory. A mismatched
identity refuses startup. Native state lives beneath agentloops/<registration>/<session_id>/; closing
or passivating a session retains it, while session deletion removes it.
Brain records brain.harness.activation before entering native execution and removes
it only after successful completion. A remaining marker refuses subsequent execution,
including after restart. Create a new session after an interrupted or failed native
activation. The journal and transcript remain readable. This prevents an upstream
at-least-once recovery routine from replaying an uncertain tool effect.
The journal is authoritative for Brain's effects. Upstream files are authoritative for agent continuation. There is no cross-harness checkpoint conversion or transactional crash recovery spanning the two stores. Pre-0.41 active sessions are not compatible; start with a new data directory.
Upstream adapters
The ghcr.io/aexhq/brain-agentloops image composes the public Brain image with
extension-owned runtimes. For a custom deployment, install the corresponding package:
npm install @aexhq/brain@0.41.0 @aexhq/agentloop-pi@11.0.0
npm install @aexhq/agentloop-codex@11.0.0 @aexhq/agentloop-opencode@1.0.0They provide brain-agentloop-pi, brain-agentloop-codex and brain-agentloop-opencode.
Pi uses Node 22.19+, Codex launches its pinned Rust executable, and OpenCode uses
Bun 1.3.14. Client exports construct selections; /runtime exports the implementation.
The packages own upstream compatibility tests, configuration types and dependency pins.
| Adapter | Configuration |
|---|---|
| Pi | contextWindow?, maxTokens?, compaction?: { enabled?, reserveTokens?, keepRecentTokens? } |
| Codex | contextWindow?, compactAtTokens? |
| OpenCode | contextWindow?, maxTokens?, compaction?: { auto?, buffer?, keep?: { tokens? } } |
Session values override deployment values. Missing context-window or output-limit metadata must be supplied explicitly where required. Unknown keys fail.
Pi uses the complete coding-agent session SDK. OpenCode uses its embedded SDK with a custom provider and tool catalogue. Codex uses its Rust app-server, dynamic tools, and a local Responses gateway into Brain's model adapter; its dynamic-tool API is experimental and version-pinned. All three delegate compaction to upstream, retain opaque model continuation, exclude unadmitted tools, and disable transport retries.
Pi accepts HTTPS media and imported transcripts. Codex and OpenCode currently accept text only and start from native state. All three keep a fixed tool-placement catalogue per native session. A changed catalogue fails explicitly; incarnation references are resolved from the current activation. Custom harnesses may support additional features.
Environment control
Grant a harness access with agentloop.environments, and declare the corresponding
Environments in the session. Call ctx.environments in a custom implementation.
Upstream adapters do not invent management tools or interpret these grants as model
tools. See Environment control.