The planning loop
Five steps from a rough note to filed, dispatchable work — and not one of them is reported. Every step is derived from an event or from the board, so a plan has no field it could lie in.
in-flux
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. This page is about the one structural property that makes them trustworthy.
No step is reported. Every step is derived
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
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
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:
- The human clicks the second option.
- 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 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.
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
- 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
2 · Find the planning state your system is guessing at
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 -- <path>`. 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 the `AskUserQuestion` tool rather than assuming, and include a recommended answer with the question.The board
One GitHub project board is the only tracker. An agent writes two fields; every other field is derived on a runner, and status changes only when a merged pull request closes an issue.
The dispatch runtime
Two shell scripts and systemd are the entire agent runtime. No daemon, no queue, no watchdog — starting a run is the assignment, and the close-out comment is the only liveness there is.