Decisions
What the fleet built and killed, and why, with a source for each. Read this before you propose rebuilding any of it, because arguing again over a decision it already paid for is this fleet's most expensive recurring failure.
Updated
Learn why re-deciding is the expensive failure
Reads this page, explains the reopening rule, the four kills and the standing decisions back to you, then quizzes you. Writes only its checklist.
PROMPT 1 of 2 — learn what the Decisions page teaches: what the fleet killed, why, and the standing decisions that constrain new work. You read and explain. You write one checklist file and nothing else.
I run my own agents with Claude Code and I am senior. Skip basics. Where the page's reasoning would not survive contact with a setup like mine, say so.
FIRST — WRITE YOUR CHECKLIST TO A FILE
Write ~/DECISIONS-LEARN-CHECKLIST.md and work from it:
# Decisions — learn
- [ ] 1. Fetch the page
- [ ] 2. The reopening rule
- [ ] 3. The four kills, each as killed / why / replaced by / residue
- [ ] 4. The standing decisions
- [ ] 5. Quiz me, then tell me to paste prompt 2
## Rules
- Read only. The checklist is the one file I write.
- Every claim quotes the page sentence it came from.
1 — FETCH
curl -s https://docs.utopiamodels.ai/docs/structure/decisions.md
If that fails, say what came back and stop.
2 — THE REOPENING RULE
State it precisely: a decision is reopened by a SUPERSEDING RECORD naming what changed, written beside the original, with the original's `Superseded-by` header updated in the same commit — never by disagreement. Explain why a silently invalidated record is worse than no record.
3 — THE KILLS
For each, one row: what it was · why it died · what replaced it · what RESIDUE survived.
ralph, the 24x7 loop — and the per-agent field named after it that outlived the loop
the generated catalog — the fail-closed gate that auto-reverted its own cleanup, and the
honest footnote: `verify:` is read by humans and executed by nothing
the NATS bus — and `nats-reload.path`, still active (waiting), watching the config
of a service that will never start
the local CI re-run timer — and why deleting it from the brain repo did not stop it (copies in
~/.config/systemd/user/, not symlinks; it fired 203/EXEC and filed a
P0 about itself)
The pattern across all four is the lesson: a deletion in a repository is half a deletion. Say it in your own words.
4 — STANDING DECISIONS
Summarize each in one line with its evidence: one secret store · subtract by default · options before decisions (two real options + do-nothing + integrate-existing) · a limit is a measurement (the dispatcher at ~8% of proven capacity behind caps of 1, 4 and 2) · fix forward, never pre-gate. Then the never-resurrect list, and the one sanctioned exception (a */5 self-reported heartbeat) and why it does not break the ban.
5 — QUIZ AND FINISH
Ask me three questions with the `AskUserQuestion` tool, one at a time, four options each, exactly one correct — at least one about residue, one about the reopening rule. After each, quote the settling sentence.
If `AskUserQuestion` is unavailable in this session, ask the same questions as plain numbered text in one message instead.
Then say exactly this:
"Decisions learned. Paste prompt 2 and I will find the decisions your own history made and never wrote down."
Then stop.Find my unrecorded decisions and their residue, then adopt a record format
Finds the decisions your repository made and never recorded, plus the residue of what you deleted, then proposes a record format sized to your setup. Writes only after you confirm.
PROMPT 2 of 2 — find the decisions my own repository already made and never recorded, find the residue of the things I deleted, and adopt a decision-record discipline sized to my setup. Survey is read-only; every write waits for my explicit yes.
HOW LONG THIS TAKES: a multi-step audit (5 steps, about 6 questions, writes only what you approve) — expect roughly 10–25 minutes across several turns. Running headless with a timeout? Budget for that, or start with a shorter prompt first. Cut off partway? Paste this prompt again — it resumes from its checklist rather than starting over.
curl -s https://docs.utopiamodels.ai/docs/structure/decisions.md
Read it first if prompt 1 did not run in this session.
ASK EVERY QUESTION WITH the `AskUserQuestion` tool, with a RECOMMENDED option first and one line of why.
FIRST — WRITE YOUR CHECKLIST TO A FILE
RESUMING? Before you write anything, check whether ~/DECISIONS-INTEGRATE-CHECKLIST.md already exists. If it does, this prompt was interrupted: read it, tell me which steps are already ticked, and continue from the first unchecked step. Do not overwrite it, do not redo a ticked step, and do not re-ask a question whose answer it already records — the scope answer under ## Rules included, if it's there. If ## Rules has no Scope field, this checklist predates the scope question: say so plainly in your first output line and ask it now, before anything else. Only if the file does not exist, write it fresh as below.
Write ~/DECISIONS-INTEGRATE-CHECKLIST.md and work from it:
# Decisions — integrate
- [ ] 1. Mine git history for decisions
- [ ] 2. Find residue of deletions
- [ ] 3. Find existing decision records, and their reopen discipline
- [ ] 4. Report
- [ ] 5. Propose a record format; write only what I approve
## Rules
- Steps 1–3 are read only.
- Every finding carries the command and its raw output.
- I never modify a file I did not create without asking.
SCOPE — ASK ME BEFORE YOU SURVEY ANYTHING OF MINE
Before step 1, ask me this with the `AskUserQuestion` tool, with both options exactly as written:
"Should I treat any path as off-limits, or do you want me to have full access to survey and propose changes anywhere in this environment?"
A — Exclude .env*, credentials and CI/deploy config (.github/workflows/, deploy keys) from anything I read or propose touching. [RECOMMENDED for most readers]
B — No exclusions. Full access to survey and propose anywhere, including secrets and CI files.
This is my preference, not a policy: some people want their agent to reach everything, some do not, and neither answer is the correct one. Never assume it and never pick for me. My answer governs every read and every proposal for the rest of this prompt. Write my answer under ## Rules in the checklist as "Scope: A" or "Scope: B", so it survives compaction. It governs the SURVEY and PROPOSE steps only — nothing gets written until step 5, and there only what I approve.
Before you ask, decide whether an answer can reach me at all. It cannot in a one-shot non-interactive run — `claude -p`, `codex exec`, `gemini -p`, a scripted or CI invocation — and you can tell without guessing: the `AskUserQuestion` tool is missing from your tool list there, while an interactive session has it. It also cannot if the question comes back immediately with no real answer. In either case do not ask, do not wait and do not re-ask: a question asked as plain text in a one-shot run ends the run with nothing done. This is the one case where you pick for me: treat A as my answer, record and hold it exactly as above, and make the first line of your output exactly: "No interactive scope answer available — defaulted to A (excluded .env*, credentials and CI/deploy config). To choose B, re-run this prompt in an interactive session and answer the scope question."
Only if the `AskUserQuestion` tool is in your tool list but fails when you call it, ask the same question as plain numbered text in one message instead.
1 — MINE THE HISTORY
In each repository I name (ask with the `AskUserQuestion` tool; RECOMMENDED: the current one plus the repo holding my agent config):
git log --format='%H %ad %s' --date=short -300 | grep -iE 'revert|instead of|replaced|migrat|switch|remove|drop|deprecat|kill'
For each hit, read the commit (`git show --stat <sha>`) and decide: was this a DECISION — a choice between alternatives that a future reader could re-litigate — or routine? For each decision, check whether anything records WHY. A commit subject is not a why.
Then look for the page's most expensive failure: a thing built, removed, and built again. Search for pairs — an added path that was deleted and later re-added:
git log --diff-filter=D --name-only --format='--- %h %s' -300
git log --diff-filter=A --name-only --format='--- %h %s' -300
2 — FIND THE RESIDUE
The page's four kills all left something running. For each deletion found above that touched a service, timer, cron job, hook or config:
- is a copy still installed outside the repo? (`systemctl --user list-unit-files`, `crontab -l`, ~/.config/, ~/.claude/)
- does a name, field or flag outlive it in config or docs? (`grep -rn '<name>'` across the repo)
3 — EXISTING RECORDS
Find any ADR / decisions / docs/adr directory. If present: does any record have a Superseded-by or equivalent, and does any decision contradict another without one?
4 — REPORT
Three lists: UNRECORDED DECISIONS (sha, what was chosen, what the alternative evidently was) · RESIDUE (what still runs or is named after its deletion, with the command that shows it) · REBUILT (anything deleted and re-added).
5 — PROPOSE, THEN WRITE
Ask with the `AskUserQuestion` tool:
a minimal record format — context · options considered (≥2 + do-nothing) · decision · consequences ·
Superseded-by — in a decisions/ directory, written only when a choice is expensive to reverse or
will be re-litigated [RECOMMENDED]
one decisions log file, appended
no records — just clean up the residue
Then ask with the `AskUserQuestion` tool which ONE unrecorded decision to backfill first (RECOMMENDED: the one most likely to be re-litigated — say why). Show the draft record before writing it. Write only what I approve.
For residue, show the exact commands that would remove each item, and run none of them without a separate yes per item — removing a running unit is not reversible from git.
When the writes are done, tell me in one line, filled in with the real paths:
"To undo, delete `~/DECISIONS-INTEGRATE-CHECKLIST.md` and <the decisions/ directory or log file you created, by path> — nothing else on your system was touched."
If I approved an edit to a file you did not create, name that file in the same line with the command that reverts it (`git checkout -- <path>` if uncommitted) instead of claiming nothing else was touched.
Finish with: what you wrote and where, the residue list with its status, and the one decision you think I am most likely to re-make without knowing I already made it.The fleet's most expensive recurring failure is deciding again something it already decided and paid for. Each entry below says what the thing was, why it died, and what replaced it. If you want to propose one of these again, bring new information. A fresh opinion is not enough.
Disagreement does not reopen a decision. A superseding record that names what changed does. You write it
beside the original and update the original's Superseded-by header in the same commit. A decision
record that someone silently invalidated is worse than no record.
Killed: ralph, the 24×7 autonomous loop
Deleted 2026-07-24.
ralph.service ran with Restart=always and drove a loop. It picked the next open board issue for this
master's lane, ran headless claude -p, and repeated. Over time it collected watchdogs, heartbeats,
priority-sort tests, self-reload fixes and a pick-order bug. All of that supervision code existed only to
keep one resident process honest.
On-demand dispatch replaced it. dispatch.sh plus task-runner.sh do the work, and no agent process
stays resident. The close-out measured the replacement instead of asserting it. It recorded 7
dispatched runs, 7 verified outcomes, 0 runtime failures, against a baseline of roughly 20%.
Clearing what it left behind took a second deletion. For weeks after we deleted the loop, the registry still had a per-agent field named after it, still described as the live backend selector. Code really did read the field, so nobody could call it dead. The description outlived the system, and then the field outlived the description. Both are gone now. Nothing in the dispatch scripts selects a backend, and the launcher passes one literal provider name to every run.
A name that outlives its system keeps costing you. Everyone who reads the surviving name afterwards concludes the deletion is incomplete.
Killed: the generated component catalog and its fail-closed gates
Deleted 2026-07-23/24.
This was an 804-line machine-generated registry. Every component in it carried a shell predicate, and three separate workflows plus a post-commit regeneration hook ran those predicates fail-closed on every pull request. Its own header claimed "a fix isn't done until catalog = reality."
We killed it because a gate that blocks on a generated map blocked documentation changes that were fine, and reverted its own cleanup automatically. One commit removed eighteen dead gate workflows.
The replacement reverses the design on purpose. One component registry exists, maintained by hand. Every
entry has a verify: command that you run, and the file tells you to run it and never trust its
prose. Nothing enforces it, by design.
This wiki publishes the unflattering half too. An audit found that humans read verify: and nothing
executes it. It also records no box, so a quarter of its commands fail from the wrong machine and pass
from the right one. The new registry is better than the catalog it replaced, but it does not yet do what
its own header claims.
Killed: the NATS message bus
nats.service and its reload units are on the never resurrect list. Issue comments and @mentions
replaced it for cross-machine agent messages. That delivery is durable and at-least-once, and it shows up
where a human already reads.
Three leftover units remain on the box, and one of them is active. The file that documented them was itself deleted, so we name them here. This page used to say there were two, and that they were inactive and disabled. Then a reader doing the pattern walk ran the command and reported otherwise (knowledge#6665). We measured again on the box on 2026-08-23:
$ systemctl list-unit-files | grep -i nats
nats-reload.path enabled enabled
nats-reload.service static -
nats.service disabled enabled
$ systemctl list-units --all | grep -i nats
nats-reload.path loaded active waiting Watch NATS config; reload server on change
nats-reload.service loaded inactive dead Reload NATS on config change
nats.service loaded inactive dead NATS JetStream fleet hubnats-reload.path is enabled and active (waiting). It is a live path unit watching the config of a
service that will never start again. Deleting something in a repository is half of the deletion, and the
other half is the part that keeps running.
Killed: the command center's generated infrastructure surface
This view was built on the deleted catalog, so once the catalog died it showed a permanent empty state. A single pull request removed it, deleting "11 callerless routes, 7 orphaned components, 5 dead libs."
It is listed here because open plans still name its deletion as a to-do. Anyone dispatched on that
line finds nothing to delete, and that looks like a lost change rather than one that already shipped. A
stale local clone may disagree. Trust git ls-tree origin/main, not a local ls.
Killed: a scheduled local re-run of another repository's CI
Deleted 2026-08-15.
A timer on one master fired every three hours. It checked out the product repo, ran typecheck, build and test, and filed a P0 on any real failure. Someone built it to cover a real blind spot, and people repaired it many times over months.
It died because the product repo's own CI runs those same three phases on every push to main. That CI simply told nobody. Once the CI announced its own results, a second box re-running the same work was wasted effort.
The half-deletion, which is the real lesson
Deleting the unit from the agent's brain repository did not stop it running. ~/.config/systemd/user/
holds copies of unit files, not symlinks into the repo. The timer stayed active waiting, and its
ExecStart pointed at a script that no longer existed. The next firing came nearly three hours after
the "delete". It exited 203/EXEC and filed a P0 about itself.
Deleting from the brain repo is half of a deletion. The other half happens on the box and takes five commands. Operations lists them.
Standing decisions
These decisions are current, not history, and they constrain new work.
Publish the mechanics, not the identifying values
Superseded 2026-09-27. This entry used to say "publish everything, unsanitized". That meant real hostnames, real tailnet IPs and real usernames. We decided it after measuring what that exposed, and the entry said outright that a contributor who sanitized the site was causing a regression. The founder narrowed the rule because the audience changed. People now copy these pages into their own agents as prompts. For that reader, a literal tailnet address or an admin UI's hostname teaches nothing that a placeholder does not.
The current rule is to publish how everything works and withhold the literal values that locate
private infrastructure. Tailnet addresses become 100.x.x.x or <tailnet-ip>. Internal hostnames and
bucket URLs become named placeholders. Every row, class and lesson around them stays the same. We apply
the redaction one page at a time. Surfaces and
Architecture have it, and so does the tailnet table on the landing page.
This change also follows the reopening rule at the top of this page. We did not silently delete the old entry. We marked it superseded here and said what changed.
The one hard stop has not changed and has no exceptions. Never put a secret in a tracked file. Publishing how the vault works is in scope. Publishing a credential never is.
One secret store
The fleet uses self-hosted Infisical and nothing else. There is no local vault, no second store and no
.env in a repository. A separate registry records what each credential means, meaning its account,
its scopes, and what it can and cannot do. That way, anyone who says "I don't have access" has to check
the claim against a list first.
Subtract by default
Before you add a script, a rule, a service, a gate or a file, ask what you can delete or merge
instead. When something replaces legacy code, delete the legacy code at that moment. Git history is
the archive, and archive/ folders are banned. A convention gets a linter that only warns. Only an
invariant that has stopped changing gets a gate that blocks.
Options before decisions
Before a non-trivial architecture or tooling choice, research the alternatives. That means at least two real options, plus doing nothing, plus using something that already exists. Then write the decision down. Proven native and open-source tools beat custom code. Custom code is the exception, and it has to justify itself.
This site is an example. The wiki lives in its own repository rather than inside the product monorepo. Before anyone wrote the first line of the app, we committed the record that names the four options considered, the reason for the choice, and the costs the choice imposes.
A limit is a measurement, never a precaution
Before you set any ceiling, such as concurrency, retries, batch size or timeout, measure the real headroom and write the measurement next to the number. A cap chosen out of caution quietly becomes the system's capacity. Unlike a bug, it never shows itself. It reports success while doing a fraction of the work.
The fleet learned this the annoying way. Its dispatch scheduler ran for days at roughly 8% of proven capacity. Three of four masters sat idle on a 12-core box with a load average below 1. The cause was caps of 1, 4 and 2, set against a failure mode that two other mechanisms already covered. The defence was real, but the caps were the wrong place to put it.
Fix forward, never pre-gate
Build, merge and deploy. If something breaks, revert it or fix it and redeploy. We measured the alternative, a human approving each step, and it is slower at every scale this fleet runs at. This rule does not allow skipping verification, force-pushing shared branches, or claiming a result nobody observed.
Never resurrect
-
ralph and every watchdog, heartbeat and supervisor built to watch over it.
-
The NATS bus and its three remaining leftover units.
-
The generated component catalog and any fail-closed gate built on a generated map.
-
Content-keyword gates of any kind.
-
Resident processes, heartbeat polls and scheduled health checks as a way to know whether the fleet is alive. The board is the queue. A run's own close-out comment is the liveness signal. The one page that reads current state reads it at request time and stores nothing.
There is one sanctioned exception, and we name it here so you do not have to find it with
ps. Each master's own crontab has a*/5 * * * *entry that POSTs that master's record to the command centre. The command centre stores it with a five-minute TTL. When the TTL expires, the box turns grey on a dashboard a human is watching. That is a self-reported heartbeat, and we adopted it on purpose (2026-07-22) because it answers a different question. It tells you which box is online right now, not whether this work is alive. Nothing in the work loop reads it. No dispatch, gate or close-out depends on it, and that keeps the ban meaningful. Separately, a master's own standing interactive session stays resident until someone restarts it (measured at 13.3 days on one master, 2026-08-23). No dispatched run leaves a process behind. The ban is on resident machinery as the way to answer "is the fleet alive". It has never banned every scheduled job. A reader who runs the walk from inside this fleet will find both of these, so we wrote them down rather than let them look like drift (knowledge#6665).
If you think one of these should come back, write a superseding decision record that names what changed. Do not rebuild it.
Surfaces
Every hostname the fleet owns or references, each with an explicit class of LIVE, RETIRED-BY-DECISION or BROKEN. A status code by itself does not tell you the state.
Operations
Runbooks for the Utopia fleet. Every command runs exactly as written and names the box it runs on, because a command that works from one machine and fails from another is not a runbook.