The board
One GitHub project board is the only tracker. An agent writes two fields, a job on a runner computes every other field, and status changes only when a merged pull request closes an issue.
Updated
stable
Learn how one board stays true
Reads this page, explains the two-written-fields model back to you, and proves the iteration trap against the live GitHub schema. Writes nothing.
PROMPT 1 of 2 — learn what this module teaches. You write nothing to any repository in this prompt and you change nothing in any tracker.
ASK EVERY QUESTION WITH the `AskUserQuestion` tool, with a RECOMMENDED option first. Not prose.
Fetch and read the whole page:
curl -s https://docs.utopiamodels.ai/docs/operating/board.md
If the fetch fails, say what came back and stop. Everything below is about that page, not about issue trackers in general.
1 — EXPLAIN IT BACK, IN YOUR OWN WORDS, UNDER THESE FIVE HEADINGS
TWO FIELDS What an agent writes on that board, and the full list of what is derived instead —
Lane, Priority, Sprint, Status, Archived, Week/Cycle — each with what it is derived from.
WRITE ONLY ON DIFF Why the reconciler writes only on diff, and the two properties that buys (no agent-side
rate-limit outage; running twice is indistinguishable from running once).
CHAIN vs WAVE What --chain and --wave each make dispatchable, and what happened to the plan of five
parallel waves filed as a fourteen-deep chain.
THE ITERATION TRAP Why a write to an iteration configuration orphaned 84 assignments.
FOUR LIES The four ways the page says the board has been made to lie, one line each.
Quote the page where you lean on it. Do not pad with general project-management advice.
2 — PROVE THE ITERATION CLAIM YOURSELF
The page says not to trust its paragraph and to reproduce the schema instead. Do it, if gh is authenticated (check with `gh auth status` and say so if it is not):
gh api graphql -f query='{ a: __type(name:"ProjectV2IterationFieldIterationInput"){name}
e: __type(name:"ProjectV2Iteration"){kind inputFields{name}} }'
Paste the raw output. Then say, from that output alone, why an iteration cannot be "updated", only replaced — and state the transferable rule the page draws from it.
3 — CHECK YOURSELF ON THE CLOSING-KEYWORD TRAP
Write the exact pull-request sentence the page warns about, using the placeholder #1234, and explain why it closes the issue. Then write the two-step recovery, and name the state (open AND done) that the first step alone leaves behind.
4 — QUIZ ME
Ask me three questions with the `AskUserQuestion` tool, one at a time, each with four options and the correct one NOT always first. Draw them from: which fields an agent writes, chain vs wave, why a Feature never closes itself, and why `gh api` errors must be checked by shape. After each answer, tell me whether I was right and quote the line of the page that settles it.
If `AskUserQuestion` is unavailable in this session, ask the same questions as plain numbered text in one message instead.
Then say exactly this:
"Module learned. Paste prompt 2 and I will measure your own tracker against it."
Then stop.Measure your tracker, then derive what a human types
Reads your real tracker through its API, finds every field typed twice or typed at all, and proposes a derive-on-diff reconciler. Writes only after you confirm.
PROMPT 2 of 2 — integrate this module into my own setup. Survey read-only first; propose; write only what I confirm.
HOW LONG THIS TAKES: a multi-step audit (5 steps, about 5 questions, writes only what you approve) — expect roughly 10–25 minutes across several turns. Running headless with a timeout? Budget for that, or start with a shorter prompt first. Cut off partway? Paste this prompt again — it resumes from its checklist rather than starting over.
ASK EVERY QUESTION WITH the `AskUserQuestion` tool, each with a RECOMMENDED option first and one line of why. If I accept every default without reading, I must still end up with something that works.
Reference model, read it before anything else:
curl -s https://docs.utopiamodels.ai/docs/operating/board.md
That fleet has ONE board. An agent writes two fields on it; everything else is computed by a job that writes only on diff, and status moves only when a merged pull request closes an issue.
FIRST — WRITE YOUR CHECKLIST TO A FILE
RESUMING? Before you write anything, check whether ~/BOARD-INTEGRATE-CHECKLIST.md already exists. If it does, this prompt was interrupted: read it, tell me which steps are already ticked, and continue from the first unchecked step. Do not overwrite it, do not redo a ticked step, and do not re-ask a question whose answer it already records — the scope answer under ## Rules included, if it's there. If ## Rules has no Scope field, this checklist predates the scope question: say so plainly in your first output line and ask it now, before anything else. Only if the file does not exist, write it fresh as below.
Long sessions compress their own history; a file survives that. Write ~/BOARD-INTEGRATE-CHECKLIST.md and work from it:
# Board module — integrate
- [ ] 1. Find the tracker and the credential
- [ ] 2. List every surface that carries work state
- [ ] 3. Pull a real sample and measure every field
- [ ] 4. Propose the adoption plan
- [ ] 5. Write only what I confirmed
## Rules
- Every call to a tracker is a READ until step 5. No create, edit, close, label or comment.
- I report that a credential is SET, never its value.
- Every finding carries the command that produced it.
SCOPE — ASK ME BEFORE YOU SURVEY ANYTHING OF MINE
Before step 1, ask me this with the `AskUserQuestion` tool, with both options exactly as written:
"Should I treat any path as off-limits, or do you want me to have full access to survey and propose changes anywhere in this environment?"
A — Exclude .env*, credentials and CI/deploy config (.github/workflows/, deploy keys) from anything I read or propose touching. [RECOMMENDED for most readers]
B — No exclusions. Full access to survey and propose anywhere, including secrets and CI files.
This is my preference, not a policy: some people want their agent to reach everything, some do not, and neither answer is the correct one. Never assume it and never pick for me. My answer governs every read and every proposal for the rest of this prompt. Write my answer under ## Rules in the checklist as "Scope: A" or "Scope: B", so it survives compaction. It governs the SURVEY and PROPOSE steps only — nothing gets written until step 5, and there only what I confirm.
Before you ask, decide whether an answer can reach me at all. It cannot in a one-shot non-interactive run — `claude -p`, `codex exec`, `gemini -p`, a scripted or CI invocation — and you can tell without guessing: the `AskUserQuestion` tool is missing from your tool list there, while an interactive session has it. It also cannot if the question comes back immediately with no real answer. In either case do not ask, do not wait and do not re-ask: a question asked as plain text in a one-shot run ends the run with nothing done. This is the one case where you pick for me: treat A as my answer, record and hold it exactly as above, and make the first line of your output exactly: "No interactive scope answer available — defaulted to A (excluded .env*, credentials and CI/deploy config). To choose B, re-run this prompt in an interactive session and answer the scope question."
Only if the `AskUserQuestion` tool is in your tool list but fails when you call it, ask the same question as plain numbered text in one message instead.
1 — FIND THE TRACKER
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/=.*/=<set>/'
If nothing authenticates, say so and stop — there is no honest version of this against a tracker you cannot read.
2 — FIND THE SECOND TRACKER I DO NOT KNOW I HAVE
Search this repository for surfaces that also carry work state: ROADMAP / TODO / BACKLOG files, checklists in docs, a status column in a spreadsheet the repo links to, a changelog written by hand. Then ask me with the `AskUserQuestion` tool which other surfaces exist outside the repo (chat channels, standup docs, personal to-do lists) — prompt me with those categories rather than waiting for me to remember.
Build one table: one row per FACT (status, owner, priority, due date, scope), one column per surface. A fact in more than one column is a finding.
3 — MEASURE, DO NOT EYEBALL
Pull the most recent 100 items, open and closed, with every field the API gives. Two traps from the reference page:
`gh api` writes its ERRORS to standard output — check the SHAPE of what you captured, not whether it is non-empty.
Never write to an iteration configuration. An iteration has no id in its own input type; a write replaces the list and orphans every assignment.
For every field report fill rate, distinct-value distribution, and — the whole point — what it CORRELATES with that the system already knows: does status track a merged pull request? priority track a label? lane track the assignee? A field that correlates near-perfectly is a field a job could compute.
4 — PROPOSE
Write the proposal to the terminal, in three lists:
DERIVABLE NOW field · what it correlates with · the rule that would compute it
TYPED AND LOAD-BEARING field · why it resists derivation (if you cannot say why, it belongs above)
DEAD / DUPLICATED fields nothing depends on, and second copies of a fact — delete these first
Then the concrete adoption: a reconciler that computes the DERIVABLE fields and writes ONLY ON DIFF, triggered on the tracker's own events plus a schedule as the backstop. Name the file it would live in (for GitHub, a workflow under .github/workflows/), what it reads, what it writes, and why it is idempotent.
Ask me with the `AskUserQuestion` tool which part to build first. RECOMMEND the dead fields and duplicates — they cost nothing to remove and they are what makes a board look more informative than it is.
5 — WRITE ONLY WHAT I CONFIRMED
Create only the files I accepted, never modify a file you did not create without asking first, and never change tracker fields in this session — the reconciler does that once it is merged and running. If a piece does not work, say so and stop.
When the writes are done, tell me in one line, filled in with the real paths:
"To undo, delete `~/BOARD-INTEGRATE-CHECKLIST.md` and <each file created in step 5, by path> — nothing else on your system was touched."
If I approved an edit to a file you did not create, name that file in the same line with the command that reverts it (`git checkout -- <path>` if uncommitted) instead of claiming nothing else was touched.
FINISH
Every file created, with its path. The one number I should watch — not a dashboard — and what it means when it moves. For runbook-level detail on reading a fleet's live state, point me at https://docs.utopiamodels.ai/docs/operating/operations.md.This page comes from the fleet's own component document for board. We call it stable because every
script addresses the board through one constant. Five separate files bind that constant, and every pull
request checks it again. That constant is the only part of a hosted product a repository can pin.
If work is not on the board, it does not exist.
A body of work is a Feature issue with ordered sub-issues. Its status comes from a merged pull request whose body closes the issue, and nobody ever types a status. If the board is wrong, the fleet works on the wrong thing. We keep no second list to check it against, on purpose, and this component exists to stop anyone from creating one.
The vocabulary (plan, Feature, issue) and the lane routing that decides which agent serves a row are covered on How work flows. This page covers the mechanism underneath. It explains which fields an agent writes, which ones a job computes, and the four ways the board has ended up showing something false.
The fleet owns no file that is the board
The component's list of owned files is empty. That is accurate, not something we forgot to fill in. The board is a hosted product. Everything in the repository that touches it belongs to some other component, and exactly three parts split the work. One script owns the path that writes to the board. One set of workflows owns the checks that keep it accurate. The scheduler owns the only thing that reads the Todo status.
Because we name that boundary explicitly, no fourth component can quietly start keeping its own copy of the board.
An agent writes two fields. Everything else is derived
An agent's whole job on the board is to assign itself and give the issue a title. It can also add a priority label if it wants. A reconciliation workflow on a hosted runner computes every other field, and writes a field only when its value has changed.
| Field | Derived from |
|---|---|
| Lane | the login of the primary assignee. The lane names the box, never the portfolio |
| Priority | the priority label, or the middle value if there is no label |
| Sprint | the current iteration if the field is empty. The reconciler never overwrites an iteration someone chose on purpose |
| Status | the board's own built-in workflows. The reconciler only acts as the backstop that moves closed issues to done |
| Archived | an issue that is closed and done comes off the board. An issue that is open and archived goes back onto it |
| Week / Cycle | no script ever writes this field. See below |
This design has two useful results:
- Agents make zero field calls. However busy the agents get, they cannot cause a rate-limit outage against the tracker from their side.
- Because the reconciler writes only when a value changed, it is idempotent. Running it twice has the same effect as running it once, so it is safe to run on a timer and on an event.
The shape of a filing is a real choice
--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 onceSomeone once designed a plan as five parallel waves and filed it as a chain fourteen issues deep. Nothing reported an error. The board just had one issue ready to dispatch instead of five, and the fleet ran at a fifth of the parallel capacity the plan had, until someone looked at the dependency graph.
If two phases can run at the same time, file them as a wave. The filing tool now refuses a long chain when its tasks share no file, and it prints the same plan rewritten as waves. A serial order needs a stated reason. It is not the default.
The iteration field is read-only to every script, and this one is worth stealing
Any API write to a board's iteration configuration creates new iteration ids, and every assignment that pointed at the old ids is left pointing at nothing. On this board, a single call lost 84 field assignments.
The schema shows why, and the documentation does not, so reproduce it yourself instead of trusting this paragraph:
gh api graphql -f query='{ a: __type(name:"ProjectV2IterationFieldIterationInput"){name}
e: __type(name:"ProjectV2Iteration"){kind inputFields{name}} }'a comes back null, which means the input type a caller would need does not exist. And e.inputFields
has no id. The full input for an iteration is a start date, a duration and a title. So you cannot say
"update this one". You can only say "here is the new list".
The field id is durable, and it is the only id worth hardcoding. An iteration id changes as soon as a human edits the field in the interface. For that reason the filing script looks up the current iteration live every time it files an issue, and treats the result as advisory.
The rule that applies elsewhere is this. Before you write to a hosted API, check through introspection whether the API keeps the identity of the thing you are updating. If its input type has no id field, you are not updating it. You are replacing it.
Read the board, never recall it
Every count on the board changes by the minute, so this page tells you how to read each number instead of stating it.
| You want | Run |
|---|---|
| Open issues | gh api repos/<org>/<repo>/issues --jq 'length' |
| Progress for every Feature, in one call | bash _infra/scripts/feature-progress.sh |
| One issue with its comments | gh api repos/<org>/<repo>/issues/<n> |
| One line for each queued issue, with the reason it is held | bash _infra/scripts/sweep-dispatch.sh --explain |
That table has two traps. gh issue view currently fails against this board with a deprecation error from
the older projects API, so use the gh api form, which works. Also, gh api prints its errors to
standard output, not standard error. Check the shape of what you captured. Do not only check that it
is non-empty.
Four ways this board has been made to lie
-
Saying you are not closing an issue closes it. GitHub matches a closing keyword followed directly by an issue reference anywhere in a pull request body or commit message, and it ignores the words around it. A sentence like "this does NOT close
#1234" counts as a closing reference. This happened twice in one day on one issue. The first time, it came from a pull request whose whole purpose was to keep the issue open. The second time, it came from the follow-up pull request that documented the trap, because that pull request quoted the sentence in its own body.Recovery takes two steps, and only the first one is obvious. A close also sets the status to done, and reopening the issue does not reset it. The issue ends up open and done, and no selector reads that state. So reopen the issue, and set the status back, then read both values back to confirm. To mention an issue without acting on it, use a form with no keyword, and use a placeholder number in any example. That is why the example above uses
#1234. -
The final state arrives eventually, not instantly. The close event tries to archive the issue, and a scheduled sweep guarantees it. In one measured window, concurrency cancellation killed 25 of 30 event runs in a row. Expect a few rows at any moment that are closed but still listed. This fleet has already raised a false alarm by filing "done is stuck at zero" after reading the board once.
-
A Feature never closes itself. A parent issue has no pull request, so a closing keyword can never fire on it. Instead, the agent that finishes the last child issue attests the Feature, and a separate sweep closes it. If a Feature is complete but has no attestation, the sweep flags it and does not close it. That is the right way to fail, because the alternative is a parent that closes itself while its own verifier objects.
-
Nothing gates the default branch. On this plan tier, branch protection and rulesets both return 403 in every repository. A red check is information. A merge never waits on a human. The full argument is on How work flows.
How work flows
The board is the queue and git holds the state. A merged pull request is the only thing that changes status. This page covers plans, features, issues, lanes and specs, and explains why no check can stop a merge.
The planning loop
Five steps take a rough note to filed, dispatchable work, and nobody reports any of them. Each step is derived from an event or from the board, so a plan has no field that could hold a wrong value.