Aex Brain
Customize and operate

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

For 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/ready

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

DirectionAddress
Brain → EnvironmentThe URL in the application's Environment configuration, reachable from Brain.
Environment → BrainBRAIN_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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.tgz

Set 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/brain

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

SymptomCheck
Container exits before listeningSupply BRAIN_API_TOKEN and BRAIN_AGENTLOOPS; check data-directory permissions, format and whether another writer holds it.
401 from an application routeSend the configured server bearer token. Host and Environment callbacks use their own scoped tokens instead.
400 mentioning idempotency-keySend 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 cursorRead 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 connectCheck the Environment URL from Brain and BRAIN_PUBLIC_URL from the Environment, including DNS, TLS and proxy routing.
Request completed but work failedRead 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.

On this page