Skip to main content

Phase 1 runbook: the proxy

Built and deployed on 2026-09-20 (v0.2.0; v0.3.x since). Every LLM request from the CLIs and the apps goes through brain serve on the VPS, and no /v1/* route answers without a client key. Server setup: VPS deployment; every command and variable: Configuration reference.

What exists​

PieceWhereTests
Client keys brain_<profile>_<random>: sha256 only, name, optional expiry and IP/CIDR allowlist, revoke, audit of failurescrates/brain/src/auth.rs, db.rsgeneration/shape, log prefix never leaks the token, revocation/expiry/IP checks, unique active name per profile
Endpoints /v1/chat/completions, /v1/models (OpenAI); /v1/messages, /v1/messages/count_tokens (Anthropic); HEAD /api/helloproxy/mod.rsend-to-end against a mock OpenRouter
Middleware chain: header → syntax → hash lookup (60 s cache) → revoked/expired/IP → per-key and per-user rate limit → budget → forward → recordproxy/mod.rs401/403 in the client's dialect, x-should-retry: false, X-Forwarded-For, IP block after repeated failures, upstream key never sent to the client
Budget ring 2: daily and monthly spend from requests; 70% → fast only, 85% → max_tokens cap, 100% → 429 with retry-after ≥ 3600 + x-should-retry: falsebudget.rsthresholds, retry-after at the window reset, effective tier
Streaming pass-through with a tap that records usage at end-of-stream or client disconnectproxy/tap.rscomplete and interrupted streams
Usage in both dialects, streamed or not; OpenRouter's cost when present, tier-price estimate otherwiseproxy/usage_parse.rsOpenAI/Anthropic bodies and SSE, OpenRouter's message_delta shape
Model resolution (brain/<tier>, claude-* → profile tier, explicit ids), sanitizer for Claude Code fields, models[] fallbacks, end-user id (Claude Code's metadata reduced to its session id)proxy/sanitize.rsexact field lists
brain/auto: one tier per session picked by a decision model (Auto routing)proxy/route.rssession key, TTL, decisions request/answer, off-ladder choices
max_tokens cap = min(provider max from the OpenRouter catalog, refreshed hourly; tier's max_output_tokens)catalog.rs, proxy/mod.rscatalog parse, cap selection
Serving backend recorded per request (OpenRouter's provider field; since v0.3.2, 2026-09-22 17:24 UTC — earlier rows are empty)proxy/usage_parse.rs, db.rsOpenAI body and Anthropic message_start
brain keys create|list|revoke; OpenRouter provisioning under brain upstream provision|sync|listmain.rs—
The board (see below)dashboard.rssummary shape

Verified live on 2026-09-20: curl in both dialects and Claude Code headless through the proxy (pong, 2 turns, 6.2 s, 20.8k input tokens, 0.00075 $ per request, sanitizer removed output_config and thinking.adaptive even with the client flags set).

Issue a key (on the server, admin only)​

ssh root@<vps> 'cd /opt/llm_brain && sudo -u brain env BRAIN_HOME=/opt/llm_brain BRAIN_DB=/opt/llm_brain/data/brain.db \
./brain keys create --profile dev --name laptop [--expires 2027-01-01] [--ip 1.2.3.4/32]'

Printed once. brain keys list shows prefixes only; brain keys revoke --profile dev --name laptop cuts it (the proxy forgets cached keys within 60 s).

Point the clients at the proxy​

BRAIN_BASE_URL and BRAIN_DEV_KEY live in the git-ignored deploy/server.local.env, sourced by ~/.bashrc. The shell functions are printed by brain setup claude-code --proxy and brain setup aider --proxy (full text in Configuration):

ClientHowModel
Claude Code — the daily agentpx-claude (ANTHROPIC_BASE_URL=$BRAIN_BASE_URL, ANTHROPIC_AUTH_TOKEN=$BRAIN_DEV_KEY)ANTHROPIC_MODEL=brain/auto, background tasks ANTHROPIC_DEFAULT_HAIKU_MODEL=brain/fast; pin a tier with ANTHROPIC_MODEL=brain/agent px-claude
aider — cheap editor for targeted changespx-aider (OPENAI_API_BASE=$BRAIN_BASE_URL/v1)architect openai/brain/reasoning, editor openai/brain/fast
apps (OpenAI SDK)OpenAI(base_url="https://brain.<host>/v1", api_key="brain_car_…")brain/fast or brain/auto + x-brain-session: <conversation id>; pass user= for per-user rate limits
benchmarkbrain bench run --via-proxy https://brain.<host> with BRAIN_BENCH_KEY (a benchmark client key)brain/<--tier> unless --model is given

brain/<tier> and brain/auto are stable aliases: the models behind them are config/tiers.yaml on the server. The proxy adds the models[] fallback itself on the OpenAI dialect.

What the VPS exposes​

RouteAuth
/v1/*client key (Bearer or x-api-key) — no exceptions
/api/hellonone: Claude Code's connectivity probe, always 204
/, /api/summary (board)Caddy basic auth
/healthopen, returns ok

Rate limits: 60 req/min per key, 10 req/min per (key, end user); 20 auth failures in 10 minutes block the IP for the window. Ring 1 (the OpenRouter key's own daily limit) still holds if all of this fails.

The board​

https://brain.<host>/ behind basic auth (locally: brain serve or the Docker board); phone-friendly, reloads every 30 s, JSON on /api/summary. Sections, top to bottom:

SectionWhat it answers
Month · Budget · Spend per dayspend vs the daily limit per profile (ring-2 state), last 30 days by profile
Requests per dayok / upstream errors / refused
Modelsquality and cost per model, last 7 days
Auto routingbrain/auto decisions per tier and the saving against the router's baseline tier
Providerswhich OpenRouter backend served each model, and its prompt-cache share
Prompt cachecold turns (≥ 5k prompt tokens, nothing read from cache) and what those re-reads cost
Healthproxy version, catalog age, auth failures last hour, refused / upstream errors today
Sessions · Proxy · Liveprofile × end user, day × profile × model, last requests
Anomalies · Auth failuresrunaway outputs, long requests, refusals, upstream errors; last 20 auth failures
Tool logs · Benchmark runsaider / Claude Code log files (Phase 0), brain bench rows

Provider pinning (provider.order to keep a session on the backend that holds its cache) is the next step and not built: today the proxy only measures which backend served each request.

Agents compared through the proxy​

2026-09-20, task ago-0001, model deepseek-v4-pro via brain/agent:

ToolResultTimeTurns / tool callsCost (proxy)
Claude Code (px-claude)PASS120 s14 turns~0.03 $
OpenCode (brain bench --tool opencode)PASS41 s3 tool calls (grep, read, edit)~0.005 $
OpenCode on fast (deepseek-v4-flash, direct)PASS58 s10 tool calls0.001 $
aider, architect bonsai + editor deepseek-v4-flash (direct)PASS74 s— (no tools)< 0.001 $

Before the system-turn fix, Claude Code on non-Claude models failed this task with an empty reply; through the proxy it now completes it. OpenCode is the open-source agent that matches Claude Code's autonomy at a fraction of the tokens (its system prompt is far smaller). Both are usable through the proxy today; brain bench run --via-proxy <url> runs the suite the way the clients actually use it (client key of the benchmark profile).

:exacto A/B (2026-09-22, Claude Code through the proxy, two runs each)​

ModelRun 1Run 2Cost of both runs (proxy, real)
deepseek/deepseek-v4-proPASS, 72 s, 9 turnsPASS, 345 s, 17 requests0.22 $
deepseek/deepseek-v4-pro:exactoPASS, 123 s, 20 turnsPASS, 154 s0.14 $

All four pass; the spread between two runs of the same model (72 s vs 345 s) is larger than any difference between the two sides, and every request landed on the same provider (StreamLake) either way. For this model Auto Exacto is already what the default route does, so the suffix changes nothing today; it stays available as a config switch (Provider routing). Per-task cost is now ≈ 0.07–0.12 $, up from 0.03 $ on 2026-09-20: the catalog price of deepseek-v4-pro doubled in between (tiers.yaml updated).

Known gaps​

  • No models[] fallback on the Anthropic dialect yet (OpenRouter uses a different parameter there): Claude Code's requests go to the tier's primary model only.
  • Provider pinning not built (see the board section above).
  • L2 response cache (l2_cache: in profiles.yaml is read but nothing caches yet) and escalation on failure: not built, Phase 2.
  • count_tokens is forwarded as-is; OpenRouter may not implement it, and Claude Code then estimates locally.