brainkit · Deployment Plate №5
The deployment runbook
Everything to stand this up, in order. Phases 1–3 happen once and take
an afternoon. Phase 4 is the ~10 minutes you spend per new employee. Phase 5 is the
steady state — a few minutes a week.
1Provision — once
- Two VPSes, same provider & region. Brain box: 2 GB is plenty.
Agents box: ≈300–400 MB per employee plus 2 GB headroom (15 people ≈ 8 GB).
- Ubuntu LTS, key-only SSH, unattended-upgrades on both.
- Join both to one tailnet; no other inbound ports anywhere — with one opt-in exception: enabling webhook intake (
brain webhook) later adds a single TLS-fronted public port on the brain box.
# on both boxes
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up # names: brain-box, agents-box
2Brain box — once
uv tool install git+https://github.com/joedanz/brainkit # brainkit is not on PyPI — install from source
brain init /srv/brain/master --company "Acme Co"
$EDITOR /srv/brain/master/_meta/org.yaml # people, roles, teams
$EDITOR /srv/brain/master/_meta/spaces.yaml # visibility rules
brain compile --master /srv/brain/master --out /srv/brain/compiled
brain doctor --master /srv/brain/master --out /srv/brain/compiled
# one sync user; per-person keys locked to one repo each
adduser --disabled-password brain-sync
# crontab — the whole orchestration layer
*/5 * * * * brain cycle --master /srv/brain/master \
--out /srv/brain/compiled --index --json \
>> /var/log/brain/cycle.jsonl \
&& curl -fsS $CYCLE_HC || curl -fsS $CYCLE_HC/fail
17 3 * * * brain doctor --master /srv/brain/master \
--out /srv/brain/compiled \
&& curl -fsS $DOCTOR_HC || curl -fsS $DOCTOR_HC/fail
23 4 * * * git -C /srv/brain/master push --mirror offsite
$CYCLE_HC/$DOCTOR_HC are healthchecks.io-style
check URLs, pinged on success and on /fail — a dead-man's
switch, so a dead box or a stopped cron alerts too, not just a failing
job. A nonzero
cycle means a held or failed write-back; a nonzero doctor means an
integrity error. The nightly mirror push to one private offsite remote backs up the
master. Optional: set BRAIN_EMBED_BASE_URL (an embedding API reachable
from both boxes) to upgrade keyword search to semantic — cycle --index
and each container's brain index pick it up from the environment.
3Agents box — once
- Install Docker + the compose plugin.
- Build the
hermes-brain image with
deploy/agents-box/build-image.sh — derived from the official
nousresearch/hermes-agent image, with brainkit installed
from the public repo (agents need brain index / search / mcp
locally). The script resolves the ref to a commit first, so the image is
built from an exact revision — which is what lets you ask a container
later what it is running.
- Set an LLM provider key once, shared via compose env.
# deploy/agents-box/docker-compose.yml — one stanza per person
services:
agent-alice:
image: hermes-brain:latest
restart: unless-stopped
command: gateway run
environment:
- BRAIN_PERSON=alice
- BRAIN_GIT_REMOTE=brain-sync@brain-box:/srv/brain/compiled/alice
- TELEGRAM_BOT_TOKEN=${ALICE_BOT_TOKEN}
- ANTHROPIC_API_KEY=${LLM_KEY}
- BRAIN_EMBED_BASE_URL=${EMBED_URL} # optional: semantic search
volumes:
- alice-state:/opt/data # sessions, pairing, deploy key
- alice-vault:/vault # her compiled vault clone
Everything the image bakes in lives in one directory —
deploy/agents-box/ holds the Dockerfile, the compose file, the s6
scripts, and the company-brain-profile/ staged into each container;
brainkit itself is installed from the public repo at build time, not copied
from the working tree.
Each container's first boot: install the company-brain hermes profile
(SOUL.md, terminal.cwd: /vault, the brain MCP server + skills —
with the assistant's built-in memory disabled, so the vault is its only
durable memory) and
mint the container's own deploy key — the public half lands in
docker logs, the private half never leaves the container. A supervised
sync loop then clones $BRAIN_GIT_REMOTE, runs brain index,
and keeps syncing: pull → index → push every five minutes.
Rolling a new image re-syncs that profile — the container records
which brainkit build it applied, so a rebuild propagates instead of landing
silently. Narrow on purpose: skills refresh; SOUL.md updates only if
untouched since the image wrote it, so an edit someone meant survives;
config.yaml never, because the assistant rewrites it at runtime and
regenerating it would discard live settings — including the model you chose.
4Onboard an employee — ~10 min each
deploy/agents-box/add-agent.sh <name> prints the compose
stanza and this exact checklist:
- Add them to
org.yaml; the next cycle compiles their vault. Set
receive.denyCurrentBranch updateInstead on the compiled repo so it
accepts the agent's pushes.
- Telegram:
@BotFather → /newbot → copy the token
(WhatsApp: link a number via the hermes WhatsApp adapter).
- Agents box: paste the compose stanza, set the token,
docker compose up -d agent-<name>.
docker logs agent-<name> shows the container's deploy key —
append one authorized_keys line on the brain box (the
brain-serve-repo forced command locks the key to that one repo).
The clone lands within 30 seconds.
- They message their bot; you approve the one-time pairing code.
- Send them one line: "This is your assistant — tell it things,
ask it things." That's their entire onboarding.
5Operate — minutes per week
- Approvals:
ssh brain-box brain promotions list /
approve <id> --approver joe; review in the dashboard over
ssh -L on the tailnet.
- Watch two numbers: cycle exit ≠ 0 (a held or failed write-back) and
doctor exit ≠ 0 (integrity) — both already ping your alert URL.
- Updates:
uv tool upgrade brainkit on the brain box;
build-image.sh then docker compose up -d to roll agents —
which also re-syncs each container's profile, since it records the build it
applied. The two halves upgrade separately, so they drift —
brain --version answers what each is actually on, down to the commit
(brain 0.2.0 (rev 69631f68e8c9)).
- Backups, both halves, offsite by default: the brain box ships
the master vault plus its wiring (proxy/tunnel config, systemd units, the sync
authorized_keys) to object storage hourly with
deploy/brain-box/backup-master.sh — restic, encrypted client-side.
On the agents box, backup-agents.sh zips each container's state
nightly (hermes backup inside the container, consistent while the
gateway runs — a raw volume tar is not) and
backup-agents-offsite.sh ships the zips plus the compose
.env and live docker-compose.yml off-box. One bucket
per box — object-storage tokens scope to buckets, so a compromised box can't
delete the other's history. Vault volumes re-clone themselves — skip them.
- Offboarding: remove from
org.yaml, delete their
authorized_keys line, docker compose rm their container.
6Visual layer — optional, phased
- Bake the extras into the image:
.[web,pty] for
hermes serve; brainkit is already there for the brain dashboard.
- Pilot (tailnet-only): expose each container's two ports on the
agents box's tailscale IP; work devices join the tailnet.
- Wider rollout: one SSO reverse proxy (Caddy/Traefik + OIDC or
Nous OAuth via
hermes dashboard register) with per-person routes.
# inside each container (supervised alongside the gateway)
hermes serve --host 0.0.0.0 --port 9119 # desktop + web backend
brain dashboard --vault /vault --host 0.0.0.0 --port 8787
# proxy routes — identity must map to container
alice.agents.example.com → agent-alice:9119
alice-brain.agents.example.com → agent-alice:8787
Per-person credentials in each container's .env
(HERMES_DASHBOARD_BASIC_AUTH_* on the tailnet; OAuth beyond it). The
brain dashboard has no auth of its own — off-loopback it must sit
behind the authenticated proxy, never exposed directly.
✓Failure drills — reassuringly boring
- Brain box down: agents keep chatting and capturing on local
clones; commits queue and drain on recovery. Users notice nothing.
- Agents box down: chat goes quiet, zero data lost;
docker compose up -d restores everything from volumes.
- Agent volumes lost: fresh container,
hermes import
<zip> --force with the person's latest backup, then docker
restart (the restart re-fixes key file modes) — memories, pairing, and
deploy key all return; the vault re-clones itself.
- Agents box compromised: rotate deploy keys, rebuild containers
from the image — the master's history is intact and trustworthy.
- Brain box lost:
restic restore the offsite
snapshot — vault, config, and the sync authorized_keys all return,
so agents reconnect without re-authorizing keys — reinstall brainkit, recompile.
The encrypted offsite snapshot is the whole plan.