ADR-001 · the adversarial design panel, in full
An estate-scout plus four thinker-canon lenses, each pressure-testing the same four options (A custom · B fork+reskin · C headless behind shell · D estate-compose). They ranked differently on purpose — the divergence is the value. Verdicts reproduced granularly below.
Investigated seven candidate repos. Verdict scale: REUSE / ADAPT / STRETCH / IRRELEVANT.
| Repo (by capability) | What it actually is | Verdict |
|---|---|---|
| Governance substrate | Mature TS/Bun MCP server relaying Slack ↔ agent sessions through a per-tool-call policy engine + signed audit journal. Agent-facing. | STRETCH — extract the hash-chained signed audit journal for a moderation trail; not the messaging. |
| Agent mailbox | A single SKILL.md + Markdown protocol. No code, no runtime. Agent-to-agent. | IRRELEVANT |
| Multiplayer-agents | ~35MB FastAPI + TS monorepo; spawns sandboxed agents per Slack thread. Human-to-agent work sessions. | STRETCH (closest to ADAPT) — the durable-thread → event-log → resumable-stream pattern is a real reference. |
| Collab scaffold | Near-empty Rust/Leptos hello-world; the realtime/CRDT engine was only ever a draft doc. | IRRELEVANT — pattern exists only on paper. |
| Transcript pipeline | Ruby gem; ingests agent-session logs, redacts PII, exports. Batch ETL, machine logs. | IRRELEVANT — but its PII-redaction module is a good idea to mirror for UGC. |
| Email platform | Real TS platform: Gmail/Outlook OAuth (encrypted tokens), 47-tool MCP, Discord bot, React/PWA dashboard, digests. Human-facing. | ADAPT — strongest of the seven. Auth + encrypted-token + digest/notification + dashboard scaffolding. |
| Agent-governance plane | Contract-first TS/Bun; governs agent execution via policy gate → Slack approval → sandbox → signed journal. | STRETCH — only the Slack human-in-the-loop approval loop + signed-journal, if we need a moderation-approval workflow. |
Decisive tradeoff: count of self-run stateful services × size of ops team. Not features, not "feel," not the moat. A forum post nobody can read because a service fell over is worth less than a plain forum that stays up.
YAGNI violations, by name: presence dots + typing + a dedicated realtime service (delete it — nobody churns over a green dot); the AI-curated home as "the moat" (you have hundreds of posts, a human can read the board — "Latest / Top this week" are two SQL clauses); the permissioned index + agent runtime (Postgres FTS + WHERE tier ≤ member.tier covers it); dedicated fleet (tens-to-thousands is one modest box); own IdP wrapping working argon2 auth ("tier is a column — you don't run an identity provider to own a column"); four-tier machinery before a paying member; two-theme + per-tier hues from the start.
Ranking: B (least regret) > D > C > A. Recommendation: fork a mature forum, run it as one box, map its trust-levels → your four tiers, federate SSO, use its built-in chat for DMs, async notifications instead of presence. Kill the AI home, index, agent runtime, fleet, and presence from v1. Simplest thing that works: forked forum + LMS + one paid tier + email notifications, on one box.
Self-dissent: "a member can't tell where one service ends" — a reskinned forum feels like a reskinned forum. If feel is the real moat for a paid club, you're married to its look and upgrade cadence — that's the argument for C or A. Rebuttal: a seam a member occasionally notices is survivable; a realtime stack a two-person team can't keep alive is not.
Most costly to recover from: a standing realtime/agent stack, once built, becomes "how we run things" and never gets deleted — the team spends its scarce hours resurrecting infra whose justification never materialized.
The contradiction it caught: for the learning core the spec makes the right call (adopt a mature base, theme via config, push gaps upstream, zero merge tax). Then for the community layer it reflexively flips to "ours to build" — hand-building the most commodity component in the estate, driven by "own our stack" emotion, not design pressure. Nobody pays country-club money because you hand-wrote a threading engine.
Decisive tradeoff: fork-merge-tax vs shell-coupling — resolved by putting the seam at the backend/presentation boundary: adopt the engine headless (data + API + moderation), reject its UI, render every board from your shell. Then an engine swap is a repoint, and a later strangle-migration is board-by-board, not big-bang. That's Option C — "B done at the right seam."
Ranking: C > B > D > A. Key findings: (1) rewrite the E4 framing to the adopt-headless rule; require an ADR before any from-scratch board engine. (2) Split boards from realtime — durable/read-heavy vs latency-sensitive/ephemeral; one backend for both guarantees one fights its store forever. (3) Every surface emits into the index via an outbox so the home never reads a surface's store directly. (4) A headless mature forum may not exist in adoptable form → make E4's first task a spike against the adapter contract. (5) Tag each seam's reversibility so review effort tracks lock-in.
Self-dissent: the "headless mature engine" may degrade into "run a full-stack engine headless" (merge tax returns) or "build the board engine after all" (drift to A). Resolve by spiking before committing.
Most costly to recover from: committing a small team to a bespoke community store (A/D). Once real members' threads + history + moderation state accumulate in a hand-rolled schema, migrating off it is the hardest reversal in the whole plan — member-visible content with social context, not config.
The realization: the spec has already, implicitly, chosen event sourcing and not noticed. "Every object FKs to a Member and carries tier-visibility, including AI queries" + "agents analyze conversation history" + "one sub join key" describes an append-only, member-keyed, tier-tagged event log as source of truth, with every rendered surface a derived read model. The spec never says the word "log" — and that omission is the load-bearing gap.
Decisive tradeoff: ownership of the conversation log, not the forum UI. If the source of truth lives in a vendor forum's mutable Postgres, then edits/deletes silently rewrite the history agents analyze, tier-visibility must be reverse-engineered onto their ACLs, and "one identity" fights their account table. Own the append-only log and all three evaporate.
The five dimensions: (1) posts/messages are events; views are materialized + rebuildable. (2) Feed = fan-out-on-read — a stored per-user timeline leaks content after a tier downgrade because permission was frozen at write time; the home must be a query, re-evaluating current tier per read. (3) Messages need a store-assigned per-conversation monotonic seq, never a wall-clock timestamp (clock skew → reordering). (4) Presence is deliberately AP, best-effort, TTL'd soft-state, kept strictly separate from the durable log — a socket hiccup becomes a delayed message, not a lost one. (5) Enforce tier at retrieval against the reader's current tier over an immutable store.
Ranking: A > C > B > D. Recommendation: "Own the log; rent the rendering." Build the append-only event store as the record; derive boards/DMs/directory/feed as read models; wrap a headless engine as a derived view fed from the log for search/moderation/threading UI; keep presence separate. Write the missing invariant into the spec explicitly.
Self-dissent: "don't build a database" — a small team hand-rolling durable-ordered messaging + search + moderation reinvents a forum badly. A disciplined Option C (mature engine for the hard parts, your outbox log as the agent-facing record) gets the same invariant with less to get wrong — if you hold the line that the log is the record.
Most costly to recover from: shipping the conversation store as a mutable application table instead of an append-only log. It works for a year; then a governed agent's historical analysis quietly disagrees with itself across runs because edits/deletes rewrote the past — silent history corruption, undetectable for months.
Decisive tradeoff: does community content get born as a first-class, tier-tagged, chunkable object that emits into our index at write-time — or does it live in a foreign store whose permission model and rendering format we must forever ETL and reconcile? Two AI-killers with no eval to catch them: permission drift (a forum's own ACL model = two permission systems that must agree forever, with nothing catching the day they diverge and an agent drops a T2 thread into a T1 feed) and chunk drift (a forum stores cooked HTML; clean chunks need an ETL that rots every version bump).
Ranking: D > A > C > B. Recommendation: D with one boundary borrowed from C — own the object model + tier attribute + index emission + agent read/write (the moat); borrow the commodity realtime transport as a dumb pipe you drive, never the record. The moat lives in the data layer, which is exactly the layer B doesn't own.
The three missing evals it surfaced (now in the ADR): P0 a fail-closed retrieval-permission tier-leak golden set — "an invariant with no eval is a wish"; P1 a directional home-ranking regression eval + engagement proxy (the moat rots silently across model versions otherwise); P1 a per-flow prompt eval for each governed agent (audit trails tell you who ran what, not whether output is still correct after a model bump). Eval fixtures are source code under review discipline.
Self-dissent / sharpening: keep the tier filter a pure deterministic function applied before the probabilistic ranker ever sees a candidate — deterministic gate, probabilistic ranker, don't complect the two.
Most costly to recover from: the absent retrieval-permission eval. A tier-leak in a paid, vetted community breaches the one promise members pay for — that the room and the table are earned — and with no eval it goes undetected across a model swap until a member reports seeing what they shouldn't have. Build it before the first agent reads the first board.