Operate Brain
Deploy a server, connect remote environments, and preserve data during upgrades.
Brain runs as a single server with a local data directory. Put that directory on persistent storage and point only one server at it. The commands below need Docker and a POSIX shell. For every flag and environment variable, see Configuration.
Choose a release
Use an SDK and image that the release workflow tested together. The extension image,
ghcr.io/aexhq/brain-agentloops:sha-<extensions-commit>, includes Pi, Codex, and OpenCode.
Its build pins an immutable Brain image. A digest pins the exact image contents.
latest is handy for the quickstart but moves over time. brain --version currently reports the
Rust package version, not the image's source revision.
The extension release lists its packages and composed image. The Brain release records the neutral SDK and base image:
manifest.json: source commit and exact npm package versions and integrity hashes.brain-image.json: the same source commit, image name and manifest digest.
Download the current mapping with GitHub CLI:
gh release download --repo aexhq/brain \
--pattern manifest.json --pattern brain-image.json --dir brain-releaseFor a specific release, add its release/sha-<commit> tag after download. Check that
manifest.json's source matches brain-image.json's source_sha, then install the listed SDK
version. For an upstream agent, use the extension release's agentloops-image.json and run
its <image>@<manifest_digest>. Keep these files with your deployment configuration.
Older releases may only have the staging run's brain-npm-release-<commit> artifact in the
release workflow, and those
artifacts expire after 30 days. If an older mapping is gone, obtain it before upgrading rather than
assuming a moving image matches an older npm version.
Start the server
Set BRAIN_IMAGE to the immutable image you chose and supply a private API token:
export BRAIN_IMAGE='ghcr.io/aexhq/brain-agentloops@sha256:REPLACE_WITH_RELEASE_DIGEST'
export BRAIN_API_TOKEN='REPLACE_WITH_A_PRIVATE_TOKEN'
docker run -d --name brain --restart unless-stopped \
-p 127.0.0.1:8080:8080 -e BRAIN_API_TOKEN \
-e BRAIN_AGENTLOOPS='{"pi":{"command":"brain-agentloop-pi"}}' \
-v brain-data:/var/lib/brain "$BRAIN_IMAGE"
curl --fail --silent --show-error http://127.0.0.1:8080/health/readyReadiness returns 204 with no body. It checks for an available agentloop worker and the built-in Tool worker pool, not model credentials,
remote provider health or disk space. /health/live is a public process check. Run
docker logs brain to see startup and execution errors.
The extension image includes Node, Bun, and the upstream runtimes. The base Brain image includes Node and the neutral protocol SDK. Install custom harness dependencies in a separate build stage and copy them into the runtime image; npm is a build-time dependency.
Keep the loopback publication for local access or a reverse proxy on the same host. For remote
clients, terminate HTTPS at your proxy and forward the Authorization header. Application
requests send Authorization: Bearer <BRAIN_API_TOKEN>. Give the token only to trusted callers:
it grants access to the server, not an individual tenant or session.
Choose your own server address when copying API reference examples. For the local server above:
export BRAIN_URL='http://127.0.0.1:8080'
curl --fail --silent --show-error \
-H "Authorization: Bearer $BRAIN_API_TOKEN" "$BRAIN_URL/v1/sessions"To read a session's recorded events, set SESSION_ID to its returned id:
curl --fail --silent --show-error \
-H "Authorization: Bearer $BRAIN_API_TOKEN" \
"$BRAIN_URL/v1/sessions/$SESSION_ID/events?after=0"The repository's compose.yaml builds from local source. Before docker compose up --build -d,
set BRAIN_API_TOKEN and BRAIN_AGENTLOOPS, and set BRAIN_DATA_DIR to an absolute host directory. On Linux, make that
directory writable by uid/gid 10001, the image's user. To run a published image instead, set
BRAIN_SERVER_IMAGE to its immutable reference and use docker compose up --no-build -d.
Connect remote environments
Two addresses need configuring:
| Direction | Address |
|---|---|
| Brain → Environment | The URL in the application's Environment configuration, reachable from Brain. |
| Environment → Brain | BRAIN_PUBLIC_URL, reachable from the Environment, for granted service calls and observations. |
For example, an Environment at https://tools.example.com can call a Brain server at
https://brain.example.com when Brain starts with BRAIN_PUBLIC_URL=https://brain.example.com.
This applies to remote tools, controllers and independent Environment reporters. The server
hands out scoped callback tokens, so the Environment never needs the server API token. Pass the
setting with -e BRAIN_PUBLIC_URL in docker run, or export it before starting Compose.
BRAIN_PUBLIC_URL defaults to the listen address. A container's 0.0.0.0:8080 binds its network
interfaces but isn't an address another machine can use, and 127.0.0.1 always means the caller's
own container or machine. Set a reachable URL, and when a callback fails, check both directions
from the actual containers. See the Environment integration API
for callback authentication and request shapes.
Back up and upgrade
Pre-1.0 releases don't promise compatibility with arbitrary retained data, so read the release's contract changes first. The bundled checker covers media/model history and the explicit Tool-completion transition; passing it isn't a general migration certificate.
- Stop accepting new work in your application. Wait for accepted turns and background tools to finish, or interrupt them explicitly and inspect their recorded outcomes.
- Stop Brain with
docker stop brain. Never copy data while a writer is running. A new server process doesn't resume a stopped or crashed turn. - Back up the whole data directory, the release mapping you chose, server configuration and any external provider files. The data includes sealed credentials, so protect the backup. Remote Environment files and resources need their own provider backup procedure.
- When the transition calls for it, check a copy with the candidate runtime. If the check fails, keep the prior runtime and data together and plan an explicit migration. Don't delete sessions to make a check pass.
- Start the candidate only once its stated compatibility requirements are met. Verify readiness, read an existing session and run a representative application operation.
For the named volume above, archive the data after stopping the server:
mkdir -p backups
docker run --rm --user 0 --entrypoint tar \
--mount type=volume,src=brain-data,dst=/data,readonly \
--mount type=bind,src="$PWD/backups",dst=/backup \
"$BRAIN_IMAGE" -C /data -czf /backup/brain-data.tgz .Restore into a new volume so the original backup stays intact:
docker volume create brain-restored
docker run --rm --user 0 --entrypoint tar \
--mount type=volume,src=brain-restored,dst=/data \
--mount type=bind,src="$PWD/backups",dst=/backup,readonly \
"$BRAIN_IMAGE" -C /data -xzf /backup/brain-data.tgzSet CANDIDATE_IMAGE to the candidate's immutable image. For current media/model checks:
docker run --rm --entrypoint /usr/local/bin/brain-check-media-upgrade \
--mount type=volume,src=brain-restored,dst=/var/lib/brain,readonly \
"$CANDIDATE_IMAGE" --data-dir /var/lib/brainAdd --from-chat when upgrading from the former Chat/inline-media contract, and
--from-tool-return when moving from the former Tool return contract to explicit completion; open
retained sessions that use that old execution contract are rejected. Add
--from-agentloop-acknowledge when upgrading to automatic Agentloop event completion; open sessions
bound to the previous immutable loop contract must keep their matching runtime. Version 0.41
requires a new data directory for registered agentloops (brain-data/3); the checker does not migrate old
Agentloop sessions. Back up native harness files together with the journal. Interrupted native
activations cannot be resumed; start a new session. If you configured custom providers, mount their
file read-only and pass --providers-file with its container path. The checker locks the stopped
store and inspects it without rewriting any records.
To restore service, start the matching prior image with brain-restored:/var/lib/brain and the
saved configuration. Never run an older image against data a candidate has already modified;
restore the pre-upgrade backup instead. Host tools also need their original application handlers
reattached, because retained history alone doesn't restore those processes.
Troubleshoot a request
| Symptom | Check |
|---|---|
| Container exits before listening | Supply BRAIN_API_TOKEN and BRAIN_AGENTLOOPS; check data-directory permissions, format and whether another writer holds it. |
| 401 from an application route | Send the configured server bearer token. Host and Environment callbacks use their own scoped tokens instead. |
400 mentioning idempotency-key | Send a key of 1–256 bytes. Environment control requires it for list/get requests too. Reuse it only for the same request and mode. |
| Invalid request body or cursor | Read the JSON error's message. Malformed JSON, path and query values return 400; invalid JSON fields return 422; incorrect content type returns 415; an oversized body returns 413. |
| Callback cannot connect | Check the Environment URL from Brain and BRAIN_PUBLIC_URL from the Environment, including DNS, TLS and proxy routing. |
| Request completed but work failed | Read the turn's terminal outcome and recorded error. An idle session or successful HTTP response alone does not establish task success. |
See Session history for reconnecting, uncertain outcomes and where recovery stops.