The format
---issuegraph: blocked-by: [231, 234] decomposed-from: 230 serialize-with: 232 priority: 1---
Standard YAML frontmatter. Any tracker can store it, any parser already reads it. Nothing is installed and nothing is migrated.
If a body somehow ends up with two frontmatter blocks, the first one containing an issuegraph: key is the real data. Later ones are ignored, so a bot's banner or a wrapper can't quietly override what you wrote.
Write it plain, exactly as above. Some trackers garble bare frontmatter when they render it — GitHub, for one, draws --- as a horizontal line and turns the line above it into a heading. For exactly that case, the spec explicitly permits wrapping the block in a normal triple-backtick code block — it is a sanctioned part of the format, not a workaround. It changes only how the block looks on screen: the text inside is byte-for-byte identical, it stays portable between trackers, and readers look straight through the wrapper to find it.
Fields
| Field | Type | Meaning |
|---|---|---|
| blocked-by | list of refs | These issues must reach closure before this one may start. The only hard ordering fact. |
| decomposed-from | ref | Provenance: this issue was produced by decomposing that one. Not a scheduling edge. |
| duplicate-of | ref | This issue is the same work as that one; readers ignore this issue and treat the target as canonical. |
| serialize-with | ref | This issue must not run concurrently with the referenced issue or anything transitively linked to it (see 4.3.4). No ordering implied. |
| together-with | ref | This issue and the referenced issue (and anything transitively linked) are distinct issues forming one unit of work — selected, claimed, and worked together (see 4.3.7). |
| priority | integer 0–3 | 0 is most urgent. Absent means 2. Carrier precedence in 4.3.5. |
| evidence | asserted | verified | Whether the issue's factual claims were verified by the writer or merely asserted. Absent means asserted for machine-written issues. |
An issue with no issuegraph frontmatter is still valid — it just has no edges and default priority. You add fields where you actually know something.
decomposed-from only records where an issue came from. It has no effect on ordering. There are no parent issues and no container issues: an issue that got split closes as soon as the split is done, and “what came out of #230?” is answered by asking the graph, not by a tracking issue someone has to keep tidy.
evidence is the odd one out — it changes no ordering at all. It says whether whoever wrote the issue actually verified its claims or just asserted them. Machine-filed issues are frequently wrong in small ways, and splitting an unverified issue produces a whole subtree of work built on a false premise, so the next worker wants to know before building on it. It earns its place by protecting decomposition, which is what produces the relationships in the first place.
Relationships
You never write the group down. serialize-with and together-with both describe a set of issues, but no issue ever holds the list. To join, point at any one issue already in the set — it does not matter which. The reader follows the links and works out the full set for you.
#232 writes serialize-with: 231 · #233 writes serialize-with: 232 · #234 writes serialize-with: 231
→ the reader sees one group: { 231, 232, 233, 234 } — and only one of them may run at a time.
This is the part that stops it rotting. To leave a group you delete your own field, and nothing else. Closing an issue never means going back to edit the others. No issue ever writes to another issue's body, and there is no membership list anywhere that can drift out of date — the usual way this kind of bookkeeping dies. One line per issue, and nothing to maintain.
If you accidentally link two groups they merge, which costs you some parallelism and never costs you correctness.
Reading the graph
#230 was too big to work, so the only work permitted on it was decomposition: four leaves and a verification gate, with every edge written at split time. #230 then closed — there is no tracking issue.
| Key | Summary | Status | Pri |
|---|
Click a ready issue to claim it, and again to close it. Blocked and held issues do not respond.
ready — the issue is open, every issue in its blocked-by has closed, and nothing in its serialize group is being worked. Anything not ready is invisible to selection: a scheduler cannot pick it up, so nobody has to protect it.
effective priority — the highest declared priority among the issue itself and every open issue that depends on it, however far down the chain. Readers select by this, not by the number written on the issue. That is why #233 above shows P2 → 1: it is a P2 holding up a P1.
parallelism — two ready issues with no path between them can be worked at the same time. Nothing else needs to say so.
Scope
In the format
- what blocks what
- what must not run at the same time
- what must be worked as one unit
- which issues are the same work
- where an issue came from
- how urgent it is
- whether its claims were checked
Not in the format — and where it goes instead
- who is working on it, right nowYour tracker's assignee and status fields, or whatever your runner uses to claim work.
- retries · heartbeats · logsThe runner's own logs and metrics. None of it is a fact about the work; it's a fact about one attempt at the work.
- “don't start this yet”A tracker label or paused state —
needs-human,waiting. Every tracker already has this, and duplicating it in the body means two places to fix. If the hold is really a specific question, file the question as its own issue and put it inblocked-byinstead. - what kind of work this isEach runner decides that from its own capabilities. “Pay the vendor invoice” is human work for everyone; “write this migration” depends entirely on the runner.
- how many things may run at onceA setting on your scheduler. The graph says what may run together; how much you actually run is your call.
- how far along a feature isA query, run when someone asks. Stored progress numbers go stale the moment nobody updates them.
- “prefer after” · “not near” · affinityNowhere — on purpose. These are suggestions rather than rules. The first scheduler ignores the suggestion, so nobody keeps it accurate; a later scheduler decides to respect it and acts on something that went stale months ago. Every Issuegraph field is a hard rule with one exact behavior, so a reader can't be misled by one.
- issues that aren't work — trackers, checklists, logsNothing to add: they stay in your tracker and your reader skips them, normally by label. Issuegraph defines no way to mark an issue as excluded, because that is the reader's judgment call, not a fact about the work.
Where the line is. Issuegraph records what the work is and what it depends on — things that stay true no matter who or what picks it up, or how many times they try. Anything describing a particular attempt at the work belongs to the thing making the attempt.
The one-question version: if you had a flawless scheduler, would it still need this written on the issue? If it could work the answer out for itself, or get along fine without it, the field stays out. “Is #232 blocked?” it cannot possibly guess — that's a fact somebody knows. “Is anyone working on #232 right now?” it already knows, because it's the one handing out the work.
The reason to be strict about this: the moment a format starts carrying run state, it's welded to whichever tool wrote that state, and it stops being portable — which was the whole point.
Every issue is a piece of work. Issuegraph has no issue kinds — no PM issues, no tracking issues that exist only to hold other issues, no standing checklists, no issues used as a log. An issue is either small enough to work, or the only thing you may do to it is split it, and the split closes as soon as the smaller issues exist. That is what stops the graph filling up with nodes nobody will ever finish.
So where do those issues go? They stay in your tracker, and the spec says nothing about them on purpose. Readiness is the graph's answer to “may this start?”; whether a reader is willing to pick something up is the reader's own business, layered on top. Filtering by label is the normal way to do it — tag your standing checklists meta, your discussion threads rfc, and have your reader ignore those labels. Issuegraph never learns why you skipped them, and no field is added to describe it.
At backlog scale
The same 48 issues, two orderings. Order by newest is the backlog as a tracker shows it — newest first, read left to right. Order by issuegraph is the same 48 issues arranged by what their frontmatter already says — blockers above, dependents below, edges drawn between them. Nothing was added to get from one to the other.
Then play it — whack-a-mole, two rounds. The reader deals the backlog in waves: everything ready lights up at once — one to four issues, your pick — holds for a blink, then the board rests before the next wave. Whack what you can while it shows; whatever you miss closes anyway, because the frontier moves with or without you. Round 1 plays ordered by newest, where every wave lands somewhere you didn’t expect. Round 2 replays the identical waves ordered by issuegraph, where every closure points at what it frees up next. The sequence never changes; only your ability to see it coming does.
Idle — nothing is lit. The reader deals the same waves every time; ordered by newest, nothing tells you where they’ll land — ordered by issuegraph, the board itself does. Press start.