00 — SYNTHESIS: Stack architecture for the four factory apps
AMENDED 2026-07-19 (read first): after a second research + steelman pass on Robert’s “why not Postgres for everything” challenge,
05-postgres-only-comparison.mdsupersedes this doc on the data layer: FleetOps hook events move from SQLite into the shared Postgres (insert-only partitionedfleetmonschema, partition-drop retention); SQLite and the frser Grafana plugin exit the design. Amendment 2 (Robert’s call, same day, 05 §5b): VictoriaMetrics dropped too — the Go binary gains a ~150–400 LOC OTLP receiver writing metrics into PG; Grafana deferred (Board renders the panels). Final: ONE database (Postgres). Always-on ≈250–450MB. Languages, products, and everything else below stand.
2026-07-19. Written by Fable (orchestrator/judge) after the full oh-architecture pass: 10-packet phase-1 research workflow (11 agents, ~861k tokens), 4-candidate steelman court (12 agents, ~556k tokens), 4 writer agents. Evidence: ./evidence/. Where this doc revises a prior plan’s ADR, it says so explicitly — with the prior plan’s own escape hatch cited. Readable standalone in 5 minutes.
The decision
One polyglot-by-plane stack, chosen per plane, not per fashion:
| Product | Language / runtime | Database | Web surface | Always-on? |
|---|---|---|---|---|
| WorkLedger | TypeScript / Encore.ts (pinned by plan 002 ADR D5 — unchanged) | Postgres (shared local instance, tuned; SKIP LOCKED claim path needs real row-level locking [E11]) |
Grafana panels (PG datasource) + views read by the Board | Yes — as built binary (encore build), never the encore run dev daemon |
| Atlas (answer loop) | TypeScript / Encore.ts (pinned by plan 003 — unchanged) | Postgres (same shared instance; pg_trgm) | Renderings (docs/atlas/) + capped digest rows via FleetOps |
Yes — same built binary/process family as WorkLedger |
| Parity Harness | TypeScript workspace + CLI (pinned by its own architecture.md:27 — unchanged); mitmproxy stays a quarantined Python process; Bun runtime allowed for the CLI | Content-addressed file store (fixtures) + verdict rows written into WorkLedger’s Postgres | None (evidence bundles + one Grafana panel, per its ADR) | No — batch/episodic. The footprint constraint doesn’t bite here; that’s why TS stays. |
| FleetOps (monitor) | Go — ONE static binary (ingest + SSE board + pollers + digest in-process); existing Rust fleetops TUI and Go ghmonitor stay as-is, consumed via their JSON seams |
SQLite WAL (hook events, watch items — single writer goroutine) | The Board is the program’s web shell: server-rendered htmx/Datastar + SSE, go:embed assets, links out to Grafana |
Yes — ~15–40MB |
| Observability | — | VictoriaMetrics single-node (native OTLP ingest — Claude Code exports straight to it, no OTel Collector); no Loki — events live in FleetOps SQLite | ONE shared Grafana (dashboard 25255 + PG + SQLite datasources), linked from the Board, never embedded | Yes — vmsingle + Grafana |
Sequencing follows candidate D’s instinct inside candidate C’s architecture: ship the Go binary as ingest-only + Grafana + existing TUI first (days), add the Board SSE page second. This also preserves plan 004’s own F5+F6-first sequence.
Always-on budget (the number Robert asked for):
| Process | RSS budget | Note |
|---|---|---|
Postgres (one shared, tuned shared_buffers 128–256MB) |
~130–150MB | serves ledger + atlas [E11] |
| Encore built binary (ledger + atlas) | ~100–150MB (UNVERIFIED — measure at walking-skeleton time, V1 below) | Encore publishes no RSS numbers [E10] |
| VictoriaMetrics single-node | ~50–100MB | tiny series count; native OTLP [E5] |
| Grafana (one instance, enforced) | ~180–330MB | the single most expensive tool we keep |
| FleetOps Go binary | ~15–40MB | GOMEMLIMIT set explicitly |
| Total | ≈475–770MB | vs ~2.3–2.4GB as-previously-documented — ~70% cut, dominated by dropping Loki (~1.5GB idle) and the OTel Collector |
The fleet itself (hundreds of Claude Code processes at ~156MB measured average [E1]) costs tens of GB — the tooling is now noise against it, which is the point.
Decision matrix (weights stated, scores 1–5, justified by court + evidence)
Criteria weights: footprint is Robert’s stated hard constraint (25%); AI agents are the primary developers, so agentic maintainability is second (20%); the rest per the constitution. Sensitivity-checked below.
| Criterion | Wt | A all-TS | B Rust | C Go | D do-less |
|---|---|---|---|---|---|
| Always-on footprint | 25% | 4 | 5 | 5 | 5 |
| Agentic maintainability | 20% | 4 | 2 | 5 | 4 |
| Simplicity / ops surface | 15% | 4 | 3 | 4 | 5 |
| Speed to ship | 15% | 4 | 2 | 4 | 5 |
| Robustness at fleet scale | 15% | 4 | 4 | 5 | 2 |
| Coherence & evolution | 10% | 4 | 3 | 4 | 3 |
| Weighted | 4.00 | 3.30 | 4.60 | 4.15 |
Score justifications (compressed; full arguments in 04-steelman.md):
- B’s agentic 2: Rust is the only benchmarked language with real failures (38/40), repo-level real-issue resolution 21–28% vs ~70% Python-tier, fixes touch 9.8 files/139.9 LOC avg [E7]; under the Haiku-first routing rule this also means silent model-tier escalation cost. Its unique advantage (in-process reuse of the 6.4k-LOC fleetops crate) rests on an unverified premise — nobody has confirmed fleetops is a lib crate with an async-safe API (court residual).
- C’s robustness 5: SQLite’s multi-writer cliff is answered structurally — pollers fan into a channel, ONE writer goroutine owns the handle; that preserves the single-writer-process envelope (8.4k writes/s real-world [E6]) at any poller count. Driver: modernc.org/sqlite (pure Go — no CGO thread growth; its ~2× write penalty still yields ~400k inserts/s in benchmarks, orders above need).
- D’s robustness 2 — the disqualifier: daemon-less ingest turns every hook event into an independent OS process contending on one SQLite file, and Claude Code hooks execute synchronously — ingest contention becomes agent tool-call latency at exactly peak fleet load. The court judged this the strongest single attack of the whole proceeding. D’s saving over C is only ~50–100MB.
- A vs C: the court settled that the maintainability evidence establishes TS-over-Rust, not TS-over-Go. Go matches A on every coherence argument that survives (both are one new process; both toolchains already installed), beats it on footprint floor and runtime robustness (goroutines vs one event loop; no Bun SSE idle-timeout gotcha), and carries the best agentic grade among low-footprint languages (fast compile loop = no
cargojobserver-style core grab against the running fleet [E7][E9]).
Sensitivity: C’s win survives every plausible weight shift. Raising footprint to 35% doesn’t reorder (B/C/D all score 5). D overtakes C only if robustness drops below ~5% weight — i.e., only if the ingest-latency coupling is proven harmless, which is exactly what it isn’t. Discounting the Rust repo-level study (single source) lifts B to ~3.5 — still last. A never leads under any tested shift.
Winner architecture
flowchart LR
subgraph FLEET["Claude Code fleet (hundreds of agents)"]
CC["agent sessions"]
end
CC -- "OTLP metrics, direct (no collector)" --> VM[("VictoriaMetrics\nsingle-node")]
CC -- "hook events (stripped) HTTP" --> GOB
subgraph GOB["FleetOps — ONE Go binary (~15-40MB)"]
ING["ingest handler"] --> CH["channel"] --> WR["single writer goroutine"]
PL["pollers (goroutines):\nfleet snapshot · ghmonitor · groupon2 REST"] --> CH
BRD["Board: htmx/Datastar + SSE\n(go:embed, the web shell)"]
DIG["digest cron (in-process)"]
end
WR --> SQ[("SQLite WAL")]
SQ --> BRD
FO["fleetops TUI (Rust, as-is)"] -. "fleet snapshot JSON" .-> PL
GH["ghmonitor (Go, as-is)"] -. "JSON" .-> PL
subgraph ENC["Encore built binary (TS)"]
WL["WorkLedger svc"]
AT["Atlas svc"]
end
WL --> PG[("Postgres\n(one shared, tuned)")]
AT --> PG
HAR["Parity Harness (TS CLI, batch)\n+ mitmproxy (Python, quarantined)"] -- "verdict rows / block()" --> WL
HAR --> FS[("content-addressed\nfixture files")]
VM --> GRAF["Grafana (ONE instance)\n25255 + PG + SQLite datasources"]
PG -. "read-only views" .-> GRAF
PG -. "read-only views" .-> BRD
BRD -. "links, never embeds" .-> GRAF
Top-3 reasons
- The only genuinely open slot gets the best-fit language. WorkLedger/Atlas (Encore.ts) and Harness (TS CLI) were already pinned by their own ADRs; the research confirmed those pins are sound and cheap (harness is batch — footprint irrelevant; ledger/atlas ride the substrate). FleetOps was open, and Go wins it on the joint evidence: near-Rust footprint, best agentic iteration loop, structural burst-robustness, and precedent (PocketBase/Beszel/ghmonitor are this exact shape).
- The observability cut is the real footprint win — not the language choice. Dropping the OTel Collector (Claude Code exports OTLP per-signal natively) and Loki (~1.5GB idle for content-free events that already land in SQLite), and swapping Prometheus for vmsingle, takes the always-on base from ~2.3–2.4GB to under ~800MB. No candidate disagreement here; the court was unanimous.
- Hundreds-of-agents robustness is designed, not assumed: single-writer-goroutine ingest, GOMEMLIMIT, loopback-only binding, poll intervals as calibration knobs — and the two genuinely unknown numbers (hook burst rate, Encore binary RSS) are named validation spikes, not hopes.
Top-3 risks (winner’s prosecution = risk register; full list in 04)
- No Go data exists at the repo-level maintenance depth where Rust collapsed (21–28%). Go’s A- grade is directional consensus, not a measured repo-level number. Tripwire: track agent PR failure rate on FleetOps in the ledger from week 1; if Haiku/Sonnet agents can’t hold the Go binary, the A-grade was wrong and A (all-TS) is the fallback (board is one small process behind a stable HTTP/SSE contract — swap cost is days).
- Encore built-binary RSS is unpublished [E10] — the budget table’s ~100–150MB line is an assumption in an exercise that is entirely about RSS. Spike V1 measures it at ledger walking-skeleton time; if it lands fat (>400MB), revisit ledger/atlas packaging (A-lite standalone repo fallback per plan 002 D5 — same code, different deploy target).
- Real hook-event burst shape is unmeasured at hundreds-of-agents scale — nobody anywhere has published it [E9]. The ingest envelope (8.4k writes/s) has ~40× headroom over any modeled rate, but the model is a model. Spike V2 instruments the next real wave and reads events/sec before the Board ships.
Supersessions this decision makes (each uses the prior plan’s own escape hatch)
| Prior decision | Status | Why |
|---|---|---|
| Plan 004 ADR-9: reinstate Loki | SUPERSEDED — no Loki | ~1.5GB idle RSS [E5] for content-free events already in SQLite; ADR-9’s own reversibility note (“one more compose service, additive”) cuts both ways. Grafana SQLite datasource + Board search cover the read path. VictoriaLogs (0.6–2GiB at 500GB/7d scale) is the named upgrade if log search ever demands a real store. |
| Plan 004 §2.5 / component table: OTel Collector + Prometheus | SUPERSEDED — vmsingle, direct OTLP | Collector is provably optional (per-signal endpoint override documented); VM is listed as a supported backend by dashboard 25255 itself [E5]. Config change, not architecture change. |
| Plan 004 ADR-6 (SQLite ingest), ADR-2 (web board), ADR-5 (Grafana linked) | CONFIRMED | Unchanged; board language now decided (Go) — ADR-2 said “UI framework choice, data model unaffected”. |
| Plan 004 §7 compose layout (ingest as compose service, pollers as host cron) | REVISED | Pollers+digest move into the one Go binary (in-process timers) — fewer moving parts than cron + daemon split. |
| Plans 002/003 stack pins (Encore.ts+Drizzle+PG), harness TS ADR | CONFIRMED | Re-examined under the new constraint; survive it (batch/substrate reasoning above). One addition: always-on = built binary, never encore run. |
Validation spikes (before/at build start — all cheap)
- V1 (at ledger walking-skeleton): measure Encore built-binary RSS idle + under claim load. Gate: <400MB or packaging revisited.
- V2 (next real wave): instrument hook events/sec + burst shape via a 20-line stub ingest. Feeds the ingest golden test’s load numbers.
- V3 (day 1 of Go build): modernc.org/sqlite single-writer-goroutine throughput golden test — burst 1k events, assert no
SQLITE_BUSY, p99 write <5ms. - V4:
fleet snapshotsubprocess cost at current session count; set default poll interval (start 5s, calibration knob).
First implementation steps (test-first, inside plan 004’s 2–4 day envelope)
- RED: Go repo
fleetmon/— failing test: POST /ingest with a stripped hook JSON → row in SQLite via the writer channel; concurrent-burst test (V3) alongside. - GREEN: one
main.go+ingest.go+store.go(modernc, WAL, busy_timeout, GOMEMLIMIT);go:embedempty shell page. - Claude Code hook config → POST to :PORT/ingest; OTLP env → vmsingle; import dashboard 25255 into the ONE Grafana; wire PG datasource to ledger views (when ledger exists).
- Board SSE page (htmx/Datastar): queue derivation golden test first (given fixture events → expected attention queue), then render.
- Pollers as goroutines behind the channel (fleet snapshot, groupon2 REST) — contract-tolerant parsing tests (fields may drift, per plan 004 risk 5).
Status: draft. Flip to active on Robert’s acceptance. Full reasoning: 01 (constraints + footprint evidence), 02 (case studies + agentic-fit), 03 (candidates + diagrams), 04 (steelman court), evidence/ (raw packets).