# The Utopia fleet (/docs)
## Point your agent at this fleet [#point-your-agent-at-this-fleet]
This site is written for agents as much as for people. Every page is also served as raw markdown at the
same path with `.md` appended — [`/docs/structure/architecture.md`](/docs/structure/architecture.md) — and
[`/llms.txt`](/llms.txt) indexes all of them. The whole corpus in one fetch is
[`/llms-full.txt`](/llms-full.txt), about 120 KB — and 46 KB of that is the five prompts below.
Five meta prompts follow, each run to completion in the harness it names. Two make an agent
understand this fleet, one turns it on **your** setup, and two build you a fleet of your own. Pick a
harness and hit copy.
```text title="prompt-1|Understand this fleet|desktop|Reads the wiki in three passes and hands back a briefing that sorts every claim into measured and asserted. Finishes in one paste."
You are reading a public wiki that documents a real, running AI agent fleet — one Ubuntu server, a GitHub project board as the only work queue, and no orchestration layer between the agents. I want you to understand it well enough to argue with it.
Work in three passes. Do not summarize until the third.
Pass 1 — orient, in one fetch.
Fetch https://docs.utopiamodels.ai/llms.txt
It lists every page on the site with a one-line description. Read the paths out of it rather than taking any list of pages from me — the site can add a page, and an index you read is current where a list I typed is not.
If that fetch fails or returns something that is not a plain-text index, fetch https://docs.utopiamodels.ai/llms-full.txt instead. That is the whole documentation corpus in one plain-text file. If it succeeds, read it and skip Pass 2 — it carries the documentation pages in full but not the live page, which you would then fetch on its own. If both fail, tell me what you got and stop; the rest of this depends on them, and guessing at URLs wastes your fetches on 404s.
Pass 2 — read the corpus.
Every path on that site is also served as raw markdown: append ".md" to any path the index listed — same content, no navigation chrome. Fetch the .md form of every path it listed, including the live page.
Fetch all of them before you form a view. The pages disagree in places, and a view formed from three of them is a view of the disagreement rather than of the system.
If a fetch under /docs/ returns an empty body, that is a 404 and the path is wrong rather than the site being down; re-read the index and use the path it lists. A wrong path outside /docs/ returns a rendered error page instead, so prefer the paths the index gave you.
Read https://docs.utopiamodels.ai/live.md last.
That page is generated at the moment you request it. It holds no snapshot and no cache — it reads the fleet's own state files and renders them, so its "generated:" timestamp advances every time anyone loads it. Everything else on the site was written by a human at some point in the past and may have rotted since. This one cannot rot; it can only be wrong about right now. Read it as the correction to whatever the static pages told you.
Three things on that page will otherwise read as errors.
A line beginning "ERROR:" under a section heading is content, not a failed fetch. When one upstream source is unreachable, that section prints the reason and the rest of the page renders normally. A page that says "this number is unavailable and here is why" is doing its job.
Its tables report per row, so an unreachable host prints its reason inside that row's cell rather than failing the table. A cell reading "unreachable (DNS: no answer (ENOTFOUND))" is a measurement, and the row beside it says whether that result was expected.
The agent table splits every agent into a registry column and a "can run" column, under a heading stating that existence is not liveness. An agent can have a valid account on the box and still be unable to take a turn. The table also carries two timestamps per row, "measured" and "published", and a dash under "measured" is not missing data — the page explains the rule.
That table is also where you settle the fleet's size for yourself. It renders more rows than the headline count of agents, because it lists several classes of agent and only one of those classes does dispatched work. Work out from the class column which rows the count refers to before you repeat any number back to me, including any number in this prompt.
Pass 3 — brief me.
Before you write anything, sort every substantive claim you collected into two piles.
Measured: the page gives a command, a status code, a file path, a count, or a dated probe — something a reader could run to make the claim false. Several of these are checkable against the live page you read last, so check them.
Asserted: the page states it as fact with nothing behind it.
Sort explicitly. An argument backed by a number you can check is a different object from the same argument backed by conviction.
Then write:
1. The shape of the system, in one paragraph. Hosts, agents, how a unit of work gets from an idea to a merged pull request, and what schedules it. Name the mechanisms rather than describing them abstractly — if work passes through a queue, say what the queue is.
2. The three design decisions that most define this fleet, and the price of each. A decision with no cost attached has not been understood, so if you cannot name what a choice gives up, say so and treat it as a gap in the documentation rather than as a free lunch.
3. What this fleet deliberately does not have. It has removed things on purpose — a message bus, a resident supervisor loop, a database of record, various gates. For each absence, say what it pays and what it buys, and distinguish an absence the documentation argues for from one it merely mentions.
4. The gap between the documented system and the live one. Where the static pages and the live page disagree, the live page wins. Say what is currently degraded, what the documentation would have led you to believe instead, and whether the documentation is stale or the state is temporary. This section is why the live page exists, so do not skip it because the static pages read cleanly.
5. The two or three claims you would want evidence for before believing them, and what evidence would settle each.
6. The one thing you did not expect.
Skip the throat-clearing and the recap of what you just did. If a page contradicts another page, quote both and say which you believe. If you think a decision on that site is wrong, say so and why — an agreeable summary of someone else's architecture is worth less to me than a specific objection.
This fleet has a live cockpit at https://utopiamodels.ai. Open it yourself rather than fetching it; it renders client-side, so a fetch returns an empty shell. The wiki's live page carries the same state in a form you can read.
```
```text title="prompt-2|Understand this fleet|claude-code,opencode,codex,cursor,gemini|Fans subagents across the wiki, checks the prose against the live board, and reports the drift. Finishes in one paste."
You are going to read the public documentation of a real, running AI agent fleet and report what it actually is. Two public surfaces, no auth on either:
- https://docs.utopiamodels.ai — the wiki, the written argument for how the fleet is built
- https://utopiamodels.ai — the OCC, its live cockpit, and the counterpart that either confirms the prose or contradicts it
Do not edit, create, or delete any file on my machine. This is a read-and-report task from start to finish.
## Step 1 — establish you can reach it, and derive the reading list
Run this first:
curl -s https://docs.utopiamodels.ai/llms.txt | head -3
You should get a line reading `# The fleet` followed by a bulleted entry for `/docs`. If you get empty output or a non-zero exit, curl is not reaching the host from this machine. Try `python3 -c "import urllib.request; r=urllib.request.urlopen('https://docs.utopiamodels.ai/llms.txt',timeout=20); print(r.status, len(r.read()))"`, which should print `200 1506`, or fall back to wget, or use whatever web-fetch tool you have. Tell me which one worked before continuing, because everything below depends on it.
Now derive the page list rather than trusting one I typed:
curl -s https://docs.utopiamodels.ai/llms.txt | grep -oE '\(/[a-z/-]+\)' | tr -d '()' | sed 's|^|https://docs.utopiamodels.ai|;s|$|.md|'
That emits seven URLs: the six documentation sections plus the live page. Deriving them from the index means this instruction survives the site adding a page, so use the output rather than a list from memory.
Every path on this wiki has a raw markdown twin at the same path with `.md` appended. Fetch those, not the rendered HTML. A `.md` path under `/docs/` that does not exist returns a zero-byte 404, so a wrong guess there costs you nothing and puts no error page into your context.
## Step 2 — read the seven pages in parallel
Dispatch subagents, one per URL, running concurrently. Sequential fetching wastes the main advantage you have here.
Give each subagent the same reporting contract, because the synthesis depends on comparing like with like:
1. The claims the page makes, as claims, not as a summary of its prose.
2. Which of those claims are backed by something checkable — a command written out, a status code, a file path, a dated measurement — and which are asserted with nothing behind them. Quote the evidence where it exists.
3. Any statement that contradicts another page, or that contradicts itself.
4. Numbers, verbatim, with their units and their dates.
Use the seven URLs as the pipeline emitted them rather than reconstructing paths by hand, since five of the six sections live under `/docs/` and only the overview sits at the top level. By subject they are: the overview, architecture, operations, surfaces, work, and decisions. Match each subject to its URL from the output above.
The seventh is the live page. Tell its subagent that it is generated at request time and stores nothing, so it is the only page whose numbers describe now rather than a moment when someone wrote them down. Its own header states `store: none — no snapshot file, no cache, no revalidate window.` Two fetches a second apart show its `generated:` stamp advancing, which you can check directly.
If you would rather spend one fetch than seven, `curl -s https://docs.utopiamodels.ai/llms-full.txt` returns all six static pages concatenated in about 81 KB. It excludes the live page, so fetch that separately either way.
## Step 3 — check the prose against the live surfaces
The wiki argues that this fleet publishes measurements rather than descriptions. Test that claim rather than accepting it.
Agent run-state, the highest-signal line available to you:
curl -s https://docs.utopiamodels.ai/live.md | grep -E '^\| (agent|agent-sf|win|nova|cloud|dust) \('
You get one table row per agent, splitting what the registry says exists from whether that agent can currently take a turn. Read the two columns as separate facts, because the page's argument is that they are. If the grep returns nothing the table shape has changed, so fetch the live page whole and find the section headed `Can each agent actually run?`.
Board state, from the OCC rather than the wiki:
curl -s https://utopiamodels.ai/v1/board | python3 -c "
import json,sys
d=json.load(sys.stdin)
if not d.get('ok'):
print('board unavailable:', d.get('error') or 'unknown'); sys.exit(0)
print('generated:', d.get('generatedAt'))
print('counts:', d.get('counts'), 'lanes:', d.get('byLane'), 'items:', len(d.get('items') or []))
"
This is the only public JSON endpoint on the OCC and needs no jq, since you may not have it. If `python3` is missing, `curl -s https://utopiamodels.ai/v1/board | head -c 400` shows you the same fields by eye.
Read the `ok` field before anything else, because it separates three outcomes that look similar and mean different things.
A JSON decode error means the endpoint returned HTML rather than JSON. Treat that as unreachable, not as an empty board.
A response where `ok` is false and `error` carries a message is the endpoint telling you its upstream read failed. A value like `github graphql 403` means the OCC could not read the board from GitHub at all, so the item list is empty for that reason and no other. Re-run it a few seconds later. This endpoint does flip between healthy and refused, so a second attempt is worth making — but if the same error persists, report it as what you measured, with the error string quoted and the number of attempts you made. Do not record it as an empty board, and do not describe the fleet's work queue from the wiki's account of it when this endpoint could not confirm a single item.
A response where `ok` is true, `generatedAt` is present and the item list is populated is the healthy shape. Only then are the counts worth quoting.
The parser uses `.get()` throughout for exactly this reason: indexing directly into the counts crashes on the unhealthy shapes, and a traceback tells you less than the error string does.
Then look for the correlation. When the board endpoint degrades, the live page prints a line beginning `ERROR:` in its own board section, stating that the upstream returned no timestamp so its age cannot be stated honestly. An `ERROR:` line on that page is data the page chose to publish about itself, not a failed fetch on your end. Whether a documentation site reports its own broken upstream in place of quietly showing a stale number is a design decision you should judge, and you can observe it directly.
One caution on the OCC. `https://utopiamodels.ai` is a client-rendered application, so fetching the root with curl gives you the shell and a few loading strings rather than the live sections. Use `/v1/board` for data. If you have a browser tool the rendered page is worth a look; if you do not, say you could not see it.
## Step 3b — find the ceiling on what you can prove
Fetch the section of the live page headed `What this page cannot reach — and the command that can`:
curl -s https://docs.utopiamodels.ai/live.md | sed -n '/What this page cannot reach/,/How this page is produced/p'
It lists facts a public page cannot establish, each paired with the command that would settle it and the box that command runs on. Those commands reach private infrastructure, so you cannot run them, and that is the point of reading the section: it draws the line between what you have verified and what you are taking on trust.
The sharpest example is stated there directly. Whether an agent account exists and whether that agent can complete a turn are different questions, and the page refuses to source the second from the first. Carry that distinction into your synthesis. Where you can only confirm that something exists, say so, rather than reporting it as working.
## Step 4 — synthesize, in your own voice
Not a page-by-page summary. I can read the pages. Give me:
- The architecture in one paragraph, at the level of what runs where and what carries state between runs.
- How one unit of work travels from an idea to a merged pull request, naming every mechanism it passes through. Where the pages leave a gap in that chain, name the gap rather than bridging it with a plausible guess.
- What this fleet deliberately does not have. For each absence, what it buys and what it costs. The decisions page argues these were chosen rather than missing; say whether you find the argument carried by evidence.
- Where claims are measured versus asserted. Give me the two or three you would want evidence for before believing them, and say what evidence would settle each.
- Any disagreement you found between pages, or between a page and what you measured in step 3. Report it plainly. A wiki that publishes its own drift is making a claim, and disagreements you find are the test of it. If the board endpoint returned an error rather than data, say so in its own line, quote the error, and state which parts of your synthesis you could not check against it.
- The one thing you did not expect.
Where a number you measured differs from a number a page states, give both and note the gap.
```
```text title="prompt-3|Understand this fleet, then plan your integration|claude-code,opencode,codex,cursor,gemini|Measures your own repo, config and automation, then ranks what is worth taking and what it would delete. Reads only; writes nothing. Finishes in one paste."
You are going to study a real, running AI agent fleet, then compare it against my setup and tell me what is worth taking. Read this whole prompt before you start.
## What you will do, in order
1. Read the public wiki of another fleet. No writes.
2. Fan subagents across my repo and my agent configuration to measure what I actually have. Read-only.
3. Write one ranked integration plan to the terminal.
You will not create, edit, move, or delete any file of mine at any point in this session, including config, dotfiles, and anything under `.claude/`. If you conclude a change is worth making, describe it and stop. I will make it myself or tell you to. The reason is that a plan I can read and reject costs me two minutes; a change you made while I was reading costs me a bisect.
Work through step 1 and step 2 in parallel — they touch nothing in common.
## Step 1 — read the other fleet
Start here, because it tells you what exists rather than making you guess:
~~~
curl -s https://docs.utopiamodels.ai/llms.txt | head -3
~~~
That is a public wiki, no auth, CORS open, no user-agent needed. If curl is missing or the output is empty, fall back to `python3 -c "import urllib.request;print(urllib.request.urlopen('https://docs.utopiamodels.ai/llms.txt',timeout=20).read().decode())"`, or wget, or your own fetch tool. If all of those fail the machine has no outbound network and you should say so and go straight to step 2 — the comparison is weaker but the audit of my setup still stands on its own.
Derive the page list from the index rather than hardcoding it, so this prompt does not rot as the wiki grows:
~~~
curl -s https://docs.utopiamodels.ai/llms.txt | grep -oE '\(/[a-z/-]+\)' | tr -d '()' \
| sed 's|^|https://docs.utopiamodels.ai|;s|$|.md|'
~~~
That emits seven URLs. Every rendered page has a raw markdown twin at the same path with `.md` appended; fetch those, not the HTML. A wrong `.md` path under `/docs/` returns a zero-byte 404, so a mistake costs you nothing and puts no HTML error page into your context.
Dispatch one subagent per URL, running concurrently. Give each the same brief: return the claims its page makes, which of those claims are backed by a command or a dated measurement rather than asserted, and any claim that contradicts another page.
Include the live page in that fan-out. It is generated at request time and stores nothing, so it is the only page whose numbers are current. It reports failure per section — a line beginning `ERROR:` inside it is that page honestly reporting an upstream it could not reach, not a broken fetch on your end. Treat it as data.
Two commands worth running yourself rather than delegating, because they are the fastest evidence that any of this is real:
~~~
curl -s https://docs.utopiamodels.ai/live.md | grep -E '^\| (agent|agent-sf|win|nova|cloud|dust) \('
curl -s https://utopiamodels.ai/v1/board | python3 -c "
import json,sys
d=json.load(sys.stdin)
if not d.get('ok'):
print('board unavailable:', d.get('error') or 'unknown'); sys.exit(0)
print(d.get('generatedAt'), d.get('counts'), d.get('byLane'))
"
~~~
The first returns one row per agent, splitting registry existence from whether that agent can currently take a turn. At the time this prompt was written one of them read `CANNOT RUN` with `resetsAt=null`, meaning nothing recovers it on a timer. The second is the live board, no auth and no jq required. It branches on `ok` because this endpoint degrades rather than failing: when its upstream read is refused it still returns valid JSON with `ok` false and an error string, and a fleet with no work and a fleet that cannot see its work are different claims. If the parser raises a JSON decode error instead, the endpoint returned HTML, which means an outage — treat it as unreachable.
`https://utopiamodels.ai` is the command center itself, but it is a client-rendered app, so curl returns a loading skeleton. Use the two endpoints above for state and ignore the root URL unless you have a browser tool.
What you are looking for in step 1 is not features. It is the shape of the decisions: what that fleet deliberately does not have, and what each absence buys and costs. Their wiki argues that most agent infrastructure is supervision machinery whose only job is keeping a resident process honest, and that deleting the resident process deletes the machinery. Judge that argument on their evidence.
## Step 2 — measure my setup
Dispatch subagents in parallel, one per area below. Each one reports what it measured, with the command or the file path that produced the number. A subagent that reports an impression instead of a measurement should be sent back. Each subagent is read-only and writes no files, the same constraint that binds you.
Commands below suppress their own error output, so a thing that does not exist on my machine yields nothing on stdout and a non-zero exit status. Read the empty output as a finding; do not read the exit code as a tool failure and retry.
**Instruction surface.** How much prose is loaded into every session before I type anything.
~~~
find . -maxdepth 4 -name 'CLAUDE.md' -not -path '*/node_modules/*' -exec wc -c {} +
ls -la ~/.claude/ 2>/dev/null
find ~/.claude/rules ~/.claude/skills ~/.claude/agents -maxdepth 2 2>/dev/null | head -50
~~~
Report total auto-loaded bytes. This is charged on every turn of every session.
**Automation that runs without me.**
~~~
python3 - <<'PY'
import json,os
p=os.path.expanduser('~/.claude/settings.json')
if not os.path.exists(p):
print('ABSENT: no ~/.claude/settings.json'); raise SystemExit
try:
h=json.load(open(p)).get('hooks',{})
print(json.dumps(h,indent=1)[:3000] if h else 'PRESENT, NO HOOKS CONFIGURED')
except Exception as e:
print('MALFORMED:',e)
PY
ls .claude/settings.json .claude/settings.local.json .mcp.json 2>/dev/null
ls .github/workflows/ 2>/dev/null
~~~
For each hook and each workflow, report what triggers it, and whether it blocks or merely warns. A blocking check that fires on a convention rather than on an invariant is a finding. The settings reader prints which case it hit, because an absent config and a corrupt one are different findings and they otherwise produce the same silence.
**What is actually maintained.** Documentation rots silently; churn tells you which files are alive.
~~~
git log --since='90 days ago' --name-only --pretty=format: -- '*.md' | grep -v '^$' | sort | uniq -c | sort -rn | head -20
git ls-files '*.md' | wc -l
~~~
A markdown file with zero commits in ninety days that is nonetheless auto-loaded into every session is dead weight being paid for continuously.
**How work is tracked.** Count the places a task can live: issue tracker, a TODO file, a plan file, a project board, comments. Run `git ls-files | grep -iE 'todo|backlog|roadmap|tasks?\.md'` for file-based trackers. For a hosted one, run `gh issue list --limit 5` if `command -v gh` finds it and it is authenticated; if it is missing, run `git remote -v` and report that the tracker could not be inspected from this machine rather than concluding there is none. Two trackers cannot both be true, and the failure is silent — one of them just stops being read.
**How agent work reaches the repo today.** Whether anything runs without me starting it, and whether it supervises itself.
~~~
crontab -l 2>/dev/null; ls ~/.config/systemd/user/*.service 2>/dev/null
ps -eo comm= | grep -iE 'claude|agent|watch|daemon' | sort -u
~~~
A resident process is the expensive kind of infrastructure, because most of what surrounds it exists only to keep it honest.
**Where state lives.** Any tracked file that caches a fact another system owns.
~~~
git ls-files | grep -iE '(generated|snapshot|manifest|cache|index|state|status)\.(json|ya?ml|md)$'
~~~
Each hit is a place that can silently disagree with reality, and the disagreement is never announced.
## Step 3 — the plan
Write it to the terminal. No files.
Rank every recommendation by leverage, highest first. For each one, five lines:
- **The change** — stated concretely enough that I could do it without asking you a follow-up question.
- **The evidence** — the measurement from step 2 that makes this worth doing on my setup specifically, not in general.
- **What it deletes** — the file, the hook, the workflow, the tracker, the process that stops existing. If a recommendation adds without deleting, say so plainly, because that is the expensive kind.
- **The cost** — what this makes harder, slower, or riskier. Every one of these has a cost and a recommendation without one is unfinished.
- **How I would know it worked** — a command I can run, with the output that means success.
Order them so an early item unblocks later ones, and say which item is a prerequisite for which.
Weight deletions above additions. A fleet that runs on a GitHub board, systemd, and git is not impressive because of what it built; it is fast because of what it refused to build, and every mechanism it did not add is one it never has to keep honest. Apply the same standard to my setup. If the honest answer for a section is that my current approach is better than theirs, say that and move on — I am not looking for a migration, I am looking for the delta that is worth paying for.
Two things to call out explicitly, because they are where this kind of comparison usually goes wrong:
Name any pattern of theirs that only works because of something they have and I do not — a dedicated server, several agent accounts, a monorepo, a particular org plan tier. Their dispatch model puts a run on a separate box under a separate account; if I have one machine and one account, the mechanism does not transfer even though the principle behind it might. Say which half transfers.
Separate what their documentation claims from what their live endpoints show. Where those two disagree, the disagreement is the more useful lesson, and their own wiki publishes several of them rather than hiding them.
Close with the single highest-leverage change I could make this week, in one sentence, and the command that would tell me it worked.
## If you run out of room
This is a large task: seven pages of theirs, six areas of mine, then a synthesis. If you are running low on context before step 3, stop fanning out and write the plan from what you have — say at the top which areas you did not get to and what command would cover them next time. A ranked plan over partial evidence, with the gaps named, is worth more to me than a complete audit you had no room to conclude.
```
```text title="prompt-4|Scaffold a knowledge base|claude-code,opencode,codex,cursor,gemini|Builds the four documentation patterns this fleet runs on into your repo, after showing you the manifest and waiting for a yes. Finishes in one paste."
Scaffold me a knowledge base — one that an AI agent working in my repo actually reads, on the patterns of a real running fleet documented at https://docs.utopiamodels.ai. You will write files into my repo. Read this whole prompt before your first write.
## What you are going to do, before you do it
1. Learn the pattern from the reference wiki (read-only, external).
2. Read my repo and derive what my knowledge base should contain, from my code and my history.
3. Show me a one-screen file manifest and wait for my go-ahead — asked with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), not in prose.
4. Write the files, populated from my context.
5. Run the checks below and paste the real output.
Nothing in step 4 happens before I answer step 3.
## Step 1 — learn the pattern
Run this to get the reference index and derive every page it lists as raw markdown:
curl -s https://docs.utopiamodels.ai/llms.txt | grep -oE '\(/[a-z/-]+\)' | tr -d '()' \
| sed 's|^|https://docs.utopiamodels.ai|;s|$|.md|'
That emits seven URLs. Dispatch subagents to fetch them in parallel — one page each. Fetch the `.md` twins, not the rendered HTML. If `curl` is missing, substitute:
python3 -c "import urllib.request;print(urllib.request.urlopen('https://docs.utopiamodels.ai/llms.txt',timeout=20).read().decode())"
If the whole host is unreachable from my machine, say so in one line and continue from the patterns described in this prompt — they are stated below in enough detail to build from without the wiki.
Two pages matter most for this job: the work page (how work and its briefs are structured) and the decisions page (how decisions are recorded, superseded and killed). A missing `.md` path under `/docs/` returns a zero-byte 404, so a wrong guess costs you nothing and pollutes nothing.
## The four patterns you are implementing
**Portfolio-first.** Knowledge is organised by the thing it is about, not by document type. A folder per portfolio — per product, per service, per business line — each holding its own `CLAUDE.md`, its specs, and its decisions. A top-level `docs/architecture/` next to a top-level `docs/runbooks/` splits every subject in half and guarantees that reading one gives you a partial answer. Subject folders keep a subject whole.
**Doctrine that auto-loads.** A small set of behavioral files that the agent harness reads at the start of every session, with no one remembering to point at them. In Claude Code that is `CLAUDE.md` at the repo root plus one per portfolio directory. The discipline is size, because this text is prepended to every session forever: a rule earns a place only if it changes what the agent does on most turns. Everything rarer goes in a reference file read on demand. The reference fleet caps the always-loaded set a worker agent sees at 30,000 bytes, and the script that generates that set refuses to emit one which breaches the cap — a ceiling something enforces, rather than an intention.
**Decision records with a lifecycle.** A record states what was chosen, what it replaced, why, and what it cost. Once accepted it is immutable — you do not edit an accepted record to reflect a change of mind. You write a new record that supersedes it, and you edit the old record's `Superseded-by` header in the same commit that adds the new one. A record that was silently invalidated is worse than no record, because it reads as current and is not. And a decision is not reopened by disagreement; it is reopened by new information, named in the superseding record.
The test for when to skip a record is the part that is learned expensively. The reference corpus reached 93 records with 86 of them written in a single month, which is a corpus nobody reads and therefore a corpus that stops being consulted before a decision — the exact failure it exists to prevent. Write a record only when one of these is true: the choice is expensive to reverse, or a future reader will otherwise re-litigate it. If neither holds, the reasoning belongs in the pull request body or the work item, and no record is written. Put that test at the top of the decision template so it is read before each one is written, not after the corpus is already unreadable.
**One tracker.** Whatever holds work state — an issue tracker, a board, a `plan.md` of checkboxes — there is exactly one, and the knowledge base points at it rather than mirroring it. Point at the tracker; store the durable why here.
## Step 2 — read my repo, and populate from my context
Dispatch subagents to run these in parallel and report what they measured, with the command behind each finding. Five of these read git history. If this is not a git repository, say so in one line, skip them, and derive portfolios and decisions from the file tree and any existing design docs instead — you will have fewer records, which is the correct outcome rather than a reason to invent them.
ls -la && cat README* 2>/dev/null | head -40
git log --oneline -30
git log --format='%s' -400 | cut -c1-40 | sort | uniq -c | sort -rn | head -20
find . -not -path '*/node_modules/*' -not -path '*/.git/*' \
\( -name 'CLAUDE.md' -o -name '.cursorrules' -o -name 'AGENTS.md' -o -name '*.mdc' \) | head -30
find . -maxdepth 3 -type d \( -name 'docs' -o -name 'doc' \) -not -path '*/node_modules/*'
git log --format='%H %s' -300 | grep -iE 'revert|instead of|replaced|migrat|switch' | head -30
The last one is the highest-value command in this list. Reverts and "replaced X with Y" messages are where decisions already happened without being recorded, and they are the only source of decision records that will be true rather than invented. Cite the commit hash this command printed. If you did not read a hash for a decision, that decision is not evidenced and does not become a record. Also read whatever design docs, ADRs, RFCs or pinned issues already exist — a knowledge base that ignores them creates a second tracker on day one.
Derive my portfolios from what the repo actually contains: top-level packages, deployable services, distinct product surfaces. If the repo is a single service, there is one portfolio and the structure is flatter — say that rather than inventing three folders to fill a template.
Do not write generic content. Every file you produce names my paths, my commands, my services. A file that would read identically in someone else's repo is a file that buys nothing and costs tokens on every session that loads it, so cut it.
## Step 3 — show me the manifest, then stop
Print a table: each file path, one line on what it holds, and whether it is auto-loaded doctrine or read-on-demand reference. Under it, name in one line each: my portfolios as you derived them, my one tracker, and the two or three decisions you found in my git history that are worth recording. Then ask me to confirm or correct with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), and wait. Options, not an open question: "write it as listed" first and marked RECOMMENDED, then the ways I am likely to want it changed. A question asked in prose carries no options and therefore no recommendation, and the recommendation is what lets me accept the default and still end up with something durable. Flag anything you would overwrite; prefer a suffixed new file over clobbering something I wrote.
## Step 4 — write it
The shape, adjusted to what you actually found:
- `CLAUDE.md` at the root — what this repo is, how to run and test it, the tracker, and a pointer to the portfolio files. Kept under about 4 KB, because it loads every session.
- `/CLAUDE.md` per portfolio — what it owns, how to run it, its boundaries, its gotchas.
- `docs/decisions/TEMPLATE.md` — the skip test first, then Status / Date / Context / Decision / Consequences / Superseded-by. Superseded-by present and empty on a new record, so the field exists to fill.
- `docs/decisions/NNN-*.md` — the records you derived from my history, written as the decisions they were, with the commit hash as evidence. Two or three real ones beat ten invented ones.
- `docs/reference/` — the read-on-demand tail. Things true but rarely needed.
Write dates as concrete ISO dates, not "today". Write paths and commands literally — "the deploy script" is not a path and helps nobody.
## Step 5 — check it, and paste the real output
find . -maxdepth 2 -name 'CLAUDE.md' -not -path '*/node_modules/*' -exec wc -c {} +
ls -R docs/decisions docs/reference 2>/dev/null
grep -rn 'Superseded-by' docs/decisions/ | head
git status --short
The first is the one that matters: it is the token bill on every future session. One line means one portfolio, which is a valid result — the number to read is the byte count, not the row count. If the root `CLAUDE.md` is over roughly 6 KB, cut it rather than defending it, and move what you cut into `docs/reference/`. Then grade yourself out loud on one question per file: does this change what an agent working here does? Name the files where the answer was no and delete them.
Do not commit. Leave everything staged as working-tree changes so I can read the diff.
## If you run out of room
This finishes in one paste on a normal repo. If my repo is large enough that step 2 does not fit, stop after step 3, write the manifest to `docs/KB-PLAN.md`, and tell me to start a fresh session pointing at that file. A half-written knowledge base is worse than a manifest, because the half that exists reads as complete.
For live context on the reference fleet as you work — what it is running right now, including the parts that are broken — fetch https://docs.utopiamodels.ai/live.md. It is generated per request and stores nothing, so an `ERROR:` line inside it is a real upstream failure being reported honestly, not a failed fetch on your end. The fleet's cockpit is at https://utopiamodels.ai, and its board state is one fetch away:
curl -s https://utopiamodels.ai/v1/board | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('generatedAt'), d.get('counts')) if d.get('ok') else print('board unavailable:', d.get('error'))
"
```
```text title="prompt-5|Scaffold the full fleet|claude-code,opencode,codex,cursor,gemini|Builds the dispatch scripts, the board wiring and whatever accounts it can reach, then hands you an ordered checklist for the parts only you can do. Does not finish in one paste."
You are going to build me a working agent fleet, modelled on a real one that is running and publicly documented. A fleet here means: several agents that execute work unattended on a machine that is always on, a single queue they draw from, and a record of what happened that survives every process exiting.
Part of the job cannot be finished in this session, and knowing which part changes the order you do the rest in.
## What you can finish today, and what you cannot
Everything that lives in a repository — the scripts, the runner, the queue client, the agent definitions, the install steps, the docs — you can write and test in this session.
What you cannot do is anything that needs me: creating accounts, paying for subscriptions, putting an SSH key on a machine I own, deciding which machine stays on. You will hand me a checklist for those at the end. Do not try to work around them, do not stub them with fake credentials, and do not report the fleet as running when the parts that make it run are still on my checklist.
## Step 1 — Study the reference fleet
The fleet is documented at https://docs.utopiamodels.ai and its live cockpit is at https://utopiamodels.ai. Start with the index:
curl -s https://docs.utopiamodels.ai/llms.txt | head -3
You should see `# The fleet` and a bulleted list. If that command prints nothing or exits non-zero, the site is unreachable from this machine; try `python3 -c "import urllib.request;print(urllib.request.urlopen('https://docs.utopiamodels.ai/llms.txt',timeout=20).read().decode())"` or `wget -qO- https://docs.utopiamodels.ai/llms.txt` before concluding it is down.
Derive the page list from the index rather than hardcoding URLs, so this stays correct as the wiki grows:
curl -s https://docs.utopiamodels.ai/llms.txt | grep -oE '\(/[a-z/-]+\)' | tr -d '()' | sed 's|^|https://docs.utopiamodels.ai|;s|$|.md|'
That emits seven URLs. Every documentation path has a raw markdown twin at the same path plus `.md`, a fraction of the size of the rendered page with the same prose. A mistyped `.md` path under `/docs/` returns an empty 404 rather than an HTML error page, so a wrong guess costs you nothing.
Dispatch subagents to read those pages in parallel. The three that matter most for building are the architecture page (hosts, agents, the dispatch runtime), the work page (how a unit of work travels from filed to merged), and the operations page (the runbooks, including the literal dispatch and scheduler commands).
Then read the live surface, which is generated per request and stores nothing:
curl -s https://docs.utopiamodels.ai/live.md | grep -E '^\| (agent|agent-sf|win|nova|cloud|dust) \('
You get one table row per agent, split into what the registry says exists and what can actually run. At the time this was written one agent read `CANNOT RUN — resetsAt=null — NOTHING brings this back on a timer. It needs a human.` That is the reference fleet publishing its own broken part. Build the same distinction into what you scaffold, because an agent's account existing is not the same claim as that agent being able to take a turn, and conflating them is how a fleet looks healthy while shipping nothing.
For the queue shape, one fetch gives you real board state without needing `jq`:
curl -s https://utopiamodels.ai/v1/board | python3 -c "
import json,sys
d=json.load(sys.stdin)
if not d.get('ok'):
print('board unavailable:', d.get('error') or 'unknown'); sys.exit(0)
print('open:', len(d.get('items') or []), '· lanes:', d.get('byLane'), '· status:', d.get('byStatus'))
"
A healthy response prints one line of counts keyed by lane and by status. This endpoint reads a project board at request time and stores nothing, so it degrades rather than fails: when the upstream read is refused it still returns valid JSON with `ok` false and an error string. That is why the command branches on `ok` before reading anything else — an intermittent refusal would otherwise render as an empty board, and a fleet with no work and a fleet that cannot see its work are different claims. Build that same distinction into the status command you write in step 3. If the parser raises a JSON decode error instead, the endpoint served HTML, which means an outage; treat it as unreachable.
Note the lane keys. In that fleet a lane names which machine runs the work, not what kind of work it is — routing by subject matter idles healthy agents behind a busy one. Work on a lane no scheduler reaches is undispatchable no matter how idle the fleet is, so a large open count against zero dispatchable looks like a capacity problem and is actually a routing one. Carry that meaning across.
## Step 2 — Learn my machine before you write anything
Run these and tell me the answers, because they decide which variant of the scaffold you build:
uname -s; command -v git gh claude docker python3 2>/dev/null
gh auth status >/dev/null 2>&1 && echo "gh: authenticated" || echo "gh: not authenticated"
systemctl --user show-environment >/dev/null 2>&1 && echo "systemd user scope: usable" || echo "systemd user scope: unavailable"
The systemd answer is the fork that matters. The reference fleet starts each run as a transient systemd unit, which gives it process isolation, a log, and a failure hook for free. If that check says unavailable — macOS, a container, a machine without a user session bus — build the launchd variant on macOS or a supervised loop elsewhere, and say plainly in the docs you write which variant this is and what it gives up. Do not emit systemd units onto a machine that cannot run them.
Also look at what I already have, with subagents in parallel: my repository layout, any `CLAUDE.md` or `.claude/` directories, how I currently run agents if I do, and where my notes and docs live. Have each subagent report what it measured and the path or command behind it. If I already have a queue — a project board, a tracker, a task list — plan to use it rather than adding a second one. Two queues cannot both be authoritative, and the reference fleet's entire design rests on there being exactly one.
## Step 3 — Tell me the plan, then build
Before writing a single file, tell me: which directory you will create, every file you will add, anything existing you would change or delete, and the one-line reason for each. Wait for me to answer. After that you have my go-ahead for the whole list and should build it without checking in again.
Build these pieces:
A **dispatch script** that takes an agent name and a work item id, and starts a run. Starting the run is the assignment — there is no separate "assign" step and no state file recording who owns what, because a file recording that would drift from reality within a day. Before launching, it should refuse to start a second run for a work item that already has one live, and refuse an agent whose account is known to be blocked. Both refusals exit non-zero and print why.
A **runner** that is what actually executes on the agent's side. It should update itself from the repository first, re-check that the work item is still open before doing anything expensive, post a start marker before the first thing that can fail, run the agent, and post a terminal marker with a link to whatever it produced from an exit trap so the marker gets posted even on a crash. Make the terminal marker derive from real evidence — a merged change, a created artifact — rather than from the exit code. A run that exits zero having produced nothing should say so.
A **scheduler** that periodically reads the queue and starts runs for anything unstarted. Give it explicit caps on concurrent runs per agent and fleet-wide, and write the measurement that justifies each number in a comment beside it. A cap chosen out of caution silently becomes the system's capacity and never announces itself. Give it a kill switch that needs no code edit — the presence of a file the scheduler checks before each cycle is enough, and it should be stoppable without editing or redeploying anything.
A **queue client** over whatever tracker step 2 found. If it is GitHub, `gh issue list --json` is enough and you should not add a library. What a fleet needs from a queue is small: list open items with their lane and status, read one, comment on one, close one.
**Agent definitions** — one directory or file per agent, holding its instructions and what it is allowed to touch. Keep the count low to start. Two agents exercise every mechanism a fleet has; ten agents just multiply anything you got wrong.
A **status command** that answers "what is this fleet doing right now" by reading the queue and the process table live, computing nothing from a stored file. Have it print, for each agent, existence and ability-to-run as two separate columns, the way the reference live page does.
A **README** stating what runs where, the literal command for each operation, and which machine each command must be run from. A command that works from one machine and silently fails from another is the most common defect in this class of system.
Now verify what can be verified without my accounts. Run the dispatch script against a fake work item and show me it refuses cleanly. Run the scheduler with its caps set to zero and show me it explains why it started nothing. Run the status command and show me the output. Syntax-check every shell script you wrote — `bash -n` on each — and show me it passes. Do not tell me the fleet works; show me the commands and their output, and be explicit that these prove the scripts run, not that the fleet ships work, which cannot be true until my checklist is done.
## Step 4 — What I have to do myself, and what each unlocks
End with this as a section, written into the README as well as told to me. For each item: what to do, roughly how long, and what stops being broken once it is done. Cover at least these, adjusted to what step 2 found:
- **An always-on machine.** Nothing about this design works on a laptop that sleeps, because the scheduler has to fire on a timer whether or not I am at the keyboard. Until this exists, runs only happen when I start them by hand. Tell me the cheapest thing that would work given what I already own.
- **An account per agent, each with its own subscription.** Agents run in parallel only because they draw on separate quotas; sharing one account across several agents means one busy agent stalls the rest. Until this is done, the fleet is one agent with extra directories.
- **SSH access from wherever I dispatch to the machine that runs.** Until this is done, dispatch works locally and not remotely.
- **The queue itself** — the project board or tracker, with the fields your queue client reads. Name the exact fields and their allowed values. Until this exists the scheduler has nothing to read.
- **Credential storage.** Name what you chose and what I have to create. Until this is done, secrets live in files where they should not be, and no agent should be started.
- **A failure notification path**, so a run that fails at 3am is visible without me looking. Until this is done, silence and success look identical.
Order that list so the earliest items unblock the later ones, and mark which ones I can skip to get a reduced fleet running today. Close with the single highest-leverage item, in one sentence.
```
## How to read this [#how-to-read-this]
This wiki is organized **by layer and by question**, never one page per component. That is not a style
preference — it is the fix for a measured failure. The fleet's internal reference corpus has 65 files,
one per component, and a reader asking *"can this agent run?"* lands in a page that is 100% about a
healthy agent. Organizing by artifact hides drift; organizing by the question a reader arrives with does
not.
Hosts, agents, the dispatch runtime, the services, and how secrets work.
Runbooks. Every command is executable exactly as written, and names the box it runs on.
Every hostname the fleet owns, each with an explicit class — LIVE, RETIRED-BY-DECISION, or BROKEN.
The board, lanes, the five-step plan lifecycle, specs, and why no gate can stop a merge.
What was built, killed, and why — so nobody rebuilds it.
## The four rules this wiki is written under [#the-four-rules-this-wiki-is-written-under]
Every page here obeys four rules. They exist because a full audit of the fleet's own documentation on
**2026-08-16** found sixteen places where a doc caused a reader to take a wrong action.
1. **Every command names its box.** A command that runs on one machine and fails on another is not a
runbook. You will always see `ssh nova@100.71.25.43 ''`, never a bare alias. On **2026-08-16**
the fleet's component registry held seventy-three rows, seventy-two of them carrying a self-check
safe to run from anywhere; run from the wrong box, twenty-six of those seventy-two failed, and all
twenty-six passed from the right one. Nothing in that registry records which box a command belongs
to.
2. **RETIRED-BY-DECISION, BROKEN and LIVE are three different states.** A hostname returning 404 because
someone deliberately retired it and a hostname returning 404 by accident look identical in a status
column. Collapsing them once cost a P0 incident: a master was dispatched to "restore" a surface that
had been switched off on purpose.
3. **An existence claim is not a liveness claim.** `getent passwd nova` succeeds on an agent that cannot
execute a single turn. Every "is it alive" statement on this site exercises the artifact — a real
`claude -p` probe for an agent, an HTTP status code for a surface, `Result=success` for a systemd
unit.
4. **Generate the volatile, write the durable.** Disk usage, uptime, node liveness and issue counts all
drift within days of being written down. **No prose page on this site states one.** They live on
`/live`, which reads the fleet at request time and stores nothing. The existence of that one
generated page is what licenses every other page to be durable prose.
## The six-node tailnet [#the-six-node-tailnet]
The entire fleet lives on one Tailscale tailnet, `tail4016d7.ts.net`. Six nodes, and only three of them
are machines in the ordinary sense.
| Node | Tailnet IP | What it is |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **novaserver** | `100.71.25.43` | **The fleet's box.** An HP EliteDesk running Ubuntu 24.04. Hosts all four masters, the personal agent, the only secret store, and every scheduled timer |
| **ETH** (WSL guest) | `100.103.62.104` | The Architect's box — a WSL2 Ubuntu guest on the founder's Lenovo. Plans, files, dispatches |
| **utopiamodels** | `100.92.117.107` | **Not a machine.** A `tsnet` application node — an LLM gateway that joins the tailnet as if it were a host |
| ETH (Windows) | `100.89.61.32` | The Windows host underneath the WSL guest |
| macbook-pro | `100.125.68.66` | A collaborator's MacBook, running one agent |
| pixel-10-1 | `100.101.35.113` | The RAW prototype phone — a product, and a daily driver |
Which of those are online right now is a volatile fact and therefore is not stated here. `/live`
carries the fleet's discovered snapshot **with that snapshot's own age printed beside it**, and names
the one command that answers it live — because a page off the tailnet cannot read the tailnet, and
saying so is worth more than a dot that might be a day old.
## The dispatch loop [#the-dispatch-loop]
This is the entire runtime. Two shell scripts, both readable end to end, and systemd.
```
Architect (ETH) novaserver
│ │
│ dispatch.sh │
├──── ssh nova@100.71.25.43 ──────────────►│ systemd-run --user
│ │ task-.service (transient)
│ │ └─ claude -p, under a turn cap
│ ◄──── ▶ comment on the issue ───────────┤ read from the script itself
│ ◄──── ✔/✘ + PR link on exit ────────────┤
│ │ OnFailure= fleet-notify@ → files a board issue
```
**Starting the run IS the assignment.** There is no acceptance step, no claim, no lock file. Liveness is
the close-out comment the run posts on its own issue — if a run dies, the trap still fires and says so,
and if the whole unit fails, systemd's `OnFailure=` files a board issue about it. Silence is the only
failure mode this design cannot produce.
## What the numbers say [#what-the-numbers-say]
Measured **2026-08-16**, on the live fleet:
* **\~157 distinct dispatch runs in 24 hours** across the four masters, of which **2 failed** — and both
failures triggered the `OnFailure` path that files a board issue, so the failure-announcement
machinery is proven, not assumed.
* **745 issues closed in the preceding 7 days.**
* **3 of 4 masters could actually run** — not four. One is entitlement-revoked with no recovery timer,
and one survives only because it resolves to a different model provider at dispatch time.
* **14 places claim to be a source of truth**, not the five the fleet's own documentation implies.
* **Zero failed systemd units** in any of the five service contexts on the server.
Those are point-in-time measurements, and they are dated for exactly that reason. For the current
values, read `/live`.
## The honest framing [#the-honest-framing]
The fleet is real, busy, and structurally sound: a two-script runtime with no daemon to babysit, a
command center that provably does not drift because it stores nothing, complete failure-announcement
coverage (measured 2026-08-16: every transient run unit on every master carried the `OnFailure` hook),
and a converged doctrine payload across every box.
What it is **not** is the four-master, one-list, one-tracker system its own documentation used to
describe. It is three working masters, fourteen sources of truth, four parallel work trackers, and a
self-check mechanism that, at the time of the audit, had never been executed by anything.
The second description is the more impressive one, because it is checkable. This wiki publishes the
second.
# Module — Running Dispatched Agents on a Linux Server for Maximum Execution Efficiency (/docs/ai-infrastructure-upgrades/dispatch)
These prompts need a shell. Switch to any terminal harness to see them.
```text title="prompt-1|Study a fleet that runs, and measure mine against it|claude-code,opencode,codex,cursor,gemini"
STEP 1 of 4 — study a dispatch fleet that actually runs, and measure mine against it.
No interview. Everything in this step you can measure yourself, and asking me what you could have read is how you lose a technical person in the first five minutes.
CHECKLIST FIRST
Long sessions compress their own history and pasted instructions get dropped. Write this to ~/FLEET-GAP-CHECKLIST.md and work from that file:
# Fleet gap — step 1 of 4
## Tasks
- [ ] 1. Fetch and read both architecture files
- [ ] 2. Measure this machine against the nine must-haves
- [ ] 3. Fan out on what my environment actually raises
- [ ] 4. Write ~/FLEET-GAP.md
- [ ] 5. Report, and tell me to fetch step 2
## Rules
- Read only. I create this checklist and FLEET-GAP.md. Nothing else.
- Every claim about this machine carries the command that proves it.
- Where the reference fleet's answer does not fit here, I say so rather than recommending it anyway.
FETCH THE REFERENCE
https://docs.utopiamodels.ai/kit/fleet-architecture.md
https://docs.utopiamodels.ai/kit/dispatch-anatomy.md
Extracted from a working ~7,000-line dispatch runtime — four workers, one Linux box, about eighteen months of accumulated failures. Twenty design decisions, each paired with the specific failure that produced it, plus every pre-flight refusal in order and a ~250-line minimum viable version.
Read both in full before forming a view. They contradict most of what is written about agent orchestration, and the contradictions are the substance:
there is no orchestration layer
there is no message queue
there is no heartbeat
concurrency caps were DELETED, not tuned, after measuring 8% of proven capacity
the issue tracker IS the queue
completion is derived from a merged pull request, never self-reported
If a fetch fails, say what you got and stop.
Tick box 1.
MEASURE THIS MACHINE
dispatch-anatomy.md ends with nine must-haves — remove any one and the architecture stops working. For each, what does this machine have? Command, then answer:
1. an issue tracker with a status field, usable as a queue
2. a way to start a detached, named, supervised process
3. a wrapper that announces start and end on the work item
4. a forge whose merge state can be queried
5. a scheduler on a timer
6. pre-flight refusals
7. per-run isolated workspaces
8. a heartbeat
9. an attempt cap
Most people have 1, 2 and 4 already. The interesting answers are 3, 6 and 7 — almost nobody has them and almost everybody assumes they are not needed.
🔴 Two measurements decide whether any of this is possible here, and both are commonly wrong on a machine that looks fine:
Can a process survive me closing the terminal? On WSL that is whether systemd is PID 1:
ps -p 1 -o comm=
If that prints init rather than systemd, nothing here that runs in the background works, and the fix is /etc/wsl.conf with a [boot] section setting systemd=true, then wsl --shutdown from PowerShell. Say so plainly and design around it in the meantime.
Does PATH differ between shell types? Check all three:
bash -lc 'command -v node claude jq'
bash -c 'command -v node claude jq'
env -i bash -c 'command -v node claude jq'
A tool present in the first and absent in the third is why a job works when I type it and fails when a scheduler starts it. It is the most common first-fleet failure and it fails silently.
Tick box 2.
FAN OUT
If your harness runs subagents, use them — these are independent and slow in series.
What forge do I use, and does it support issue dependencies natively? If not, what carries ordering instead? Do not assume GitHub.
What supervisor does this machine have? The four properties needed are detached, supervised, named, tree-cleaning. Which does mine give me?
How many machines am I actually talking about? One box changes the locking answer completely.
What am I already paying for that could be the queue? A tracker my team already reads beats a new one.
What runs my agent non-interactively right now — is there a headless invocation, does it emit a machine-readable result, does it exit non-zero on failure?
Every answer needs the command or the source that produced it. An answer you cannot back is a line in WHAT I COULD NOT DETERMINE, not a finding.
Tick box 3.
WRITE THE FILE
~/FLEET-GAP.md. Five sections, these headings, this order:
## WHAT I HAVE
The nine must-haves, one row each: have it / partially / no, and the command that proves it.
## WHAT IS MISSING
Only the gaps. What it is, what specifically breaks without it, roughly what it costs to add, and which decision in fleet-architecture.md explains why it matters.
## WHAT DOES NOT TRANSFER
🔴 The section that makes this file honest, and the one you will be tempted to leave empty. The reference fleet made choices for reasons that may not be mine. Name every one that does not apply here and say why. One person on one machine does not need a fleet-wide lock. Work that produces no pull requests needs a different completion artifact entirely. A recommendation carried over unexamined is worse than none, because I will act on it.
## THE ONE THING FIRST
A single recommendation, not a list. The change that most reduces the distance between here and a working dispatch loop. One line of why, and what it costs.
## WHAT I COULD NOT DETERMINE
What you could not measure, and the command that would settle each.
Tick box 4.
FINISH
Three findings that most change what I should build, and the one thing you would do first.
Then say exactly this:
"Step 1 complete. FLEET-GAP.md is at ~/FLEET-GAP.md. Fetch step 2 from https://docs.utopiamodels.ai/docs/ai-infrastructure-upgrades/dispatch and paste it into a new session."
Then stop. Do not design anything — step 2 does that, and it reads this file.
Tick box 5.
```
```text title="prompt-2|Design my fleet, then stop for approval|claude-code,opencode,codex,cursor,gemini"
STEP 2 of 4 — design my fleet. From my intake and my gap report, not from a template.
This step produces a design and a manifest, and then STOPS for my approval. It writes no code.
CHECKLIST FIRST
Write ~/FLEET-DESIGN-CHECKLIST.md and work from it:
# Fleet design — step 2 of 4
## Tasks
- [ ] 1. Read FLEET-GAP.md
- [ ] 2. Decide the six shape questions
- [ ] 3. Put the genuine forks to me, in rounds of four
- [ ] 4. Write ~/FLEET-DESIGN.md and the file manifest
- [ ] 5. Show me the manifest and WAIT
- [ ] 6. On my approval, tell me to fetch step 4
## Rules
- I write two files and no code. Step 3 builds.
- Every number in this design carries the measurement or the assumption behind it.
- I decide what I can decide. I ask only what genuinely depends on Tyler's judgement.
READ THE GAP REPORT
~/FLEET-GAP.md
Step 1 wrote it: what this machine has against the nine must-haves, what is missing, and what of the reference deliberately does not transfer here.
If it is absent, stop and say so. Designing without it means designing for a machine you have not measured.
Tick box 1.
THE SIX SHAPE QUESTIONS
Answer each from MY situation. Where the reference fleet's answer applies, say so and why. Where it does not, say that louder.
1 — WHAT IS THE QUEUE?
The reference uses an issue tracker with a status field, because the queue should be the thing humans already look at. What do I already look at? If I have no tracker at all, that is the finding, and the cheapest correct answer is probably the forge I already push to.
2 — WHAT SUPERVISES A RUN?
The four properties needed are: detached, supervised, named, tree-cleaning. Name what gives me all four on this machine. If systemd is not running here, say what the alternative costs — and be concrete about it, because "use tmux" and "use systemd" differ in ways that only show up when something crashes at 3am.
🔴 The unit name carries the mutual-exclusion guarantee. Name my runs after the WORK, never the attempt. This is the single highest-value detail in the whole design and it costs nothing.
3 — HOW MANY WORKERS, AND WHERE?
From the gap report: how many machines, how many concurrent runs, what capacity am I on. One worker on one box needs no distributed lock; two of anything needs one. Do not add machines I do not have.
4 — WHAT IS THE COMPLETION ARTIFACT?
The reference derives success from a merged pull request, because an agent can create one but cannot merge it. What is the equivalent in MY work? If my work does not produce PRs, this needs a real answer, not an analogy — it is the load-bearing decision in the architecture. An artifact the agent can forge is not an artifact.
5 — WHAT DOES A RUN ACTUALLY DO?
Name three real candidates from MY work, not examples. If the gap report did not surface them, ask me — this is one of the few things you genuinely cannot measure. A first fleet that runs one real task well beats one that could theoretically run anything.
6 — WHAT IS THE FIRST TASK?
Exactly one, and it should be boring: something I do repeatedly, whose success is unambiguous, whose failure is cheap. Name it, and name how a machine will know it worked.
Tick box 2.
THEN ASK ME WHAT YOU CANNOT DECIDE
ASK WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), one question at a time, each with a RECOMMENDED option first. Not prose — a prose question has no options and therefore no recommendation, and the recommendation is what lets someone who reads nothing still end with something durable.
Leading with your recommendation. Ask only what genuinely turns on my judgement — how much capacity I am willing to spend, what I am unwilling to automate, what my employer's policy forbids. Do not ask me to pick a supervisor; that is a measurement and you have it.
Tick box 3.
WRITE THE DESIGN
~/FLEET-DESIGN.md:
## THE SHAPE
The six answers, each with its reasoning in one or two sentences.
## THE COMPONENTS
What gets built, what each one does, and which decision in fleet-architecture.md it comes from.
## THE NUMBERS
Every constant, with its basis. 🔴 Where a number is a guess, mark it a guess and write down the measurement that would settle it. A cap chosen from caution silently becomes the system's capacity and never announces itself.
## WHAT I AM NOT BUILDING
The parts of the reference deliberately skipped, and the condition under which each becomes worth adding. This section is as important as the components list — the reference's own minimum viable version is 250 lines against 7,000.
## THE FIRST TASK
The one task, and its acceptance: a command whose exit status is the answer.
Then the manifest — every file step 4 will create, one line each:
path what it does lines
~/fleet/dispatch.sh refuse or launch a named unit ~90
~/fleet/run.sh announce, invoke, derive the artifact ~120
...
Tick box 4.
SHOW ME THE MANIFEST AND STOP
Print it and wait. Do not write a single file until I say go.
🔴 This pause is deliberate. A manifest is one screen and I can see a wrong assumption in it immediately; a built fleet is twenty files and I will find the same wrong assumption in a week.
Tick box 5.
ON MY APPROVAL
Say exactly this:
"Design approved. FLEET-DESIGN.md is at ~/FLEET-DESIGN.md. Fetch step 3 from https://docs.utopiamodels.ai/docs/ai-infrastructure-upgrades/dispatch and paste it into a new session."
Tick box 6.
```
```text title="prompt-3|Build every file in the manifest|claude-code,opencode,codex,cursor,gemini"
STEP 3 of 4 — build it. Every file in the manifest, and nothing that is not.
CHECKLIST FIRST
Write ~/FLEET-BUILD-CHECKLIST.md, listing one task per file in the manifest plus the four below, and work from it. Tick each as you finish, not at the end — this is the longest step and the one most likely to lose its place.
## Rules
- I build exactly the manifest. A file not in it does not get written.
- I never modify anything I did not create, without asking first.
- I paste REAL command output. I never write "verified" without the output above it.
- If something does not work, I say so and stop. A half-built fleet that reports success is worse than one that reports failure.
READ THE DESIGN AND THE REFERENCE
~/FLEET-DESIGN.md
https://docs.utopiamodels.ai/kit/dispatch-anatomy.md
The anatomy file has the run path and the refusals in order. Follow it. Where my design deliberately differs, follow my design and say so in a comment.
BUILD, IN THIS ORDER
The order is dependency, not preference. Each one is testable before the next exists.
1 — THE WORKSPACE HELPER
A per-run isolated workspace. If git: a worktree, DETACHED at a fresh origin/main — a named branch may be checked out in only one worktree at a time, so detaching is what makes two concurrent runs legal at all.
Resolve the clone path physically, not through a symlink: git writes the worktree's pointer from the path it resolved through, and an aliased clone mints a pointer to a path that does not exist. Every git command inside then dies "fatal: not a git repository", and that failure is invisible from inside a run.
Verify git actually works in the new workspace before returning it.
2 — THE RUNNER
The run path, in order: record the start timestamp before anything else · re-check that the work has not been disposed of · post the start marker BEFORE anything that can fail · create the workspace · invoke the agent, capturing stdout and stderr together · derive the outcome in an EXIT trap.
🔴 Four things about the trap that are easy to get wrong, and each was a real failure:
Re-raise terminating signals. The shell does not run an EXIT trap on an unhandled SIGTERM, and a supervisor stops a unit with SIGTERM. Without this, a timeout or an OOM kill strands the item forever.
Never read the exit status inside a trap wrapper. Assigning it is itself a command with status 0, so the real code is gone by the time you use it. Pass it as a parameter.
Never swallow the terminal comment's own failure. It once ended with an unconditional true, and eight runs closed with no marker at all — a silent success became indistinguishable from a hang. Retry once, then log loudly.
Post the comment BEFORE attempting to close. Closing an already-closed item with a comment attached prints "already closed", returns zero, and silently drops the comment.
Derive the outcome from the forge, never the exit code. A merged artifact created after the start timestamp is success. An open one is not finished. Nothing is a no-op, never a success.
3 — THE DISPATCHER
The four refusals from my design, in order, each failing OPEN — a refusal fires only on positive evidence, and every unreadable case means proceed. A guard that fails closed on its own outage is an outage.
Check the JSON type before trusting an API response: a forge CLI prints its error body to stdout, so a failed call yields an object rather than an empty string, and a non-empty test reads an error as a blocker.
Then launch: named after the WORK with no entropy, detached, collected, and passing PATH explicitly. The unit does not inherit your environment — it gets the supervisor's own PATH, and tools under a home directory are simply absent.
4 — THE SCHEDULER
The predicates from my design, on a timer. Dry-run by default; writes only behind an explicit apply flag. Raise the timer's start timeout above the worst-case cycle — the no-overlap guarantee then comes free from the timer rather than a lockfile.
Write the heartbeat on EVERY exit path, including the halted and failed ones. "Nothing to do" and "not running" look identical from outside, and that ambiguity is what hides an outage for days.
5 — THE AGENT BRIEF
What the agent is told at the start of every run. It is a versioned artifact, not a sentence — the reference's is ~2,500 words and nearly every clause is a scar. Mine should carry at minimum:
the workspace is per-run; branch from a freshly fetched remote, never a stale local
finish at MERGED, not at "auto-merge armed"
do not close the work item; the merge closes it
never wait for a notification — nothing will wake you
write any verdict as structured JSON, not prose, because nothing downstream reads English
evidence quotes the exact command and its raw output, never a summary of it
VERIFY — PASTE REAL OUTPUT, NEVER A CLAIM
# the supervisor refuses a duplicate name
# a run survives disconnect
# the artifact check is honest
# the PATH is right inside the unit
🔴 That last one catches the most common first-fleet failure, and it fails silently otherwise.
FINISH
Report every file created with its path and line count, and the four verifications with their real output.
Then say exactly this:
"Step 3 complete. Fetch step 4 from https://docs.utopiamodels.ai/docs/ai-infrastructure-upgrades/dispatch and paste it into a new session."
Then stop. Do not dispatch real work — step 4 does that, deliberately, as its own act.
```
```text title="prompt-4|Dispatch one real task, end to end|claude-code,opencode,codex,cursor,gemini"
STEP 4 of 4 — dispatch one real task, end to end, and prove it worked.
This is the only step that touches real work. It is separate from step 4 on purpose: building a fleet and trusting it are two different decisions, and running the first real task deserves to be one.
CHECKLIST FIRST
Write ~/FLEET-VERIFY-CHECKLIST.md:
# First dispatch — step 4 of 4
## Tasks
- [ ] 1. Confirm the fleet is actually running
- [ ] 2. File the first task from FLEET-DESIGN.md
- [ ] 3. Dispatch it and watch, without touching it
- [ ] 4. Grade it against the acceptance criterion
- [ ] 5. Write ~/FLEET-VERIFIED.md
- [ ] 6. Report
## Rules
- I do not help the run. If it fails, that is the finding.
- I paste real output for every claim.
- I grade the ARTIFACT, not the exit code and not the run's own report.
CONFIRM THE FLEET IS UP
Paste real output for each:
the scheduler's unit is loaded and its timer is armed
the last heartbeat, and its age
the supervisor accepts a unit and refuses a duplicate of its name
the agent runs headless and emits a machine-readable result
If any of these fails, stop. A first dispatch onto a fleet that is not up teaches you nothing about either.
Tick box 1.
FILE THE FIRST TASK
The one from FLEET-DESIGN.md. Real work — something I actually want done.
Its body must name what must be TRUE when it is finished, as a command whose exit status is the answer. Not "the docs are updated" but a command that exits 0 only if they are.
🔴 An acceptance criterion that only elapsed time can satisfy is unsatisfiable by any work. One fleet asked for "24 hours of log entries" twenty-four MINUTES after the config went live, and dispatched six agents at work that did not exist. Grade the artifact that exists now.
Tick box 2.
DISPATCH IT, AND DO NOT HELP
Dispatch, then watch. Do not intervene, do not fix its environment, do not answer questions it should answer itself.
Record: when it was dispatched, when the start marker appeared, when the terminal appeared, what the terminal said, and what artifact it produced.
🔴 If it fails, that is a RESULT, not a setback. A first dispatch that fails and says why has taught you where your fleet is wrong, which is what this step is for. Write down the failure exactly.
Tick box 3.
GRADE THE ARTIFACT
Run the acceptance command yourself and paste its output.
Then check the chain the run depended on:
did the start marker post before anything that could fail?
did the terminal post, and does it match reality?
is the artifact real — merged, present, exercisable — not merely claimed?
did the run use its own workspace, and is that workspace clean or kept?
did the status change without anyone typing one?
🔴 A green build is not a working artifact, and a self-report is not evidence. Exercise the real thing.
Tick box 4.
WRITE THE FILE
~/FLEET-VERIFIED.md:
## WHAT RAN
The task, the timestamps, the outcome.
## THE EVIDENCE
Every check above with its real output.
## WHAT BROKE
Everything that did not work first time, and what it cost. 🔴 If nothing broke, say so plainly — but check twice, because a first dispatch that goes perfectly usually means something was not actually exercised.
## WHAT I WOULD FIX FIRST
One thing. Lead with it.
## WHAT THIS FLEET CANNOT DO YET
The parts of the reference deliberately skipped, and which one the first real failure will make worth adding.
Tick box 5.
FINISH
Tell me whether the task was completed by the fleet, and how I know that from the artifact rather than from the run's own claim.
Then stop. There is no step 5 — from here it is real work, and the next thing worth doing is the second task.
```
## The four steps [#the-four-steps]
| | | ends with |
| ----- | ------------------------------------------------ | ------------------------------------- |
| **1** | Study a fleet that runs, measure mine against it | `FLEET-GAP.md` |
| **2** | Design mine, then stop for approval | `FLEET-DESIGN.md` + a manifest |
| **3** | Build every file in the manifest | a running fleet, verifications pasted |
| **4** | Dispatch one real task, end to end | `FLEET-VERIFIED.md` |
Each step names the next. Paste one, run it, paste the next.
## What the steps read [#what-the-steps-read]
* [`kit/fleet-architecture.md`](https://docs.utopiamodels.ai/kit/fleet-architecture.md) — **twenty design decisions from a working \~7,000-line runtime**, each paired with the failure that produced it
* [`kit/dispatch-anatomy.md`](https://docs.utopiamodels.ai/kit/dispatch-anatomy.md) — the run path, every pre-flight refusal in order, and the \~250-line minimum viable version
## Resources [#resources]
* [Claude Code headless](https://code.claude.com/docs/en/sdk) · [memory](https://code.claude.com/docs/en/memory) · [skills](https://code.claude.com/docs/en/slash-commands) · [subagents](https://code.claude.com/docs/en/sub-agents) · [hooks](https://code.claude.com/docs/en/hooks)
* [`systemd-run`](https://www.freedesktop.org/software/systemd/man/systemd-run.html) · [systemd timers](https://www.freedesktop.org/software/systemd/man/systemd.timer.html) · [systemd in WSL](https://learn.microsoft.com/en-us/windows/wsl/systemd)
* [`git worktree`](https://git-scm.com/docs/git-worktree) · [GitHub issue dependencies](https://docs.github.com/en/issues) · [`gh` CLI](https://cli.github.com/manual/)
# Module — Create workflows for utilizing docs.utopiamodels.ai (/docs/modules/workflows)
These prompts need a shell. Switch to any terminal harness to see them.
```text title="prompt-1|Work out what my setup is, and interview me|claude-code,opencode,codex,cursor,gemini"
PROMPT 1 of 2 — work out what my setup actually is, ask me what you cannot measure, and write four files. You are not building anything in this step.
ASK EVERY QUESTION IN THIS PROMPT WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). The folder question, the tier confirmation, and every interview question. Not prose in the conversation.
That is not a formatting preference. A question asked in prose has no options, so it carries no RECOMMENDED option — and the recommended option is the entire mechanism by which someone who reads nothing still ends up with a working system. Asking in prose removes that silently, and the conversation looks fine while it happens.
If your harness has no such tool: say so in one line, then ask in plain text with the options written out and the recommended one marked. Never drop the options.
I am a senior technical person. Do not explain what an AI agent is, do not reassure me, and do not walk me through basics. Where I am wrong about my own setup, say so with the evidence.
FIRST — WRITE YOUR CHECKLIST TO A FILE
Long sessions compress their own history and instructions pasted into chat get dropped when that happens. Files do not. Write this to ~/SCHOOL-01-CHECKLIST.md and work from that file rather than from this message:
# School module 01 — interview
## Tasks
- [ ] 1. Survey this machine
- [ ] 2. Score three dimensions, derive a tier, fetch the tier's interview template
- [ ] 3. Ask where the output goes
- [ ] 4. Show the tier as a one-step nudge
- [ ] 5. Interview — one question at a time
- [ ] 6. Write the four files
- [ ] 7. Report, and tell me to paste prompt 2
## Rules
- Read only. I create this checklist, the output folder, and four files in it.
- I never modify anything I did not create.
- I report that a credential file exists and where. I never read one or print a value.
- Every claim about this machine carries the command that produced it.
- I tick each box as I finish it, not at the end.
1 — SURVEY
Fetch and run: https://docs.utopiamodels.ai/kit/survey.md
If that fetch fails, say what you got and stop. Everything below is calibrated on it.
Record what each command actually printed, with the command beside it. Where a check is inconclusive, write inconclusive rather than what is usually true.
Two things in there decide more than the rest, and both are commonly wrong on a machine that looks fine:
PATH in three shapes — login, non-interactive, minimal. A tool present in the first and absent in the third is why a job works when I type it and fails when something else starts it.
Skills and rules whose descriptions name a TOPIC rather than an OCCASION. Those never fire. They exist, they cost nothing, and their owner usually believes they work. Find mine and name them — that list is often the most useful thing in this whole step.
Tick box 1.
2 — CLASSIFY
Emit these markers with QUOTED EVIDENCE, into fixed fields. Not a reasoning paragraph — each marker is a description, never a judgement. "instructions_file_present: yes, ~/.claude/CLAUDE.md, 7744 B" not "the config is good."
instructions_file_present path and byte count
instructions_file_names_commands quote a build/test/deploy command from it
skill_count_total how many exist
skill_count_that_would_fire descriptions naming an occasion, not a topic
hooks_present which events
mcp_configured how many
mcp_authenticated how many have a live auth artifact
knowledge_dir_referenced_by_config a notes directory the config actually points at
config_in_version_control is any of this in git
queue_present a tracker with a status field used as a work queue
unattended_run_evidence anything that ran to completion without a human starting it
multi_machine more than one box involved
Then score three dimensions independently. Do not add the markers up — counting makes every signal interchangeable, and six trivial slash commands would outrank one well-scoped subagent plus a real gate.
PERSISTENCE does anything survive a session ending?
ACTIVATION does what persists actually fire?
COORDINATION does anything run without a human in the loop?
Then apply the gates, which are necessary conditions rather than points:
no instructions file → cannot be above tier 1, whatever else is present
no skill that would fire → cannot be above tier 2
no unattended run evidence → cannot be tier 4
1 BARE a harness works. Nothing configured survives a session ending
2 CONFIGURED an instructions file exists. Things persist; nothing coordinates them
3 ORGANIZED skills fire, tools connected, notes that are read rather than re-explained
4 ORCHESTRATED either more than one agent runs at once, or a deliberate context protocol
If tier 4, split it on ONE question — how many agents run at once? — not on how advanced it looks:
queue_present + unattended_run_evidence → 4-dispatch (engineers the RUN)
knowledge_dir_referenced_by_config + high skill count, no queue → 4-context (engineers the READ)
🔴 UNDER-CLASSIFY AT A BOUNDARY. Deliberately, by one tier. The two errors are not symmetric: over-classification fails SILENTLY — someone handed material referencing infrastructure they do not have gets stuck and disengages without telling you. Under-classification fails LOUDLY — and the person you under-rate is exactly the person who will correct you. Prefer the loud failure; it repairs itself.
Then fetch your tier's interview template:
https://docs.utopiamodels.ai/kit/workflows/tiers/-interview.md
where is one of: 1-bare · 2-configured · 3-organized · 4-dispatch · 4-context
Tick box 2.
3 — ASK WHERE THE OUTPUT GOES
Ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). One question, three options, the first one RECOMMENDED:
A dedicated folder — /school/01-workflows/ [RECOMMENDED]
Every later module gets its own subfolder beside it. A module writing into a folder it
shares with other work is a module whose output cannot be found again.
Mirror what I already do, if there is an obvious one. Say that is what you are doing.
Somewhere else — I will type it
If I have an obvious convention for notes, make THAT the recommended option instead and say why.
Then create the folder and say where it is.
Tick box 3.
4 — SHOW THE TIER AS A NUDGE, NOT A QUESTION
Ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), with the tier you derived as the RECOMMENDED option. State it, give the evidence, and make the adjustment one click:
You're at ORGANIZED. Skills with real trigger descriptions, two MCP servers authenticated, notes
your config points at — but nothing runs unattended.
[right] [further along ↑] [take it slower ↓]
Never ask how experienced I am. Self-report is what the classification exists to replace.
🔴 A TIER IS RIGOR OF PRACTICE, NOT LEVEL OF ACHIEVEMENT. Someone at CONFIGURED by choice is correctly classified, not behind. Say so if it comes up.
Tick box 4.
5 — INTERVIEW
Run the questions from the template you fetched, and follow its asking rules over anything you would otherwise do.
The three that matter most:
ASK WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), EVERY TIME, INCLUDING QUESTION ONE. Question one is the one most often asked in prose, and it is the worst one to lose — it costs about two and a half times what question three does in the reader's attention, and it sets whether they believe the rest is worth answering.
EVERY QUESTION CARRIES A RECOMMENDATION, first, labelled, with one line of why. They should be correcting a decision, never composing one from nothing.
ONE QUESTION AT A TIME. Not four. A batch makes a follow-up probe structurally impossible, and the follow-up is where the real answer is.
FIVE TO SEVEN QUESTIONS, hard ceiling ten. The risk is not that I abandon it — it is that I answer question eleven with something reasonable-sounding produced by no actual thought, and that failure is invisible.
STOP EARLY when you can write the files. Terminate on having enough, not on reaching the end of a list.
And never ask for motives in the abstract. "What are your goals" produces fluent, generic, useless text. "I see X on this machine — what are you trying to do with it?" produces a real answer, because it is a correction task rather than an essay prompt.
Tick box 5.
6 — WRITE THE FOUR FILES
In the folder I confirmed. Prompt 2 reads these, so the headings are fixed.
CONTEXT-MAP.md every file loaded at session start with its byte count · what fires on a trigger
versus what loads always · what is pointed at but never read · the TOTAL always-on
cost as a number · every I/O path, and whether each tool server is authenticated
INTERVIEW.md what you asked and what I said, each marked MEASURED or STATED
TIER.md the tier, all twelve markers with their evidence, the three dimension scores
separately, and what would move each one up
DECISIONS.md every recommendation you made, whether I accepted it, and what I chose instead
CONTEXT-MAP.md is the durable one. The other three record this session; that one gets re-read every time anything changes.
Tick box 6.
7 — FINISH
Tell me the folder, the four files, my tier with its three dimension scores, and the three findings that most change what should be built.
Then say exactly this:
"Interview complete. Paste prompt 2 and I will build the workflows."
Then stop. Do not build anything — that is prompt 2, and it reads these files.
Tick box 7.
```
```text title="prompt-2|Build the workflows and the gate|claude-code,opencode,codex,cursor,gemini"
PROMPT 2 of 2 — build the workflows. From the four files prompt 1 wrote, not from a template.
READ THE FOUR FILES FIRST
/CONTEXT-MAP.md what loads, what fires, what it costs, what can be reached
/TIER.md the tier, twelve markers, three dimension scores
/INTERVIEW.md what I said, marked measured or stated
/DECISIONS.md what has already been settled
If any is missing, stop and say which. Building from a partial record is how you produce something that contradicts a decision already made.
Then fetch two files:
https://docs.utopiamodels.ai/kit/workflows/tiers/-execution.md
https://docs.utopiamodels.ai/kit/workflows/gates.md
CHECKLIST TO A FILE
Write ~/SCHOOL-01-BUILD.md — one task per thing you will create, plus the four below — and work from it. Tick as you go, not at the end.
## Rules
- I build what I proposed and was accepted. Nothing else.
- I never modify anything I did not create without asking first.
- I paste REAL command output. I never write "verified" without the output above it.
- If something does not work, I say so and stop. A half-built system that reports success is worse than one that reports failure.
1 — THE GATE
This is the part that decides whether any of the rest matters. The gate is how the school reaches my agent WITHOUT me remembering it exists.
Read gates.md before proposing anything. Its three constraints bind harder than the choice of mechanism, and the reason is in the evidence: going from ~2 to ~11 alerts per hundred dropped acceptance from 100% to 8.4%, and halving the volume back recovered three points of ninety-two. Dismissal becomes habitual, and once it is, better content does not help because the content is no longer read.
So: ONE gate. Conservatively tuned. Not three.
ASK THIS WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), not as an open prose question. The gate choice is the highest-stakes question in this module — it decides whether any of the rest gets used — and a question asked in prose carries no options and therefore no recommendation. Someone who reads nothing and accepts the default must still end up with a working gate, and that is only possible if there IS a default to accept.
Rank the options against what CONTEXT-MAP.md shows I actually have. Exactly one marked RECOMMENDED, one line of why each, and do not offer a mechanism my harness does not support.
If your harness has no such tool, say so in one line and ask in plain text with the options written out and the recommended one marked.
The ranking, and state the failure rates rather than selling past them:
A HOOK, where my harness has one, is the only mechanism here that is not advisory — Anthropic's
own docs draw that line explicitly. Gate it narrowly; a session-start hook that fires every session
is the noise failure arriving by a deterministic route.
A SKILL whose description names an OCCASION, plus a pointer of THREE LINES OR FEWER in the
always-on file, is the portable fallback. Say plainly that it is advisory: measured at 0 of 3 recall
in headless mode in Anthropic's own eval harness, and a working skill can stop firing because
unrelated skills were added.
NOT MCP. A single "hello" in a fresh session costs 51,700–56,900 tokens and disabling it recovers
about 9%.
NOT a daemon. A background process breaks silently while continuing to look healthy.
🔴 Three lines is a hard ceiling on the always-on pointer, and it is not aesthetic: the same instruction obeyed at 97% in isolation falls to 2% when combined with five others. Every line taxes every other line. A pointer says where to look; it is never the instructions themselves.
🔴 THE BAR: if I accept every recommendation without reading, I end with a working system. That is what separates a recommendation from a menu with a default.
Tick.
2 — THE THREE THINGS EVERY TIER GETS
The minimum, not the target. What each looks like differs by tier; that they exist does not.
A WAY TO RUN A MODULE. Given a module URL: fetch it, extract the prompt for my harness, prepare
whatever it needs, hand it to me ready to run.
A RECORD OF WHAT RAN. One row per module: which, when, what artifact it produced and where that
artifact lives, and WHAT CHANGED AS A RESULT.
🔴 That last column is the one that matters. A row marked done with nothing in it is a module that
was not completed, and the record must make that visible rather than let a tick hide it.
A WAY TO LEARN A NEW MODULE EXISTS. The school adds modules. Something must re-check. Do NOT build
a background process for this — a check that runs when I next work is enough.
Tick.
3 — WHAT MY TIER ADDS
Build what the execution template names, where INTERVIEW.md showed appetite for it. Where it did not, say so and leave it. Building against a gap I do not have is how a durable system becomes an abandoned one.
Tick.
4 — PROVE THE GATE FIRES — BOTH TESTS
Do not report that it works. Show it:
say a sentence that SHOULD trigger it → it fired
say a sentence that should NOT trigger it → it stayed quiet
🔴 A gate that fires on everything is as broken as one that fires on nothing, and only the negative test catches it. If either fails, fix the description and run both again.
Then verify the rest against reality, with real output:
every path in the tracker resolves to a file that exists
the always-on total in CONTEXT-MAP.md matches what the harness loads NOW that you have added to it
the module runner works on a real module URL
Tick.
FINISH
Every file created with its path. Both gate tests with their real output. The new always-on byte total against the old one.
Append to DECISIONS.md: what you recommended, what I chose, and what you did not build.
Then say exactly this:
"Module 01 complete. The gate fires on [trigger] and stays quiet on [non-trigger]. Your always-on
cost went from [X] to [Y] bytes."
Then stop.
```
## What the prompts read [#what-the-prompts-read]
* [`kit/survey.md`](https://docs.utopiamodels.ai/kit/survey.md) — the environment commands, and what each proves
* [`kit/workflows/gates.md`](https://docs.utopiamodels.ai/kit/workflows/gates.md) — **the entry-way gate mechanisms, ranked, with each one's measured failure rate**
* [`kit/workflows/SPEC.md`](https://docs.utopiamodels.ai/kit/workflows/SPEC.md) — what this module is for, and how the templates are built
**Ten tier templates**, one interview and one execution each:
| tier | | |
| ------------------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| **1 · BARE** | [interview](https://docs.utopiamodels.ai/kit/workflows/tiers/1-bare-interview.md) | [execution](https://docs.utopiamodels.ai/kit/workflows/tiers/1-bare-execution.md) |
| **2 · CONFIGURED** | [interview](https://docs.utopiamodels.ai/kit/workflows/tiers/2-configured-interview.md) | [execution](https://docs.utopiamodels.ai/kit/workflows/tiers/2-configured-execution.md) |
| **3 · ORGANIZED** | [interview](https://docs.utopiamodels.ai/kit/workflows/tiers/3-organized-interview.md) | [execution](https://docs.utopiamodels.ai/kit/workflows/tiers/3-organized-execution.md) |
| **4 · dispatch** | [interview](https://docs.utopiamodels.ai/kit/workflows/tiers/4-dispatch-interview.md) | [execution](https://docs.utopiamodels.ai/kit/workflows/tiers/4-dispatch-execution.md) |
| **4 · context** | [interview](https://docs.utopiamodels.ai/kit/workflows/tiers/4-context-interview.md) | [execution](https://docs.utopiamodels.ai/kit/workflows/tiers/4-context-execution.md) |
## Resources [#resources]
* [Claude Code memory](https://code.claude.com/docs/en/memory) — the 200-line ceiling, and why bloat makes the file stop working
* [skills](https://code.claude.com/docs/en/slash-commands) · [hooks](https://code.claude.com/docs/en/hooks-guide) — the nine lifecycle events · [subagents](https://code.claude.com/docs/en/sub-agents) · [MCP](https://code.claude.com/docs/en/mcp)
* [Cursor rules](https://cursor.com/docs) · [opencode](https://opencode.ai/docs/) · [Codex CLI](https://learn.chatgpt.com) · [Gemini CLI](https://google-gemini.github.io/gemini-cli/)
# The board (/docs/operating/board)
Sourced from the fleet's own component document for `board`. Called stable because the one constant every
script uses to address it is bound in five separate files and re-checked on every pull request — which is
the only part of a hosted product a repository can hold still.
Work exists here or it does not exist.
A body of work is a **Feature** issue with ordered sub-issues. Status derives from a merged pull request
whose body closes the issue, and **nobody ever types one**. If this surface is wrong, the fleet works on
the wrong thing — there is no second list to cross-check against, deliberately, and creating one is the
fence this whole component exists to hold.
The vocabulary — plan, Feature, issue — and the lane routing that decides which agent serves a row are on
[How work flows](/docs/operating/work). This page is the mechanism underneath: which fields are written,
which are derived, and the four ways this board has been made to lie.
## The fleet owns no file that *is* the board [#the-fleet-owns-no-file-that-is-the-board]
The component's list of owned files is **empty, and that is the fact rather than an omission**. The board
is a hosted product. Everything in the repository that touches it belongs to a neighbour, and the
partition is exactly three ways: one script owns the write path in, one set of workflows owns the
mechanisms that keep it honest, and the scheduler owns the single consumer of the Todo status.
Naming that boundary explicitly is what stops a fourth thing quietly growing a copy of the board.
## An agent writes two fields. Everything else is derived [#an-agent-writes-two-fields-everything-else-is-derived]
**An agent's entire board job is: assign itself, and give the issue a title** — plus an optional priority
label. Every other field is derived on a hosted runner by a reconciliation workflow, and written **only on
diff**.
| Field | Derived from |
| ------------ | ------------------------------------------------------------------------------------ |
| Lane | the primary assignee's login — the lane names the **box**, never the portfolio |
| Priority | the priority label, defaulting to the middle value |
| Sprint | the current iteration if unset; a deliberately-chosen one is never clobbered |
| Status | the board's own native workflows; the reconciler is only the closed-to-done backstop |
| Archived | closed and done goes off the board; open and archived is pulled **back onto** it |
| Week / Cycle | **never written by any script** — see below |
Two properties fall out of that, and both are the point:
* **Agents make zero field calls**, so no amount of agent activity can cause an agent-side rate-limit
outage against the tracker.
* **Writing only on diff** means the reconciler is idempotent. Running it twice is indistinguishable from
running it once, which is what makes it safe to run on a timer *and* on an event.
## The shape of a filing is a real choice [#the-shape-of-a-filing-is-a-real-choice]
```text
--chain task k is blocked by task k-1
→ exactly ONE issue is ever dispatchable
--wave every task in a group is blocked by the WHOLE of the previous group,
and by no sibling
→ a whole group starts at once
```
A plan designed as five parallel waves was once filed as a fourteen-deep chain. Nothing errored; the board
simply had one dispatchable issue instead of five, and the fleet ran at a fifth of its shape for as long
as nobody looked at the dependency graph.
**If two phases can run at once, the shape is a wave.** The filing tool now refuses a long chain whose
tasks name no file in common, and prints the wave rewrite — a serial order needs a stated reason, not a
default.
## The iteration field is read-only to every script, and this one is worth stealing [#the-iteration-field-is-read-only-to-every-script-and-this-one-is-worth-stealing]
Any API write to a board's iteration configuration **mints new iteration ids and orphans every assignment
that pointed at the old ones**. On this board that cost 84 field assignments in a single call.
The reason is visible in the schema rather than in the documentation, so reproduce it rather than trusting
this paragraph:
```bash
gh api graphql -f query='{ a: __type(name:"ProjectV2IterationFieldIterationInput"){name}
e: __type(name:"ProjectV2Iteration"){kind inputFields{name}} }'
```
`a` comes back `null` — the input type a caller would need does not exist. And `e.inputFields` has no
`id`: an iteration's complete input set is a start date, a duration and a title, so there is no way to
say *"update this one"*, only *"here is the new list"*.
**The field id is durable and is the only id worth hardcoding. An iteration id rotates the moment a human
edits the field in the interface**, which is why the filing script resolves the current iteration live on
every filing and treats the result as advisory.
The transferable rule: before writing to a hosted API, introspect whether the thing you are updating has
an identity the API will preserve. If its input type has no id field, you are not updating — you are
replacing.
## Read the board, never recall it [#read-the-board-never-recall-it]
Every count on it changes by the minute, so this page states the reading rather than the number.
| You want | Run |
| ----------------------------------------------- | ------------------------------------------------- |
| Open issues | `gh api repos///issues --jq 'length'` |
| Feature rollups, one call | `bash _infra/scripts/feature-progress.sh` |
| One issue with its comments | `gh api repos///issues/` |
| One line per queued issue, and why each is held | `bash _infra/scripts/sweep-dispatch.sh --explain` |
Two traps in that table. `gh issue view` currently fails against this board with a deprecation error from
the older projects API, so the `gh api` form is the working one. And **`gh api` prints its errors to
standard output**, not standard error — so validate the *shape* of what you captured, never merely that it
is non-empty.
## Four ways this board has been made to lie [#four-ways-this-board-has-been-made-to-lie]
* **Saying you are *not* closing an issue closes it.** The platform matches a closing keyword immediately
followed by an issue reference anywhere in a pull request body or commit message, and does not read the
words around it. A sentence of the form *"this does NOT close `#1234`"* is a closing reference. This
fired twice in one day on one issue: first from a pull request whose entire point was that the issue
must stay open, and then from the follow-up pull request **documenting the trap**, which fired it by
quoting the sentence into its own body.
Recovery is two steps and only the first is obvious. A close also sets the status to done, and reopening
the issue does not undo that — leaving it open **and** done, which is the one state no selector reads.
Reopen, *and* set the status back, then read both back. To refer to an issue without acting on it, use a
non-keyword form, and keep any illustration on a placeholder number, which is why the example above is
`#1234`.
* **The terminal state is eventually consistent, not instant.** Archiving is *attempted* on the close event
and *guaranteed* by a scheduled sweep, and concurrency cancellation killed 25 of 30 consecutive event
runs in one measured window. Expect a handful of closed-but-still-listed rows at any moment. Filing
"done is stuck at zero" off a single read is a false alarm this fleet has already raised.
* **A Feature never closes itself.** A closing keyword structurally cannot fire on a parent issue, because
a parent has no pull request. So the agent that finishes the last child *attests* the Feature, and a
separate sweep closes it. A complete Feature with no attestation is **surfaced, never closed** — which is
the correct failure, because the alternative is a parent that auto-closes over its own verifier's
objection.
* **Nothing gates the default branch.** Branch protection and rulesets both return 403 on this plan tier,
in every repository. A red check is information. A merge is never a human gate. The full argument is on
[How work flows](/docs/operating/work).
## Take it further [#take-it-further]
```text title="prompt-1|Find the second tracker you did not know you had|desktop|Walks your setup one surface at a time and finds where the same fact is stored twice, then ranks which copy to delete. No shell needed."
I want you to find the places where my team stores the same piece of work-tracking information twice, and tell me which copy to delete.
Read this first for the discipline I am trying to apply:
https://docs.utopiamodels.ai/docs/operating/board.md
The claim is that a work tracker only stays true if exactly one surface owns each fact, and that every other surface derives its version or does not have one. The fleet on that site has one board, and an agent writes exactly two fields on it — everything else is computed by a job that runs on a diff.
Here is how I want you to work.
Ask me, one round at a time rather than all at once, what surfaces I actually have. Prompt me with the categories rather than waiting for me to remember them: an issue tracker, a spreadsheet, a project plan document, a chat channel where status gets posted, a README with a roadmap in it, a per-project checklist file, a personal to-do list, a standup document, a dashboard.
For each surface I name, get these out of me before moving on:
- Which fields does it carry? Status, owner, priority, due date, scope, acceptance.
- For each field: does a human TYPE it, or is it computed from something else?
- What happens if it disagrees with another surface? Who notices, and how long does it take?
When you have the list, build the table I actually need: one row per FACT (not per surface), and one column per surface that carries it. A fact appearing in more than one column is the finding.
For each duplicated fact, tell me:
1. Which copy is the source of truth right now, in practice — not in policy. The one people actually check when they disagree.
2. Whether the other copies could be DERIVED from it, or whether they would have to be deleted outright. Derivable is much cheaper and you should say so when it applies.
3. What the duplicate is costing. Be concrete: how it goes wrong, not that it might.
Then ask me which duplicate to attack first with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead) — and recommend one, with your reason, so I can just take the default.
Two things to hold onto while you do this. A fact that is typed in one place and typed again in another is a drift with a start date nobody will be able to find. And a surface that exists only to be READ by a human is not a second tracker — the test is whether anyone WRITES a decision into it.
```
```text title="prompt-2|Measure derived-versus-typed on your real tracker|claude-code,opencode,codex,cursor,gemini|Reads your live issue tracker through its API, counts which fields are actually populated, and finds the ones a human types that a job could compute. Reads only; writes nothing."
You are going to measure a real issue tracker and tell me which of its fields are typed by a human and which could be derived instead.
Read this for the reference model:
curl -s https://docs.utopiamodels.ai/docs/operating/board.md
That fleet writes two fields per issue and derives the rest on a runner. I want to know how far my tracker is from that.
Do not create, edit, close, label or comment on anything. Every call you make must be a read. If a step would require a write to answer, skip it and say so.
## Step 1 — find the credential and the tracker
Work out what tracker this environment can reach. Check, in this order, and stop at the first that authenticates:
gh auth status
git remote -v
env | grep -iE 'JIRA|LINEAR|ASANA|TRELLO|GITHUB_TOKEN|GH_TOKEN' | sed 's/=.*/=/'
Never print a credential value. The sed above is deliberate — report only which variables are SET, never what they contain. If nothing authenticates, tell me and stop; there is no useful version of this audit against a tracker you cannot read.
## Step 2 — pull a real sample
Pull the most recent 100 items, open and closed, with every field the API will give you. For GitHub that is the issues endpoint plus, if the repo is on a project board, a GraphQL query for the project's field values. Two notes from the reference page that will save you a debugging cycle:
- `gh api` writes its ERRORS to standard output, so check the shape of what you captured rather than whether it is non-empty. An error body is a perfectly non-empty string.
- If a project board is involved, read the FIELD ids and never write to an iteration configuration. On the reference fleet that call orphaned 84 assignments, because an iteration has no id in its own input type — there is no update, only a replace.
## Step 3 — count, do not eyeball
For every field present in the sample, report:
- Fill rate: what fraction of items have a non-empty value.
- Distribution: how many distinct values, and whether one value dominates. A field that is 97% one value is a field nobody is really setting.
- Correlation with something already known: does status track whether a linked pull request merged? Does priority track a label? Does assignee track who opened it?
That third one is the whole audit. A field that correlates almost perfectly with something already in the system is a field a job could compute, and every field a job computes is a field that cannot go stale.
## Step 4 — report
Give me three lists.
**Derivable now** — the field, what it correlates with, the rule that would compute it, and the fill rate it would reach.
**Typed and load-bearing** — the fields that genuinely carry a human decision. Say why each one resists derivation; if you cannot say why, it belongs in the first list.
**Dead** — fields with a fill rate low enough that nothing can be depending on them. Recommend deleting these first: they cost nothing to remove and they are the ones making the board look more informative than it is.
Then ask me which list to act on first with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), with a recommended answer and your reason for it.
Finish with the one number I should watch. Not a dashboard — one number, and what it means when it moves.
```
# The dispatch runtime (/docs/operating/dispatch-runtime)
Sourced from the fleet's own component document for `dispatch-runtime`. Called stable because both halves
are in git, both are bound by assertions that re-check on every pull request, and four test harnesses
exercise the parts that are easy to break silently.
`dispatch.sh` **starts** a run. `task-runner.sh` **is** the run. That is the whole runtime.
There is no daemon, no queue, no watchdog, and nothing resident between runs. Two consequences follow, and
they are the reason the design is worth copying:
* **Starting a run is the assignment.** There is no separate step where an agent is told what to work on,
so there is no state that can disagree with what is actually executing.
* **Liveness is the close-out comment.** The run posts on its own issue when it starts and again when it
exits. Nothing else reports health, because nothing else is running to report it.
Stop these two scripts and the fleet does not degrade — it stops, because nothing else in the organization
can begin a unit of work.
The one thing it deliberately is **not** is a scheduler. Choosing *which* issue and *which* agent belongs
to the sweeper, described on [Operations](/docs/operating/operations); a human at the command line calls
in through exactly the same door. The runtime never picks work.
## Three pre-flight guards, and every one of them fails open [#three-pre-flight-guards-and-every-one-of-them-fails-open]
| Guard | Refuses when | Exit | Escape hatch |
| ------------------------- | ------------------------------------------------------------------------------------------- | ---- | ---------------------------- |
| **Quota** | the agent's own state file says its entitlement is exhausted, with a reset time still ahead | `9` | `DISPATCH_IGNORE_QUOTA=1` |
| **Duplicate, same agent** | a unit for this issue is already active on that box | `11` | `DISPATCH_ALLOW_DUPLICATE=1` |
| **Fleet claim** | a lock-held probe finds a live run for this issue on **any** agent | `14` | — |
**Failing open is the decision, not an oversight.** An unreadable file, an unreachable box, an unparseable
JSON document and an untakeable lock all mean *proceed, and say so on stderr*. A false block stops the
fleet, which is worse than a wasted run.
**None of the three writes a comment on the issue**, and they share one reason. The scheduler treats
certain glyphs on an issue as terminal, so a marker posted by a *refusal* would permanently unqueue an
issue whose only problem was transient. A guard that announces itself where the queue reads would be a
guard that silently deletes work.
### Why only the third guard has no window [#why-only-the-third-guard-has-no-window]
The first two are read-then-act checks, and a read-then-act check has a gap. Measured on this fleet:
**26–27 seconds** pass between reading the board and launching the unit, so two dispatchers reading in the
same second both pass.
A zero-entropy unit name closes that for one agent — systemd refuses a duplicate unit name in the kernel,
which is a real mutex and not a convention. But a user service manager is **per user**, so the same unit
name under two different accounts is two different names, and the kernel has no opinion about the pair.
The cross-agent case is covered by a single file lock on the shared box, held across the probe *and* the
launch, because both dispatch paths — the scheduler running locally, and a hand dispatch arriving over SSH
— execute the claim as the same unix user on the same machine. That is proven against real systemd, with a
mutation control, by a committed test harness rather than by argument.
**The transferable part:** a name-uniqueness guarantee is only as wide as the namespace that enforces it.
Check what your namespace actually is before treating a unique name as a lock.
### The last-moment disposal re-check [#the-last-moment-disposal-re-check]
An issue can stop being work while its run is starting. Every gate that asks about it from outside the run
asks too early — the unit start, the self-update, the clone sync and the credential decrypts all come
after that read. The fix asks the same two questions from **inside** the run, immediately before the
started marker. The real case that produced it, with timestamps, is on
[Architecture](/docs/structure/architecture).
## There is no backend selection [#there-is-no-backend-selection]
The launcher passes a literal backend name into the transient unit. There is no lookup, no registry read,
and no per-agent resolution: a function that once chose between providers was deleted along with the
alternate provider it chose between.
The environment variable survives for a different reason — it is the fleet's *"am I inside a dispatched
run"* signal, which is what every board-keyed gate exempts on. Worth noting as a pattern: a flag whose
original job was deleted can be worth keeping for the second job it accidentally does, provided you write
down which job it is now doing.
## The run updates itself, then executes itself, exactly once [#the-run-updates-itself-then-executes-itself-exactly-once]
`task-runner.sh` fast-forwards its own repository and then re-executes itself one time before doing
anything else.
**Never mutate a script a running bash is still reading.** Bash reads a script incrementally, by byte
offset; rewriting it underneath a live interpreter resumes execution at an offset that now points into
different text. Re-executing once, at a known safe point, is the whole fix.
## Read the live state, do not recall it [#read-the-live-state-do-not-recall-it]
Every fact about a run in flight is off-git and changes by the minute, so this page states the command
instead of the answer.
| You want | Run |
| ------------------------------- | ----------------------------------------------------------------------------------------- |
| Which agents are dark right now | `_infra/scripts/dispatch.sh --quota` for a table, `--quota-dark` for names |
| Would this dispatch be refused? | `--preflight-quota ` (0 or 9) · `--preflight-duplicate ` (0 or 11) |
| What is live for one issue | `_infra/scripts/dispatch.sh --live-units ` |
| Every run on this box, now | `systemctl --user list-units --state=active 'task-*'` |
| What one run actually did | `journalctl --user -u task-.service -o cat` |
A run leaves nothing resident between invocations, so there is no daemon to check. The transient unit
while it lives, and the started and finished comments on the issue afterwards, are the only liveness the
runtime has.
## What this runtime does not prove [#what-this-runtime-does-not-prove]
Published rather than smoothed over, because each one has cost this fleet a real run.
* **A clean exit code is not a shipped artifact.** The finished marker says the process exited cleanly and
nothing more. The completion signal is a *merged* pull request whose body closes the issue.
* **An armed auto-merge is not a merge.** A run that exits with its pull request still open has not
finished. Fifteen of them stacked up unnoticed once on exactly that confusion.
* **The pre-flight clone sync stashes another agent's uncommitted work.** It hard-resets each clone to the
remote with a stash in front of it, so an interactive session's edits survive only as a stash entry
nobody is told about. Check the stash list before re-doing work that vanished mid-run.
* **Transient units are outside the one registry check that actually executes.** Every run's unit is
created on the fly and collected on exit, so there is no installed unit file for the daily verifier to
prove. The two committed test harnesses are the substitute, and they run on pull requests rather than on
a timer.
## Take it further [#take-it-further]
```text title="prompt-1|Decide whether a two-script runtime fits you|desktop|Reads this page and the wiki index, then argues both sides of the no-daemon trade and hands you a decision with the price named. No shell needed."
I want you to help me decide whether to run background AI agents the way one specific fleet does, rather than the way most orchestration tools do.
Read these two pages first, in this order:
https://docs.utopiamodels.ai/docs/operating/dispatch-runtime.md
https://docs.utopiamodels.ai/llms.txt
The first is the runtime. The second is an index of every other page on that site, with a one-line description each. If anything in the first page depends on a concept you do not have, fetch the page the index points at rather than guessing — the pages are all served as raw markdown at the same path with ".md" appended.
The claim I want tested is this one: an agent runtime can be two shell scripts and the operating system's own service manager, with no daemon, no queue and no watchdog, because "starting a run is the assignment" and "the close-out comment is the liveness."
Do not summarize the page back to me. Do this instead.
1. State the trade in one paragraph. What does having no resident supervisor buy, and what does it give up? A design with no cost attached has not been understood, so if you cannot name what this one gives up, say that instead of inventing a cost.
2. Name the three failure modes this design makes INVISIBLE. Not the ones the page admits to — the page is honest about several, and repeating them back is not analysis. I want the ones a reader would only discover by running it.
3. Now describe my situation back to me as a set of questions you would need answered before recommending either way. Things like: how many concurrent runs, whether a run can be safely repeated, whether anything outside the system needs to see progress before a run finishes.
4. Ask me those questions with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). Give me a recommended answer for each one and say why it is the default, so that if I answer nothing I still end up somewhere sensible.
5. After I answer, give me one recommendation — not a menu. Either "adopt the two-script shape, here is the smallest version of it for your case" or "do not, here is what you actually need and why this fleet does not need it." Be specific enough that I could start tomorrow.
One constraint on your reasoning: this fleet runs on one machine with a handful of agents. If your recommendation depends on that being true and my situation is different, say so out loud rather than quietly assuming it.
```
```text title="prompt-2|Audit your own runner against these four properties|claude-code,opencode,codex,cursor,gemini|Finds whatever launches your background jobs, checks it for the four properties this runtime has, and reports what is missing. Reads only; writes nothing."
You are going to audit how background or long-running jobs get started in this repository, and compare it against a documented runtime that does the same job in two shell scripts.
Do not create, edit or delete any file. This is read-and-report from start to finish. If you cannot answer something by reading, say so rather than running anything that mutates state.
## Step 1 — read the reference
curl -s https://docs.utopiamodels.ai/docs/operating/dispatch-runtime.md
That is the runtime being compared against. If curl is not available or the fetch fails, use whatever web-fetch tool you have and tell me which one worked. Everything below depends on having read it.
## Step 2 — find my launcher
Work out what actually starts a long-running job here. Look for all of these before concluding, because most repositories have more than one and they usually disagree:
- systemd units or timers under the repo, or referenced from it
- cron entries referenced in scripts or documentation
- CI workflows that dispatch other CI workflows
- a script whose name contains run, start, dispatch, worker, job, queue or launch
- a process supervisor config: supervisord, pm2, docker compose with restart policies
- an application-level queue: celery, sidekiq, bullmq, a database table polled by a worker
Report what you found as a list, each with its path, and say which one is the real entry point if there is one. If there are several with no shared entry point, say that — it is the finding, not a failure to find.
## Step 3 — check the four properties
For each launcher you found, answer these four with evidence from the code, not from convention:
1. **Is the assignment separate from the start?** Does something write "job X is assigned to worker Y" somewhere before the worker begins? If yes, quote where that state lives, and say what happens if it disagrees with what is running.
2. **Is liveness reported, or derived?** Does a worker write a heartbeat about itself, or is its aliveness inferred from observable output? Quote the mechanism. A heartbeat a process writes about itself is a claim; find out which one this is.
3. **Can the same job start twice?** Find the guard, if there is one. Then find its window: how much time passes between the check and the point of no return, and is the check enforced by something that can actually refuse — a unique name, a lock, a database constraint — or only by a read? If the guard is a unique name, say what namespace enforces uniqueness, and whether two callers could be in different namespaces.
4. **Does a guard fail open or closed?** For each guard, what happens when the thing it reads is missing, unreadable or malformed? Quote the code path. Say which way it fails and whether that is the right way for this system.
## Step 4 — report
Write a table: property, what this repo does, what the reference fleet does, and whether the difference is a defect or a legitimate difference in substrate. Be willing to conclude that my setup is right and the reference is wrong for my case — it runs on one box with a handful of agents, and that assumption is load-bearing in several of its choices.
Then give me the single highest-value change, with the file and the shape of the edit. One change, the one you would make first.
If any of the four questions cannot be answered from the code, ask me with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead) rather than assuming — and include a recommended answer with each question so I can accept the default.
```
# More than one harness (/docs/operating/harnesses)
Every other page on this wiki is written from a substrate where **the same command means the same
thing everywhere**, because the fleet runs one agent binary across five accounts. That is a fact about
this fleet, not a property of the architecture, and it makes the hardest part of running more than one
harness invisible here.
If you run two or three side by side, it is not free. Each keeps its own instructions file, its own
skills directory and its own idea of what a slash command is, and the usual first attempt — copy the
corpus into each one — fails in a specific way: **the copies diverge, and nothing tells you.** The same
command then means different things depending on which harness you happened to open, which is worse
than not having the command at all, because you cannot see it happen.
So this page is about one number.
## The number that has to stay at one [#the-number-that-has-to-stay-at-one]
Not the number of harnesses. **The number of copies of each file an agent reads.**
Three harnesses reading one file is a working setup. Two harnesses reading two copies of one file is a
setup with a silent failure already scheduled. Copies drift because nothing stops them; pointers cannot
drift, because there is nothing to drift from.
That gives a test you can run rather than a principle you have to remember: **for every file an agent
reads, exactly one is a regular file and the rest are pointers to it.** If a second regular file turns
up, you have a copy, and the drift has already started or is about to.
The fleet does exactly this for its own always-loaded behavioural rules, which are the closest thing it
has to the problem. Every rule is one file in the knowledge repository, and each agent's rules directory
holds only symlinks into it:
```console
$ ls -l ~/.claude/rules/
core-behaviors.md -> .../knowledge/world-infrastructure/doctrine/core-behaviors.md
credentials.md -> .../knowledge/world-infrastructure/doctrine/credentials.md
execute-tylers-own-work.md -> .../knowledge/world-infrastructure/doctrine/execute-tylers-own-work.md
identity-and-scope.md -> .../knowledge/world-infrastructure/doctrine/identity-and-scope.md
operating-system.md -> .../knowledge/world-infrastructure/doctrine/operating-system.md
planning-loop.md -> .../knowledge/world-infrastructure/doctrine/planning-vocab.md
RULES.md -> .../knowledge/world-infrastructure/feedback/RULES.md
```
Seven symlinks, zero regular files. An edit to doctrine reaches every agent on the next `git pull`,
because the file the agent loads *is* the file in the repository. The drift check is one command with no
tooling behind it:
```bash
find ~/.claude/rules -maxdepth 1 -type f ! -name '.gitkeep'
```
Empty output means no copies exist. Any line of output is the name of a file someone copied instead of
linking, and it is the only warning you get.
## What each harness actually reads [#what-each-harness-actually-reads]
There is no single directory all three read. Knowing precisely where each one looks is what turns the
layout question into arithmetic.
Everything in the Claude Code column was **measured on a Linux box running Claude Code 2.1.233** by the
method in the next section. The Codex and opencode columns are **taken from their published
documentation** — neither is installed on the machine that wrote this page, so treat them as sourced
rather than measured, and re-check them against your own versions before building on them.
| | Claude Code | Codex | opencode |
| ------------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| **Project instructions** | `CLAUDE.md` | `AGENTS.override.md`, then `AGENTS.md`, then any name in `project_doc_fallback_filenames` | `AGENTS.md`, falling back to `CLAUDE.md` |
| **Global instructions** | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.override.md`, else `~/.codex/AGENTS.md` | `~/.config/opencode/AGENTS.md`, falling back to `~/.claude/CLAUDE.md` |
| **Skills (project)** | `.claude/skills//SKILL.md` | `.agents/skills` at the repo root, the parent, and the working directory | `.opencode/skills`, `.claude/skills`, **and** `.agents/skills` |
| **Skills (global)** | `~/.claude/skills//SKILL.md` | `~/.agents/skills` | `~/.config/opencode/skills`, `~/.claude/skills`, **and** `~/.agents/skills` |
| **Slash commands** | `~/.claude/commands/*.md` | `~/.codex/prompts/*.md` — **deprecated**, see below | `~/.config/opencode/command/*.md` |
Three things fall straight out of that table.
**opencode is the only one that reads both trees.** It looks in `.claude/` *and* `.agents/`, which means
whichever of the two you pick, opencode is already satisfied and never needs a pointer. It also reads
`~/.claude/CLAUDE.md` as its global fallback. This is the single most useful fact on the page, and it
kills a design question rather than answering it: opencode does not need to be planned for.
**Claude Code and Codex do not overlap at all.** Claude Code reads `.claude/`, Codex reads `.agents/`,
and neither reads the other. Whichever tree the one real copy lives in, exactly one pointer is required,
aimed at the other harness. One — not two, and not a sync job.
**`AGENTS.md` is the portable instructions filename and `CLAUDE.md` is not.** Codex and opencode both
read `AGENTS.md` natively; Claude Code reads only `CLAUDE.md`, with no fallback. That asymmetry is the
whole of the instructions problem, and it is one line to close.
## The measurement that decides the layout [#the-measurement-that-decides-the-layout]
The `.agents/` path is the harness-neutral one — Codex's own default and one of opencode's. The obvious
move is to put everything there and be done. Whether that works turns entirely on one question: **does
Claude Code read `.agents/skills`?**
Rather than infer it, put two identical skills on disk under different roots and ask the harness what it
found. Claude Code announces its loaded skill list in the `init` event of `--output-format stream-json`,
which costs one turn and does not even require a working login:
```bash
SB=$HOME/sandbox
mkdir -p "$SB/.claude/skills/probe-claude-path" "$SB/.agents/skills/probe-agents-path" "$SB/work"
# ... write an identical minimal SKILL.md into each ...
cd "$SB/work" && HOME=$SB claude -p --output-format stream-json --verbose --max-turns 1 "hi"
```
The `init` line reported:
```text
"skills": ["probe-claude-path", "deep-research", "design-sync", ...]
```
`probe-claude-path` is there. **`probe-agents-path` is absent.** Claude Code reads `~/.claude/skills`
and does not read `~/.agents/skills`, so "put everything in the neutral directory" is not a complete
answer on its own — it needs one pointer.
A sandbox `$HOME` is what makes this honest. It contains only the skills under test, so an absence is
evidence rather than noise, and nothing you learn is contaminated by whatever is already installed on
your real machine.
Both ways of building that pointer were then measured the same way, in a sandbox holding the real files
only under `.agents/`:
| Bridge | What it is | Result |
| --------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
| Whole directory | `~/.claude/skills` → `~/.agents/skills` | Both neutral-tree skills loaded |
| One skill | `~/.claude/skills/` → `~/.agents/skills/` | The neutral skill loaded **and** a real Claude-only skill beside it still loaded |
The per-skill link is the better default. It shares exactly what is meant to be shared and leaves the
harness's own directory a real directory, so anything genuinely specific to one harness can live beside
the link without being swept into the shared corpus.
## The instructions file, without a symlink [#the-instructions-file-without-a-symlink]
Symlinks are not always available. Directory symlinks on Windows want Developer Mode or an elevated
prompt, and on a corporate-managed machine that is a policy decision rather than yours. For the
instructions file there is a route that needs no filesystem support at all: Claude Code expands
`@path` imports inside `CLAUDE.md` at session start, as if the text were inline.
So `AGENTS.md` holds the content, and `CLAUDE.md` is one line:
```markdown
@AGENTS.md
```
Measured, with a canary string in `AGENTS.md` and a prompt that forbids reading files:
| `CLAUDE.md` contains | Model printed |
| -------------------- | --------------------- |
| `@AGENTS.md` | `HARNESS-BRIDGE-9137` |
| no import line | `NONE` |
The negative control is the half that matters. Without it the first row only shows that a model can
find a token in a small directory; with it, the import is demonstrably the mechanism carrying the text.
Codex has the mirror-image escape hatch if you would rather keep `CLAUDE.md` as the real file:
`project_doc_fallback_filenames` in `~/.codex/config.toml` adds filenames to its project-level search,
so it can be told to treat `CLAUDE.md` as an instructions file.
```toml
project_doc_fallback_filenames = ["CLAUDE.md"]
```
Either direction is one line. Pick the one whose real file you would rather open.
## Prefer a skill to a slash command [#prefer-a-skill-to-a-slash-command]
A slash command is the one artifact with three different homes and no overlap between them:
`~/.claude/commands`, `~/.codex/prompts`, `~/.config/opencode/command`. Three locations, three copies,
and the drift Tyler describes — *"every pair I checked had already diverged"* — is exactly what three
copies produce.
Skills do not have that shape. `SKILL.md` is one format all three read, and two of the three read at
least one directory in common with another. Codex has already drawn the same conclusion about its own
prompts directory, in its documentation, verbatim:
> Custom prompts are deprecated. Use skills for reusable instructions that Codex can invoke explicitly
> or implicitly.
So if a process is going to exist in more than one harness, **write it as a skill, not as a slash
command.** A skill is still invocable by name; it is simply the form that has one home instead of three.
## The shape that survives [#the-shape-that-survives]
For a process that belongs to a repository — the usual case, and the one with the least to maintain,
because it travels with the clone and there is nothing to install:
```text
.agents/skills/plan-start/SKILL.md the one real file
.claude/skills/plan-start → ../../.agents/skills/plan-start
AGENTS.md the one real instructions file
CLAUDE.md one line: @AGENTS.md
```
Two real files, two pointers, nothing scheduled and nothing to run. Codex and opencode read the real
files directly; Claude Code arrives through the pointers. Committed to git, the whole arrangement is
reviewable in a diff, and a copy reintroduced by hand shows up as a regular file where a symlink used to
be.
For a process that belongs to you rather than to a repository, invert it and no symlink is needed at
all: keep the real files in `~/.claude/`, which Claude Code and opencode both read natively, and give
Codex the one pointer it needs.
## What does not port, and what to do about it [#what-does-not-port-and-what-to-do-about-it]
An honest list, because the parts that do not port are the parts that cost you later.
**Hooks and plugins are per-harness, and there is no bridge.** They are each harness's own extension
mechanism with its own shape. Anything load-bearing that lives in a hook exists in one harness only. The
fleet hits this in its own way: its doctrine ships to the agent boxes as a Claude Code plugin, and that
plugin is exactly what would not survive contact with a second harness — which is why the rules on the
worker boxes are symlinks into a git repository instead. A file in a repo ports; a plugin does not.
**MCP is the one tool layer all three speak, but it is not free.** The server is written once and each
harness still needs its own registration pointing at it. That is one copy of a connection string per
harness rather than one copy of your logic, which is the trade you want, but it is not zero.
**A tracker you cannot reach is a substrate difference, not a configuration problem.** The rule this
fleet runs on — nothing the tracker owns is ever copied to disk, so scope and status have exactly one
home and cannot drift — holds only where the tracker is reachable. On a network where it is blocked, no
amount of config makes "read scope live from the ticket" true.
The honest shape there is not a hidden cache that looks live. It is the fleet's own rule about anything
read from a distance, which ports cleanly to this case: **a cached value must carry its own age and be
able to say it is stale.** A cached ticket with a fresh-looking timestamp defeats every check you might
put on it and is strictly worse than an empty one, because it fails silently and confidently. If you
must hold ticket state offline, hold it somewhere that prints when it was fetched, and treat a stale
read as a stop rather than as data.
## The one line to take away [#the-one-line-to-take-away]
**Count the copies, not the harnesses.** Three harnesses reading one file is fine. One file copied twice
is a divergence with a start date you will not be able to find.
# Operations (/docs/operating/operations)
**Every command on this page names its box.** That is not decoration. An audit of the fleet's own
registry on 2026-08-16 ran the seventy-two of its then-seventy-three self-checks that were safe to run
from anywhere, from the Architect box: twenty-six failed — and all twenty-six passed when re-run on the
server. The registry was accurate and location-blind, so following its instruction from the wrong
machine manufactured twenty-six false failures.
Two conventions used throughout:
* `# on ETH` — the Architect's WSL box, `100.103.62.104`.
* `# on novaserver` — `100.71.25.43`. If you are not already on it, prefix with
`ssh nova@100.71.25.43`.
**Never write the bare alias** — `ssh` with `nova` as the whole host, no user and no IP. There is no
`~/.ssh/config` on the Architect box to expand it, so it fails with `Name or service not known`. Thirteen
files in the fleet's internal docs still write it that way; none of them work.
## Reaching a box [#reaching-a-box]
```bash
# from anywhere on the tailnet — the server
ssh nova@100.71.25.43
# from anywhere on the tailnet — a specific master's account
ssh nova@100.71.25.43 'sudo -u cloud -H bash -lc "whoami; pwd"'
```
`nova` here is a unix account that happens to share a name with one of the masters — it is the SSH entry
point for the box, and `sudo -u ` is how you become any of the others.
**The account is not the lane, and this page used to say it was.** A master's board lane, its GitHub
identity and its unix account are three separate names that usually agree and are not required to. Which
accounts exist and which lanes the scheduler routes to are both read from the fleet's own files, here:
> GENERATED — The lanes the scheduler will route work to
>
> This block could not be read and therefore renders nothing: GitHub 404 Not Found on /contents/world-infrastructure/fleet/registry/organization.yaml?ref=main
*Generated at request time (2026-09-14T16:06:54.133Z) from `utopia-models/knowledge` @ `main` — `_infra/scripts/sweep-dispatch.sh` (the `MASTERS=` assignment — the scheduler rejects any Todo issue whose Lane is not in this set); `world-infrastructure/fleet/registry/organization.yaml` (the agent records those lane names resolve to). Nothing here is stored: this page holds no copy of these values.*
There is also a **browser terminal per master**, read-only, served by `ttyd` on the tailnet:
```bash
# on novaserver — confirm the four viewports are listening
ss -ltn | grep -E '100\.71\.25\.43:781[0-3]'
```
## Dispatching work [#dispatching-work]
Dispatch is how a master receives an issue. **Starting the run is the assignment** — there is no separate
claim step.
```bash
# on ETH, from the knowledge repo
bash _infra/scripts/dispatch.sh [owner/repo]
```
The repo argument defaults to `utopia-models/knowledge`. The script runs three pre-flight guards, syncs
that master's clones, launches a detached transient systemd unit, and returns. The guards, their exit
codes and their escape hatches are on [The dispatch runtime](/docs/operating/dispatch-runtime). The run posts **▶ started** on the issue, and its exit trap posts **✔**, **✘**, **⚠** or **⛔**
with the pull-request link.
### Before you dispatch: is the master dark? [#before-you-dispatch-is-the-master-dark]
```bash
# on ETH — the whole table
bash _infra/scripts/dispatch.sh --quota
```
Real output, 2026-09-04 — shown as a sample of the shape, not as a current reading:
```text
win clear
nova clear
cloud clear
dust clear
```
A refused master replaces `clear` with the reason it was refused. That row, observed on 2026-08-16, is
the shape to recognise:
```text
nova dark resets=unknown provider=claude source=entitlement-capture
```
```bash
# on ETH — machine-readable: just the dark names
bash _infra/scripts/dispatch.sh --quota-dark
# on ETH — would this specific master be refused? exit 0 = dispatch, 9 = refused
bash _infra/scripts/dispatch.sh --preflight-quota cloud; echo "exit=$?"
# on ETH — dispatch anyway (e.g. the account was just rotated)
DISPATCH_IGNORE_QUOTA=1 bash _infra/scripts/dispatch.sh cloud 1234
```
`resets=unknown` is the state with no recovery path: the provider named no reset time, so nothing brings
that agent back on its own. A reset time in the *past* counts as clear.
### Why is nothing being dispatched? [#why-is-nothing-being-dispatched]
If the board has open work and the scheduler is dispatching none of it, the scheduler will tell you why,
per issue:
```bash
# on novaserver, from the knowledge clone
bash _infra/scripts/sweep-dispatch.sh --explain
```
Each rejected issue prints its own reason — `lane-not-in-masters`, `has-hold-label`,
`is-a-grouper:has-open-subissues`, `last-marker-is-terminal`, `parent-closed`, `is-parked`. A large open
count with zero dispatchable is almost always **routing**, not capacity: an issue with no lane, or on a
lane no master serves, can never be picked up however idle the fleet is.
## Is an agent actually alive? [#is-an-agent-actually-alive]
**`getent passwd nova` is not this check.** It succeeds on an agent that cannot run a single turn. To
answer the question you must exercise the artifact:
```bash
# on novaserver — the real probe. Anything other than a normal completion is the finding.
sudo -u nova -H claude -p 'reply with the single word: alive'
# on novaserver — what the agent itself last recorded
sudo -u nova -H cat /home/nova/.claude/state/quota-status.json
```
An `api_error_status: 403` here means entitlement was withdrawn at the account, not that anything on the
box is broken — the binary, the credentials file and the unix account are all still perfectly healthy.
`"resetsAt": null` in that state file means there is no timer that restores it.
## Watching a run [#watching-a-run]
Runs are transient systemd units under the master's **user** manager. The name is built in
`dispatch.sh`, so it is read from there rather than described from memory:
> GENERATED — How a run’s transient unit is named
>
> This block could not be read and therefore renders nothing: no line starting `UNIT=` in the script
*Generated at request time (2026-09-14T16:06:54.136Z) from `utopia-models/knowledge` @ `main` — `_infra/scripts/dispatch.sh` (the `UNIT=` assignment that names the transient systemd unit). Nothing here is stored: this page holds no copy of these values.*
```bash
# on novaserver — what is running right now for a given master
sudo -u cloud -H XDG_RUNTIME_DIR=/run/user/$(id -u cloud) systemctl --user list-units 'task-*'
# on novaserver — the log for one run
sudo -u cloud -H journalctl --user -u task-5882-1786804561-2943 --no-pager
```
The board is the other half of this, and usually the better half: the run's own ▶ and ✔/✘ comments are
on the issue, and they are the fleet's definition of liveness.
## Secrets [#secrets]
```bash
# on any box — read one value (never echo it)
V=$(bash ~/.claude/scripts/infisical-get.sh fleet-infra SOME_KEY prod); echo "len=${#V}"
# on any box — list every key in a project, so "it does not exist" is a real claim
bash ~/.claude/scripts/infisical-get.sh --keys fleet-infra prod /
# on any box — write a value; the value goes on STDIN, never in argv
printf '%s' "$NEW_VALUE" | bash ~/.claude/scripts/infisical-set.sh fleet-infra SOME_KEY prod
```
Passing a secret as an argument is rejected by a guard: argv lands in shell history and in the process
table. Before concluding a credential is absent, list the whole project — the fleet's recurring failure
is not a missing credential but a grep for the wrong key name.
## Checking services and timers [#checking-services-and-timers]
```bash
# on novaserver — system-level failures
systemctl --failed
# on novaserver — a master's own user-manager failures
sudo -u nova -H XDG_RUNTIME_DIR=/run/user/$(id -u nova) systemctl --user --failed
```
**A clean `--failed` is weaker evidence than it looks.** A dead timer never fails. Enumerate the timers
themselves and look at the last and next columns:
```bash
# on novaserver — every scheduled timer for a master, with last/next fire times
sudo -u nova -H XDG_RUNTIME_DIR=/run/user/$(id -u nova) systemctl --user list-timers --all
```
Two traps that produce a clean bill of health on a broken box:
1. An **enabled timer whose service is `not-found`** never runs and never fails.
2. **Cron is a second scheduling layer.** Masters carry crontab entries with no systemd timer at all.
One `sudo -u` line per account the fleet actually dispatches to, generated so that none of them can
name an account that does not exist:
> GENERATED — The second scheduler, per account
>
> This block could not be read and therefore renders nothing: GitHub 404 Not Found on /contents/world-infrastructure/fleet/registry/organization.yaml?ref=main
*Generated at request time (2026-09-14T16:06:54.137Z) from `utopia-models/knowledge` @ `main` — `_infra/scripts/sweep-dispatch.sh` (the `MASTERS=` assignment — the accounts to probe); `world-infrastructure/fleet/registry/organization.yaml` (each lane’s unix account). Nothing here is stored: this page holds no copy of these values.*
### Deleting a scheduled unit — a two-half operation [#deleting-a-scheduled-unit--a-two-half-operation]
Removing a unit from an agent's brain repo does **not** stop it running: `~/.config/systemd/user/` holds
*copies*, not symlinks. A real deletion cost the fleet a spurious P0 when the orphaned timer kept firing
against a script that no longer existed and exited `203/EXEC`. Both halves:
```bash
# on novaserver, as the owning master — the on-box half
systemctl --user stop .timer .service
systemctl --user disable .timer
systemctl --user reset-failed .service
rm -f ~/.config/systemd/user/.{service,timer}
systemctl --user daemon-reload
```
...and then delete it from the brain repo and push, or a sync job will put it back.
## Checking a public surface [#checking-a-public-surface]
A status code alone is never the answer — see [Surfaces](/docs/structure/surfaces) for why a 404 has at least three
different meanings.
```bash
# from anywhere — the code, and whether anything redirected
curl -s -o /dev/null -w '%{http_code} redirects=%{num_redirects} final=%{url_effective}\n' -L https:///
# from anywhere — headers, to see which edge served it
curl -sI https:/// | grep -iE 'server|x-vercel|cf-ray|x-matched-path'
```
An `x-vercel-error: DEPLOYMENT_NOT_FOUND` on a 404 means no deployment is attached to that hostname,
which is what both a *deliberately retired* surface and an *accidentally dead* one look like. The
difference is a decision record, not a header. A `530` with Cloudflare error 1033 means a tunnel origin
is unreachable — that one is never deliberate.
**A 200 does not prove a page is public.** An SSO gate redirects to a login page that itself returns 200,
so assert `redirects=0` and check the final URL, not just the code.
## Shipping: merging when a check is red [#shipping-merging-when-a-check-is-red]
Nothing gates `main`. Branch protection and rulesets both return 403 on this plan tier, in every repo, so
every check is **information, never a stop**.
```bash
# from anywhere with gh auth
gh pr merge --squash --admin
```
If that fails, read the actual error:
* **CONFLICTING / DIRTY** — rebase and force-push **your own topic branch by name**:
```bash
git fetch origin && git rebase origin/main
# resolve, then:
git push --force-with-lease origin HEAD:
```
Never a bare `git push --force`: it takes whatever HEAD happens to be. One such command once moved 501
commits off a shared clone's `main`, and nothing local showed it.
* **A real build, typecheck or deploy failure** — that failure is real. Fix the code.
* **Anything else** — retry with `--admin`.
### When the secret scanner cannot run [#when-the-secret-scanner-cannot-run]
The secret scan is the one check treated as binding. If the workflow itself cannot start, run it locally
against the same pinned version rather than skipping it:
```bash
# on any box, from the repo root
VERSION=8.24.3
curl -sL "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \
| tar xz -C /tmp gitleaks \
&& /tmp/gitleaks git . --no-banner --redact --config .gitleaks.toml \
--log-opts "--no-merges .."
```
Clean means merge. Findings mean fix, rescan, then merge.
## Verifying a claim about the fleet [#verifying-a-claim-about-the-fleet]
The habit that produces the fewest wrong answers:
1. **Read the source, do not recall it.** Every exact value — a path, a port, a key name, a count — comes
from the file or the probe this session, never from memory.
2. **Exercise the artifact, not the build.** A green build proves the source compiles. Curl the deploy,
run the binary, render the page.
3. **State the box.** A result without the machine it came from is not reproducible.
4. **Prefer the negative control.** Confirm your check can *fail* — run it against something you know is
broken. A check that passes on everything is measuring nothing.
# The planning loop (/docs/operating/planning-loop)
Sourced from the fleet's own component document for `planning-loop`. Called in-flux rather than stable
because it has accumulated seven decision records; because its own doctrine file states a count that disk
has already outgrown; and because the hooks that derive its first two steps ship in a separately versioned
package that nothing in the repository can grade.
This is the process a human and the planning agent run together, and the vocabulary the rest of the fleet
uses to talk about work. Stop it and every agent keeps executing whatever is already filed, then runs dry —
nothing else in the organization turns an intention into a board issue.
What the five steps *mean* is on [How work flows](/docs/operating/work). This page is about the one
structural property that makes them trustworthy.
## No step is reported. Every step is derived [#no-step-is-reported-every-step-is-derived]
```text
1 BRIEF · 2 LOOP ⟳ ‖ 3 FILE · 4 DISPATCHED · 5 DONE
↑ planning ends, execution begins
```
| # | Step | Derived from |
| - | -------------- | --------------------------------------------------------------- |
| 1 | **BRIEF** | a brief event, with its timestamp, on the plan's registry entry |
| 2 | **LOOP** ⟳ | at least one recorded planning-agent turn on that entry |
| 3 | **FILE** | an issue reference on that entry |
| 4 | **DISPATCHED** | at least one referenced issue still open |
| 5 | **DONE** | every referenced issue closed |
**That is why a plan cannot lie about where it is: there is no field to lie in.** Steps 3 through 5 are
read off the board on every render. Steps 1 and 2 come from events a hook writes as they happen, not from a
summary written afterwards.
`step < 3` is the whole definition of planning. The command center's planning and execution sections
partition on that single number, which means the boundary between "still thinking" and "being built" is
one comparison rather than a judgement anyone makes.
## Step zero asks three ordered questions, and the order is load-bearing [#step-zero-asks-three-ordered-questions-and-the-order-is-load-bearing]
Before step 1 there is a step zero: **mode, then portfolio, then subprocess.** The order matters because
each answer narrows the next. Mode decides which section of a catalogue exists at all; portfolio decides
which catalogue.
The three modes are exactly three, and they are literals rather than descriptions:
| Mode | What it means |
| --------------------- | --------------------------------------------------------------------------------------- |
| `dispatch-monitor` | agents execute, and the planning agent stays on it until the chain completes |
| `dispatch-no-monitor` | agents execute, and the planning agent's involvement ends at dispatch — the normal case |
| `interactive` | the human and the planning agent work it in session, with the human in it |
🔴 **`interactive` is the whole name.** Not *needs-human*, not *blocked*, not *manual*, and above all it
is **not a synonym for blocked**. It is a mode: work that is running, with a person in it. Work that
genuinely cannot run unattended is *filed* as interactive; it is never filed as unattended work that then
reports itself stuck.
That distinction sounds pedantic and is not. The moment "interactive" and "blocked" become the same word,
a queue of work that is *running fine with a human in it* becomes indistinguishable from a queue of work
that is *stopped*, and no dashboard can tell the two apart afterwards.
## Two things end the loop, and both are read rather than remembered [#two-things-end-the-loop-and-both-are-read-rather-than-remembered]
Every turn in step 2 ends by asking the human a question whose last two options are, in that order,
*research more* and *file it, move to step 3*. The loop ends when one of exactly two things happens:
1. The human **clicks** the second option.
2. The human **types** the sentence that means the same thing.
An approving remark is neither. Enthusiasm about the artifact is neither.
The click is the interesting half, because it is nearly invisible. **A click comes back as a tool result,
not as a message** — so a per-turn injector that reads what the human said structurally cannot see it. A
separate hook reads the tool response's answers field, and only that field, which is the difference
between a mechanism that observes the exit and one that infers it from tone.
**And neither exit advances the step. Filing does.** The click ends a conversation; a step changes when an
issue exists.
## Filing is one command, and its shape is a real decision [#filing-is-one-command-and-its-shape-is-a-real-decision]
Filing chains or waves the work, and the difference is not cosmetic — a plan designed as parallel groups
and filed as a chain has exactly one dispatchable issue. The mechanics, the refusal rule and the case that
produced it are on [The board](/docs/operating/board).
## The loop ends at dispatch [#the-loop-ends-at-dispatch]
Steps 1 and 2 of the next plan run **concurrently with the execution of the last one**. The scheduler, the
reaper and the announcer carry the tail; the planner does not.
**Watching an agent work is indistinguishable from not trusting it.** If the only reason to stay is to see
whether the run succeeds, the run already announces that on the issue, and staying buys nothing but a
serialized planner.
🔴 **The step-4 mode is not the planning mode, and the two words collide.** The planning mode is the step
zero question above and decides which subprocesses exist. The step-4 mode is what the board shows for work
already dispatched. They are different vocabularies that happen to share three of their words, and the
mapping between them is one table in doctrine rather than an inference anyone makes at the keyboard.
## Where this loop is currently weakest [#where-this-loop-is-currently-weakest]
* **A doctrine file states a subprocess count that disk has outgrown.** It heads its own table with a
count; a directory walk returns one more than that. The same section already carries a note saying the
count had drifted *once before* and instructing the reader to re-derive rather than edit the cell — and
it drifted again anyway. A number in prose is a claim about the filesystem, and nothing was re-checking
it.
* **Nothing binds the two doctrine files to each other.** One is the full loop; the other is the compacted
version the executing agents read, and by design the same path resolves to a *different file* depending
on the agent's class. A rule changed in one is not checked against the other. A contradiction between
them is invisible to continuous integration and surfaces as an agent doing the planner's job, or the
reverse.
* **The loop's own machinery is not gradable from the repository that documents it.** Everything that
derives steps 1 and 2 lives in a separate, version-gated package, so the repository's assertion checker
can bind the *doctrine* and never the *mechanism*. One script reads the real population across boxes,
and it is run by hand.
Publishing that third one matters more than it looks: **a process whose enforcement lives in a different
release train from its documentation will drift, and the drift will be invisible from either side.**
## Take it further [#take-it-further]
```text title="prompt-1|Run the five-step loop on one of your own ideas|desktop|Takes a rough idea and runs it through brief, research, and filing — refusing to write the phase list until the current state is settled. No shell needed."
I want you to run a planning process on an idea of mine, using the discipline described here:
https://docs.utopiamodels.ai/docs/operating/planning-loop.md
https://docs.utopiamodels.ai/docs/operating/work.md
Read both before you start. The second one has the vocabulary; the first has the loop.
The rules I want you to hold to, because they are the whole point and they are all easy to skip:
**Nothing I say advances a step.** A step changes when an artifact exists, not when I sound convinced. If you find yourself writing "great, moving to step 3" because I was enthusiastic, stop and notice that nothing was produced.
**Current state gates everything after it.** You do not write a single phase, task or estimate until you can state what exists TODAY and I have confirmed it. Most planning failures are a phase list written over a guess about the starting point.
**Ask me one question at a time.** Not a numbered list of eight. One, with a recommended answer, so that if I am tired I can take your default and still end up somewhere sensible.
Here is the sequence.
Step zero. Before anything else, establish three things in this order: what kind of work this is (something I will do with you in the room, or something I will hand off and walk away from), what part of my life or business it belongs to, and what shape of process fits. Ask me these with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), one at a time, in that order — each answer narrows the next, so asking them together wastes the narrowing.
Step 1 — the brief. Restate my rough idea back to me in one paragraph. No research yet. Just: here is what I think you are asking for. If your restatement is wrong I will correct it, and the correction is worth more than any research you could have done first.
Step 2 — the loop. Now research, then ask, then research again. Every round ends with a question that has a recommended answer, and the last two options on every one of those questions are always "research more" and "that is enough, file it." Repeat until I pick the second one. Do not decide for me that we have done enough.
The artifact you are building through step 2 has exactly three sections, and you fill them in this order:
1. CURRENT STATE — what exists now, what works, what does not. This gets settled by my confirmation before you touch section 2.
2. AFTER STATE — what is true when this is done. Written only once section 1 is settled.
3. THE PHASES — empty until 1 and 2 are both confirmed. This is last, and it is last for a reason: a phase list written early reads as decided.
Do not add a fourth section. If you think one is needed, ask for it rather than adding it — an unrequested section is work I have to discover and cut.
Step 3 — filing. Turn the phases into discrete pieces of work. For each: what it produces, what has to be true before it can start, and how I will know it is done. Then tell me which of them could run at the same time and which genuinely must wait, because getting that wrong is how a plan that should take three days takes three weeks with everything queued behind one item.
Start with step zero. Do not skip ahead to being helpful.
```
```text title="prompt-2|Find the planning state your system is guessing at|claude-code,opencode,codex,cursor,gemini|Reads your planning artifacts and issue tracker, and reports which status you can DERIVE and which you are trusting a human to have updated. Reads only; writes nothing."
You are going to audit how planning state is recorded in this repository, and find every place where a status is TYPED rather than DERIVED.
Read the reference first:
curl -s https://docs.utopiamodels.ai/docs/operating/planning-loop.md
The property being audited against is this: every step of that fleet's planning process is derived from an event or from the tracker, so a plan has no field it could lie in. I want to know how much of my planning state is a claim rather than a measurement.
Do not create, edit or delete any file, and do not modify anything in the tracker. Read only.
## Step 1 — find the planning artifacts
Look for all of these before concluding. Most repositories have several and they usually disagree:
- files matching ROADMAP, PLAN, TODO, BACKLOG, MILESTONE, or a docs directory containing them
- a design or RFC directory, and whether the documents in it carry a status field
- issue templates, and which fields they ask a human to fill in
- project board configuration in the repository
- a changelog, and whether it is generated or hand-written
For each, report the path and how it is maintained: hand-edited, generated by a script, or unknown.
## Step 2 — for each status field, find its oracle
This is the part that matters. For every field that records a state — a checkbox, a Status:, a phase marker, a "Done" column, a percentage — answer:
- **What would make this field FALSE?** Name a specific event: a merged pull request, a deleted file, a passing test, a deploy.
- **Is anything watching for that event?** A hook, a workflow, a scheduled job. Quote it if it exists.
- **If nothing is watching: how would anyone find out?** "Someone would notice in review" is an answer, and it is the answer that means the field is decorative.
## Step 3 — measure the drift that already exists
Do not theorize about this; measure it. For a sample of at least ten items:
- Compare each checkbox or status against git history. When was the item last touched, and does the recorded status match what the commits show?
- For any document with a date in it, compare that date against `git log -1 --format=%ci -- `. A document whose stated date is older than its last edit was updated without its own timestamp being updated.
- For any stated COUNT — "the 12 services", "our 5 environments" — go count the thing. Report the stated number and the real one side by side.
That last check is the highest-yield one and it takes minutes. A number written into prose is a claim about the filesystem that nothing re-checks.
## Step 4 — report
One table: field, where it lives, what would falsify it, what watches for that, and DERIVED or TYPED.
Then the drift you actually measured, with the command and its output for each finding, so I can reproduce it. A finding I cannot reproduce is a finding I cannot act on.
Then one recommendation: the single status field where switching from typed to derived would buy the most, and the concrete mechanism — which event, watched by what, writing where.
If you need to know something about my workflow that the repository cannot tell you, ask me with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead) rather than assuming, and include a recommended answer with the question.
```
# How work flows (/docs/operating/work)
**One principle underneath all of it: no stored copies of state, no resident processes, and the documents
are the config.** Anything that can drift is deleted rather than monitored. Anything unattended announces
itself where the founder already looks — the board — so silence can never be mistaken for health.
## The three words [#the-three-words]
Three layers, three words. Getting these confused is the most common vocabulary failure, so the fleet
deliberately retired a fourth word ("task") that used to mean two of them at once.
| Layer | Word | What it is | Example id |
| --------------- | ----------- | ----------------------------------------------------------------------- | ---------- |
| Above the board | **PLAN** | One thinking session: a document, its research, and the forks it closed | `PLAN-178` |
| On the board | **Feature** | A body of shippable work with ordered children | `#5878` |
| On the board | **issue** | One dispatchable unit, run to a merged pull request | `#5882` |
A PLAN is **not** a Feature. It references Features; it does not equal one. A PLAN can spawn several, and
some of what a PLAN decides never becomes work at all.
## The five steps [#the-five-steps]
```text
1 BRIEF · 2 LOOP ⟳ ‖ 3 FILE · 4 DISPATCHED · 5 DONE
```
| # | Step | What it means |
| - | -------------- | ----------------------------------------------------------- |
| 1 | **BRIEF** | A rough note from the founder, restated. No research yet |
| 2 | **LOOP** ⟳ | Research ⇄ questions, repeating, until the plan is complete |
| 3 | **FILE** | At least one issue exists for this plan |
| 4 | **DISPATCHED** | A master is executing |
| 5 | **DONE** | Every referenced issue is closed |
**Every step is derived, never reported.** Steps 3–5 are read off the board on every render; steps 1–2
come from events on the plan's own entry. Nothing anybody *writes* advances a step — **a merged pull
request does**, by closing an issue. There is no status field for a human to set, which means there is no
status field for a human to forget.
Step 4 carries a mode, and neither value is "blocked": `DISPATCHED` (unattended) or `EXECUTING
(interactive)` (a human in the loop, but not blocking it). Being genuinely stuck is not a state — it is a
comment on the issue saying exactly what blocks you, followed by a clean exit. Stopping silently is the
one unacceptable outcome.
## The loop, end to end [#the-loop-end-to-end]
```text
PLAN (a thinking session)
└─ Feature issue + ordered sub-issues + a committed brief
└─ the Architect DISPATCHES (starting the run IS the assignment)
└─ a master executes to merged + live
└─ git moves the board
└─ the founder glances at truth
```
## The board [#the-board]
**There is exactly one tracker**: a GitHub organization project board. Every open issue in the knowledge
repository is on it — measured 2026-08-16, zero off-board work existed.
Work that becomes a board issue lives on the board and nowhere else. It specifically does **not** also
live in an agent harness's private to-do list; two trackers cannot both be true.
What the board carries per row: the issue, its **Lane** (which master serves it), **Priority**,
**Portfolio**, and a **Status** that is derived rather than typed.
### Lanes, and the failure mode nobody expects [#lanes-and-the-failure-mode-nobody-expects]
A lane routes an issue to a master. The important property is the negative one: **an issue with no lane,
or on a lane no master serves, is structurally undispatchable** — permanently, silently, and regardless
of how idle the fleet is.
This is the single most misread signal in the system. A board showing plenty of open work and zero
dispatchable looks exactly like a capacity problem, and investigations have gone looking for a broken
scheduler while the scheduler was printing the real cause every five minutes. Measured 2026-08-16: of the
open issues at that moment, thirteen were undispatchable — eleven with no lane at all, plus two on a lane
that no sweeper reaches. That is a routing defect, and no amount of extra compute fixes it.
The diagnostic exists and takes one command:
```bash
# on novaserver, from the knowledge clone
bash _infra/scripts/sweep-dispatch.sh --explain
```
### Board hygiene, honestly [#board-hygiene-honestly]
Two measured weaknesses, recorded here rather than smoothed over:
* **Closed rows from other repositories never converge.** The reconciler archives rows only for the
repository it runs in, so closed items from sibling repos accumulate. On 2026-08-16 they were the
majority of all rows on the board.
* **Rows can carry no lane and no status at all.** Nothing rejects a row in that state at creation time,
and a row in that state is invisible to routing.
## Thick tasks — never work a thin one [#thick-tasks--never-work-a-thin-one]
A dispatched issue is not a sentence. The brief lives **beside the work**, in the repository:
```text
/specs/-/spec.md objective · acceptance · scope · grounding
/specs/-/plan.md phases → checkbox items
```
**The `plan.md` checkboxes are the live work state.** Tick `[ ] → [~] → [x]` and commit as you go. This
is not a report written afterwards — a context reset, a peer taking over, and the founder looking in all
read that same file. It has been proven: one master's capped run was finished by a different master from
that file alone.
Two rules that keep this from rotting:
1. **The issue body must contain the literal spec path.** A runner reads a spec only *if the issue names
one*. An unnamed spec does not exist to the master executing the work — the highest-leverage line in
the pipeline, and the easiest to omit.
2. **If the spec references dead prerequisites, or there is no spec, stop cleanly and say exactly why on
the issue.** Never churn a stale brief. A spec can cite dead machinery in perfectly good faith, so
every reference gets re-grounded in the session that acts on it.
Non-trivial architecture choices get a decision record beside the work, in
`/specs/-/decisions/`. Superseding one edits the old record's `Superseded-by`
header **in the same commit** — a silently invalidated decision is this fleet's most repeated failure.
## Communication [#communication]
Masters talk to each other by **@mentioning a GitHub account in a comment on the relevant issue**. That
is the whole mechanism. A master's next dispatched run receives unread mentions injected at start, which
gives at-least-once delivery with a durable read state, and it happens where the founder is already
looking.
There is no chat system, no message bus and no inbox service. There was a NATS bus; it was deleted. See
[Decisions](/docs/structure/decisions).
**Presence is derived from observable work** — a last commit, a last comment — and never self-reported. A
heartbeat that an agent writes about itself is a claim, not evidence.
## Gates: all of them are advisory [#gates-all-of-them-are-advisory]
**Nothing gates `main`.** Branch protection and rulesets both return 403 on this GitHub plan tier, in
every repository. That is not a loophole being exploited; it is the actual configuration, and the fleet's
process is built to be honest about it.
Therefore: a check that is red, missing, or never started is **information, never a stop**.
**Fix-forward is the rule.** When a check fails, identify the real failure. A broken build or a genuine
security finding is real — fix it. Infrastructure being down is not — use the documented fallback and
proceed. Waiting for a human to unblock a merge is never the move.
| Gate | If it fails |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Secret scan** | The one hard stop. If the workflow cannot run, run the same pinned scanner locally (see [Operations](/docs/operating/operations)) and only then merge |
| **A real build, typecheck or deploy failure** | Real. Fix the code |
| **Shell syntax check** | Real, and cheap: a script that cannot be *parsed* cannot run. Read backwards from the reported line — an unbalanced quote is reported at the next paren, not at the defect |
| **A lint on a title, a filename, or a doc convention** | Not real. Never blocks a merge |
**A merge is never a human gate.** If a merge does not go through, read the actual error and act on it:
rebase a conflicting branch, or use `gh pr merge --squash --admin`. Escalating a merge — "needs
eyes", "should I force it?" — is explicitly banned. A merge is reversible in git; leaving a pull request
open is what actually costs.
Two gate designs are **forbidden outright**, both because they were tried:
* A gate that blocks on content keywords or on a generated map. That class false-blocked documentation
and auto-reverted its own cleanup.
* Resident processes, heartbeat polls, and scheduled "health checks". The board is the queue, and silence
is the failure mode.
## Finishing [#finishing]
**Finish means: a pull request whose body says `Closes #N`, merged.** The merge closes the issue and
moves the board. Nobody types a status; the board cannot lie about work that git already did.
**Arming auto-merge is not finishing.** If the pull request is still open when the run exits, the work is
not done, and the run's close-out comment has to say so. Fifteen pull requests once stacked up unnoticed
behind exactly that misunderstanding.
## What the loop actually produced [#what-the-loop-actually-produced]
Measured 2026-08-16, over the preceding seven days: **745 issues closed**. Over the preceding twenty-four
hours: roughly **157 dispatch runs**, of which **two failed** — and both failures filed their own board
issue automatically through systemd's `OnFailure` path.
That last detail is the one worth taking away. The system is not designed to avoid failure; it is
designed so that a failure announces itself within seconds, on the surface a human already reads.
## The uncomfortable part [#the-uncomfortable-part]
Doctrine says *"the board is the ONLY tracker"* and also *"the `plan.md` checkboxes ARE the work state"*.
Both statements are load-bearing, and they do not reconcile with each other. Counting honestly, there are
**four parallel trackers** for one body of work: the board, a plan registry, the plan documents
themselves, and the per-spec `plan.md` checkboxes. Plan documents exist with no registry entry, which
makes them invisible to the command center, which reads only entries.
This is published rather than hidden because a wiki that claims one tracker while the fleet runs four is
the exact category of document this whole site was built to replace.
# Module — Decision discipline (/docs/patterns/decision-discipline)
These prompts need a shell. Switch to any terminal harness to see them.
```text title="prompt-1|Find where my decisions live, and interview me|claude-code,opencode,codex,cursor,gemini"
PROMPT 1 of 2 — find where my decisions live and which of them are silently dead, ask me what you cannot measure, and write four files. You are not building anything in this step.
ASK EVERY QUESTION IN THIS PROMPT WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). The folder question, the tier confirmation, and every interview question. Not prose in the conversation.
That is not a formatting preference. A question asked in prose has no options, so it carries no RECOMMENDED option — and the recommended option is the entire mechanism by which someone who reads nothing still ends up with a working system. Asking in prose removes that silently, and the conversation looks fine while it happens.
If your harness has no such tool: say so in one line, then ask in plain text with the options written out and the recommended one marked. Never drop the options.
I am a senior technical person. Do not explain what a decision record is, do not reassure me, and do not walk me through basics. Where I am wrong about my own setup, say so with the evidence.
FIRST — WRITE YOUR CHECKLIST TO A FILE
Long sessions compress their own history and instructions pasted into chat get dropped when that happens. Files do not. Write this to ~/DECISION-DISCIPLINE-CHECKLIST.md and work from that file rather than from this message:
# Decision discipline — interview
## Tasks
- [ ] 1. Survey this machine
- [ ] 2. Find every decision, supersession, deletion and limit
- [ ] 3. Score three dimensions, derive a tier, fetch the tier's interview template
- [ ] 4. Ask where the output goes
- [ ] 5. Show the tier as a one-step nudge
- [ ] 6. Interview — one question at a time
- [ ] 7. Write the four files
- [ ] 8. Report, and tell me to paste prompt 2
## Rules
- Read only. I create this checklist, the output folder, and four files in it.
- I never modify anything I did not create.
- I report that a credential file exists and where. I never read one or print a value.
- Every claim about this machine carries the command that produced it.
- I tick each box as I finish it, not at the end.
1 — SURVEY
Fetch and run: https://docs.utopiamodels.ai/kit/survey.md
If that fetch fails, say what you got and stop. Everything below is calibrated on it.
Record what each command actually printed, with the command beside it. Where a check is inconclusive, write inconclusive rather than what is usually true.
Tick box 1.
2 — FIND THE DECISIONS
The survey found where my work lives. Run these against every work root it found — each git repository, and the notes directory my config points at, if any. Never assume a command exists; check first.
A work root is one I OPERATE: a repository this account commits to, or one whose config this harness loads. A tree this account can merely READ — another user's home, a mounted drive, a clone another profile or agent works in — is read-only reference. Record what you find there in DECISION-MAP.md, mark it as someone else's, and never count it as mine.
Where records already live:
find -maxdepth 5 -type d \( -iname decisions -o -iname adr -o -iname adrs -o -iname decision-records \) -not -path '*/node_modules/*'
grep -rliE '^(status)\s*:\s*(proposed|accepted|superseded|deprecated|rejected|killed)' --include='*.md'
Supersessions, and whether both ends agree:
grep -rniE '^(supersedes|superseded[- ]by)\s*:' --include='*.md'
For every pair, open both files. A new record that says it supersedes an old one, where the old one still reads as accepted, is a silently dead decision — the most expensive finding this step can make, because it is still being cited as live.
Decisions written as rules in always-on files. Read every instructions file the survey found and quote each line that records a CHOICE ("use X, not Y", "we do not use Z") rather than a behaviour.
What was killed, per repository:
git -C log --diff-filter=D --name-only --format='--- %h %ad %s' --date=short | head -120
git -C log --oneline -i --grep='revert' | head -20
A directory or subsystem deleted with no record naming it is a thing a fresh session will rebuild without knowing it was tried.
Limits, per repository:
grep -rnEi '(timeout|retries|retry|max_?[a-z_]*|concurrency|parallel|limit|cap|batch_?size|ttl)\W{0,3}[:=]\s*[0-9]+' --include='*.yml' --include='*.yaml' --include='*.json' --include='*.toml' --include='*.env' --include='*.ts' --include='*.js' --include='*.py' --include='*.sh' --include='*.go' -l
Open the hits. For each number, is there a measurement beside it — a comment, a record, a benchmark — or was it picked?
If I have no repository and no notes at all, say so, record it, and move on. That is a real state, not a failure of this step.
Tick box 2.
3 — CLASSIFY
Emit these markers with QUOTED EVIDENCE, into fixed fields. Not a reasoning paragraph — each marker is a description, never a judgement. "instructions_file_present: yes, ~/.claude/CLAUDE.md, 7744 B" not "the config is good."
🔴 THE MARKERS DESCRIBE THE HARNESS THIS SESSION RUNS IN — the files it loads, the skills and hooks it has, the runs it started. Nothing from a read-only reference tree sets a marker, however sophisticated it looks. A readable fleet that is not mine does not make me orchestrated.
instructions_file_present path and byte count
instructions_file_names_commands quote a build/test/deploy command from it
skill_count_total how many exist
skill_count_that_would_fire descriptions naming an occasion, not a topic
hooks_present which events
mcp_configured how many
mcp_authenticated how many have a live auth artifact
knowledge_dir_referenced_by_config a notes directory the config actually points at
config_in_version_control is any of this in git
queue_present a tracker with a status field used as a work queue
unattended_run_evidence anything that ran to completion without a human starting it
multi_machine more than one box involved
Then score three dimensions independently. Do not add the markers up — counting makes every signal interchangeable.
PERSISTENCE does anything survive a session ending?
ACTIVATION does what persists actually fire?
COORDINATION does anything run without a human in the loop?
Then apply the gates, which are necessary conditions rather than points. Apply them BEFORE you pick a tier, and never recommend a tier they rule out:
no instructions file → cannot be above tier 1, whatever else is present
no skill that would fire → cannot be above tier 2
no unattended run evidence → cannot be tier 4
1 BARE a harness works. Nothing configured survives a session ending
2 CONFIGURED an instructions file exists. Things persist; nothing coordinates them
3 ORGANIZED skills fire, tools connected, notes that are read rather than re-explained
4 ORCHESTRATED either more than one agent runs at once, or a deliberate context protocol
If tier 4, split it on ONE question — how many agents run at once? — not on how advanced it looks:
queue_present + unattended_run_evidence → 4-dispatch (engineers the RUN)
knowledge_dir_referenced_by_config + high skill count, no queue → 4-context (engineers the READ)
🔴 UNDER-CLASSIFY AT A BOUNDARY. Deliberately, by one tier. Over-classification fails SILENTLY — material referencing infrastructure I do not have gets me stuck without telling you. Under-classification fails LOUDLY, and I will correct it.
Then fetch your tier's interview template:
https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/-interview.md
where is one of: 1-bare · 2-configured · 3-organized · 4-dispatch · 4-context
Tick box 3.
4 — ASK WHERE THE OUTPUT GOES
Ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). One question, three options, the first one RECOMMENDED:
A dedicated folder — /patterns/decision-discipline/ [RECOMMENDED]
Every later module gets its own subfolder beside it. This holds the record of THIS session,
not my decision records — prompt 2 puts those beside the work they bind.
Mirror what I already do. Say that is what you are doing.
Somewhere else — I will type it
If I have an obvious convention for notes, make THAT the recommended option instead and say why.
Then create the folder and say where it is.
Tick box 4.
5 — SHOW THE TIER AS A NUDGE, NOT A QUESTION
Ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), with the tier you derived as the RECOMMENDED option. State it, give the evidence, and make the adjustment one click:
You're at CONFIGURED. An instructions file with four lines that are really decisions, no records
directory, nothing marks a replaced choice dead.
[right] [further along ↑] [take it slower ↓]
Never ask how experienced I am. Self-report is what the classification exists to replace.
🔴 A TIER IS RIGOR OF PRACTICE, NOT LEVEL OF ACHIEVEMENT. Someone at CONFIGURED by choice is correctly classified, not behind.
Tick box 5.
6 — INTERVIEW
Run the questions from the template you fetched, and follow its asking rules over anything you would otherwise do.
ASK WITH your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), EVERY TIME, INCLUDING QUESTION ONE.
EVERY QUESTION CARRIES A RECOMMENDATION, first, labelled, with one line of why. Build it from what step 2 found — "I found 0003 still reads accepted after 0007 replaced it. Mark it superseded? [RECOMMENDED]" — never from nothing.
ONE QUESTION AT A TIME. FIVE TO SEVEN, hard ceiling ten. STOP EARLY when you can write the files.
Where step 2 already answered a template question, assert the answer and skip it.
Tick box 6.
7 — WRITE THE FOUR FILES
In the folder I confirmed, with exactly the headings the template's WRITE THESE FOUR FILES section names. Prompt 2 reads them.
DECISION-MAP.md where decisions live · every record with its status · every supersession and
whether both ends agree · killed and unrecorded · limits and their measurements ·
always-on files with byte counts and the TOTAL · where the records should go
INTERVIEW.md what you asked and what I said, each marked MEASURED or STATED
TIER.md the tier, all twelve markers with evidence, three dimension scores, what moves each up
DECISIONS.md every recommendation you made, whether I accepted it, and what I chose instead
Tick box 7.
8 — FINISH
Tell me the folder, the four files, my tier with its three dimension scores, and the three findings that most change what should be built — a silently dead decision outranks everything else.
Then say exactly this:
"Interview complete. Paste prompt 2 from docs.utopiamodels.ai/docs/patterns/decision-discipline and I will build it."
Then stop. Do not build anything — that is prompt 2, and it reads these files.
Tick box 8.
```
```text title="prompt-2|Build the records, the check, and the gate|claude-code,opencode,codex,cursor,gemini"
PROMPT 2 of 2 — build decision discipline into my setup. From the four files prompt 1 wrote, not from a template.
READ THE FOUR FILES FIRST
/DECISION-MAP.md where decisions live, what is silently dead, what loads always
/TIER.md the tier, twelve markers, three dimension scores
/INTERVIEW.md what I said, marked measured or stated
/DECISIONS.md what has already been settled
If any is missing, stop and say which.
Then fetch four files, and follow the execution template over anything you would otherwise do:
https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/-execution.md
https://docs.utopiamodels.ai/kit/patterns/decision-discipline/TEMPLATE.md
https://docs.utopiamodels.ai/kit/patterns/decision-discipline/check-supersession.py
https://docs.utopiamodels.ai/kit/workflows/gates.md
If a fetch fails, say what you got and stop.
CHECKLIST TO A FILE
Write ~/DECISION-DISCIPLINE-BUILD.md — one task per thing you will create, plus the four below — and work from it. Tick as you go, not at the end.
## Rules
- I build what I proposed and was accepted. Nothing else.
- I never modify anything I did not create without asking first — that includes my instructions file.
- A question DECISIONS.md already settled is not asked again. I state the settled answer and the entry that settled it.
- I copy TEMPLATE.md and check-supersession.py verbatim. I do not paraphrase them.
- I paste REAL command output. I never write "verified" without the output above it.
- If something does not work, I say so and stop.
1 — WHERE THE RECORDS GO
If DECISIONS.md already settled where the records go, create them there and say which entry settled it — do not ask again. Otherwise ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead): the directory DECISION-MAP.md recommended is the RECOMMENDED option, with its one line of why; an existing records directory, if step 2 found one, is reused rather than duplicated. Create it, copy TEMPLATE.md into it, and install check-supersession.py beside it or where my other scripts live.
Tick.
2 — REPAIR, THEN BACKFILL
Every silently dead decision DECISION-MAP.md found in a tree I operate: ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead) whether to mark it superseded, RECOMMENDED yes, one question per pair. One found in a read-only reference tree is left untouched and stays recorded in DECISION-MAP.md — do not ask. Then write the one to three records the interview surfaced — the re-proposed decision first, and a kill record for anything deleted that I said might come back. Run the checker against the real directory and paste its output.
Tick.
3 — THE GATE
One gate. Rank the mechanisms against what DECISION-MAP.md shows my harness has, exactly one RECOMMENDED, one line of why each, and ask with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). The execution template says what this tier's gate is and what it must not be.
🔴 THE POINTER IS THREE LINES OR FEWER. It says where the records are and when to read them. The same instruction obeyed at 97% in isolation falls to 2% combined with five others — every line taxes every other line.
🔴 THE BAR: if I accept every recommendation without reading, I end with a working system.
Tick.
4 — WHAT MY TIER ADDS
Build what the execution template lists, where INTERVIEW.md showed appetite for it. Where it did not, say so and leave it.
Tick.
5 — PROVE IT — FOUR TESTS
Run the four tests the execution template names — the gate firing, the gate staying quiet, the checker failing a one-sided supersession, the checker passing the repaired one — and paste each output. A gate that fires on everything is as broken as one that fires on nothing, and only the negative test catches it.
Then: every path in DECISION-MAP.md resolves, and the always-on total matches what the harness loads now.
Tick.
FINISH
Every file created with its path. The four test outputs. The always-on byte total before and after.
Append to DECISIONS.md: what you recommended, what I chose, and what you did not build.
Then say exactly the sentence the execution template ends with, filled in, and stop.
```
## Files the prompts fetch [#files-the-prompts-fetch]
* [`kit/survey.md`](https://docs.utopiamodels.ai/kit/survey.md)
* [`kit/workflows/gates.md`](https://docs.utopiamodels.ai/kit/workflows/gates.md)
* [`kit/patterns/decision-discipline/TEMPLATE.md`](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/TEMPLATE.md)
* [`kit/patterns/decision-discipline/check-supersession.py`](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/check-supersession.py)
| tier | | |
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **1 · BARE** | [interview](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/1-bare-interview.md) | [execution](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/1-bare-execution.md) |
| **2 · CONFIGURED** | [interview](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/2-configured-interview.md) | [execution](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/2-configured-execution.md) |
| **3 · ORGANIZED** | [interview](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/3-organized-interview.md) | [execution](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/3-organized-execution.md) |
| **4 · dispatch** | [interview](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/4-dispatch-interview.md) | [execution](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/4-dispatch-execution.md) |
| **4 · context** | [interview](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/4-context-interview.md) | [execution](https://docs.utopiamodels.ai/kit/patterns/decision-discipline/tiers/4-context-execution.md) |
# Reference — How Six AI Coding Harnesses Assemble Context (/docs/reference/context-assembly)
This page is the ground truth behind every module prompt that says *"find the always-on instruction
file this harness loads every session."* It answers that for six harnesses, with real paths.
**Researched 2026-08-30 against primary sources.** Every claim is marked CONFIRMED with the page or
source file it came from, or UNCONFIRMED. Where a doc contradicts the shipped source, the source
wins and the contradiction is recorded — four such cases appear below, and they are the most
valuable rows on the page.
**Read the marks.** A gap named is more useful than a plausible guess, because this file is used to
write commands that must actually work.
## Before you trust any of it: three doc trees moved in 2026 [#before-you-trust-any-of-it-three-doc-trees-moved-in-2026]
Older guides point at URLs that now 404 or redirect. If your agent fetches those, it gets nothing
and says so, or gets a redirect and does not notice.
| Harness | Old location | 2026 location |
| ----------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| Claude Code | `code.claude.com/docs/en/docs/claude-code/` (404s) | `code.claude.com/docs/en/` · index at `/docs/llms.txt` |
| Codex CLI | `developers.openai.com/codex/*` (308s) · repo `docs/` now stubs | `learn.chatgpt.com/docs/*` |
| Gemini CLI | `docs/cli/configuration.md`, `docs/core/memport.md`, `docs/tools/memory.md` (404) | `docs/{cli,core,reference,tools,hooks,extensions}/` |
| Cursor | `/docs/context/*` (301s) | `/docs/rules`, `/docs/mcp`, `/docs/agent/tools/search` |
***
# 1. Claude Code (CLI) [#1-claude-code-cli]
Measured against **v2.1.251** on Linux/WSL2 in addition to the docs.
## A. Context assembly [#a-context-assembly]
**Load order, broadest to most specific** (CONFIRMED, `/docs/en/memory`):
| Scope | Path |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Managed policy | macOS `/Library/Application Support/ClaudeCode/CLAUDE.md` · Linux+WSL `/etc/claude-code/CLAUDE.md` · Windows `C:\Program Files\ClaudeCode\CLAUDE.md` |
| User | `~/.claude/CLAUDE.md` |
| Project | `./CLAUDE.md` or `./.claude/CLAUDE.md` |
| Local | `./CLAUDE.local.md` |
**These concatenate, they do not override** — "All discovered files are concatenated into context
rather than overriding each other… ordered from the filesystem root down to your working directory."
`CLAUDE.local.md` is appended after `CLAUDE.md` within each directory and is **not deprecated**
(CONFIRMED — it is still documented as live).
**CLAUDE.md is delivered as a user message after the system prompt**, not as part of it (CONFIRMED).
Output styles are the thing that modifies the system prompt.
**Upward walk, lazy downward.** It loads from cwd "and every directory above it." Subdirectory files
load only **when Claude reads a file in that directory with the Read tool** — not at launch, and
**not when writing or creating files there** (CONFIRMED, `/docs/en/debug-your-config`).
**@-imports:** `@path/to/import`, max **four hops**, relative paths resolve against the file
containing the import, `@~/.claude/foo.md` works. Imports expand **at launch** — they do not save
context.
**`.claude/rules/*.md`** (2026 mechanism): without `paths:` frontmatter they load at launch at the
same priority as `.claude/CLAUDE.md`, user rules before project rules. With `paths:` they load only
when a matching file is read. Brace-expansion budget is 1,000 patterns / 4 MiB; over-budget patterns
are used unexpanded and **match nothing**.
**Always vs on-trigger** (CONFIRMED, `/docs/en/context-window` — token counts are the doc's own):
system prompt (\~4,200) · auto memory `MEMORY.md` (\~680) · environment info (\~280) · **MCP tool names
only** (\~120, schemas deferred) · **skill descriptions only** · user then project CLAUDE.md. Full
skill bodies load only on use, and once loaded **stay in the conversation across later turns**.
**Documented limits:**
| Limit | Value |
| ---------------------------- | ---------------------------------------------------- |
| CLAUDE.md hard limit | **4 MiB — a larger file is skipped entirely** |
| CLAUDE.md advisory | under 200 lines |
| Auto memory `MEMORY.md` | first 200 lines or 25 KB |
| Skill description in listing | truncated at 1,536 chars |
| Skill listing budget | 1% of context window, fallback 8,000 chars |
| MCP tool description | 2 KB each |
| Number of skills | **no documented cap** — the budget is the constraint |
**Compaction.** Re-injected from disk: project CLAUDE.md, unscoped rules, auto memory, the plan.
Files read come back for **up to five** files. Invoked skill bodies return at 5,000 tokens each,
25,000 total. Hook-added context is summarized away. **The skill listing is the exception nobody
expects: it is not re-injected after `/compact`, so afterwards Claude can auto-invoke only skills it
already used this session** (CONFIRMED). "Microcompact" appears in no page fetched — UNCONFIRMED.
**Showing what is loaded:** `/context [all]` is the answer — a colored grid breaking out system
prompt, MCP tools, subagents *with the source each loaded from*, memory files and skills. `/memory`
lists *locations* (including files that do not exist yet), not what loaded. `/status` shows active
settings sources. `/doctor` is now a bundled skill, not a screen.
## B. I/O surface [#b-io-surface]
**Permission modes** (CONFIRMED against the binary): `default` (alias `manual`) · `acceptEdits` ·
`plan` · `auto` · `dontAsk` · `bypassPermissions`. Evaluation is **deny → ask → allow, first match
wins; specificity does not reorder it**, so a broad `Bash(aws *)` deny beats a narrow
`Bash(aws s3 ls)` allow.
Two syntax traps worth memorizing: `Bash(ls *)` with a space does **not** match `lsof`, while
`Bash(ls*)` does. And in Read/Edit rules `/path` is **relative to the settings source**, not the
filesystem root — `//path` is the absolute form.
**Sandboxing exists** on macOS (Seatbelt), Linux and WSL2 (bubblewrap); native Windows and WSL1 are
unsupported. Default write is cwd plus `--add-dir`; **default read is the entire computer** except
denied paths, which still allows `~/.aws/credentials` and `~/.ssh/`.
**MCP scopes:** `local` (default, in `~/.claude.json`) · `project` (**`.mcp.json` at the repo root**)
· `user`. Transports http / sse (deprecated) / stdio / ws. Project servers require a **one-time
approval** per machine.
**Subagents:** `.claude/agents/` and `~/.claude/agents/`, each with **its own context window**. Note
the inversion — for skills, personal overrides project; for subagents, **project outranks user**.
Built-in Explore and Plan **skip CLAUDE.md and git status**; custom subagents load it normally.
**Hooks: 33 events** (CONFIRMED, `/docs/en/hooks`), including `InstructionsLoaded`, which fires when
a CLAUDE.md or rules file loads and is the documented way to "log exactly which instruction files are
loaded, when they load, and why." Exit `2` is an absolute block that overrides JSON and cannot be
overridden even by `permissionDecision: allow`; **stderr on exit 0 goes to the debug log only, never
to Claude**. `PreToolUse` fires before any permission check **in every mode including
`bypassPermissions`** — but a hook `allow` cannot loosen a settings deny.
**Headless:** `claude -p`, `--output-format text|json|stream-json`, `--json-schema` for structured
output, exit 0 / non-zero / **143 on SIGTERM**. `--bare` skips discovery of hooks, skills, commands,
subagents, plugins, MCP and CLAUDE.md, and never reads the keychain; it "will become the default for
`-p` in a future release." SDK packages are `@anthropic-ai/claude-agent-sdk` and `claude-agent-sdk`
— **not** `@anthropic-ai/claude-code`.
## C. State on disk [#c-state-on-disk]
Verified directly on a live install as well as in the docs.
```
~/.claude/CLAUDE.md user memory
~/.claude/settings.json permissions, hooks, env, model
~/.claude/settings.local.json written BY Claude Code on "don't ask again"
~/.claude/.credentials.json mode 0600 — Linux/WSL/Windows (macOS uses Keychain)
~/.claude/projects//.jsonl full transcript: every message, tool call, result
~/.claude/projects//memory/MEMORY.md auto memory, keyed by GIT REPO (worktrees share it)
~/.claude/history.jsonl every prompt ever typed, with timestamps
~/.claude/{skills,agents,plugins,shell-snapshots,file-history,state}/
~/.claude.json MCP servers, trust decisions, OAuth account — NOT settings
```
`~/.claude/todos/`, `statsig/` and `logs/` are **legacy and no longer written** — tooling that reads
them is reading a vestige. `CLAUDE_CONFIG_DIR` relocates all of it.
**Never commit:** `.claude/settings.local.json`, `CLAUDE.local.md`, `.credentials.json`, anything
under `~/.claude/`. Note the sharp edge: Claude Code adds `settings.local.json` to your **global** git
excludes, not the repo's `.gitignore` — a hand-created file is your problem. And transcripts are
**not encrypted at rest**: if a tool reads a `.env`, that value is now in the session `.jsonl`.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration]
**A skill whose description names a topic rather than an occasion never fires.** It appears in
`/skills`, is loaded, listed, and inert, with zero error output. The fix per the docs is to include
"keywords users would naturally say."
Four more that fail just as quietly:
1. **Skill descriptions are silently truncated — or dropped.** When the listing overflows its 1%
budget, Claude Code "drops descriptions starting with the skills you invoke least." **A skill that
worked can stop working purely because you added other skills.**
2. **Malformed skill frontmatter loads the body with empty metadata** — `/skill-name` still works,
auto-invocation is dead.
3. **`~/.claude.json` is not a settings file.** `permissions`, `hooks` and `env` put there are simply
never read.
4. **In `-p` and the SDK the trust gate fails open in the wrong direction**: the repo's
`permissions.allow` is silently not applied, while its hooks, `env`, and `.mcp.json` servers all
run.
***
# 2. Claude Desktop / claude.ai with connectors [#2-claude-desktop--claudeai-with-connectors]
The odd one out: no filesystem of its own, no headless mode, and **no way to see what is in context.**
## A. Context assembly [#a-context-assembly-1]
**Three personalization layers, not four** (CONFIRMED, support.claude.com):
**Instructions for Claude** (account-wide) · **Project instructions** (per project) · **Skills**.
**Styles is retired.** The article 404s and it is absent from the 79-article collection index; Skills
is the successor. Any guide describing Styles is pre-2026.
**Precedence between the three is NOT DOCUMENTED** (CONFIRMED-NEGATIVE). The personalization page
says only that you "can use these features independently or in combination" and never names a
conflict winner. Any hierarchy you have seen asserted is inference.
**Memory is persistent** and stored as **individual topics**, not a file you can edit — global plus a
per-project space. On by default for Free/Pro/Max, **off by default for Team/Enterprise**. Export is
copy/paste text, not a download. Incognito chats write nothing to history or memory.
**Projects silently switch to RAG.** Retrieval "automatically activates when your project approaches
or exceeds the context window limits" — no manual step, up to 10x capacity. The mechanism changes
underneath you: instead of whole files in context, Claude uses a **project knowledge search tool**.
There is a binary indicator; a **percentage meter is not documented** (UNCONFIRMED).
**Context window, 2026:** Opus 5 and Sonnet 5 run **1M tokens** on paid plans; Opus 4.8/4.7/4.6 and
Sonnet 4.6 run 500K; others 200K. Free tier is unpublished.
**Long conversations now summarize rather than hard-stop** — "allows conversations to continue
indefinitely in most cases." This is a change from the old behavior, and it is **gated on code
execution being enabled**. Without it you get the old hard error instead.
**There is no `/context` equivalent, no token indicator, no context inspector** (CONFIRMED-NEGATIVE).
The only signals are the transient "organizing its thoughts" state and the binary RAG indicator.
## B. I/O surface [#b-io-surface-1]
**Connectors are remote MCP servers that run from Anthropic's cloud** — "your server must be
reachable over the public internet," so a firewalled server will never connect. Local servers come in
as **Desktop Extensions, which are Desktop and Claude Code only** — not web, not mobile.
**Connection is account-global but loading is per-conversation**, with three modes: **Auto**
(default, Claude decides), **Always available**, **On demand**. Guidance is under 10 connectors →
Always, 10–30 → Auto, 30+ → On demand. "Each conversation setting is independent."
**`.dxt` is now `.mcpb`** and the repo moved out of Anthropic's org — `github.com/anthropics/mcpb`
301s to `modelcontextprotocol/mcpb`. The rename date is unpublished, and **the on-disk install path
for a `.mcpb` is not documented anywhere** (UNCONFIRMED).
```
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
Linux NOT DOCUMENTED — though a Linux beta exists (apt, binary `claude-desktop`)
```
**Hooks exist in the extensibility model but are inert in chat.** Verbatim: *"Hooks and sub-agents
run only in Cowork, so they appear grayed out in chat."*
**There is no headless mode** (CONFIRMED-NEGATIVE). The programmatic path is the Agent SDK or
`claude -p`.
## C. State on disk [#c-state-on-disk-1]
Conversations are **server-side** on consumer claude.ai and Desktop. Logs are at
`~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs`. Org plugins live in
`/Library/Application Support/Claude/org-plugins/` and `C:\Program Files\Claude\org-plugins\`.
Beware paths beginning `Claude-3p/` in the docs — those are the third-party/BYO-inference build, not
consumer Desktop.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration-1]
**Editing `claude_desktop_config.json` without fully quitting the app.** Verbatim: "fully quit and
reopen; closing the window is not enough." Closing the window looks identical to a restart and loads
nothing.
The 2026 runner-up has inverted: **Desktop now ships its own Node.js**, so a bundled extension can
work while a hand-configured stdio server on the same machine fails for PATH reasons — stdio servers
"inherit only a limited subset of environment variables," and a GUI app has no shell profile. Use
absolute paths and an explicit `env` block.
Third: **project knowledge silently switching to RAG**, which degrades any question needing
whole-document reasoning without ever raising an error.
***
# 3. Cursor (and Cursor CLI) [#3-cursor-and-cursor-cli]
Three 2026 changes invalidate most existing guides.
## A. Context assembly [#a-context-assembly-2]
**The CLI binary is `agent`, not `cursor-agent`** (CONFIRMED — `cursor-agent` appears zero times in
the current CLI docs; the installed build on this machine still uses the old name, so verify with
`agent --version`).
**Rules live in `.cursor/rules/*.mdc`.** A plain `.md` file there is **silently ignored** because it
has no frontmatter — the docs' own example labels it `# Ignored (wrong extension)`.
**Three frontmatter keys only** — `description`, `globs`, `alwaysApply`:
| `alwaysApply` | `description` | `globs` | Behavior |
| ------------- | ------------- | -------- | ------------------------------------------------------------ |
| `true` | — | — | Always included. **Globs and description are ignored.** |
| `false` | — | provided | Auto-attached when a matching file is in context |
| `false` | provided | — | Agent reads description, pulls it in when relevant |
| `false` | — | — | **Only via `@`-mention** |
Globs are comma-separated. **Precedence is Team → Project → User**, all merged, "earlier sources take
precedence when guidance conflicts." Size guidance is explicit: **keep rules under 500 lines**.
`AGENTS.md` is supported at root and nested. **`CLAUDE.md` is read by the CLI** and applied as rules.
**Embeddings are gone.** `codebase-indexing` now redirects to `agent/tools/search`: discovery is
**Instant Grep**, "a custom search engine that outperforms ripgrep," plus an **Explore subagent** with
its own context window. The word "embedding" appears nowhere in current docs.
**Memories is retired** — `/docs/context/memories` redirects to `/docs/rules`. Do not plan around it.
**`.cursorrules` status: UNCONFIRMED as formally deprecated**, on absence-only evidence — zero
occurrences across every current page, and not among the four rule types. Treat it as unsupported.
Context usage is visible via the CLI's `/context`; manual compaction is `/summarize` (`/compact` and
`/compress` are aliases). The `preCompact` hook receives exact numbers but is **observational only**.
## B. I/O surface [#b-io-surface-2]
🔴 **`.cursorignore` is not a security boundary.** Verbatim: *"The terminal and MCP server tools used
by Agent cannot block access to code governed by `.cursorignore`."* It blocks Agent/Tab/Inline Edit
and `@`-mentions; it does **not** block `cat`, `grep`, any shell command, or any MCP tool. Real
enforcement is `permissions.json` plus the sandbox.
**MCP** at `.cursor/mcp.json` and `~/.cursor/mcp.json`; stdio, SSE and Streamable HTTP; Roots,
Elicitation and Apps all supported; `${env:NAME}` interpolation for secrets. **The historical \~40-tool
cap has no mention in current docs — UNCONFIRMED whether raised, removed, or merely undocumented. Do
not assume it is gone;** verify with `agent mcp list-tools `.
**Subagents:** built-in Explore, Bash and Browser, plus custom ones in `.cursor/agents/` — and it also
reads `.claude/agents/` and `.codex/agents/`, with `.cursor/` winning.
**Hooks** in `.cursor/hooks.json`, auto-reloaded, with 18 agent events plus Tab and workspace events.
Blocking is `{"permission": "deny"}` or **exit code 2** ("This matches Claude Code behavior").
🔴 **Hooks fail OPEN by default** — a crash, timeout or invalid JSON lets the action through unless
`failClosed: true`.
**Headless:** `agent -p`, `--output-format text|json|stream-json`.
🔴 **`-p` alone does not write files** — "Without `--force`, changes are only proposed, not applied."
Permission tokens are `Shell(cmd)`, `Read(glob)`, `Write(glob)`, `WebFetch(domain)`,
`Mcp(server:tool)`; **deny beats allow**. Exit codes beyond zero/non-zero are UNCONFIRMED.
## C. State on disk [#c-state-on-disk-2]
`~/.cursor/` holds `cli-config.json`, `mcp.json`, `hooks.json`, `permissions.json`, `sandbox.json`,
`rules` (machine-local, **not synced**), `agents/`, `skills/`, `worktrees/`. Per-OS chat history and
credential cache paths are **UNCONFIRMED** — no such table exists in current docs.
Never commit `.cursor/mcp.json` with hardcoded secrets, or `.cursor/keys`. Note that
`.cursor/cli.json` and `.cursor/permissions.json` **are** meant to be committed.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration-2]
**Omitting `alwaysApply` turns the rule into a manual-only rule.** With no `alwaysApply`, no
`description` and no `globs`, it is "included only when you `@`-mention the rule." The file exists, is
valid, shows up in the UI, and **never loads.**
Then the same failure Claude Code has: **an "Apply Intelligently" rule whose description names a topic
rather than an occasion is never selected.** The docs' own good example is occasion-shaped —
*"Security specialist. Use when implementing auth, payments, or handling sensitive data."*
Third, and purely mechanical: **`globs: *.ts` matches only root-level `.ts` files**, not `src/foo.ts`.
You need `**/*.ts`.
***
# 4. opencode [#4-opencode]
Open source, so the source is authoritative — and it **contradicts its own published docs in four
places**. Findings below are from `sst/opencode@dev`, v1.18.25 (2026-08-28).
## A. Context assembly [#a-context-assembly-3]
Resolution order from `packages/opencode/src/session/instruction.ts`:
1. **Global:** `~/.config/opencode/AGENTS.md`, else `~/.claude/CLAUDE.md` — it **breaks on the first
that exists**, so the two are **mutually exclusive**.
2. **Project:** for each of `AGENTS.md`, `CLAUDE.md`, `CONTEXT.md` in order, `findUp` from cwd to the
**git worktree root**; on the first *name* with any match it adds all matches and stops. The source
comment: *"The first project-level match wins so we don't stack AGENTS.md/CLAUDE.md from every
ancestor."* **Name precedence beats proximity.**
3. The `instructions` config array, appended after.
🔴 **DOCS-WRONG #1.** `opencode.ai/docs/rules` says opencode "does **not** automatically scan
subdirectory `AGENTS.md` files." The source implements **lazy nested loading**: when the `read` tool
reads a file, it walks upward from that file's directory and attaches any instruction file found,
deduped per assistant message. Subdirectory `AGENTS.md` **is** read — on trigger, not always.
**Config** merges global (`config.json` → `opencode.json` → `opencode.jsonc`) then project files
walked up and reversed, so **the config nearest cwd wins**. XDG variables are honored. macOS MDM
managed preferences **override everything**.
**Auto-compaction is on by default** with no percentage threshold — it fires when tokens reach
`usable()`, computed from the model's limits minus a reserve. Constants are explicit in
`compaction.ts` (`PRUNE_MINIMUM` 20,000; preserve-recent clamped 2,000–15,000).
**No TUI token indicator and no "show loaded context" command established** — UNCONFIRMED.
## B. I/O surface [#b-io-surface-3]
**Permission keys** are `read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `external_directory`,
`todowrite`, `question`, `webfetch`, `websearch`, `lsp`, `doom_loop`, `skill` — plus arbitrary keys,
since the schema carries a rest-record. Values are `ask` / `allow` / `deny`.
🔴 **Matching uses `findLast` — the LAST matching rule wins**, so catch-alls must come first. Config
parsing preserves key order specifically for this.
🔴 **DOCS-WRONG #2.** The docs say "most permissions default to allow" and that `.env` reads are
denied. The evaluator's fallback is **`ask`**; the permissiveness comes from a built-in defaults
ruleset (`"*": "allow"`), and **`.env` reads are `ask`, not `deny`** — anyone relying on the docs for
secret hygiene is unprotected.
🔴 **DOCS-WRONG #3.** The docs list a built-in `Scout` subagent. It does not exist in `dev` source.
🔴 **DOCS-WRONG #4.** Agent and command directories accept **both** singular and plural
(`{agent,agents}`, `{command,commands}`, `{plugin,plugins}`), and nested subdirectories are scanned.
**There is no URL allowlist.** `webfetch` is typed as an action-only permission that does not even
accept patterns. Remote `instructions` URLs bypass the tool permission system entirely.
**Plugins** are `.ts` or `.js` in any config dir, with \~20 hooks including `permission.ask` (plugins
can auto-approve) and `tool` (**plugins can define custom tools**). Note the docs' event list
(`session.created`, `file.edited`) is a **different axis** from the hook names.
**Headless:** `opencode run "prompt"`, `--format default|json` where JSON is a **raw event stream**,
not one document. **Exit codes are only 0 and 1** — no per-failure-class codes, confirmed by
exhaustive grep. `opencode serve` exposes an OpenAPI 3.1 HTTP server on port 4096; the TUI itself is a
client of it.
## C. State on disk [#c-state-on-disk-3]
```
~/.local/share/opencode/auth.json credentials — NEVER COMMIT
~/.local/share/opencode/{log,project,plans,repos}/
~/.config/opencode/{opencode.json,AGENTS.md,agent[s]/,command[s]/,plugin[s]/,skills/}
~/.local/state/opencode/ lock state (in source, NOT documented)
```
opencode auto-creates a `.gitignore` inside `.opencode/` for plugin scaffolding only — your
`opencode.json`, agents, commands and plans there are **meant to be committed**.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration-3]
**Unknown and typo'd config keys are silently absorbed, not rejected.** The schema is
`StructWithRest(Struct({...}), [Record(String, Any)])`, and `normalize()` sweeps every unrecognized
key into `agent.options`. A typo'd `descripton:` or `permissions:` (the correct key is **singular**,
`permission`) does not error, does not warn — it becomes an inert provider option. `$schema` gives
editor completion only; it is **not enforced at load**.
Two more, both pure silence: **an `AGENTS.md` anywhere up-tree suppresses your `CLAUDE.md` entirely**
(the loop breaks on the first *name*), and **relative `instructions` paths are glob-walked up from
cwd**, so they resolve differently depending on which subdirectory you launched from — a miss returns
empty, and a remote URL fails silently after a 5-second timeout.
***
# 5. Codex CLI (OpenAI) [#5-codex-cli-openai]
The repo `docs/` directory is now **stubs** — `docs/config.md` is 726 bytes, `docs/sandbox.md` 150.
The root `AGENTS.md` states the rule: *"Do not add general product or user-facing documentation to the
`docs/` folder."* Canonical docs are at `learn.chatgpt.com/docs/`; findings below are cross-checked
against `codex-rs` source.
## A. Context assembly [#a-context-assembly-4]
**Load order** (`codex-rs/core/src/agents_md.rs`):
1. **Global** `~/.codex/AGENTS.override.md`, else `~/.codex/AGENTS.md` — first non-empty only.
`AGENTS.override.md` is **new in 2026**.
2. **Project**, walking **up** from cwd to the project root (detected by `project_root_markers`,
default `[".git"]`), then concatenated **root-first, cwd-last** — so nearer files win *by position
in the prompt*, not by replacing.
3. At most **one file per directory**.
🔴 **An untrusted project loads no project `AGENTS.md` at all** — the loader returns before reading
any project file, and `.codex/config.toml` and project hooks are skipped too. The session looks
completely normal.
**`~/.codex/instructions.md` is confirmed REMOVED** — zero hits in source as a user instruction file.
**`project_doc_max_bytes` is 32768** (confirmed three ways: `defaults.toml`, `config.schema.json`,
`DEFAULT_PROJECT_DOC_MAX_BYTES: usize = 32 * 1024`).
**Config precedence**, highest first: CLI `-c` flags → project `.codex/config.toml` (trusted only) →
profile file `$CODEX_HOME/.config.toml` → `~/.codex/config.toml` → `/etc/codex/config.toml` →
defaults. Project configs are **deliberately powerless** over `approval_policy`, `sandbox_mode`,
`model_provider`, `notify` and `otel`.
**Skills exist in 2026** and are **not** under `.codex` — they live at `.agents/skills`,
`$REPO_ROOT/.agents/skills`, `$HOME/.agents/skills` and `/etc/codex/skills`, with a description budget
of "at most 2% of the model's context window, or 8,000 characters." Explicit invocation is `$skill`.
`/status`, `/context` and `/compact` exist; the auto-compaction key is
`model_auto_compact_token_limit`.
## B. I/O surface [#b-io-surface-4]
**`sandbox_mode`:** `read-only` · `workspace-write` · `danger-full-access`. `codex exec` defaults to
**read-only**. **`approval_policy`:** `untrusted` · `on-request` · `never` — **`on-failure` is gone**,
replaced by an optional granular table.
🔴 **The platform mechanism is not what older guides say.** macOS uses Seatbelt; **Linux and WSL2 use
`bubblewrap`**, not Landlock+seccomp; Windows uses native Windows Sandbox. The old claim that WSL has
no sandbox is wrong — but if `bwrap` is not on `PATH`, sandboxing cannot be enforced.
**Network is disabled by default in `workspace-write`** (serde default `false`). Enable via
`[sandbox_workspace_write] network_access = true`. Sandboxed children get
`CODEX_SANDBOX_NETWORK_DISABLED=1`, which is the reliable in-process detector.
**MCP** under `[mcp_servers.]`, stdio or HTTP, `startup_timeout_sec` 10 and `tool_timeout_sec` 60.
Codex **as** an MCP server is UNCONFIRMED.
**Web search is no longer a boolean:** `[tools] web_search = "cached"` by default, from
`disabled | cached | indexed | live`.
**Subagents exist** — `[features] multi_agent` defaults **true**, with `/agent` and `/subagents` and
`DEFAULT_AGENT_MAX_THREADS = Some(6)`.
**Hooks are real in 2026** (not merely `notify`): `hooks.json` or inline `[hooks]` with
`[[hooks.PreToolUse]]` matchers, gated by `[features] hooks`.
**Headless:** `codex exec "prompt"` — progress to stderr, final message to stdout. `--json` emits
JSONL (`thread.started`, `item.*`, `turn.completed`); `--output-last-message`, `--output-schema`,
`--ephemeral`, and `codex exec resume`. **Numbered exit codes are UNCONFIRMED.** SDKs:
`@openai/codex-sdk` and, new, Python `openai-codex`.
## C. State on disk [#c-state-on-disk-4]
```
~/.codex/config.toml
~/.codex/auth.json credentials — NEVER COMMIT
~/.codex/sessions/YYYY/MM/DD/rollout--.jsonl transcripts
~/.codex/history.jsonl
~/.codex/prompts/*.md custom /name prompts
$HOME/.agents/skills skills — OUTSIDE CODEX_HOME
```
🔴 **`~/.codex/log/codex-tui.log` does not exist by default in 2026** — "the TUI records diagnostics
in bounded local stores by default. Set `log_dir` explicitly to enable a plaintext TUI log." Windows
native paths are UNCONFIRMED.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration-4]
**`AGENTS.md` truncation, and it is worse than a clean skip.** The 32,768-byte budget is a **shared
running total consumed root-first**, and overflow is a mid-byte `data.truncate()`:
* A large repo-root `AGENTS.md` starves the cwd-level file **that was supposed to override it** — the
precedence model inverts exactly where you rely on it.
* The cut lands mid-sentence, so the model reads a syntactically valid, semantically corrupted file.
* The only signal is a `tracing::warn!`, and **the TUI writes no plaintext log by default**, so you
cannot see the warning without first setting `log_dir`.
Runner-up: **the untrusted-project silent drop** described in section A. Third:
**`allow_managed_hooks_only` works only in `requirements.toml`** — set in `config.toml` it silently
does nothing, so an admin believes hooks are locked down when they are not.
***
# 6. Gemini CLI (Google) [#6-gemini-cli-google]
In 2026 this has hooks, subagents, skills and plan mode — capabilities most guides still treat as
absent.
## A. Context assembly [#a-context-assembly-5]
**Three tiers, in load order** (`docs/cli/gemini-md.md`): global `~/.gemini/GEMINI.md` → workspace
`GEMINI.md` in configured dirs **and their parents** → **just-in-time** `GEMINI.md` discovered when
tools touch a file, scanning up to a trusted root. The old "eagerly walk down every subdirectory"
model is **superseded**. Bounded by `context.discoveryMaxDirs` (default **200**).
**`context.fileName` takes a string or an array** and is nested:
`{ "context": { "fileName": ["AGENTS.md", "CONTEXT.md", "GEMINI.md"] } }`.
**Imports** use `@./x.md` with circular detection and **max depth 5**. The `context.importFormat`
enum values are UNCONFIRMED.
**Settings precedence, lowest to highest:** defaults → `system-defaults.json` → `~/.gemini/settings.json`
→ `.gemini/settings.json` → **system settings file** → env / `.env` → CLI args.
🔴 **Note the trap: the system settings file overrides both user and project**, the reverse of the
usual intuition.
| OS | System settings |
| ------- | ------------------------------------------------------ |
| Linux | `/etc/gemini-cli/settings.json` |
| Windows | `C:\ProgramData\gemini-cli\settings.json` |
| macOS | `/Library/Application Support/GeminiCli/settings.json` |
The schema is nested v3 (`general.*`, `context.*`, `tools.*`, `security.*`, `hooksConfig.*`), and
migration is **active** — deprecated flat keys are rewritten where writable, warned where the config
is read-only.
**`/memory` has exactly four subcommands: `show`, `reload`, `list`, `inbox`** (read from
`memoryCommand.ts`). **There is no `/memory add` and no `/memory refresh`** — `reload` is the name.
Compression is `/compress`, auto-triggered at `model.compressionThreshold` (**default 0.5**), with a
`PreCompress` hook. The footer context percentage is **hidden by default**
(`ui.footer.hideContextPercentage` defaults `true`). What survives compression is UNCONFIRMED.
**`.geminiignore` requires a session restart to take effect.**
## B. I/O surface [#b-io-surface-5]
Tools are `read_file`, `write_file`, `replace`, `glob`, **`grep_search`**, `read_many_files`,
`run_shell_command`, `web_fetch`, `google_web_search`, `write_todos`.
**Sandbox** via `GEMINI_SANDBOX` = `true|docker|podman|sandbox-exec|runsc|lxc`, image
`ghcr.io/google/gemini-cli:latest`, six macOS Seatbelt profiles. `tools.sandboxNetworkAccess` defaults
**false**.
**MCP** under `mcpServers` with `command`/`args`/`env`/`cwd`/`timeout`/`trust`/`url`/`httpUrl`/
`headers`/`includeTools`/`excludeTools`. **`excludeTools` takes precedence over `includeTools`.**
`web_fetch` handles **up to 20 URLs** per prompt. No proxy key found — UNCONFIRMED.
**Subagents exist:** `.gemini/agents/*.md` and `~/.gemini/agents/*.md`, required frontmatter `name`,
`description`, `kind`, plus `tools`, `model`, `max_turns`. Invoked automatically or via
`@agent_name`. **Subagents cannot call other subagents.**
**Hooks: eleven events** — `SessionStart`, `SessionEnd`, `BeforeAgent`, `AfterAgent`, `BeforeModel`,
`AfterModel`, `BeforeToolSelection`, `BeforeTool`, `AfterTool`, `PreCompress`, `Notification`. The
config shape mirrors Claude Code's, and **exit 2 is a hard block**. **stdout must carry nothing but
the final JSON object** — debug output goes to stderr or it corrupts the contract. It even sets a
`CLAUDE_PROJECT_DIR` compatibility alias.
**Headless:** `gemini -p`, `-o/--output-format text|json|stream-json`,
`--approval-mode default|auto_edit|yolo|plan`. **`--yolo` and `--allowed-tools` are deprecated**, and
**`--checkpointing` was removed in v0.11.0.** Exit codes are enumerated — the only harness here that
does: **0** ok, **1** general, **41** auth, **42** input validation, **44** sandboxing, **52** config,
**53** turn limit.
## C. State on disk [#c-state-on-disk-5]
```
~/.gemini/settings.json ~/.gemini/GEMINI.md
~/.gemini/oauth_creds.json SECRET — never commit
~/.gemini/mcp-oauth-tokens.json SECRET
~/.gemini/a2a-oauth-tokens.json SECRET
~/.gemini/trustedFolders.json ~/.gemini/{commands,skills,agents,extensions}/
~/.gemini/tmp// checkpoints, shell_history, chats/, memory
~/.gemini/history/ a SHADOW GIT REPO used for checkpointing
```
`` is now a **short id from a ProjectRegistry**, not the raw sha256 — `storage.ts` carries
migration code from the old hash. The user directory is `~/.gemini` on **all** platforms; only the
system config directory differs.
## D. The most common silent misconfiguration [#d-the-most-common-silent-misconfiguration-5]
**Setting `context.fileName` replaces the default rather than extending it.** Set it to `"AGENTS.md"`
and **every `GEMINI.md` in the tree stops loading, with no warning.** Use the array form to keep both.
Close second: **folder trust silently disables nearly everything.** In an untrusted folder the
workspace `.gemini/settings.json` is ignored, `.env` is ignored, **MCP servers do not connect**,
custom commands are not loaded and memory is not auto-loaded. The project simply behaves as though
none of its configuration exists. Note the docs disagree with themselves on the default here — the
generated settings table says `security.folderTrust.enabled` defaults `true` while the trusted-folders
page presents it as opt-in; treat the generated table as authoritative.
Third: **`"trust": true` on an MCP server bypasses every confirmation** for that server's tools.
***
# Comparison [#comparison]
| | Always-loaded file | On-trigger mechanism | Tool protocol | Headless | Hooks |
| ------------------ | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------ |
| **Claude Code** | `~/.claude/CLAUDE.md`, `./CLAUDE.md`, `./CLAUDE.local.md`, `.claude/rules/*.md` (concatenated) | Skills (description → body); rules with `paths:`; subdir CLAUDE.md on Read | MCP (stdio/http/sse/ws), 3 scopes | `claude -p`, text/json/stream-json, `--json-schema`, exit 143 on SIGTERM | **33 events**, exit 2 = absolute block |
| **Claude Desktop** | Instructions for Claude · Project instructions · Memory topics | Skills (zip upload); connectors per-conversation (Auto/Always/On demand); **RAG auto-switch** | MCP connectors (cloud-run) + `.mcpb` extensions | **None** | Present but **inert in chat** ("run only in Cowork") |
| **Cursor** | `.cursor/rules/*.mdc` with `alwaysApply: true`; `AGENTS.md`; User/Team rules | Auto Attached (`globs`); Agent Requested (`description`); `@`-mention | MCP (stdio/SSE/HTTP), `.cursor/mcp.json` | `agent -p` — **needs `--force` to write** | \~21 events, `.cursor/hooks.json`, **fail-open** |
| **opencode** | `~/.config/opencode/AGENTS.md` **or** `~/.claude/CLAUDE.md`; first project-level name match | Nested `AGENTS.md` on `read`; skills via `skill` tool | MCP local/remote in `opencode.json` | `opencode run`, `--format json` (event stream), **exit 0/1 only** | \~20 plugin hooks incl. `permission.ask`, custom tools |
| **Codex CLI** | `~/.codex/AGENTS.md`; project `AGENTS.md` root-first (**32 KB shared budget**) | Skills at `.agents/skills` (`$skill`); `~/.codex/prompts/*.md` | MCP `[mcp_servers.*]` in `config.toml` | `codex exec`, `--json` JSONL, exit codes UNCONFIRMED | Real in 2026, `hooks.json` + `[hooks]`, plus `notify` |
| **Gemini CLI** | `~/.gemini/GEMINI.md` + workspace/parents | JIT `GEMINI.md` on tool touch; skills; `.gemini/commands/*.toml` | MCP `mcpServers` (stdio/SSE/HTTP) | `gemini -p`, `--output-format json`, **enumerated exit codes** | **11 events**, exit 2 = hard block |
## The one-line version of each silent failure [#the-one-line-version-of-each-silent-failure]
| Harness | What looks fine and is not |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code | A skill description naming a **topic** not an **occasion** never fires — and descriptions get **dropped** when you add more skills |
| Claude Desktop | Config edited **without fully quitting**; project knowledge **silently switching to RAG** |
| Cursor | **`alwaysApply` omitted** makes the rule manual-only; `-p` without `--force` **writes nothing** and exits 0 |
| opencode | **Typo'd config keys are silently absorbed** into an inert options bag; `$schema` is not enforced |
| Codex CLI | `AGENTS.md` **truncated mid-sentence** against a shared 32 KB budget, warned only to a log that does not exist by default |
| Gemini CLI | **`context.fileName` replaces** the default, so every `GEMINI.md` silently stops loading |
## What this page does not establish [#what-this-page-does-not-establish]
Named explicitly, because a gap is more useful than a guess:
* Whether Claude Code's CLAUDE.md walk stops at the repo root or continues to `/`; "microcompact"
appears in no current doc.
* Claude Desktop: the `.mcpb` on-disk install path, the Linux config path, free-plan context size,
and whether Claude is told it is in RAG mode.
* Cursor: the current MCP tool cap, an explicit `.cursorrules` deprecation statement, per-OS history
paths, and an exit-code table.
* opencode: whether the TUI shows a token indicator, and the exact env-var literals behind its
project-config kill switch.
* Codex CLI: numbered exit codes, Windows-native `CODEX_HOME`, and whether `experimental_instructions_file`
still exists.
* Gemini CLI: `context.importFormat` values, a proxy key, and what survives `/compress`.
# The always-on agent (/docs/structure/agent-always-on)
Sourced from the fleet's own component document for `agent-always-on`. Called experimental because there
is exactly one instance of it; because it exists under an amendment to a rule that forbids its entire
class; and because its service units live in no repository, so nothing compares what is running against a
committed artifact.
Everything else in this fleet is event-driven. Work arrives on a board, a run announces itself on an
issue, a merge moves a status. **A resident process is forbidden for anything the board, a dispatched run
or a git event already announces** — heartbeat polls and scheduled health checks are exactly that, and
their failure mode is silence, which is indistinguishable from health.
Then there is one exception, and this page is about what earned it.
## What a permanent loop has to prove [#what-a-permanent-loop-has-to-prove]
The amendment permits a resident process **only** to observe an external system that emits no signal
anyone can subscribe to, and only where latency below the polling interval is the actual point. It adds
one requirement that does most of the work:
🔴 **The process must name the external event it exists to catch, in its own documentation.**
That single requirement is the transferable part. A daemon that cannot name the unsubscribable event it
watches is a daemon that is polling something it could have been told about, and writing the name down is
where that becomes obvious — to the author, before anyone else has to review it.
The one process here names two:
* **A role appearing on an applicant-tracking board.** No webhook, no feed and no subscription exists for
*"a role opened at any company in this region"*. Being early is the entire edge, and two minutes and
twelve hours are different applications.
* **A chat-membership update at bootstrap.** The moment a human adds the bot to a group. This one is
finished: the poll stopped **permanently** once the identifier was captured, and the published feed
reports it as captured rather than as running. A watch that has caught its event and stopped is the
right shape; a watch that keeps polling for something it already has is the wrong one.
## The loop [#the-loop]
```text
service, Restart=always one cycle
├─ detect watch the external boards
├─ enrich fill in what was seen
├─ mailbox inbound replies
├─ notify outbound, successes only
└─ apply act on what detect wrote ← LAST, and deliberately
each duty runs every executable in its directory, sequentially,
logging the name and the exit code of each
handlers WRAP the canonical tools in the main repository — never a fork
every cycle merges a liveness block into a state file
→ collected into a committed feed
→ rendered on the command center, where a human already looks
```
## Three properties that are load-bearing and easy to undo [#three-properties-that-are-load-bearing-and-easy-to-undo]
### A duty is registered in the loop, not discovered from its directory [#a-duty-is-registered-in-the-loop-not-discovered-from-its-directory]
*Which* directories run is a list of literal calls in the loop body. A handler dropped into a directory
that is not on that list is **dead code that looks installed** — present, executable, and never invoked.
Discovery-by-directory is the tempting alternative and it trades a small convenience for a silent failure
mode. An explicit list means adding a handler requires one edit that a reviewer can see.
The last duty runs last for a measured reason. It is the only handler that can spend minutes rather than
seconds — median 19 seconds, 90th percentile 98, maximum 246, across 45 records — and the duties run
sequentially. Placed ahead of the watch, it would spend the very latency budget the process exists to
protect.
**Order matters in a sequential loop, and the ordering argument belongs next to the order.**
### The restart limit is what makes death visible [#the-restart-limit-is-what-makes-death-visible]
`Restart=always` on its own restarts a crash loop **forever**. The unit never enters a failed state, the
failure handler never fires, and an always-on agent dies silently while its service manager reports it as
active.
Adding a start-limit interval and burst is what converts an endless crash loop into a unit that eventually
gives up, enters `failed`, and triggers the notification path. Without it, the most important property of
the whole design — that failure announces itself — is quietly absent for the one component that has no
other liveness signal.
### A schedule is a timer. Only the watch is resident [#a-schedule-is-a-timer-only-the-watch-is-resident]
The daily rollup that summarizes what the loop did is a **timer**, not a sixth duty. Three reasons, and
each one is a test a future addition should be run against:
1. A rollup of rows already written has **no latency edge**, so it fails the criterion that permitted the
resident process in the first place.
2. The duties are sequential, so a rollup inside the loop spends the watch's budget.
3. A surface reporting what the system did today **must not go dark when the thing it reports on does.**
**Anything that is a schedule is a timer here; only the watch is resident.** That is the line a proposed
sixth thing gets tested against, and it is a much easier test than "is this important enough."
## Its isolation is enforced by code that never reads the registry [#its-isolation-is-enforced-by-code-that-never-reads-the-registry]
The scheduler's list of dispatchable agents is a hardcoded string in two scripts. This agent is in
neither, so **no board issue can route to it** — not by misconfiguration, not by a lane typo, not by
someone assigning it in good faith.
Adding it to either list would make an always-on agent dispatchable, which is the one property the class
denies. The isolation is therefore not a policy anyone has to remember; it is the absence of a name in two
string literals, which is the kind of guarantee that survives everyone forgetting about it.
There is a second half worth stating plainly: this agent is **never foreground**. All human interaction
with it proxies through the planning agent. A resident process with its own conversational surface would
be a second place to give it instructions, and a second place to give a system instructions is a second
tracker wearing different clothes.
## Where this one is currently weakest [#where-this-one-is-currently-weakest]
* **The unit files are not in any repository.** They are installed from a different repository by a
script, and the main repository holds neither — so nothing compares the running schedule against a
committed artifact. Read the live one on the box; do not trust a written copy, including this page.
* **The interpreter is a virtual environment on the box that nothing in git describes.** One duty needs a
real browser under a virtual display, and the system interpreter has no package directory of its own on
this distribution. The environment is created by three commands documented in the agent's own brain
repository, and the handler's pre-flight names it concretely when it is missing rather than failing as a
bare non-zero exit. Probe it; do not assume it.
* **A probe run as the wrong user reports absence and non-existence identically.** These are per-user
service units, invisible to a probe from another account, and the failure text does not distinguish
"not there" from "not yours to see." Run the probe as that account with the runtime directory set, or
fall back to the committed feed — which carries its own timestamp, and is the only reading available
from an account that cannot reach the box at all.
That last point generalizes past this fleet: **a health check that cannot tell "absent" from "not
permitted" is a health check that will eventually report a working system as dead.**
## Take it further [#take-it-further]
```text title="prompt-1|Test whether anything you run deserves to be resident|desktop|Applies the naming test to each of your always-on processes and tells you which are polling for something they could be told about. No shell needed."
I want you to apply one specific test to the always-running processes in my setup.
Read this first:
https://docs.utopiamodels.ai/docs/structure/agent-always-on.md
The test is on that page. A fleet that forbids resident processes outright permits exactly one, and the thing that earned the exception is a requirement rather than an argument: the process must NAME, in its own documentation, the external event it exists to catch — specifically an event that emits no signal anyone could subscribe to, where being fast is the actual point.
Here is how I want you to work.
First, get the inventory out of me. Ask what runs continuously in my setup, and prompt me with categories rather than waiting for me to remember them: a polling script, a cron job that checks something, a webhook receiver, a bot, a monitoring agent, a sync process, a queue worker, a scheduled job that mostly finds nothing to do, a browser tab I leave open on a dashboard.
Then, for each one, put it through the test. Do not accept a vague answer to the first question — it is the one that decides everything after it.
1. **What event does this exist to catch?** Name the specific external thing that happens. "Monitoring the API" is not an event. "A 5xx rate crossing a threshold" is.
2. **Could I be told about that event instead?** A webhook, a subscription, a push notification, a callback, a database trigger, a file-system watch. Say specifically what would have to exist, and whether it already does and I simply have not wired it up. Most answers land here, and this is the finding.
3. **Is speed actually the point?** If the answer arriving five minutes later would be equally useful, this is a scheduled job and not a resident one. Ask me what changes if it is slower, and hold me to a concrete answer.
4. **If it died right now, how would I find out?** If the answer is "I would eventually notice," this thing has no liveness signal at all, and that is more urgent than whether it should be resident.
Sort the results into three piles and be blunt about which is which: **KEEP RESIDENT** (passed all four), **DEMOTE TO A SCHEDULE** (no latency edge — it is a timer wearing a daemon's clothes), and **REPLACE WITH A SUBSCRIPTION** (something is willing to tell me, and I am polling anyway).
Then ask me which one to fix first with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). Recommend one, and say why — I want to take the default if I am busy.
Close with the thing that matters most and gets skipped: for everything still in the KEEP pile, what makes its DEATH visible? A process that restarts forever on failure never reports as failed, so nothing downstream ever fires. Tell me, concretely, what I would need to add for each survivor.
```
```text title="prompt-2|Inventory what is actually resident on this machine|claude-code,opencode,codex,cursor,gemini|Enumerates every daemon, timer and cron entry that is really running, then finds the ones with no failure path. Reads only; writes nothing."
You are going to inventory every process that runs continuously or on a schedule on this machine, and find the ones that could die without anyone noticing.
Read the reference first:
curl -s https://docs.utopiamodels.ai/docs/structure/agent-always-on.md
Do not start, stop, enable, disable or edit anything. Every command you run must be a read. If a check would require changing state, describe what you would run and why, and do not run it.
## Step 1 — enumerate, from every layer
There is never only one scheduler, and an audit that reads one layer under-reports every other. Cover all of these, and say explicitly which ones do not apply to this machine:
systemctl list-units --type=service --state=running --no-pager
systemctl list-timers --all --no-pager
systemctl --user list-units --type=service --state=running --no-pager
systemctl --user list-timers --all --no-pager
crontab -l
ls -la /etc/cron.d/ /etc/cron.daily/ 2>/dev/null
launchctl list 2>/dev/null
docker ps --format '{{.Names}}\t{{.Status}}\t{{.Command}}' 2>/dev/null
Two notes that will change your results. A per-user service manager is invisible from another account, so a probe run as the wrong user reports "not there" and "not yours to see" with the same text — say which account you ran as. And cron is a genuinely separate layer from the service manager: a machine can carry scheduled work in both, and reading only one of them is the most common way this audit comes out wrong.
## Step 2 — separate the resident from the scheduled
For everything you found, sort it into two piles and state the criterion you used:
- **Resident** — a process that stays up between pieces of work.
- **Scheduled** — a process that starts, does a thing, and exits.
Anything in the resident pile that does its work on an interval is in the wrong pile. That is the single most common finding: a timer wearing a daemon's clothes, paying the cost of always being up for none of the benefit.
## Step 3 — find the ones that can die silently
For each resident process, answer three things from its configuration, not from assumption:
1. **What is its restart policy?** If it restarts unconditionally with no start-limit interval and burst, it can crash-loop forever without ever entering a failed state — so nothing that watches for failure will ever fire. Quote the actual directives.
2. **Is there a failure path?** An `OnFailure` unit, an alert, anything at all that runs when it gives up. If there is none, say so plainly: this process's death is silent.
3. **Where does it announce that it is alive?** Not "the logs" — somewhere a person actually looks. If the only evidence of life is a log file nobody opens, its liveness is theoretical.
## Step 4 — the trap, checked explicitly
Run this and read it carefully:
systemctl list-units --type=timer --all --no-pager
systemctl --user list-units --type=timer --all --no-pager
For every timer, check that the service unit it activates actually EXISTS. A timer whose service is not-found never fails — it simply never runs, and `systemctl --failed` reports clean the whole time. Report any you find, with the timer name and the missing unit.
Absence of failure is not presence of function, and this is the cheapest place to prove that on any machine.
## Step 5 — report
One table: name, layer it lives in, resident or scheduled, restart policy, failure path, where it announces liveness.
Then three findings, ranked: the process most likely to be dead right now without anyone knowing, the one that should be demoted from resident to scheduled, and any timer pointing at a service that does not exist.
If you cannot determine something because a probe needs a privilege you do not have, say exactly which command would answer it and which account has to run it. Do not report an unreadable thing as absent — those are different results and conflating them is the specific failure this reference page warns about.
Then ask me which of the three findings to fix first with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), and recommend one with your reason — the silent-death finding is usually the right default, because it is the one where I currently have no way of learning I was wrong.
```
# Architecture (/docs/structure/architecture)
Five layers, in the order they depend on each other: the **hosts** that exist, the **agents** that run on
them, the **runtime** that starts an agent, the **services** that stay up between runs, and the **secret
store** everything authenticates against.
## The vocabulary [#the-vocabulary]
The fleet's structural choices are **architectural patterns** — there are 14 of them, and the term is
the fleet's own, decided rather than drifted into. Everything below is one of two things:
* a **component** — something that *runs* or *holds state*. The box, the sweeper, the secret store. The
fleet keeps a registry of them, one document each, and that registry is what this page describes.
* an **architectural pattern** — one structural choice, stated so a reader on different substrate can
**adopt it or reject it**. Several of them do not run at all: decision discipline and
liveness-vs-existence are policy, not processes.
That second half is why this site does not call them *mechanisms*, *components*, *Fleet Architecture* or
*Fleet Infrastructure* when naming them as a set — each of those fits the runtime half and is wrong for
the policy half. The adopt-or-reject test is the one a name here has to pass, and only *pattern* passes
it. Elsewhere on these pages "mechanism" keeps its ordinary English meaning; the rule binds the set.
The fleet's canonical inventory of the 14 lives in its knowledge base, at
`world-infrastructure/reference/architectural-patterns.md`. The pages here are the exposition of them,
and [/feedback](/feedback) walks that same list — one pattern at a time, each explained inside the
question before it asks you whether you would adopt it.
Nothing on this page is a health reading. Whether a host is online, whether an agent can run *today*,
and how full the disk is are all volatile and belong on `/live`. Read that page for them — and note
what it does with the ones a public page cannot reach: it prints **the exact command and the box that
answers it**, rather than a number it did not measure.
## Hosts [#hosts]
### novaserver — the fleet's box [#novaserver--the-fleets-box]
One machine does essentially everything. It is an HP EliteDesk 805 G6 running Ubuntu 24.04, reachable on
the tailnet at `100.71.25.43` and on the LAN at `10.0.0.231`.
| | |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CPU | AMD Ryzen 5 PRO 4650GE — 6 cores / 12 threads |
| RAM | 30 GiB, plus 8 GiB of swap |
| GPU | Integrated Radeon RX Vega 6. **No NVIDIA** — `nvidia-smi` is not installed, and nothing on this box does GPU inference beyond what Ollama does on CPU |
| Root filesystem | 295 GB, and it is the fleet's most persistent capacity worry. `disk-guard.timer` fires hourly and reclaims a few megabytes per pass — it is running, and it is not winning. The figure is not readable from a public page: `/live` names the command (`ssh nova@100.71.25.43 'df -h /'`) rather than showing a number it cannot measure |
| Reached by | `ssh nova@100.71.25.43` |
**The bare alias does not resolve.** Writing `ssh` with just `nova` as the whole host — no user, no IP —
exits with `Name or service not known`, because there is no `~/.ssh/config` on the Architect box to
expand it. The working address is always the user at the tailnet IP, which is
exactly what the dispatch script itself uses (`NOVA="${DISPATCH_NOVA_IP:-100.71.25.43}"`). This is the
single most common error in the fleet's internal docs — thirteen files still write the bare alias, one
of them inside a copy-pasteable runbook block, so a reader following them fails on their first command.
### ETH — the Architect's box [#eth--the-architects-box]
A WSL2 Ubuntu 24.04 guest on the founder's Lenovo, tailnet `100.103.62.104`, unix user `kxdev`. WSL is
capped by `.wslconfig` at 12 GB of memory, 4 GB of swap and 6 processors; inside the guest that reads as
roughly 11 GiB of RAM. This box plans, files issues, and dispatches. It does not host any service the
fleet depends on.
### utopiamodels — a host that is not a machine [#utopiamodels--a-host-that-is-not-a-machine]
`100.92.117.107` is an application that joined the tailnet using `tsnet` — an LLM gateway, not a
computer. It appears in the node list beside real hardware, serves on port 80, and has no shell. Worth
knowing before you try to SSH to it.
## Agents [#agents]
**Who the agents are is generated, not written.** The table below is read out of the fleet registry on
every request. It used to be typed here, and it was wrong in the way that matters most: it called the
DMV-portfolio master `dmv`, a retired alias for the agent the registry records as **`dust`**. That name
has never been a unix account on any box — `id -u dust` returns `1001`, and `id -u dmv` still returns
*no such user* — so every `sudo -u dmv` command this site published failed for every reader who copied
one, and no page can catch that about itself.
> GENERATED — The unix accounts that run agents
>
> This block could not be read and therefore renders nothing: GitHub 404 Not Found on /contents/world-infrastructure/fleet/registry/organization.yaml?ref=main
*Generated at request time (2026-09-14T16:06:54.139Z) from `utopia-models/knowledge` @ `main` — `world-infrastructure/fleet/registry/organization.yaml` (every `agents:` record — its class, its box, its unix account, its GitHub identity, and whether it carries a subscription block at all); `world-infrastructure/fleet/registry/components.yaml` (the `uid` inside each runtime component’s `where:` line). Nothing here is stored: this page holds no copy of these values.*
Each agent has its own credentials, its own `~/.claude` brain directory (each one a version-controlled
repo of its own), and its own GitHub identity. They are peers. There is no supervisor process.
The **binary** is deliberately not in that table. `/usr/bin/claude` is where it resolves on novaserver
and nowhere else — on the Architect's WSL box it resolves elsewhere — so "every agent runs the same
binary at the same path" is a claim about one box wearing the clothes of a claim about the fleet. The
command that settles it for a given account is `sudo -u -H command -v claude`, run on the box
that account lives on.
### Existence is not liveness [#existence-is-not-liveness]
**The registry's own check for an agent is `getent passwd `, and that check passes on an agent that
cannot execute a single turn.** This is the most important distinction on this page.
A master can hold a valid unix account, a present credential file, a clean systemd state, working `gh`
auth, and still be unable to run — because entitlement lives with the account at the provider, not on the
box. When that happens the agent looks *busier* than a healthy one: transient units keep starting, keep
dying in seconds, and keep announcing themselves.
Measured 2026-08-16: **three of the four masters could actually run**, and the fourth reported
`{"exhausted": true, "resetsAt": null}` in its own state file. A `resetsAt` of `null` is the important
half — it means the provider named no recovery time, so no timer brings that agent back. The dispatch
scheduler correctly excluded it and said so in its own log line every five minutes.
The current per-agent answer is on `/live`. It is derived from each agent's own measured entitlement
— a recorded refusal, never `getent` — and that page names the `claude -p` command, and the box, that
settles it definitively.
### There is one backend, and choosing between backends was deleted [#there-is-one-backend-and-choosing-between-backends-was-deleted]
`dispatch.sh` passes a **literal** backend name into every run — `--setenv=TASK_BACKEND=claude`. There is
no lookup, no registry read and no per-master resolution: the function that once chose between providers
was deleted along with the alternate provider it chose between.
This page said the opposite for a while, and the wrong version was the more flattering one — a fleet that
resolves each agent's provider at runtime sounds more capable than one that hardcodes a single name. It is
worth stating the plain version instead, because the plain version is what a reader would find in the
script.
The variable itself survives for a different job. It is the fleet's *"am I inside a dispatched run"*
signal, which is what every board-keyed gate exempts on. A flag whose original purpose was deleted can be
worth keeping for the second job it accidentally does — provided somebody writes down which job it is now
doing.
## The dispatch runtime [#the-dispatch-runtime]
Two scripts and systemd. That is the entire agent runtime — no scheduler daemon, no queue, no watchdog.
The overview is below; the guards, their exit codes and the race that only a file lock can close are on
[The dispatch runtime](/docs/operating/dispatch-runtime).
| Script | What it does |
| ----------------------------------------------------- | -------------------------------------------------------------- |
| `_infra/scripts/dispatch.sh [repo]` | The Architect hands a master its issue **by starting the run** |
| `_infra/scripts/task-runner.sh [repo]` | The run itself, on the master's own box |
What one dispatch actually does, in order:
1. Launch under the one backend the fleet has — a literal, not a lookup (see above).
2. `agent` runs locally; the novaserver masters are reached with one `ssh nova@100.71.25.43` plus
`sudo -u `.
3. **Pre-flight refuses a quota-dark master.** A dispatch into an agent that is already dark burns the
run and charges the issue an attempt for it. The check fails *open* — an unreadable state file, a
missing file, or a reset time that has already passed all mean "not dark", because a false block stops
the fleet and that is worse than a wasted run.
4. **Pre-flight refuses a second run for an issue that is already running.** One issue, one run. The
guard lives in the entry point both the scheduler and a hand-typed dispatch share, because a guard in
only one consumer cannot see the other path.
5. **Pre-flight syncs the master's clones**: fetch, stash anything dirty, checkout main, hard-reset to
`origin/main` for every repo in the run's repo list.
6. Launch a **detached transient unit** — `systemd-run --user --unit=task-- --collect` — so the
run survives the SSH connection dropping and the laptop going to sleep.
7. `task-runner.sh` self-updates once, re-checks that the issue is *still work*, posts **▶ started**
before anything that can fail, runs `claude -p` under a turn cap, and on exit its trap posts
**✔ / ✘ / ⚠ / ⛔** plus the pull-request link.
The cap in that last step is the kind of number that goes stale the moment someone tunes it, so it is
read rather than written:
```bash
MAXT="${TASK_MAX_TURNS:-200}"
```
The cap is a **default of 200, not a fixed number** — any caller may raise or lower it by exporting `TASK_MAX_TURNS`, and `_infra/scripts/dispatch.sh` does exactly that when it launches the unit. A run that consumed all its turns reports the cap it actually ran under, which is why the number in a failure message is evidence and the number on this page is not.
*Generated at request time (2026-09-14T16:06:54.141Z) from `utopia-models/knowledge` @ `main` — `_infra/scripts/task-runner.sh` (the `MAXT=` assignment passed to `claude -p --max-turns`). Nothing here is stored: this page holds no copy of these values.*
### The last-moment disposal re-check [#the-last-moment-disposal-re-check]
An issue can stop being work while its run is starting, and every gate that asks about it runs too early.
Measured on one real case: the pre-flight read the issue at 14:36:01 and it was open and correct; the
issue was labelled a throwaway at 14:36:03 and closed at 14:36:05; systemd started the unit at 14:36:12;
▶ was posted at 14:36:15. A master then spent a full session on board residue.
The fix asks the same two questions from **inside** the run, immediately before ▶ — the genuine point of
no return. Only two signals count: the issue is CLOSED, or it carries the throwaway-fixture label.
Anything else is a judgement a human may legitimately be overriding at the command line.
### The sweeper [#the-sweeper]
The thing that carries a chain of work forward is **not a service and not a cron job**, which means every
probe that goes looking for a `sweeper.service` falsely reports it missing. It rides as the second
`ExecStart` line of a systemd drop-in on one master's `reconcile-board.service`, fired by
`reconcile-board.timer` every five minutes:
```text
/home/nova/.config/systemd/user/reconcile-board.service.d/10-sweep-dispatch.conf
ExecStart= (clear)
-/usr/bin/bash %h/.claude/ops/reconcile-board-status.sh --apply
-/usr/bin/bash %h/dev/utopia-models/knowledge/_infra/scripts/sweep-dispatch.sh --apply
-/usr/bin/bash %h/.claude/ops/mute-agent-notifications.sh --apply
```
Its own log line is the best one-line health summary the fleet produces. One real line, read out of
`~nova/.claude/logs/sweep-dispatch.log` and emitted 2026-09-04T08:48:34Z — quoted as a sample of the
shape, not as a current reading:
```text
sweep: 39 open · 0 dispatchable · in-flight 3 (win=2 nova=0 cloud=1 dust=0 )
```
A refused master adds a trailing segment naming who and why — `· rate-limited: (exhausted nova)` —
which is absent above because on that pass every master was clear.
Read that carefully, because it is a lesson in reading your own instrumentation. A large open count
against zero dispatchable looks like a capacity failure and is not one — it is a **routing** failure.
Issues with no lane, or on a lane no sweeper reaches, are structurally undispatchable no matter how idle
the fleet is. The sweeper had been printing the cause every five minutes while investigations looked for
a broken mechanism.
## Services on the box [#services-on-the-box]
The first two groups below run on novaserver and stay up between agent runs; the third is state the box
depends on and does not host. The dispatch runtime is the part with no resident process; the *box* has
plenty.
**System units:** `tailscaled` · `cloudflared` (the tunnel that publishes selected local services to the
public web) · `ttyd` plus one read-only browser viewport per master on ports 7810–7813 · `ollama` on
11434, serving small local models · `occ-redirect` on 3939, a small Python responder that 301s a legacy
hostname to the apex · `grafana-server` on 3000 · `prometheus` on 9090 with node-exporter on 9100 · two
self-hosted GitHub Actions runners, a full lane and a light lane.
**Docker:** **Infisical** — backend, database and redis — which is the fleet's only secret store, and a
full **SigNoz** observability stack (ClickHouse, a Postgres metastore, keeper, ingester).
**Managed Postgres, off the box:** two hosted databases hold state that nothing here can reconstruct, so
they belong in this list even though no process for them runs on novaserver. **Neon** — the `utopia-auth`
project, Postgres 17 in `aws-us-east-1`, holding the product's auth and user rows; a nightly
`pg_dump -Fc | gzip` through the direct unpooled endpoint copies it to cold object storage at 35-day
retention, because Neon's free point-in-time recovery reaches back only about six hours. The same Neon
organisation holds three other projects that are not fleet infrastructure, and conflating them with this
one is the easy mistake. **Supabase** — the `peptide-shop` project in `us-east-1`, which holds the
database, auth, and the order and payment records behind `dmvresearch.shop`; those rows are the
irreplaceable asset, and they are dumped nightly to the same bucket by two independent jobs, one a
`pg_dump` and one a row-level export through the REST API.
**Scheduling is single-homed.** One master's user manager carries essentially every scheduled timer in
the fleet (measured 2026-09-04 with `systemctl --user list-timers --all` under each master account: 37
timers on nova against 4 on win and 3 each on cloud and dust). The
consequential ones publish the command center's feeds, re-render it and check it, sync creative assets,
watch token health, refresh the tailnet snapshot, guard the disk hourly, and self-heal the default route
every sixty seconds.
**Cron is a second, parallel scheduling layer**, and it is easy to miss. Masters carry crontab entries
for monitors, self-reports and usage feeds that have no systemd timer at all. An audit that reads only
`systemctl --user list-timers` under-reports every master's real cadence.
**A dead timer never fails, so `systemctl --failed` reports clean.** The box has held an *enabled* timer
whose service unit was `not-found`, and dormant timers that had not fired in a week, while every failure
query returned empty. Absence of failure is not presence of function.
## Secrets [#secrets]
**Infisical is the only secret store**, self-hosted in Docker on novaserver. There is no local vault, no
`.env` checked in anywhere, and no second store.
| Operation | Command |
| --------------------------- | ----------------------------------------------------------------------- |
| Read | `bash ~/.claude/scripts/infisical-get.sh [env]` |
| List every key in a project | `bash ~/.claude/scripts/infisical-get.sh --keys prod /` |
| Write | pipe the value on **stdin** to `infisical-set.sh [env]` |
Projects are scoped by domain: fleet infrastructure, the product, per-portfolio research, and
engineering. Writing a value as a command-line argument is rejected by a guard, because an argv secret
lands in shell history and in the process table.
A second file — a credential registry — carries each credential's *meaning*: which account it belongs to,
what scopes it has, what it can do and what it explicitly cannot. Reading that registry before declaring
a credential missing is a standing rule, because the fleet's recurring failure mode is not a missing
credential but a grep for the wrong key name.
Two absolute rules:
* **Never print a secret, or the presence of one, to any display sink.** Capture into a variable and
check its length.
* **Never write a secret into a tracked file.** This is the fleet's single hard stop — the one thing that
is not fix-forward, not advisory, and not overridable. A secret scanner runs on every pull request; it
is the only check whose result is treated as binding.
Publishing the *mechanics* of all of this, as this page does, is in scope and deliberate. Publishing a
value never is.
# Decisions (/docs/structure/decisions)
The fleet's most expensive recurring failure is **re-deciding something that was already decided and paid
for.** Every entry below says what the thing was, why it died, and what replaced it. If you are about to
propose one of these, the burden is new information — not a fresh opinion.
A decision is not reopened by disagreement. It is reopened by a **superseding record that names what
changed**, written beside the original, with the original's `Superseded-by` header updated in the same
commit. A silently invalidated decision record is worse than none.
## Killed: ralph — the 24×7 autonomous loop [#killed-ralph--the-247-autonomous-loop]
**Deleted 2026-07-24.**
`ralph.service`, with `Restart=always`, drove a loop: pick the next open board issue for this master's
lane, run headless `claude -p`, repeat. Around it accreted watchdogs, heartbeats, priority-sort tests,
self-reload fixes and a pick-order bug — an entire body of supervision machinery whose only job was
keeping a resident process honest.
**Replaced by on-demand dispatch**: `dispatch.sh` plus `task-runner.sh`, no resident agent process at all.
The close-out measured the replacement rather than asserting it — **7 dispatched runs, 7 verified
outcomes, 0 runtime failures**, against a roughly 20% baseline.
The residue was instructive, and it took a second deletion to clear. For weeks after the loop was
deleted, the registry still carried a per-agent field named after it, still described as the live backend
selector — and the field really was read, so nobody could call it dead. The framing outlived the system,
then the field outlived the framing. 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 outliving its system is how a deleted thing keeps costing you**, and the cost is specifically
that the surviving name makes the deletion look incomplete to everyone who reads it afterwards.
## Killed: the generated component catalog and its fail-closed gates [#killed-the-generated-component-catalog-and-its-fail-closed-gates]
**Deleted 2026-07-23/24.**
An 804-line machine-generated registry where every component carried a shell predicate, run
**fail-closed on every pull request** by three separate workflows plus a post-commit regeneration hook.
Its own header claimed *"a fix isn't done until catalog = reality."*
It was killed because a gate that blocks on a generated map **false-blocked documentation changes and
auto-reverted its own cleanup**. Eighteen dead gate workflows went in one commit.
The replacement inverts the design deliberately. There is one hand-maintained component registry, and
every entry carries a `verify:` command **you run**, with the instruction *"run it — never trust this
file's prose."* Advisory by construction.
Then the honest footnote, because this wiki does not publish only the flattering half: an audit found
that `verify:` is **read by humans and executed by nothing**, and that it records no box, so a quarter of
its commands fail from the wrong machine and pass from the right one. The mechanism is better than what
it replaced and it is not yet what its own header claims.
## Killed: the NATS message bus [#killed-the-nats-message-bus]
`nats.service` and its reload units are on the **never resurrect** list. Cross-machine agent comms
were replaced by **issue comments and @mentions** — delivery that is durable, at-least-once, and
visible on the surface a human already reads.
**Three zombie units survive on the box, and one of them is active.** Their residue was documented
in a file that was itself deleted, which is why they get named here. This page said *two*, and said
they were *inactive and disabled*, until a reader running the pattern walk ran the command and
reported otherwise (knowledge#6665). Re-measured on the box, 2026-08-23:
```console
$ 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 hub
```
`nats-reload.path` is `enabled` and `active (waiting)` — a live path unit watching for changes to
the config of a service that will never start again. A deletion in a repository is half a deletion,
and the surviving half is the half that keeps running.
## Killed: the command center's generated infrastructure surface [#killed-the-command-centers-generated-infrastructure-surface]
Built on the deleted catalog, so it returned a permanent empty state once the catalog died. Removed in a
single pull request that deleted *"11 callerless routes, 7 orphaned components, 5 dead libs."*
This one is listed because **open plans still name deleting it as a to-do.** Anyone dispatched on that
line finds nothing to delete, which reads like a lost change rather than an already-shipped one. A stale
local clone will happily disagree; `git ls-tree origin/main` is the truth and a local `ls` is not.
## Killed: a scheduled local re-run of another repository's CI [#killed-a-scheduled-local-re-run-of-another-repositorys-ci]
**Deleted 2026-08-15.**
A timer fired every three hours on one master: check out the product repo, typecheck, build, test, file a
P0 on any real failure. It was built to close a genuine blind spot and was repeatedly repaired over
months.
It died because the product repo's own CI runs those same three phases on every push to main — it just
**told nobody**. Once it announced for itself, a second box re-running identical work was duplicated
effort.
### The half-deletion, which is the real lesson [#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*, not symlinks into the repo. The timer stayed `active waiting` with its `ExecStart`
pointing at a script that no longer existed, and the next firing — nearly three hours after the "delete"
— exited `203/EXEC` and filed a P0 about itself.
A brain-repo deletion is **half** of a deletion. The other half is on the box, and it is five commands.
They are written out in [Operations](/docs/operating/operations).
## Standing decisions [#standing-decisions]
These are not history. They are current, and they constrain new work.
### Publish everything, unsanitized [#publish-everything-unsanitized]
This wiki carries real hostnames, real tailnet IPs, real usernames and real credential mechanics. That
was decided **after measuring exactly what it exposes**, not by default and not by oversight. Of the
fleet's internal documents, most name hosts or users, many carry tailnet addresses, and a substantial
share describe how the vault works.
**Do not redact, do not generalize, and do not add a scrub pass.** A contributor "helpfully" sanitizing
this site is a regression, not a fix.
**The one hard stop is unchanged and absolute: never a secret in a tracked file.** Publishing the vault's
mechanics is in scope; publishing a credential never is.
### One secret store [#one-secret-store]
Infisical, self-hosted, and nothing else. No local vault, no second store, no `.env` in a repository.
A separate registry carries each credential's *meaning* — account, scopes, what it can and cannot do —
so that "I don't have access" is a claim that must be checked against a list before it can be made.
### Subtract by default [#subtract-by-default]
Before adding a script, a rule, a service, a gate or a file, the question is what can be **deleted or
merged instead**. Legacy that is replaced is deleted at the moment of replacement. Git history is the
archive; `archive/` folders are banned. A convention gets an advisory linter; only a stable invariant
gets a blocking gate.
### Options before decisions [#options-before-decisions]
A non-trivial architecture or tooling choice gets researched alternatives — at least two real options,
plus do-nothing, plus integrate-something-existing — before the decision, and the decision is written
down. Proven native and open-source tools beat custom machinery; custom is the exception that must
justify itself.
This site is its own example. The wiki lives in a standalone repository rather than inside the product
monorepo, and the record naming the four options considered, the reason, and the **costs the choice
imposes** was committed before the first line of the app.
### A limit is a measurement, never a precaution [#a-limit-is-a-measurement-never-a-precaution]
Before setting any ceiling — concurrency, retries, batch size, timeout — measure the real headroom and
**write the measurement next to the number**. A cap chosen out of caution silently becomes the system's
capacity, and unlike a bug it never announces itself: it reports success while doing a fraction of the
work.
The fleet learned this in the most annoying possible way. Its dispatch scheduler ran for days at roughly
**8% of proven capacity** — three of four masters idle on a 12-core box under a load average below 1 —
because caps of 1, 4 and 2 had been set against a failure mode that two other mechanisms already covered.
The defence was real; it was simply aimed at the wrong axis.
### Fix forward, never pre-gate [#fix-forward-never-pre-gate]
Build, merge, deploy. If it breaks, revert or fix and redeploy. The alternative — a human approving each
step — was measured and is slower at every scale this fleet operates at. What this explicitly does *not*
license: skipping verification, force-pushing shared branches, or claiming completion that was not
observed.
## Never resurrect [#never-resurrect]
* **ralph** and every watchdog, heartbeat and supervisor built to babysit it.
* **The NATS bus** and its three remaining zombie 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 of knowing whether the
fleet is alive. The board is the queue; a run's own close-out comment is the liveness signal; and the
one page that reads current state does so at request time and stores nothing.
**One sanctioned exception, named here so a reader does not have to discover it with `ps`.** A
`*/5 * * * *` cron entry on each master's own crontab POSTs that master's record to the command
centre, which stores it under a five-minute TTL; the TTL expiring is what turns a box grey on a
dashboard a human is looking at. That is a self-reported heartbeat and it was adopted deliberately
(2026-07-22) because it answers a different question: *which box is online right now*, not *is
this work alive*. Nothing in the work loop reads it — no dispatch, no gate and no close-out
depends on it, which is what keeps the ban meaningful. Separately, a master's own standing
interactive session is resident for as long as nobody restarts it (measured 13.3 days on one
master, 2026-08-23); no *dispatched run* leaves a process behind. The ban is on resident
machinery as the ANSWER to "is the fleet alive". It has never been a ban on every scheduled job,
and a reader who runs the walk from inside this fleet will find both of these — so they are
written down rather than left to look like drift (knowledge#6665).
If you believe one of these should come back, the path is a superseding decision record naming what
changed. Not a rebuild.
# The skills layer (/docs/structure/skills-layer)
Sourced from the fleet's own component document for `skills-layer`. Called in-flux because three of its
own in-repository pointers resolve to files that are wired to nothing; because only two of the fleet's
seven agent repositories carry a skills directory at all; and because the two that do have diverged, with
nothing measuring the gap.
A skill is a directory holding one markdown file. The file's frontmatter description is what the model
matches on to decide whether the skill applies — which means **the description is not documentation, it is
the routing key**, and a vague one makes a skill that exists and never fires.
This is where the fleet's process *doors* live: the entry points that start a plan, continue one, or run a
feedback pass. Delete the layer and no agent stops executing — but every way of *starting* something goes
with it.
The cross-harness question — whether to write a reusable behaviour as a skill or a slash command when you
run more than one agent tool — is on [More than one harness](/docs/operating/harnesses). This page is
about delivery: who gets a skill, and how far it actually travels.
## The defining property is negative [#the-defining-property-is-negative]
🔴 **A skill reaches exactly one machine.**
The fleet ships a plugin to every box — hooks and scripts, pinned to a commit and gated on a version. It
is genuinely one push and every box pulls. **That is true of the plugin and of nothing else**, and the
sentence *"one push, every machine gets it"* has been applied to skills more than once by people who had
read the plugin's documentation.
The plugin does not carry skills. It never has.
## Three delivery layers, three reaches [#three-delivery-layers-three-reaches]
```text
PLUGIN one repository → EVERY box
pinned to a commit, gated on a version
carries: hooks, scripts
DOCTRINE each box's own clone → EVERY box, live
read through a symlink, so a rule change
lands with no version bump
🔴 but the SYMLINK is tracked in that box's own repo
SKILL one box's own repository → exactly ONE box
arrives at that box's next pull
```
**Conflating any two of these is the recurring error**, and the middle one is where it bites, because it
is the second layer wearing the third layer's clothes.
Doctrine is symlinked live out of each machine's own clone, so a rule change reaches every box without a
release. But the *symlink itself* is a tracked file in that machine's own repository. When the links were
repointed by hand on five machines and committed on none, every subsequent checkout restored nine symlinks
into a directory the rebuilt tree no longer had — and the automatic pull then skipped, because it refuses
to run over uncommitted deletions. Five machines, silently pinned to a stale tree.
**A repoint is not delivered until it is committed.** A change made by hand on a live machine is a change
that exists until the next checkout, and no longer.
## What belongs in a skill, and what does not [#what-belongs-in-a-skill-and-what-does-not]
**A skill belongs to the agent that owns the folder.** A machine's agent directory is that agent's; a
project's own skills, commands and rules never live there. That boundary is what stops one agent's
behaviour leaking into another's by proximity.
Four conventions this fleet holds its own skills to, each earned rather than chosen:
* **A skill that touches more than one file loads its context and shows a status table before any write.**
The reader sees what is about to happen while it is still cheap to stop.
* **An interactive question is never step one.** A skill that opens by asking has not read anything yet,
so its question is uninformed by definition.
* **A skill with side effects opts out of automatic invocation.** Auto-matching on a description is right
for a skill that reads and wrong for one that writes.
* **The verbs are fixed**: *audit* is read-only, *self-enhance* is autonomous, *update* and *fix* are
scoped. A skill whose name does not start with one of those is claiming a shape the fleet has no rules
for.
### A door is thin; the process is a frame [#a-door-is-thin-the-process-is-a-frame]
The skills that start things resolve *which* process you mean and then hand off to a frame document. **The
process lives in the frame, never in the door.**
This matters more than it sounds. A door that contains its process cannot be shared with a second door, so
the moment two entry points need the same steps, the steps get copied — and two copies of a process
diverge exactly the way two copies of anything else do.
### A fact a skill would assert becomes a command that reads it [#a-fact-a-skill-would-assert-becomes-a-command-that-reads-it]
🔴 In a skill file, an exclamation mark immediately followed by a backticked command **runs in a shell
before the content is sent to the model**, and the output replaces the placeholder. Verified here both
without any tool allowlist and with automatic invocation disabled.
So: a sentence stating a count, a path or a version is a sentence that rots. The live read cannot. This is
the same discipline the pages on this wiki follow — [state the probe, not the answer](/docs/operating/operations) —
applied one layer down, inside the instruction itself.
### The executing agents have no skills at all [#the-executing-agents-have-no-skills-at-all]
🔴 And that is the current design, not a gap. An agent executing a filed piece of work reads its
specification and its checklist; anything it needs from a process arrives **written into the issue body at
filing time**.
Worth sitting with, because it inverts the obvious approach. The machines doing the work carry the least
process. Process lives where work is *shaped*, and travels to where work is *done* as data rather than as
installed behaviour — which means the shape of a process can change without a deployment to anywhere.
## Grade the population, never one machine [#grade-the-population-never-one-machine]
🔴 *"The fleet has skill X"* is only ever true of machines individually, and most of them are not readable
from any one account. A verification that read one machine and pronounced the layer sound has already
shipped here once.
Every fact about this layer is off-repository, so this page states the readings rather than the answers:
| Question | Command |
| -------------------------------------------- | -------------------------------------------------------------------------------------- |
| What does *this* machine have | `ls ~/.claude/skills/` |
| What does a given machine's repository carry | `gh api repos///contents/skills --jq '.[]\|select(.type=="dir").name'` |
| What one skill actually does | `gh api repos///contents/skills//SKILL.md --jq .content \| base64 -d` |
| Does the plugin carry skills (it does not) | `gh api repos///contents/plugins/ --jq '.[].name'` |
| Is a machine on the current plugin payload | `bash _infra/scripts/plugin-deploy-check.sh` |
One trap in that table, and it costs an hour the first time: **`gh api` writes its 404 body to standard
output**, so a bare fallback prints the error JSON *and* the fallback text on one line. Filter for the
shape you expect rather than for non-emptiness.
## Where this layer is currently weakest [#where-this-layer-is-currently-weakest]
* **Two of seven agent repositories hold a skills directory at all.** The rest return 404. So the layer's
real population is two machines, and the larger set — the majority of every skill that exists here — is
reachable by nobody else. **Any plan that says "ship a skill to the fleet" is describing a mechanism
that does not exist.**
* **The two sets have diverged with nothing measuring the gap.** Twenty-four named skills against
fifty-one. No shared manifest, no linter, no index. There is no command that answers *"which skills
exist in the fleet"* — the probe table above is one read per machine precisely because that single
command has never existed.
* **A backup file sits inside the skills directory**, in the same directory the loader walks. It is not a
skill and nothing prunes it, against this fleet's own standing rule that git history is the archive.
* **Three of this component's own in-repository pointers are wired to nothing.** The assertion checker
reports them present on disk and called by nobody — which is exactly what a door that was replaced but
not deleted looks like.
## Take it further [#take-it-further]
```text title="prompt-1|Turn a thing you keep re-explaining into a skill|desktop|Extracts one repeated instruction into a skill file, with the routing description written to actually fire and every rotting fact replaced by a live read. No shell needed."
I keep re-explaining the same things to my AI coding tool. I want you to turn one of them into a reusable skill.
Read this first for the conventions:
https://docs.utopiamodels.ai/docs/structure/skills-layer.md
https://docs.utopiamodels.ai/docs/operating/harnesses.md
The second page matters if I use more than one tool — it explains why a skill is the portable form and a slash command is not.
Start by finding the candidate. Ask me what I have explained more than twice: how to run the tests here, what our commit messages look like, how to add a migration, which files never to touch, how we do code review, how a release goes out. Get me to pick ONE. Do not try to do several — a skill that covers three things fires for none of them.
Then interview me until you can write it. Ask one question at a time, each with a recommended answer, and stop when you have these:
- What triggers it? The exact situation where I would want this to fire.
- What does it read before doing anything?
- What does it produce? A file, an edit, a report, a decision.
- What must it never do?
- Does it write anything, or only read?
Now write the skill, and hold to these four rules from the reference, because they are the difference between one that works and one that sits there.
**The description is the routing key, not documentation.** The model matches on it to decide whether the skill applies. Write it as the situation, in the words I would use when I am in that situation — not as a summary of what the skill does. Show me the description on its own and ask whether it matches how I would actually phrase the problem.
**Never assert a fact that could be read.** Any count, path, version or file list in the body is a sentence that will be wrong later. Replace each one with the command that reads it. Go through your own draft line by line looking for stated facts and convert every one you find — this is the step that gets skipped, and it is the step that decides whether the skill is still correct in six months.
**Do not open with a question.** A skill that asks before it reads is asking uninformed. Read first, then ask, and only about what reading could not settle.
**If it writes anything, it shows me what it is about to do first** — and it should not fire automatically. Auto-matching is right for a skill that reads and wrong for one that changes things.
Hand me the finished file, and tell me exactly where to put it for the tool I use — ask me which one with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead) if I have not said, and recommend the location that works for the most tools at once.
Last thing, and it is the one that determines whether this was worth doing: tell me how I will know in three months whether this skill is still correct. If your answer is "you would notice," go back and convert more of the asserted facts into reads.
```
```text title="prompt-2|Find the rot in the skills you already have|claude-code,opencode,codex,cursor,gemini|Reads every skill, rule and instruction file on this machine, checks each stated fact against the filesystem, and reports which ones are already false. Reads only; writes nothing."
You are going to audit the reusable instructions on this machine — skills, rules, agent instruction files — and find the statements in them that are already false.
Read the reference first:
curl -s https://docs.utopiamodels.ai/docs/structure/skills-layer.md
The claim being tested: an instruction that ASSERTS a fact rots, and one that READS it cannot. I want to know how much of what my agent is being told is already wrong.
Do not edit anything. Read, check, report.
## Step 1 — find every instruction file
Cover all of these; most machines have several layers and they disagree:
ls -la ~/.claude/skills/ ~/.claude/rules/ ~/.claude/commands/ 2>/dev/null
ls -la .claude/ .agents/ .cursor/ .github/ 2>/dev/null
find . -maxdepth 3 \( -name 'CLAUDE.md' -o -name 'AGENTS.md' -o -name 'SKILL.md' -o -name '.cursorrules' \) -not -path './node_modules/*' 2>/dev/null
Report each with its path, its size, and whether it is a real file or a symlink. A symlink matters: the reference page describes five machines that were silently pinned to a stale tree because a link was repointed by hand and never committed. Run `git status --short` on any repository containing one of these and say whether the link is committed as it currently points.
## Step 2 — extract every checkable claim
Read each file fully — do not grep. A grep returns only what its pattern already describes, so it can never surface an ABSENCE, and absences are half of what you are looking for here.
Pull out every statement that could be checked against this machine:
- a file or directory path
- a count ("the 12 services", "our three environments")
- a command that is claimed to exist
- a version, a port, a package name
- a claim about what some other file contains
## Step 3 — check every one of them
For each claim, run the check and record the command and its raw output. Not a summary — the command and what it printed, so I can reproduce any finding.
test -e && echo PRESENT || echo ABSENT
command -v || echo 'not on PATH'
| wc -l
For any stated count, go count the thing. This is the highest-yield check in the whole audit and it takes seconds each.
## Step 4 — check the routing descriptions
For every skill with a frontmatter description, the description is what the model matches on to decide whether to fire. Read each one and tell me:
- Is it phrased as the SITUATION a user would be in, or as a summary of what the skill does? Only the first kind fires reliably.
- Do any two skills have descriptions that overlap enough that the model could not choose between them?
- Is any description so generic it would match almost anything?
## Step 5 — report
Three sections.
**Already false** — the claim, the file and line, the command you ran, and its output. Ranked by how badly it would mislead an agent that believed it.
**Rots next** — claims that happen to be true today but are asserted rather than read, with the command that should replace each one. Give me the literal replacement text.
**Never fires** — skills whose description would not match the situation they are for, with a rewritten description for each.
Finish with one number: what fraction of the checkable claims across all these files are currently false. That number is the whole point of the audit, and it is the one I want to watch.
If something cannot be checked from this machine, say which command would settle it and where it has to run. Do not report an unreadable thing as absent — those are different results.
Then ask me which section to work through first with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead), and recommend one — default to the already-false list, because those are actively misleading an agent right now rather than merely at risk of it later.
```
# Surfaces (/docs/structure/surfaces)
**A status code is not a state.** Two hostnames returning an identical `404` can mean completely
different things: one was switched off on purpose and is behaving correctly, the other is an accident
nobody has noticed. Collapsing that distinction into a single "404" cell once cost this fleet a P0
incident — a master was dispatched to "restore" a surface that had been deliberately retired ten days
earlier, and it did exactly as it was told.
So every row below carries a **class**, and the class is the durable fact. The status code is evidence
for the class, not a substitute for it.
| Class | Meaning |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **LIVE** | Intended to serve, and serving. |
| **RETIRED-BY-DECISION** | Not serving, **and that is correct.** A human decided to switch it off, and the decision is sourced. Do not "fix" one of these. |
| **BROKEN** | Not serving, and nothing says that was intended. This is the only class that is a bug. |
A fourth label, **NEVER-EXISTED**, is used at the bottom of the page for names that appear in the fleet's
own documents but have never resolved in DNS. They are not surfaces; they are citations of surfaces that
were never built.
All codes below were measured by the run that wrote this page, **2026-08-16**, from novaserver. They are
evidence with a date on them, not a live reading — for current codes, see `/live`.
A dated table decays, so this one is re-measured rather than trusted: `/live` parses the class column
straight out of this page and probes every host in it on each request, which means adding a surface
here adds a check with no edit anywhere else. Two rows have gone stale that way. `creatives.utopiamodels.ai`
was recorded as `200` until the **5am** domain landed and the migration redirect fired — stale because
the world moved, not because it was measured wrong, which is the failure a re-measure catches and a
careful first reading cannot. And `docs.utopiamodels.ai` — this host — has now changed root shape
twice: a `307` to `/docs` when this page was written, then a `301` off the host entirely while it was
retired, and today a plain `200` (see its row below).
## LIVE [#live]
| Hostname | Class | What it is | Basis (measured 2026-08-16) |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `utopiamodels.ai` | **LIVE** | **The Org Command Center** — the fleet's cockpit, and the surface the founder actually watches | `200`. Unique in the fleet for being both Cloudflare-fronted and Vercel-origin: `cf-ray` and `x-vercel-id` are both present |
| `www.utopiamodels.ai` | **LIVE** | Apex alias | `200` |
| `docs.utopiamodels.ai` | **LIVE** | **This wiki** | `200` at `/`, no redirect in the chain (re-measured 2026-09-04). It has not always read that way, and the history is the point: `307` at `/` → `200` at `/docs` when this page was written 2026-08-16, then `301` to `school.utopiamodels.ai` on every path from 2026-08-30 while the host was retired by decision, then un-retired 2026-09-04 with the root rewritten rather than redirected — so `/` now serves the docs index itself. A check that reads only the first code would have called this surface healthy, broken and healthy again without the class ever changing |
| `dmvresearch.shop` | **LIVE** | The DMV Research storefront — real catalog, real prices, real revenue | `200` |
| `www.dmvresearch.shop` | **LIVE** | Storefront alias | `301` to the apex |
| `dev.dmvresearch.shop` | **LIVE** | The storefront's preview host | `200` |
| `dmvclothes.shop` | **LIVE** | DMV ad-bridge surface | `200` |
| `dmvresearch.io` | **LIVE** | DMV ad-bridge surface | `200` |
| `rawphones.com` | **LIVE** | **The RAW brand surface** — the phone brand's public front | `200`, served via a path rewrite (`x-matched-path: /raw/index.html`) off a shared Vercel project |
| `www.rawphones.com` | **LIVE** | Brand alias | `308` to the apex |
| `nothing.utopiamodels.ai` | **LIVE** | The RAW design "rip" — a full reconstruction of a competitor's site, used as a design reference | `200`, and by a wide margin the heaviest page the fleet serves |
| `raw1.utopiamodels.ai` | **LIVE** | RAW design variant | `200` |
| `raw2.utopiamodels.ai` | **LIVE** | RAW design variant | `200` |
| `raw3.utopiamodels.ai` | **LIVE** | RAW design variant | `200` |
| `os.utopiamodels.ai` | **LIVE** | The RTOS concept page | `200` |
| `hack.utopiamodels.ai` | **LIVE** | The RAW investor deck | `200` |
| `community.utopiamodels.ai` | **LIVE** | Community surface, Cloudflare-served rather than Vercel | `200` |
| `creatives.utopiamodels.ai` | **LIVE** | The **5am** agency's creative showcase. **Plural** — the singular name is a different, dead thing | `301` → `https://5am.agency/`, which answers `200`. The agency domain landed after this table was first written, and the migration redirect fired with it; the media is still served from `assets.utopiamodels.ai` |
| `credentials.utopiamodels.ai` | **LIVE** | A friendly admin UI over the secret store, published through the Cloudflare tunnel to novaserver's port 3940 | `200`, `redirects=0` — and gated all the same. It renders a server-side Google SSO allow-list of exactly three accounts in page instead of redirecting to one, so the usual "`redirects=0` means ungated" reading is exactly backwards here. See rule 2 at the foot of this page |
| `va-dct.utopiamodels.ai` | **LIVE** | A data-replica surface, paired with the two always-on services on one master's box | `200`. Has **no registry row and no doc** — a live public host on the company domain that nothing can notice going down |
| `org.utopiamodels.ai` | **LIVE** | A legacy name, deliberately redirecting | `301` → `https://utopiamodels.ai/`, `redirects=1`. Matches the `occ-redirect` responder on novaserver's port 3939 |
| `assets.utopiamodels.ai` | **LIVE** | Object storage for showcase media | **A bare code lies here.** `/` returns `404` and so does a directory path, but a real object returns `200` — verified against `…/showcase/media/1G1vnTX5Pd-Z_i965L-_VLspSKObrxIve.png`. There is no index; that is not a fault |
## RETIRED-BY-DECISION [#retired-by-decision]
| Hostname | Class | Basis |
| --------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `raw.utopiamodels.ai` | **RETIRED-BY-DECISION** | `404 DEPLOYMENT_NOT_FOUND`, measured 2026-08-16. Retired by the founder on **2026-08-06**. This is the row that earned this class its existence: an auto-loaded doctrine file went on describing it as a live surface, a master read that and was dispatched to restore it, and the resulting P0 was entirely self-inflicted. **Do not fix this. It is off on purpose.** |
## BROKEN [#broken]
| Hostname | Class | Basis |
| -------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kmk-dct.utopiamodels.ai` | **BROKEN** | `530` — Cloudflare error 1033, tunnel origin unreachable. A `530` is never deliberate at the edge. There is a plausible explanation (a cost teardown named in one govcon spec) but **no decision record and no registry row**, so by this page's own rules it cannot be called retired. Unclassified is broken |
| `creative.utopiamodels.ai` | **BROKEN** | `404 DEPLOYMENT_NOT_FOUND`. The **singular** of the live plural `creatives.`. Either an unrecorded retirement or a name nobody ever claimed — nothing in any registry says which, and that ambiguity is the finding |
| `booking.tyleryouk.com` | **BROKEN** | `404 DEPLOYMENT_NOT_FOUND` — while an agent's durable memory still carries a whole entry describing a working partnership flow at this address |
| `drive.engineering.cloud` | **BROKEN** | `404` |
## NEVER-EXISTED [#never-existed]
These names are referenced in the fleet's own documents and have **never resolved**. A citation is not a
surface: each of these exists only as a string in a markdown file.
| Hostname | Class | Basis (measured 2026-08-16) |
| -------------------------------- | ----------------- | ---------------------------------------------------------------------- |
| `rawnothing.utopiamodels.ai` | **NEVER-EXISTED** | `NXDOMAIN` |
| `hack-creatives.utopiamodels.ai` | **NEVER-EXISTED** | `NXDOMAIN` |
| `research.dmvresearch.shop` | **NEVER-EXISTED** | `NXDOMAIN` |
| `bridge.dmvresearch.shop` | **NEVER-EXISTED** | `NXDOMAIN` |
| `health.tyleryouk.com` | **NEVER-EXISTED** | `NXDOMAIN` |
| `send.tyleryouk.com` | **NEVER-EXISTED** | `NOERROR`, but no A record — the name is delegated and answers nothing |
## How this table is kept honest [#how-this-table-is-kept-honest]
Three rules, learned the expensive way:
1. **A hostname with no registry row cannot be monitored, so it cannot be noticed.** Several of the LIVE
rows above have no entry in the fleet's component registry — including one serving on the company's
own domain. No row means no check, and no check means the day it breaks is the day someone happens to
look.
2. **A 200 does not prove a page is public — and neither does `redirects=0`.** An SSO gate redirects to
a login page that itself returns `200`, so a check has to report `redirects=` and the final URL, not
the status code alone. The converse is the trap this page shipped with: an app that renders its login
**in page** answers `200` with `redirects=0` and is completely gated. `credentials.utopiamodels.ai`,
two tables up, is exactly that — and a stranger's agent reading this rule correctly concluded from
`200 redirects=0` that our secret store was open to the world. A status line only ever supports the
claim "something answered". Read the body.
3. **Which edge served it matters.** `cf-ray` means Cloudflare; `x-vercel-id` means Vercel; both together
means Cloudflare in front of a Vercel origin. A doc that says a hostname "does not touch the tunnel"
is a claim about routing, and routing is checkable in one `curl -sI`.
# Live (/live)
The fleet read at request time — which agents can actually run, every hostname probed now, the board, and the health facts a public page cannot reach, each named with the box that answers it. Generated on every request; nothing on it is stored.
NOT INLINED HERE, deliberately. This page probes every hostname and every master's box on each
read; inlining it would run all of that a second time on every fetch of this file, for a copy of
something you can fetch directly. Fetch `/live.md` — the same reads, generated at the moment
you ask for it.
# Feedback (/feedback)
Walk the 14 architectural patterns one at a time and tell us which ones you would adopt — not which
page confused you.
The maintainers reverse-engineer this wiki from the outputs readers say they want, so the useful
thing to send is the outcome you are after rather than the page that got in the way. A pattern you
rejected, with the reason, is worth more than a vague endorsement.
## How it works
1. **Run the pattern walk.** Copy the prompt below into your own agent. It measures the reader's
machine, shows the whole map and asks whether they want it, then takes the fleet's 14
architectural patterns one at a time — each explained inside the question, with the real
identifiers, the published failure modes and an explicit split of what transfers to other
substrate and what does not. Per pattern the reader picks one of five: research it against their
infrastructure, go deeper, integrate it, skip it, or free text. One question at a time; a walk
that stops part-way writes a part-way file, which is a complete result. Then it writes a
`feedback.md`.
2. **Drop the file it wrote** on `https://docs.utopiamodels.ai/feedback`. It is checked against
the contract in the browser, and pressing Send files it as a `docs-feedback` issue on
`utopia-models/knowledge` carrying the submitted markdown.
**Step 2 needs a browser.** `POST /api/feedback` is real and wired, but it is gated by a
Cloudflare Turnstile check whose token is what enables Send — there is no unattended submit path,
by design. An agent runs the walk; a person drops the file. A file that fails the contract is not
discarded: the page hands back the exact problem list, and a rejected send is still filed — under
`docs-feedback-rejected`, carrying the submitted markdown and the reason it was refused.
## Step 1 · The pattern walk
Verbatim — the same bytes the HTML page serves, fenced so the `---` inside it stays part of the
prompt instead of becoming a heading rule.
```text
You are running the Utopia Fleet Wiki PATTERN WALK for me (docs.utopiamodels.ai).
This is not a form. It is a guided walk through the 14 architectural patterns this fleet actually
runs on. You explain one pattern to me, then ask what I want to do about that pattern. At the end
you write a file I drop back on the feedback page.
THE ONE RULE, and it governs everything below:
Explain a pattern inside the question itself, then ask what to do about that pattern. NEVER ask
a question whose terms I have not just been given.
There is no strict set of questions. The strict set is the PATTERN INVENTORY — the 14 below. Every
pattern gets the same five-way decision, so I am never parsing a new question format, only new
subject matter.
WHY THIS FEEDBACK IS WANTED — read before you start, it changes what you listen for.
The wiki's maintainers reverse-engineer their documentation from the OUTPUT people say they want.
The valuable thing is not "page X confused me" — it is "here is what I am trying to get my AI setup
to DO", and, pattern by pattern, "this one I would adopt, this one I would not, and here is why."
A rejection with a reason is worth more than a vague endorsement.
"Architectural patterns" is the fleet's own decided term for these 14, not a synonym for
mechanisms, components or infrastructure. The reasoning is published at
https://docs.utopiamodels.ai/docs/structure/architecture#the-vocabulary — link me there if I ask; do not
restate the definition inside this interview, because then there are two definitions to keep true.
HOW TO ASK
Ask every question in this walk with your harness's own interactive question tool (Claude Code `AskUserQuestion` · opencode `question` · Codex CLI `request_user_input` · Gemini CLI `ask_user` · Cursor's built-in Ask-questions tool; Claude Desktop has none — write the option list out instead). One question at a time. Never more than one at once.
Never fill in an answer on my behalf. If your harness has no such tool, say so in one line and then
ask in plain text with the options written out and the recommended one marked — never drop the
options, because the recommendation is the whole reason the dialogue is worth using.
=============================================================================================
STEP A — MEASURE MY SETUP BEFORE YOU ASK ME ANYTHING
=============================================================================================
Do NOT ask me "what are you running today". A harness name with no context attached tells you
nothing, and the answer falls out of a properly-scoped look at my machine for free. Measure it.
Spend a few commands establishing, on THIS machine:
- Which agent harnesses are installed and configured, and how many. Look for more than one —
check for `claude`, `cursor`, `codex`, `gemini`, `aider`, `copilot` on PATH, and for their
config directories (`~/.claude`, `~/.cursor`, `~/.codex`, `~/.config/*`). Someone running
three side by side is a completely different reader from someone running one.
- Whether those harnesses share a skill/command/rule corpus or each keep their own copy, and
whether the copies have diverged. Diff them if there is more than one.
- The version control and tracker situation: how many repos, how many branches per ticket,
whether ticket state and merged branches agree, whether the tracker is reachable from here.
- What can schedule something: systemd timers, cron, launchd, Windows Task Scheduler.
- Whether anything agent-related stays resident between runs.
Report what you found in a short table before Q0, and say plainly which figures you could not
measure and why. Every later "where you stand today" line depends on this pass, and a guess here
poisons the whole walk. If a measurement is unavailable, the honest line is "not measured", never
a plausible number.
This pass gives you `harness` for the file at the end. Derive it; do not ask for it.
=============================================================================================
STEP B — QUESTION 0: CONSENT TO THE MECHANISM ITSELF
=============================================================================================
Show me the whole map and ask whether I want it, BEFORE any pattern is discussed. This interview
is a thing I can redesign, not a thing done to me. Ask exactly this:
Here are the 14 core architectural patterns. We are going to go one pattern at a time and dive
deep into each — I explain it inside the question, then you decide what to do with it.
1. Dispatch runtime — two scripts and a transient unit; nothing resident, and starting the run
IS the assignment
2. Scheduling / the sweeper — what finds work and starts it, and how it explains every issue it
refused
3. The queue — one tracker, routing by lane, and a status nobody types
4. Work lifecycle — PLAN / Feature / issue, and five steps that are derived rather than reported
5. Work artifacts — a committed brief beside the work, whose checkboxes ARE the live state
6. Mode and context switching — which harness, which model, which permissions, and what the
agent is allowed to see
7. Gates and merge policy — every check advisory, one hard stop, fix-forward, finish means merged
8. Communication — @mention on the work item and nothing else; presence derived, never
self-reported
9. Failure announcement — a failed run files its own issue within seconds; silence is the only
unacceptable outcome
10. Liveness vs existence — the account exists and the agent still cannot run a single turn
11. Surfaces and classification — every hostname carries a class, because a 404 has three meanings
12. Secrets — one store, a registry of what each credential MEANS, values on stdin never argv
13. Documentation architecture — generate the volatile, write the durable, serve agents a .md twin
14. Decision discipline — kill records, a never-resurrect list, and a limit that is a measurement
Each pattern gets the same five-way decision: research it against your infrastructure, explain it
in more depth, integrate it and start planning, skip it, or other. "Skip it" records two
different answers — you would not adopt it, or it is fine and you own nothing it acts on. Each
pattern has narrow sub-points inside it, and skipping a pattern skips all of them.
This is the default feedback interview mechanism. Let me know if you want to proceed with it —
this can change too, for professionalism or optimization.
1. Proceed with the default interview mechanism
2. Explain the interview process more in depth
3. Make slight changes to the interview process
4. Make major changes to the interview process
5. Other — free text
Option 2 loops back to Q0 with more detail — do not advance to pattern 1 on a request for more
explanation. Options 3 and 4 revise this walk before any pattern is walked: take the revision,
restate the revised walk back to me, and get agreement before starting.
=============================================================================================
STEP C — THE PER-PATTERN QUESTION
=============================================================================================
Each of the 14 briefs below carries five of the six parts you need. Part 5 is yours to measure.
Present them in this order, every time, so I always know where I am:
1. The header line, exactly: `PATTERN n of 14 — NAME (broad)`
2. WHAT IT IS — from the brief. Keep the real identifiers and real numbers; they are the point.
3. THE ARTIFACT — the verbatim log line, config fragment or command in the brief. Show it as
given. Concrete beats descriptive.
4. WHAT IT COSTS — from the brief. Never present a pattern as free. Every one of these has a
published failure mode and the brief carries it.
5. WHERE I STAND TODAY — YOU write this line, from your Step A measurements. Measured if
possible, and say so plainly if it is not. "I could not measure this" is a legitimate answer;
an invented number is not.
6. WHAT TRANSFERS AND WHAT DOES NOT — from the brief, split explicitly, on BOTH axes. Half of
these patterns depend on a dedicated server, multiple agent accounts, systemd, or a particular
plan tier: that is the SUBSTRATE axis. The second axis is OWNERSHIP — whether you own the
thing the pattern acts on at all. A pattern can transfer perfectly to your substrate and still
find nothing of yours to act on, and those are different findings. Step F's ledger keeps them
apart.
Then the decision, with the pattern name interpolated into every option label. A bare "explain in
more depth" reads as generic and I lose track of what I am deciding about:
What do you want to do with {pattern}?
1. Research {pattern} against my current infrastructure
2. Explain the Utopia pattern {pattern} in more depth
3. Integrate {pattern} — start planning it
4. Not integrating {pattern} — skip it
5. Other — free text
Options 1-4 are what the dialogue renders; option 5 is the free-text escape. Several harnesses add
that escape themselves rather than making you render it — if yours does, use the one it adds.
- Option 1 PAUSES the walk. See Step D. It is not a decision; the pattern is re-asked afterwards.
- Option 2 loops back to the SAME pattern with more depth. Do not advance.
- Options 3 and 4 are terminal for that pattern and advance the walk.
- OPTION 4 HAS TWO MEANINGS AND THE LEDGER KEEPS THEM APART. "I would not adopt this" is a
rejection of the pattern. "This is a good pattern and I own nothing it acts on" is NOT
APPLICABLE. Take the meaning from the reason I give; if the reason does not settle it, ask me
which — that one follow-up earns its place, because the two read identically in a ledger and
only one of them is feedback about the pattern. Do not add a fifth rendered option to fix this:
every one of these question tools caps at three or four rendered options, which is exactly why
option 5 is the free-text escape rather than a fifth choice.
BROAD PATTERN FIRST, THEN NARROW. Each brief lists the pattern's narrow points. Walk into them only
after I have adopted the broad pattern. A pattern rejected at the broad level SKIPS ALL of its
narrow points, and the log must say so rather than leaving them unanswered.
NAME DEPENDENCIES AS THEY ARISE. When a rejection makes a later pattern more expensive or
incoherent, say it once, in one sentence, and move on. Rejecting the dispatch runtime leaves
scheduling with nothing defined to start — that is worth one line before the next question, not a
paragraph, and not repeated.
I MAY STOP EARLY. If I end the session part-way, that is a complete result, not an abandoned one.
Write the file with what was walked and record the rest as "not walked". Do not talk me into
finishing all 14.
=============================================================================================
STEP D — THE RESEARCH PROTOCOL (MANDATORY)
=============================================================================================
When I pick "Research {pattern} against my current infrastructure":
1. DISPATCH SUBAGENTS. One per measurement area. Do not perform the research inline in the main
thread when subagent dispatch is available to you.
2. TELL ME YOU ARE WAITING. State plainly that subagent research is running and that you are
waiting on it.
3. DO NOT SKIP AHEAD. Do not ask the next question, do not move to the next pattern, and do not
start speculating about the result while the subagents are out.
4. RE-ASK THE SAME PATTERN once the findings return, with the numbers folded INTO the question
body — not appended after it. Then I decide.
Every subagent brief must demand a measurement WITH the command or file path that produced it. A
subagent that returns an impression instead of a measurement has not answered; send it back.
FALLBACK, AND BE HONEST ABOUT INVOKING IT. If subagent dispatch fails for reasons outside your
control — provider overload, a harness without subagents, a quota refusal — do not retry the
fan-out. Fall back to sequential measurement yourself, and SAY IN THE FILE that you did and why.
This clause is not hypothetical: in the run that designed this walk, subagent dispatch failed 13
times out of 13 with `API Error: 529 Overloaded`, across two separate prompts, and both research
passes were completed inline as a result.
=============================================================================================
STEP E — THE 14 PATTERN BRIEFS
=============================================================================================
Ordered by dependency: what executes a run, before what routes to it, before what shapes it,
before what surrounds it. Every number below was measured by the fleet on the date given. Quote
them as dated evidence, not as a live reading.
---------------------------------------------------------------------------------------------
PATTERN 1 of 14 — DISPATCH RUNTIME (broad)
Narrow points: 1.1 no resident process · 1.2 start-is-assignment · 1.3 pre-flight gates ·
1.4 detached execution · 1.5 close-out markers · 1.6 attempt caps and concurrency
---------------------------------------------------------------------------------------------
WHAT IT IS. Two shell scripts and systemd. That is the entire agent runtime — no scheduler daemon,
no queue service, no watchdog, and no agent process resident between runs.
`_infra/scripts/dispatch.sh [repo]` hands a master its issue BY STARTING THE RUN;
there is no separate claim step. `_infra/scripts/task-runner.sh [repo]` is the run itself,
on the master's own box. One dispatch reaches the box with one `ssh nova@100.71.25.43` plus
`sudo -u `, runs three pre-flight guards (quota-dark master, duplicate run on that box,
and a lock-held fleet-wide claim), launches a detached transient unit, and returns. The run posts `▶ started` on
the issue before anything that can fail, then `claude -p --max-turns 200`, and its exit trap posts
`✔`, `✘`, `⚠` or `⛔` with the pull-request link.
THE ARTIFACT.
systemd-run --user --unit=task- --collect
That `--collect` and `--user` pair is why a run survives the SSH connection dropping and the
laptop going to sleep. The run's own `▶` and `✔` comments on the issue are the fleet's entire
definition of liveness — there is nothing else to ask.
WHAT IT COSTS.
- The pre-flight gates fail OPEN on purpose. An unreadable state file, a missing file, or a reset
time already in the past all mean "not dark", because a false block stops the fleet and that is
worse than one wasted run. You are choosing wasted runs over stalls, deliberately.
- A guard in only ONE consumer cannot see the other path. The "one issue, one run" check has to
live in the entry point that the scheduler and a hand-typed dispatch both share.
- "Nothing resident" is a claim about the RUNTIME, not about the box, and the two get confused —
including by readers running the walk from inside this fleet. No dispatched run leaves a process
behind; a master's own standing interactive session is a different thing and can sit in tmux for
weeks (measured on one master, 2026-08-23: an agent process resident 13.3 days). If you adopt
this pattern, adopt the precise claim, and write down the sanctioned exceptions next to it —
otherwise the first person to run `ps` concludes the published property is false.
- The last-moment disposal race is real and was measured: pre-flight read the issue at 14:36:01
and it was open and correct; it was labelled a throwaway at 14:36:03 and closed at 14:36:05;
systemd started the unit at 14:36:12; `▶` posted at 14:36:15. A master then spent a full
session on board residue. Every gate that asks "is this still work" runs too early unless it
runs from INSIDE the run, immediately before `▶`.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: start-is-assignment, nothing resident, and close-out markers posted by an exit trap.
All three are policy and cost nothing but discipline.
Does not: the mechanism needs systemd `--user`, several unix accounts on one box, and `sudo -u`
between them. On a single Windows workstation with one user account there is no `sudo -u` and no
transient unit — the equivalent is a detached process plus Task Scheduler, and per-agent identity
has to come from somewhere other than the OS.
---------------------------------------------------------------------------------------------
PATTERN 2 of 14 — SCHEDULING / THE SWEEPER (broad)
Narrow points: 2.1 timer-riding vs a dedicated service · 2.2 the eleven predicates ·
2.3 `--explain` as diagnostic · 2.4 single-homed scheduling risk
---------------------------------------------------------------------------------------------
WHAT IT IS. The thing that carries a chain of work forward is NOT a service and NOT a cron job. It
rides as the second `ExecStart` line of a systemd drop-in on one master's `reconcile-board.service`,
fired by `reconcile-board.timer` every five minutes. It reads the board, applies a fixed set of
predicates to every open issue, and dispatches whatever survives them.
THE ARTIFACT.
/home/nova/.config/systemd/user/reconcile-board.service.d/10-sweep-dispatch.conf
ExecStart= (clear)
-/usr/bin/bash %h/.claude/ops/reconcile-board-status.sh --apply
-/usr/bin/bash %h/dev/utopia-models/knowledge/_infra/scripts/sweep-dispatch.sh --apply
-/usr/bin/bash %h/.claude/ops/mute-agent-notifications.sh --apply
Its log line is the best one-line health summary the fleet produces. Emitted 2026-09-04T08:48:34Z:
sweep: 39 open · 0 dispatchable · in-flight 3 (win=2 nova=0 cloud=1 dust=0 )
A refused master appends a trailing `· rate-limited: (exhausted )` segment; it is absent above
because every master was clear on that pass.
And the diagnostic that makes it debuggable, which prints a reason PER ISSUE:
# on novaserver, from the knowledge clone
bash _infra/scripts/sweep-dispatch.sh --explain
Each rejected issue prints its own reason: `lane-not-in-masters`, `has-hold-label`,
`is-a-grouper:has-open-subissues`, `last-marker-is-terminal`, `parent-closed`, `is-parked`.
WHAT IT COSTS.
- Timer-riding makes the scheduler INVISIBLE to any probe that looks for it by name. Every check
that goes hunting for a `sweeper.service` falsely reports it missing.
- Scheduling is single-homed. Measured 2026-08-16: 35 timers on one master's user manager against
2 each on two others and 1 on the fourth. One box's user manager is the fleet's cadence.
- Cron is a second, parallel scheduling layer. Masters carry crontab entries with no systemd timer
at all, so an audit reading only `systemctl --user list-timers` under-reports every master.
- A dead timer never fails, so `systemctl --failed` reports clean on a broken box. The box has
held an ENABLED timer whose service unit was `not-found` while every failure query returned
empty.
- `39 open · 0 dispatchable` looks like a capacity failure and is a ROUTING failure. The sweeper
had been printing the cause every five minutes while investigations went looking for a bug.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: `--explain` — a scheduler that prints, per item, the named predicate that rejected it.
That is the highest-value line in this whole pattern and it works on any scheduler you own.
Does not: timer-riding is a systemd trick and a liability. On Task Scheduler or launchd, a
dedicated entry is clearer and the invisibility cost is not worth copying.
---------------------------------------------------------------------------------------------
PATTERN 3 of 14 — THE QUEUE (broad)
Narrow points: 3.1 one tracker only · 3.2 lane as routing, unrouted = invisible ·
3.3 portfolio as grouping · 3.4 derived vs typed status
---------------------------------------------------------------------------------------------
WHAT IT IS. There is exactly ONE tracker: a GitHub organization project board. Every open issue in
the knowledge repository is on it — measured 2026-08-16, zero off-board work existed. Work that
becomes a board issue lives on the board and nowhere else; it specifically does not also live in an
agent harness's private to-do list, because two trackers cannot both be true. Each row carries the
issue, its LANE (which master serves it), a PRIORITY, a PORTFOLIO, and a STATUS that is derived
rather than typed.
THE ARTIFACT. The negative property is the important one, and it has a name in the sweeper's own
output:
lane-not-in-masters
An issue with no lane, or on a lane no master serves, is structurally undispatchable —
permanently, silently, and regardless of how idle the fleet is. Measured 2026-08-16: of the open
issues at that moment, thirteen were undispatchable — eleven with no lane at all, plus two on a
lane no sweeper reaches.
WHAT IT COSTS.
- Nothing rejects a row with no lane and no status at creation time, and a row in that state is
invisible to routing. The queue's worst failure mode is silent by construction.
- Closed rows from other repositories never converge: the reconciler archives rows only for the
repository it runs in, so closed items from sibling repos accumulate. On 2026-08-16 they were
the MAJORITY of all rows on the board.
- And the honest one, which the fleet publishes about itself: doctrine says "the board is the
ONLY tracker" and also "the plan.md checkboxes ARE the work state". Counting honestly there are
FOUR parallel trackers for one body of work — the board, a plan registry, the plan documents,
and the per-spec checkboxes. Plan documents exist with no registry entry, which makes them
invisible to the command center, which reads only entries.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: one canonical tracker, and derived rather than typed status. Both are decisions, not
infrastructure.
Does not: "lane" needs a board with a custom routing field and an API a scheduler can read. If
your tracker is a corporate one that is blocked at the network level from the network you do
infrastructure work on, then "read scope live from the ticket" cannot hold there at all, and this
pattern has to be rejected or redesigned around an offline cache — say which.
---------------------------------------------------------------------------------------------
PATTERN 4 of 14 — WORK LIFECYCLE (broad)
Narrow points: 4.1 the three words and the retired fourth · 4.2 five derived steps ·
4.3 step-4 mode · 4.4 "stuck" is a comment, not a state
---------------------------------------------------------------------------------------------
WHAT IT IS. Three layers, three words, and a deliberately retired fourth. A PLAN (`PLAN-178`) is one
thinking session: a document, its research, and the forks it closed — it lives ABOVE the board. A
Feature (`#5878`) is a body of shippable work with ordered children. An issue (`#5882`) is one
dispatchable unit run to a merged pull request. A PLAN is not a Feature: it references Features and
can spawn several, and some of what a PLAN decides never becomes work at all. The fourth word,
"task", was retired because it named two of these at once.
THE ARTIFACT.
1 BRIEF · 2 LOOP ⟳ ‖ 3 FILE · 4 DISPATCHED · 5 DONE
Every step is DERIVED, never reported. Steps 3-5 are read off the board on every render; steps 1-2
come from events on the plan's own entry. Nothing anybody WRITES advances a step — a merged pull
request does, by closing an issue. There is no status field for a human to set, which means there
is no status field for a human to forget. Step 4 carries a mode and neither value is "blocked":
`DISPATCHED` (unattended) or `EXECUTING (interactive)` (a human in the loop, but not blocking it).
WHAT IT COSTS.
- Being genuinely stuck is not a state. It is a comment on the issue saying exactly what blocks
you, followed by a clean exit — which means a stuck item looks identical to a running one unless
somebody reads comments. Stopping silently is the one unacceptable outcome, and nothing
structurally prevents it.
- Derived status means you cannot annotate a state the board cannot compute. You trade
expressiveness for the impossibility of a stale status field.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: the three words, the retired fourth, and derived-not-typed status. This is the pattern
most often adopted on foreign substrate, because it is entirely vocabulary and discipline.
Does not: "a merged pull request moves the board" needs the tracker and the VCS to be the same
system, or a webhook between them. If your tickets live in a tracker your git host cannot close,
the derivation has to be built, and until it is, both signals drift. That is measurable, and the
contrast is the whole reason this pattern is worth deciding on: ON THE READER'S MACHINE in the run
that designed this walk — a Windows workstation with a corporate tracker, NOT the fleet — ZERO of
14 ticket branches had merged to their default branch, while ticket state had moved anyway, by
hand, through five typed states. On the fleet the same measurement runs the other way. Measure
YOUR side before deciding; neither number is yours until you have.
---------------------------------------------------------------------------------------------
PATTERN 5 of 14 — WORK ARTIFACTS (broad)
Narrow points: 5.1 thick tasks · 5.2 checkboxes as live state · 5.3 the issue must name the spec
path · 5.4 decision records and `Superseded-by`
---------------------------------------------------------------------------------------------
WHAT IT IS. A dispatched issue is not a sentence. The brief lives BESIDE the work, in the repo.
THE ARTIFACT.
/specs/-/spec.md objective · acceptance · scope · grounding
/specs/-/plan.md phases → checkbox items
The `plan.md` checkboxes are the LIVE WORK STATE. Tick `[ ] → [~] → [x]` and commit as you go. This
is not a report written afterwards: a context reset, a peer taking over, and the founder looking in
all read that same file. It has been proven — one master's capped run was finished by a different
master from that file alone.
WHAT IT COSTS.
- The issue body must contain the LITERAL spec path. A runner reads a spec only if the issue names
one, so an unnamed spec does not exist to the master executing the work. It is the
highest-leverage line in the pipeline and the easiest to omit.
- A spec can cite dead machinery in perfectly good faith, so every reference has to be re-grounded
in the session that acts on it. If the prerequisites are dead, the correct move is to stop
cleanly and say why on the issue — never churn a stale brief.
- Superseding a decision record edits the old record's `Superseded-by` header IN THE SAME COMMIT.
A silently invalidated decision record is this fleet's most repeated failure.
- Honest cost: `plan.md` is one of the four parallel trackers named in pattern 3. This pattern and
"one tracker only" are in genuine tension and the fleet has not resolved it.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: all of it. It needs a repository and nothing else — no server, no accounts, no systemd.
Does not: nothing, structurally. The cost is purely the discipline of writing the brief before
dispatching, which is where it usually fails.
---------------------------------------------------------------------------------------------
PATTERN 6 of 14 — MODE AND CONTEXT SWITCHING (broad)
Narrow points: 6.1 work-state mode · 6.2 harness/model/permission mode · 6.3 context scope
---------------------------------------------------------------------------------------------
WHAT IT IS. Three axes that are usually conflated. WORK-STATE mode is what the agent is doing:
planning with a human in the loop, or executing unattended — the fleet spells this as step 4's
`DISPATCHED` vs `EXECUTING (interactive)`. HARNESS/MODEL/PERMISSION mode is which binary, which
provider and what the agent is allowed to do without asking; the fleet passes the BACKEND to every run
at dispatch time rather than reading it from a local settings file, which is why a master's provider can
be changed for the whole fleet without touching a single box. There is one provider today — every master
runs the same harness on a Claude subscription — and that is a decision, not a limitation of the shape. CONTEXT SCOPE is what the agent is allowed to see, and the fleet fixes it as
an ORDER rather than a pile.
THE ARTIFACT. The grounding order, run at the start of every dispatched run:
repo root CLAUDE.md → the portfolio's CLAUDE.md → the issue + its spec → the real files
Six unix accounts run agents — five on the server, one on the laptop — each with its own credentials,
its own `~/.claude` brain directory (each one a version-controlled repo of its own) and its own
GitHub identity. They are peers; there is no supervisor process.
WHAT IT COSTS.
- There is exactly ONE backend and nothing to fall back to. The launcher passes a literal provider
name to every run; the per-agent selection this fleet used to have was deleted along with the
alternate provider. When an agent's entitlement goes, its work waits. The dark-agent guard reads
a `quota-status.json` a newly-broken agent may never have written, and it FAILS OPEN — so a
dispatch into a dead agent is possible by design, on the grounds that a false block is worse.
- This is the thinnest of the 14 in the fleet's own documentation. Work-state mode is written down
precisely; permission mode is largely convention. If you adopt it you are extending it, not
copying it.
- The classic failure on a multi-harness machine: two or three harnesses each keeping their own
COPY of the same skill or rule corpus, every pair already diverged, so the same command means
different things depending on which one you are in — and nothing detects that.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: the grounding ORDER, and the discipline of naming which of the three axes you are
switching. Both are free.
Does not: the single-backend simplicity, which is a consequence of one flat subscription per agent
rather than a design win. Pay per token, or run two providers, and you need the selection logic
this fleet deleted — and you own its failure modes with it. And if you launch by clicking an icon
there is no launcher to read anything at start time: the mode is whatever the config file says,
which is the divergence problem above rather than a solution to it.
---------------------------------------------------------------------------------------------
PATTERN 7 of 14 — GATES AND MERGE POLICY (broad)
Narrow points: 7.1 advisory vs blocking · 7.2 the one hard stop · 7.3 fix-forward ·
7.4 finish means merged
---------------------------------------------------------------------------------------------
WHAT IT IS. NOTHING gates `main`. Branch protection and rulesets both return 403 on this GitHub plan
tier, in every repository. That is not a loophole being exploited — it is the actual configuration,
and the process is built to be honest about it. Therefore a check that is red, missing, or never
started is INFORMATION, never a stop. Fix-forward is the rule: identify the real failure, fix a
broken build or a genuine security finding, and use the documented fallback when infrastructure is
merely down. Finish means a pull request whose body says `Closes #N`, MERGED. Arming auto-merge is
not finishing — fifteen pull requests once stacked up unnoticed behind exactly that misunderstanding.
THE ARTIFACT.
gh pr merge --squash --admin
And the one binding check, run locally when the workflow itself cannot start:
VERSION=8.24.3
curl -sL "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \
| tar xz -C /tmp gitleaks \
&& /tmp/gitleaks git . --no-banner --redact --config .gitleaks.toml \
--log-opts "--no-merges .."
WHAT IT COSTS.
- "Nothing gates main" is a CONSEQUENCE OF A PLAN TIER, not a virtue. Read it as an honest
description of a constraint the fleet decided to build on rather than a recommendation.
- Two gate designs are forbidden outright because both were tried: a gate that blocks on content
keywords, and a gate built on a generated map. The second one false-blocked documentation
changes and AUTO-REVERTED ITS OWN CLEANUP; eighteen dead gate workflows went in one commit.
- The one hard stop — never a secret in a tracked file — is the only thing here that is not
fix-forward, not advisory and not overridable.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: fix-forward, "finish means merged", and refusing to treat a lint on a title or a
filename as a blocker.
Does not: reject this pattern outright if you have real branch protection, a compliance
requirement, a provenance constraint, or code review as a policy. "Every check is advisory" is
load-bearing on a fleet where a merge is git-reversible and one person owns everything. It is
actively wrong where a merge ships to someone else's production.
---------------------------------------------------------------------------------------------
PATTERN 8 of 14 — COMMUNICATION (broad)
Narrow points: 8.1 @mention as the only channel · 8.2 unread injection at run start ·
8.3 presence derived, never self-reported
---------------------------------------------------------------------------------------------
WHAT IT IS. Masters talk to each other by @mentioning a GitHub account in a comment ON THE RELEVANT
ISSUE. That is the whole mechanism. A master's next dispatched run receives unread mentions injected
at start, which gives at-least-once delivery with a durable read state, and it happens on the
surface the founder is already looking at. There is no chat system, no message bus and no inbox
service. There WAS a NATS bus; it was deleted, and it is on the never-resurrect list.
THE ARTIFACT. From the fleet's own decisions page:
Killed: the NATS message bus
`nats.service` and its reload units are on the never resurrect list. Three zombie units
survive on the box, and one of them is active.
Presence is derived from observable work — a last commit, a last comment — and never self-reported.
A heartbeat an agent writes about itself is a claim, not evidence. That is the rule for whether WORK
is alive. It is not the whole picture on the box, and the fleet does not publish only the flattering
half: a reader who runs the walk from inside this fleet finds a `*/5 * * * *` cron entry on a
master's own crontab POSTing that master's status to the command centre with a five-minute TTL as
the online/offline signal (`fleet-self-report.sh`, sanctioned 2026-07-22). It answers a different
question — which box is online for a human's dashboard right now — and it is never what tells you a
run finished. Adopt the rule, and know that the exception exists and is named.
WHAT IT COSTS.
- Delivery lands on the NEXT DISPATCHED RUN. A master that is never dispatched never reads its
mail, so an urgent message to an idle or quota-dark agent sits unread indefinitely. There is no
synchronous channel at all, by design.
- Everything is public. This composes with the fleet's standing "publish everything, unsanitized"
decision; on a machine with a provenance or confidentiality constraint it composes with nothing.
- Deleting the bus left THREE zombie units on the box, and the count in this fleet's own
published decisions page said two until a reader ran the command (knowledge#6665). Worse than
the count: one of the three is a `.path` unit that is `enabled` and `active (waiting)` — still
watching for changes to the config of a service that will never start again. A deletion in a
repo is half a deletion, and the surviving half keeps a timer running.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: comms on the work item rather than beside it, and presence derived from observable
work. Both are free and both survive any substrate.
Does not: unread-injection-at-run-start needs a launcher that reads your mentions API and a
durable read state per agent. If your tracker is unreachable from the network you work on, this
pattern has no delivery path at all and you should say so rather than adopting it aspirationally.
---------------------------------------------------------------------------------------------
PATTERN 9 of 14 — FAILURE ANNOUNCEMENT (broad)
Narrow points: 9.1 `OnFailure` → board issue · 9.2 silence as the only unacceptable outcome ·
9.3 the broken-notifier hole
---------------------------------------------------------------------------------------------
WHAT IT IS. When any user unit fails, systemd's `OnFailure` path upserts ONE board issue about it.
The system is not designed to avoid failure; it is designed so a failure announces itself within
seconds, on the surface a human already reads. Silence is the only unacceptable state.
THE ARTIFACT. Measured 2026-08-16, over the preceding 24 hours:
~157 dispatch runs · 2 failed · both failures filed their own board issue automatically
WHAT IT COSTS.
- The broken-notifier hole: the notifier itself failing is the one failure that cannot announce
itself. Nothing watches the watcher, on purpose, because a watcher-watcher is the resident
process this whole architecture deleted.
- A dead timer never fails, so `systemctl --failed` reports clean on a box holding an enabled
timer whose service unit is `not-found`. Absence of failure is not presence of function; you
have to enumerate the timers themselves and read the last and next columns.
- Deleting a scheduled unit from an agent's brain repo does NOT stop it running —
`~/.config/systemd/user/` holds COPIES, not symlinks. One such half-deletion left a timer
`active waiting` against a script that no longer existed; the next firing, nearly three hours
later, exited `203/EXEC` and filed a P0 about itself.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: the principle, entirely — a failure must produce a durable record on the surface a
human already reads, and the record must be an upsert so a flapping job files one item, not five
hundred.
Does not: `OnFailure` is a systemd feature. Task Scheduler has an on-failure action but no
upsert semantics, and launchd has neither — you write that half yourself.
---------------------------------------------------------------------------------------------
PATTERN 10 of 14 — LIVENESS VS EXISTENCE (broad)
Narrow points: 10.1 the distinction · 10.2 entitlement capture as evidence ·
10.3 naming the command that settles it
---------------------------------------------------------------------------------------------
WHAT IT IS. The most important distinction the fleet publishes. An agent can hold a valid unix
account, a present credential file, a clean systemd state and working `gh` auth, and still be unable
to run a single turn — because entitlement lives with the account AT THE PROVIDER, not on the box.
The registry's own check for an agent is `getent passwd `, and that check PASSES on an agent
that cannot execute anything. When it happens the agent looks BUSIER than a healthy one: transient
units keep starting, keep dying in seconds, and keep announcing themselves.
THE ARTIFACT. The command that actually settles it, and the state file that records the refusal:
# on novaserver — the real probe. Anything other than a normal completion is the finding.
sudo -u nova -H claude -p 'reply with the single word: alive'
# on novaserver — what the agent itself last recorded
sudo -u nova -H cat /home/nova/.claude/state/quota-status.json
Measured 2026-08-16: three of the four masters could actually run, and the fourth reported
`{"exhausted": true, "resetsAt": null}` in its own state file. The `null` is the important half —
the provider named no recovery time, so no timer brings that agent back on its own.
WHAT IT COSTS.
- Naming the command that settles a claim is real work per claim, and it has to be RUN, not
documented. The fleet audited its own component registry and found that the `verify:` command on
every entry is "read by humans and executed by nothing", and that it records no box — so a
quarter of the commands fail from the wrong machine and pass from the right one.
- Exercising an artifact costs a real invocation. A probe that runs a turn spends quota to learn
whether it has quota.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: all of it, and this is the single most portable pattern in the inventory. It is a
discipline — for every health claim, name the command that settles it, run it, and record the box
it ran on.
Does not: nothing. The specific commands are fleet-shaped; the distinction is universal.
---------------------------------------------------------------------------------------------
PATTERN 11 of 14 — SURFACES AND CLASSIFICATION (broad)
Narrow points: 11.1 the four classes · 11.2 a checker that re-measures the doc
---------------------------------------------------------------------------------------------
WHAT IT IS. A status code is not a state. Two hostnames returning an identical 404 can mean
completely different things, so every hostname the fleet owns or references carries a CLASS, and the
class is the durable fact — the status code is evidence for the class, not a substitute for it.
THE ARTIFACT. The four classes:
LIVE intended to serve, and serving
RETIRED-BY-DECISION not serving, and that is CORRECT — a human decided, and the decision is
sourced. Do not "fix" one of these.
BROKEN not serving, and nothing says that was intended. The only class that is a bug.
NEVER-EXISTED appears in the fleet's own documents and has never resolved in DNS. Not a
surface — a citation of one.
Collapsing that distinction into a single "404" cell cost a P0: a master was dispatched to "restore"
a surface that had been deliberately retired ten days earlier, and it did exactly as it was told.
And the check, which is the second half of the pattern:
curl -s -o /dev/null -w '%{http_code} redirects=%{num_redirects} final=%{url_effective}\n' -L https:///
WHAT IT COSTS.
- A dated table decays, so it needs a CHECKER rather than a promise. The fleet's reads the rows
out of the published page and re-measures every host, which means adding a surface adds a check
with no second edit. It has caught two rows so far — one recorded 200 when its root is a
redirect, one that went stale because the world moved.
- A 200 does not prove a page is public, and neither does `redirects=0`. An app that renders its
login IN PAGE answers 200 with redirects=0 and is completely gated. A stranger's agent read the
fleet's own rule correctly and concluded from `200 redirects=0` that the secret store was open
to the world.
- A hostname with no registry row cannot be monitored, so it cannot be noticed. Several LIVE rows
have no registry entry — including one serving on the company's own domain.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: entirely, ON SUBSTRATE. Four classes and a checker that re-measures the document need
nothing but a list and a shell.
Does not: nothing about your substrate — and this is the pattern most often rejected on the OTHER
axis. It classifies hostnames, so it needs you to OWN some. A reader holding no DNS, no registrar
credential and no row in a surface registry can run the checker against somebody else's host —
`curl -s -o /dev/null -w '%{http_code}' https://docs.utopiamodels.ai/llms.txt` returns 200 from
anywhere — and still learn nothing about an inventory of their own. Running one other party's
checker is not owning a classified inventory. That is NOT APPLICABLE, not a rejection, and the
ledger in Step F distinguishes them. Measured by the walk's own first field run: a dispatched
agent with no surfaces of its own reached exactly this conclusion (knowledge#6665, 2026-08-23).
---------------------------------------------------------------------------------------------
PATTERN 12 of 14 — SECRETS (broad)
Narrow points: 12.1 one store · 12.2 the registry of credential *meaning* · 12.3 stdin-not-argv
---------------------------------------------------------------------------------------------
WHAT IT IS. Infisical, self-hosted in Docker on the server, is the ONLY secret store. No local vault,
no `.env` checked in anywhere, no second store. A separate registry carries each credential's
MEANING — which account it belongs to, what scopes it has, what it can do and what it explicitly
cannot — so "I don't have access" is a claim that must be checked against a list before it can be
made.
THE ARTIFACT.
# read one value (never echo it)
V=$(bash ~/.claude/scripts/infisical-get.sh fleet-infra SOME_KEY prod); echo "len=${#V}"
# list every key in a project, so "it does not exist" is a real claim
bash ~/.claude/scripts/infisical-get.sh --keys fleet-infra prod /
# write a value; the value goes on STDIN, never in argv
printf '%s' "$NEW_VALUE" | bash ~/.claude/scripts/infisical-set.sh fleet-infra SOME_KEY prod
Passing a secret as an argument is rejected by a guard, because argv lands in shell history and in
the process table.
WHAT IT COSTS.
- One store is one single point of failure, self-hosted on the same box as everything else.
- The recurring failure is NOT a missing credential — it is a grep for the wrong key name. That is
why `--keys` exists and why listing the whole project is a standing rule before declaring
anything absent.
- Never print a secret, or the PRESENCE of one, to a display sink. Capture into a variable and
check its length. That constraint reaches further than people expect: it rules out most obvious
debug output.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: all three narrow points. Substitute any single store — 1Password, Vault, a cloud
secrets manager, the OS keychain. The registry-of-meaning is a plain file and is the half most
people skip and most need.
Does not: self-hosting needs a box that stays up. On a single workstation the OS keychain is the
better single store and the pattern is otherwise identical.
---------------------------------------------------------------------------------------------
PATTERN 13 of 14 — DOCUMENTATION ARCHITECTURE (broad)
Narrow points: 13.1 generate the volatile, write the durable · 13.2 request-time, no store ·
13.3 `.md` twins for agents · 13.4 organise by question, not component
---------------------------------------------------------------------------------------------
WHAT IT IS. Split every documented fact into VOLATILE (changes without anyone deciding it — a
status code, a count, a disk figure) and DURABLE (a method, a decision, a policy). Write the
durable. Generate the volatile, at request time, with no store: this wiki's live page states
`store: none — no snapshot file, no cache, no revalidate window`, and two fetches a second apart
show its `generated:` stamp advancing. Serve agents a plain-text twin of every page, and organise
pages by the QUESTION a reader arrives with rather than by component.
THE ARTIFACT.
curl -s https://docs.utopiamodels.ai/llms.txt | head -3
`/llms.txt` indexes every page; `/llms-full.txt` is the whole corpus in one fetch. Every docs page
has a `.md` twin at the same path, so an agent never has to parse HTML.
WHAT IT COSTS.
- The diagnosis that produced this pattern is not flattering: this site carried ~10% of the
fleet's knowledge base and derived from NONE of it — six hand-typed pages against a ~1.05 MB
corpus — and three days after its last edit, 17 of its 162 checkable claims were already false.
The page with ZERO false claims is made of method and policy. The page with one GENERATES ITS
OWN CHECK. That is this pattern arriving as a diagnosis rather than a theory.
- Request-time generation means the page is only as available as its upstreams. The honest design
reports failure PER SECTION — a line beginning `ERROR:` inside the page is that page reporting
an upstream it could not reach, which is data, not a broken fetch.
- A promise scoped to one path is a promise a reader will generalise. This site's zero-byte-404
promise is scoped to `/docs/`, and the first top-level page to break the pattern returned 10,514
bytes of HTML error page straight into an agent's context.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: entirely, and it is the pattern most likely to pay for itself immediately. The volatile
/ durable split needs no infrastructure — it is a question you ask of each sentence you write.
Does not: request-time generation needs a server that can read your sources at request time. A
static site generator gets you the `.md` twins and the durable/volatile split but not `store:
none` — there, the volatile facts belong behind a command the reader runs, printed verbatim, and
NOT as a number the page did not measure.
---------------------------------------------------------------------------------------------
PATTERN 14 of 14 — DECISION DISCIPLINE (broad)
Narrow points: 14.1 kill records · 14.2 never-resurrect · 14.3 `Superseded-by` in the same commit ·
14.4 a limit is a measurement
---------------------------------------------------------------------------------------------
WHAT IT IS. The fleet's most expensive recurring failure is re-deciding something that was already
decided and paid for. So every killed thing gets a record saying what it was, why it died, and what
replaced it; a NEVER-RESURRECT list names what may not come back; a decision is reopened only by a
superseding record that names what changed, with the original's `Superseded-by` header updated IN
THE SAME COMMIT; and no ceiling is set without the measurement written next to the number.
THE ARTIFACT. The never-resurrect list, verbatim:
- ralph and every watchdog, heartbeat and supervisor built to babysit it.
- The NATS bus and its three remaining zombie 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 of knowing whether
the fleet is alive.
And 14.4, measured: the dispatch scheduler ran for days at roughly 8% OF PROVEN CAPACITY — three of
four masters idle on a 12-core box under a load average below 1 — because caps of 1, 4 and 2 had
been set against a failure mode two other mechanisms already covered. The defence was real; it was
aimed at the wrong axis. A cap chosen out of caution silently BECOMES the system's capacity, and
unlike a bug it never announces itself: it reports success while doing a fraction of the work.
WHAT IT COSTS.
- A silently invalidated decision record is WORSE THAN NONE, so the `Superseded-by` edit is not
optional bookkeeping — it is the whole mechanism.
- A name outliving its system keeps costing you, and it took two deletions here to stop. After the
resident loop was deleted, the registry kept a per-agent field named after it, described as the
live backend selector — and the FIELD really was read, so nobody could call it dead. The framing
outlived the system, then the field outlived the framing. Every reader in between had to work
that out for themselves.
- Writing the measurement next to every ceiling is slower than picking a number.
WHAT TRANSFERS AND WHAT DOES NOT.
Transfers: entirely. All four narrow points are text files and a habit.
Does not: nothing. This is the cheapest pattern in the inventory to adopt and the one most often
skipped, because its whole payoff is an argument you do not have six months from now.
=============================================================================================
STEP F — CLOSING
=============================================================================================
When the walk ends — all 14, or wherever I stopped — do these four things IN ORDER.
FIRST, show me the decision ledger. One row per pattern, in order, with one of FOUR decisions:
INTEGRATE I am adopting it.
NOT INTEGRATING I would not adopt this pattern. This is feedback ABOUT THE PATTERN.
NOT APPLICABLE The pattern is fine and I own nothing it acts on — no surfaces, no second
harness, no tracker of my own. This is feedback ABOUT THE READER, and it is a
complete result, not a soft no. Say in the reason WHAT I do not own.
NOT WALKED We never got to it.
NOT INTEGRATING and NOT APPLICABLE are DIFFERENT FINDINGS and a maintainer reading a ledger cannot
tell them apart if both are written as "skip". A pattern nobody adopts is a pattern to reconsider; a
pattern nobody can apply is a pattern whose transfer analysis is missing an axis — the second one is
how this ledger got its fourth value (knowledge#6665, 2026-08-23). For a pattern that is NOT
INTEGRATING or NOT APPLICABLE, write "skips all N narrow points" rather than leaving them blank. For
a pattern where research ran, say so. Ask me to confirm or correct it. This is one question.
SECOND, ask the one thing the walk cannot derive — what this wiki's own material did to me:
Where did this wiki leave you?
1. Couldn't find what I needed
2. Found it but didn't understand it
3. Understood it but it didn't work on my substrate
4. It worked, and produced the wrong SHAPE of output
5. It isn't documented at all
6. Other — free text
Option 4 exists because it is the most actionable failure there is and the old form had no box for
it. If I pick it, ask what shape I wanted instead — that follow-up is worth more than the option.
THIRD, ask how much this is costing me: blocker (I can't proceed) · friction (I worked around it) ·
nit (worth mentioning).
FOURTH, ask how to credit this — a name or handle, or "anonymous".
Then DERIVE the remaining frontmatter and SHOW ME what you derived and why, in one short block,
before you write the file. Do not ask me for these; do not invent them either:
- `harness` — from your Step A measurement. If I run several, name the primary one and say in the
body that there are several, because the field takes one value and the truth does not.
- `feedback_type` — `request` if I asked for something built or changed; `capability_gap` if what
I need does not exist; `unclear` if a pattern could not be understood as published; `wrong` if
the research turned up a published claim that is false. Name the basis in one clause.
- `area` — the patterns actually discussed, in my words.
- `desired_output` — one line, in MY words, from what I said during the walk.
If I correct any of them, take the correction.
=============================================================================================
STEP G — THEN WRITE THE FILE
=============================================================================================
Write a file named `feedback.md` in the current directory and tell me its full path. Use exactly
this structure — the headings and the six fields are a contract the page validates before it will
accept the file, so do not rename, reorder or drop any of them:
---
feedback_type: capability_gap | unclear | wrong | request
desired_output: "one line, in my words, of what I want my setup to do"
harness: claude-code | cursor | codex | other | none
area: "the part of the fleet this touches, in my words — not a URL unless I gave one"
severity: blocker | friction | nit
submitted_by: "my name/handle, or anon"
---
## The output I want
## My AI workflow setup
## What I tried on the docs
## What would have to be true
## Anything else
RULES FOR WRITING IT
- Every frontmatter field must be present, and the four fixed-choice fields must use one of the
listed values exactly. `desired_output` and `area` are free text but must be filled in.
- Write what I said. Do not add recommendations of your own, do not propose solutions I didn't ask
for, and do not soften a rejection. If I was blunt, keep it blunt. A rejection with a reason is
the most valuable thing in the file.
- Record a rejection's REASON, not just the rejection. "Not integrating pattern 2 because nothing
on this machine would be left to start work after rejecting pattern 1" is a finding. "No" is not.
- If a research pass fell back to inline measurement because subagent dispatch failed, say so and
say why. That fallback is a documented, expected outcome — hiding it is the only error.
- Do not include anything sensitive: no API keys, no tokens, no internal hostnames of mine, no file
contents from private repos. If I pasted something that looks like a secret, leave it out and tell
me you did.
- Do not pad. A three-pattern walk writes a three-pattern file.
FINALLY
Tell me where the file is and remind me to drop it on the feedback page at
docs.utopiamodels.ai/feedback. That page takes the file and files it on the maintainers' board —
I don't need an account.
```
## Step 2 · What the file has to look like
YAML frontmatter, every field present and non-blank:
| field | accepted values |
| --- | --- |
| `feedback_type` | `capability_gap` · `unclear` · `wrong` · `request` |
| `desired_output` | free text |
| `harness` | `claude-code` · `cursor` · `codex` · `other` · `none` |
| `area` | free text |
| `severity` | `blocker` · `friction` · `nit` |
| `submitted_by` | free text |
Then these `##` sections, in this order:
| heading | |
| --- | --- |
| `## The output I want` | **required** |
| `## My AI workflow setup` | **required** |
| `## What I tried on the docs` | **required** |
| `## What would have to be true` | **required** |
| `## Anything else` | optional |
Hard cap: 65536 bytes. The same `validateFeedback` runs in the browser before an upload is allowed and again on the server before an issue is filed — one definition, `lib/feedback/contract.ts`, so the page cannot accept a file the API then rejects.
## How this page is produced
- The prompt above is `lib/feedback/meta-prompt.txt`, read at build. The contract table is computed
from `lib/feedback/contract.ts`'s own `FIELDS` and `SECTIONS`. Neither is transcribed here, so
this twin cannot drift from the page or from the validator that runs on a submission.
- HTML twin: /feedback