Skip to main content

Iteration log

#DateWhat changed
492026-09-23LiteLLM page merged into Tool landscape and removed. Deploy templates aligned with the live server: the Caddyfile opens /v1/*, /api/hello and /health to the proxy's own key auth and keeps basic auth on the board; 00-hardening.conf in cloud-init; brain upstream provision in .env.example.
482026-09-23Docs cleanup against the code (v0.3.4). Every page re-checked against crates/brain/src and config/: pre-decision deliberation cut to the decision and its reason (hosting 2400 → 775 words, Phase 0 runbook 1600 → 800, LiteLLM to a note), repo sketches and module names that never existed replaced by the real layout, designs not built yet (L2 cache, server-side system prompts, escalation, nightly promotion, provider pinning) marked as such, budgets in USD from profiles.yaml, Contabo as the running host, Claude Code via px-claude / brain/auto as the daily agent. Diagrams 01, 02, 04–11 redrawn to what runs (no FastAPI, no translator). Decisions regrouped by topic with superseded rows folded in; roadmap with status per phase; overview rewritten around "where we are"; changelog ordered newest first.
472026-09-22The board answers "is the cache working?" by itself. New Prompt cache section and KPI card: per UTC day, requests, cold turns (a prompt of ≥5000 tokens that read nothing from cache — a prefix paid again, not a new conversation), share of prompt tokens served from cache, and an estimate of what the cold re-reads cost on top: nine tenths of what those turns were actually billed, since a cache read costs about a tenth of full price (cache_rows, unit-tested). Deliberately not priced from tiers.yaml: catalog prices moved ~2x on 2026-09-22, and applying them to older tokens made 2026-09-20 show more "lost" than it had spent. First 45 minutes after v0.3.2 went live: deepseek-v4-pro sat on one backend (StreamLake, 9 requests, 100 % cache) while deepseek-v4-flash was spread over four (Baidu 25, StreamLake 14, OpenInference 7, DigitalOcean 1) with 69.6 % cache and 1.13M prompt tokens at full price. Four backends for one model is cross-provider fragmentation confirmed; the low cache share on flash is partly a second effect, the dev budget ring forcing every request to the fast tier from 17:54Z, which moved a live 110k-token prefix onto a cold model. The case for pinning provider.order now rests on the backend count, not on a hypothesis.
462026-09-22Who served the request is now recorded. brain/auto measured on the first 7 real sessions: 4 on agent, 2 on fast, 1 on medium, ~$0 saved against the agent baseline — every session with actual work in it was sent to agent. Looking for the reason found a bigger one: in a live 203-request session ($2.44), 10 of the last 28 turns read nothing from the prompt cache — $0.102 each against $0.011 for a warm turn, ~$0.91 in ten minutes — and hits and misses alternate within seconds on a prefix that only grows, which is the signature of the turn landing on a different backend. OpenRouter returns the serving backend (provider, top level of a body or an OpenAI chunk, inside message in an Anthropic message_start; two identical probes came back StreamLake then SiliconFlow), so the proxy now stores it on every request row and the board has a Providers table: requests, cache-hit share, prompt tokens paid at full price, cost and latency per backend. Measurement first: provider pinning (provider.order + allow_fallbacks) is the next step, and the 16 endpoints of deepseek-v4-pro range from $0.919 to $1.91 per M input and from $0.0766 to $0.33 per M cache read.
452026-09-22brain/auto for the apps: a profile can carry its own router (context sentence, ladder, fallback, baseline) in profiles.yaml, replacing the global coding-agent one; x-brain-session header names an app's conversation. assistant gets a chat/RAG ladder (fast greetings and lookups, medium summaries and drafting, agent long analysis), fallback fast, checked on four real messages with Jev. v0.3.1.
442026-09-22Auto routing (brain/auto): a decision model — Jev, typesafe/jev-1.13 through OpenRouter's /alpha/decisions, paid by the profile's key — reads the first user message of a session and picks a rung of a ladder in tiers.yaml; the choice sticks to the session (Claude Code's session header, else a hash of the first message; TTL 2 h) so the loop never changes model. Fallback agent on error / low confidence / no text. New tiers medium (glm-5.3-flash) and max (glm-5.3). px-claude → brain/auto; /v1/models lists it. Board: Auto routing section, sessions per rung and the cost the same tokens would have had on the baseline tier. Verified live: typo → fast, refactor → agent, second turn → same tier without a new decision. Diagram 12. Page.
432026-09-22Provider routing and per-task routing answered. OpenRouter :exacto explained (provider sort by tool-call accuracy; Auto Exacto on by default for tool requests): A/B on ago-0001 shows no measurable difference for deepseek-v4-pro. Catalog lookup strips routing suffixes so model:exacto works in tiers.yaml; cost estimate follows. "Why not a cheap model for a grep": per-role split (done in the clients), per-request classifier (rejected: cache loss and broken tool calls mid-loop), escalation on failure (Phase 2) — API layer. Tier prices re-synced with the catalog (v4-pro 0.96/1.91, v4-flash 0.09/0.18, ~2× since 09-20). Budgets: assistant and car to 0.50 $/day, 15 $/month soft; dev key finally synced to 5 $/day (brain upstream sync). Assistant client key issued.
422026-09-20Dev budget raised after the first measured day (≈ 2.2 $ for 286 Claude Code requests; the 70% ring forced 48 requests to fast from 18:38 UTC — by design, but the cap was too low for a working day): dev 5 $/day, 60 $/month soft. New brain upstream sync: PATCHes the daily limit of the existing OpenRouter keys to match profiles.yaml without rotating secrets (wiremock test: only keys whose limit differs are touched). Budget page documents the measured cost per day.
412026-09-20OpenCode in the benchmark (--tool opencode, provider config written into the worktree) and brain bench run --via-proxy with a benchmark client key. Through the proxy on deepseek-v4-pro both agents pass ago-0001: Claude Code 120 s / 14 turns (failed before the system-turn fix), OpenCode 41 s / 3 tool calls; proxy-side empty replies down to 1 of 20.
402026-09-20Board v2 (v0.2.5): month KPIs with projection vs soft caps and savings vs Claude Sonnet (catalog prices), spend-per-day and requests-per-day charts (inline SVG, validated palette), per-model quality table with empty-reply rate, health strip. Empty replies root-caused and fixed (v0.2.6): Claude Code's mid-conversation role: "system" reminders made non-Claude models end the turn with zero content (29% of deepseek-v4-pro requests, 8 of 12 in one session); the sanitizer now turns them into user turns and merges consecutive user turns. First post-fix request answered normally.
392026-09-20Claude Code via the proxy as the daily agent: new agent tier (deepseek-v4-pro, fallback qwen3.7-plus) for Claude Code's main model, brain setup claude-code --proxy → px-claude; verified on find-a-car with a few-words agentic task (start the app with docker, check health and skills: 8 turns, 44 s, correct report). aider repositioned as the cheap editor for targeted changes, not an agent. Board: notes (max_tokens cap, sanitized fields) no longer counted as degradations. v0.2.4 deployed.
382026-09-20find-a-car integrated through the proxy by aider itself (architect bonsai + editor deepseek-v4-flash, one precise prompt, 0.002 $): claude_env() sets the ANTHROPIC_* variables for the skills' claude subprocess from BRAIN_BASE_URL/BRAIN_CAR_KEY in the app's .env; verified live — a skill run from the Docker app produced 4 proxied car requests. Root cause of the earlier junk (stray directory, debug endpoint): the editor ran in whole format because the settings file was empty on the proxy path and aider matches settings by name, so the brain/* aliases now have entries (architect entry decides the editor format); px-aider also loads the repo's CLAUDE.md.
372026-09-20OpenRouter catalog as the source of facts: max_tokens capped per request to min(provider max_completion_tokens, tier policy); catalog prices for cost estimates of custom models; brain models. aider like Claude Code: brain setup aider --proxy (no confirmations, architect auto-accept, auto-commits, diffs, per-repo history restore, conventions file with a plain-text rule that fixes bonsai's fake tool-call markup); verified end-to-end (edit applied + committed). Board ingests per-repo aider histories.
362026-09-20Board v0.2.2: refusals (rate limit / budget) are recorded and shown; live proxy spend next to the OpenRouter snapshot; proxy-side anomalies (runaway outputs ≥ 20k tokens, requests > 10 min, refusals, upstream errors); auth-failures table; auto/light/dark theme. Investigation: no daily limit was hit; a deepseek-v4-flash request produced 89k output tokens in 35 min (runaway), now flagged.
352026-09-20Board v0.2.1: responsive (phone layout verified by screenshot), budget cards, sessions (profile × end user), live feed of the last requests, sticky section nav, light/dark. Deployed on the VPS and locally. First app key issued: profile car for find-a-car (in the git-ignored local file).
342026-09-20v0.2.0 deployed on the VPS: proxy live at https://brain.<host>/v1/* with key auth, board behind basic auth, first dev client key issued on the server, px-aider/px-claude shell functions for the proxy path.
332026-09-20Phase 1 built: key-authenticated proxy for both dialects with rate limits, budget degradation, streaming tap, sanitizer, per-request usage; brain keys create/list/revoke; board shows proxied traffic; verified live with Claude Code headless. v0.2.0 tagged. Runbook.
322026-09-20Board on the VPS behind Caddy basic auth (401 without credentials, /health open); brain bench --docker no longer puts the key on the docker command line (-e NAME pass-through, test asserts the key is absent); secrets-handling table for the CLI.
312026-09-20Deployed to the VPS (Contabo, x86_64): SSH keys only, ufw, fail2ban, unattended upgrades, brain user, Caddy with Let's Encrypt on the sslip.io hostname, brain serve under systemd, release binary verified by checksum. Board live over HTTPS; keys copied by the user out of band. Release workflow checksum fixed (bare file name). Litestream: deb asset name changed upstream, install deferred.
302026-09-20VPS deployment prepared: release workflow (cross-built aarch64 + x86_64 binaries with checksums on every v* tag), deploy/cloud-init.yaml (brain user, Caddy, Litestream, ufw, fail2ban, SSH hardening), systemd unit for brain serve, Caddyfile with sslip.io hostname, Litestream config; deploy page. Waiting for the server (Hetzner CAX11) from the user.
292026-09-20Fast-tier price landscape from the OpenRouter API: Gemini Flash (3.8) is ~20×/50× deepseek-v4-flash, 2.5 Flash-Lite 3–5×; qwen3.7-flash marginally cheaper than deepseek. Table in Costs.
282026-09-20Both configurations PASS with diff: deepseek-v4-flash alone 39 s (was 260 s in whole), architect bonsai + editor deepseek 74 s. bonsai confirmed as the reasoning tier in the real setup.
272026-09-20The board: brain serve (axum: page, /api/summary, /health, self-refresh) packaged in Docker (deploy/docker-compose.board.yml, http://127.0.0.1:8090/); anomalies exclude subscription traffic; one DB (data/brain.db) for CLI and board. Benchmark mirrors the real aider setup (architect mode for the reasoning tier, settings file). Third anomaly: with the fallback no rate limits, but the editor's whole format failed on a large file (101k tokens/turn) → fast tier gets diff/editor-diff in the generated settings.
262026-09-20Second anomaly, cause and fix. aider on bonsai: APIConnectionError: Provider returned error + 4 retries; bonsai's single provider rejects large requests intermittently (small probes 6/6 ok), while deepseek-v4-flash has three providers behind OpenRouter. Fix without a proxy: OpenRouter models[] fallback per tier via aider model settings (brain setup aider, verified on the wire). Claude Code can't get it from env → Phase 1 proxy. Per-request latency for Claude Code events; DB migration for latency_ms.
252026-09-20brain installable on PATH (cargo install --path crates/brain --root ~/.local) and usable from any folder via BRAIN_HOME (config, db and bench paths anchored there, .env loaded from it). Runner streams tool output to files so timed-out runs keep their logs. Configuration page: install section and a symptom → cause table.
242026-09-20Tiers swapped: fast = deepseek-v4-flash, reasoning = bonsai (fallback deepseek-v4-pro); setup outputs, metadata and docs follow. Hosting monthly table rewritten for the Rust footprint (smallest tiers, Hetzner chosen, Lambda as the only cheaper option). aider+bonsai re-run with logs to find its failure cause.
232026-09-20aider + deepseek-v4-flash solves ago-0001 (PASS, 260 s, 0.004 $); Claude Code on the same model replies nothing after its first tool result. Claude Code Docker image and brain setup claude-code --docker added; the container hangs on first start (open issue, host works). Configuration page updated with all four setup commands. Proposed: fast → deepseek-v4-flash.
222026-09-20First anomaly, cause found. Both tools FAIL ago-0001 on bonsai: Claude Code got 429 Request rejected · Provider returned error from the single provider (Darkbloom) and retried for 12 min; aider ran 831 s. Direct probes: bonsai answers Anthropic-format requests with 60 KB system + 25 tools, but takes 33 s vs 5.5 s for deepseek-v4-flash, with cache_read_input_tokens: 0. Runner now saves each task's tool stdout/stderr (bench/.runs/RUN/TASK.out and .err); events ingest skips unreadable session files. Costs of the two parallel runs were mixed (same key): runs are sequential from now on.
212026-09-20Configuration reference page: profiles/tiers YAML, .env variable names, shell functions for aider (Docker) and Claude Code, aider metadata, cron, image build, command table. Variable names only, no secrets or ids.
202026-09-20Keys provisioned (5 profiles, daily limits verified via usage snapshot). Bonsai answers through aider (860 in / 38 out tokens). brain bench --docker <image> runs a tool from an image with the worktree mounted; brain setup aider --docker prints the shell function. **`brain events ingest
192026-09-19Phase 0 started. First Rust code with tests alongside: brain keys provision/list, usage snapshot/report, setup claude-code/aider, bench run/report (git worktrees, setup, fix-tests-after-tool, verify, cost by key usage delta). Real aider end-to-end in a container via testcontainers (caught a wrong flag before it reached the runner). CI: fmt, clippy, tests, docker tests. First mined task ago-0001. Tiers configured with verified prices. Deploy templates (systemd unit, Caddyfile). Runbook.
182026-09-19Hosting compared on service quality, performance and expandability, independent of where the apps go: CPU class (burstable vs shared/dedicated), egress (1 TB + $0.09/GB vs 20 TB), in-place rescale, platform services, SLA, data location. Verdict: Hetzner (CAX11 → CAX21/31 → CCX) for this architecture; AWS is the graduation path at the HA step.
172026-09-19Hetzner CAX11 vs Lightsail $5 confirmed interchangeable; hostname vs domain clarified (existing subdomain → sslip.io → bought domain with the first public app; Caddy/Let's Encrypt need a name, not a purchase).
162026-09-19Hosting re-checked with the Rust footprint: Hetzner vs Lightsail $5 is now cost parity; Lambda + DynamoDB becomes possible and cheaper (≈ $2–3) but changes the architecture; VPS decision stands on headroom and simplicity, and moving to Lightsail is penalty-free if the apps land on AWS.
152026-09-19Decided: all code in Rust (axum, tokio, reqwest, serde_json, rusqlite, clap, governor, tracing). Stack page rewritten; agent-orchestrator modules become a port; repo layout is a Cargo workspace with crates/brain/; the docs helper is rewritten in Rust (tools/sync-diagrams); honest cost section. claude-kit added as a git submodule for skills, agents and hooks.
142026-09-19Documentation translated to English with English paths; site theme and logo; repository pushed to GitHub with a Pages deploy workflow.
132026-09-19Decided: llm_brain on a Hetzner VPS, apps with no location constraint. Corrected the "never mixed" recommendation: mixed is acceptable because the public endpoint is protected by the auth design and the extra latency (+5–10 ms) is nothing next to the model. Open decision 1 closed; name/stack of the apps and benchmark repos remain.
122026-09-19Hosting AWS vs VPS: traffic estimate (~3 000 req/day, ~2.5 GB/month: negligible), monthly cost per option (Hetzner ~€4–5, Lightsail $7–12, EC2 t4g $13–20 with paid IPv4, Fargate/Lambda no), performance impact per component (the model dominates; +100–150 ms per hop to OpenRouter everywhere; SQLite and concurrency are not a limit). Recommendation: same cloud as the apps. Hosting line 5–15 €/month in the budget. Diagram 11.
112026-09-19Client compatibility verified against Claude Code's official gateway guide: matrix for Claude Code / aider / app SDKs / agent-orchestrator (headers, user, correlation, 401/429), the gateway contract (endpoints, headers to forward, system untouched, retry-after > 60 for exhausted budget), the real risk = fields non-Claude models reject → sanitizer for the dev profile. The layer never prepends a system prompt for dev.
102026-09-19Authentication flow client ↔ llm_brain: issuing only from the CLI on the server (no self-service), middleware step by step (headers, format, hash, revocation/expiry, IP allowlist, rate limit per key and per user, budget, audit), api_keys data model, client-side rules, commands. Diagram 10 (sequence).
92026-09-19Topology clarified (assistant local, find-a-car and market on VPS/AWS): no refresh tokens, static per-app keys + rotation + user for rate limiting; proposal llm_brain on the VPS with Caddy, sops+age, Litestream for SQLite; table of where each platform keeps its key (SSM/Secrets Manager, docker secrets, LoadCredential). Diagram 09.
82026-09-19Technical choices: secrets management (two families, hashed client keys, one key per profile on both sides, management key outside the runtime, diagram 08); the benchmark suite must be built from scratch (task format, sources, headless runner); repo structure decided. New open decision: where llm_brain runs relative to the apps.
72026-09-19Answers to the decisions: llm_brain is a single standalone backend (agent-orchestrator becomes a client, modules copied not imported); GPU deferred; Claude Code and aider together, measured; apps: assistant and find-a-car first; translator deferred; budget confirmed with dev up to 3 €/day + monthly ceiling 30 € soft / 40 € hard. Diagrams 01, 04, 05, 06 updated.
62026-09-19Cache logic: decision flow (diagram 07), the case of apps with no client-side cache, prompt caching through OpenRouter verified (automatic providers vs cache_control, cached_tokens, sticky routing), L2 software comparison and recommendation (SQLite exact, then sqlite-vec semantic for assistant only).
52026-09-19Budget as the primary requirement: verified OpenRouter per-key limits (limit_reset: daily) and the Anthropic-compatible /api/v1/messages endpoint. New pages Budget, Nightly benchmark, Stack. Phase 0 → OpenRouter directly without LiteLLM. Phase 3 GPU downgraded to a wish.
42026-09-19Migration from a single CONSIDERAZIONI.md to a Docusaurus site: short overview (< 2000 words) + detail pages. scripts/sync-diagrams.py keeps diagrams/*.mmd as the single source.
32026-09-19Candidate model prism-ml/ternary-bonsai-2-27b: data verified on OpenRouter and HuggingFace, the bridge between cloud and local GPU.
22026-09-19API layer abstracting caches and providers, how Claude Code and aider react (hooks and skills), integration with the GitHub apps, "OpenRouter today, GPU tomorrow" principle, 5 diagrams.
12026-09-19First draft: 90/10 plan, what agent-orchestrator has, LiteLLM, CLIs, open decisions.