Skip to content

URL: https://mkdocs.justinsforge.com/memory/general/reference_brain_server/

forge-brain-server

Persistent daemon that holds warm Claude Agent SDK clients (one CLI process per model) and serves bot brain calls over localhost HTTP, replacing per-call cold claude -p. Built 2026-06-09; plan with probe numbers at brain-latency-rewire-2026-06-09.

Pieces

Piece Path
Daemon scripts/forge_brain_server.py
Venv ~/.forge-venvs/brain-server/ (claude-agent-sdk 0.2.95, aiohttp)
Unit forge-brain-server.service (system, repo copy infra/systemd/)
Stage-1 drop-in infra/systemd/forge-inbox-capture.service.d/brain-engine.conf
Callers forge_telegram_inbox_brain.py (brain_engine, call_brain_server, _call_sonnet), forge_telegram_brain.py (_call_sonnet_once)

HTTP surface (127.0.0.1:8771)

  • GET /health -> {ok, uptime_s, models: {model: stats}}
  • POST /brain {prompt, model, persona, timeout_s} -> {ok, raw, latency_ms, num_turns, duration_api_ms, usage}

Why it is fast (and safe)

  1. Skips the ~2s Node cold boot per call (warm CLI process, ClaudeSDKClient.query(session_id=uuid) keeps calls stateless).
  2. max_turns=1: the old claude -p path ran with default CLI tool access, and 53 of 247 audited calls burned a median 37s doing agentic CLI-tool work before emitting JSON. Capped turns remove that tail. Probe: warm calls flat 3.5-4.6s vs subprocess p90 59.7s.
  3. Bills the subscription: no API key exists on Console; the SDK spawns ~/.local/bin/claude and rides its OAuth. cli_path is pinned because the SDK ships a bundled CLI that SIGILLs on Console (no AVX2, see console binary constraint topic).

Kill switch (FORGE_BRAIN_ENGINE)

Read at call time by the brain scripts; values: subprocess (default), server:capture (comma persona list), server (all). Any server error falls back to the subprocess path automatically, so sudo systemctl stop forge-brain-server degrades latency, never availability.

Full flip after soak:

sudo sed -i 's/server:capture/server/' /etc/systemd/system/forge-inbox-capture.service.d/brain-engine.conf
sudo mkdir -p /etc/systemd/system/forge-lifeos-coordinator.service.d
sudo cp /etc/systemd/system/forge-inbox-capture.service.d/brain-engine.conf /etc/systemd/system/forge-lifeos-coordinator.service.d/
sudo systemctl daemon-reload && sudo systemctl restart forge-inbox-capture forge-lifeos-coordinator

Revert: delete the drop-ins, daemon-reload, restart pollers.

Quota rows

Server-engine calls record real token counts: engine: server, num_turns, tokens: {input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens} flattened into the JSONL row alongside the legacy char fields (aggregator unchanged).

Ops notes

  • One asyncio.Lock per model client: requests queue rather than interleave (receive_response drains to the next ResultMessage; concurrent in-flight queries on one transport would cross-read).
  • Client recycles after 200 queries or any error/timeout (reconnect ~1s).
  • Selftest: ~/.forge-venvs/brain-server/bin/python scripts/forge_brain_server.py --selftest
  • Health: python3 -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8771/health').read().decode())"

[Claude Code, brain-latency session 2026-06-09]