v1.33.0 ·
The brake that was only on half the machine
The problem
The framework brakes knowledge and does not brake work. aidlc-retrospective caps lessons
at 0–3 per instance and calls an empty distillation valid; nothing caps the roadmap items an
instance emits. So closing an instance reliably grows the backlog.
Measured on a external project: items created per day track
instances closing — 9 on 2026-09-16, 5 on 2026-09-19, 5 on 2026-09-20, 8 on 2026-09-21. Of
the 18 items in inbox/ on 2026-09-23, 13 had no executive summary (so none could be
triaged from the top of the file) and 4 were findings about areas an existing item already
owned. Triage kept 3. This repo's own roadmap shows the same shape: 96 items in six weeks.
Two write paths reach inbox/ and only one is described. The capture flow in
aidlc-roadmap requires the six-line executive summary; items written by an instance
mid-phase go through aidlc-overview's one-paragraph Roadmap Capture section, which
requires nothing. That asymmetry, not discipline, is why the inbox only grew.
hold/ means four things at once in that project — dropped, merged, answered, and agreed
work parked on a trigger. Indistinguishable on disk, which had a concrete cost: an accepted
item gated on a table that does not exist had to stay in backlog/, where it counts as
ready, because dropping is moving to hold/.
How it could be solved
Five decisions, and in four of them the smaller answer won against a more complete one that was genuinely tempting.
The first was where to enforce the rule that an item cannot enter the inbox without its case written out. The natural place is the moment of writing — refuse the write, or refuse to start an instance while the inbox holds an undecidable item. Both were rejected, and the second one emphatically: an inbox size limit that blocks starting work would have been the eighth feature in this project whose entire job is watching the lifecycle, and the previous seven are where five of six recorded bugfixes came from. Every one of them taxes all future work to police the work before it. So the check went where frontmatter validity already goes, as a report: it names the items and the labels they lack, and writes nothing at all.
That choice of tier was the second decision, and it had a third option nobody would have questioned. A repair tier exists and applies with confirmation; it could have written the six lines itself from the item's own prose. It must not, for the reason the framework already says about unknown values: an invented case reads exactly like a real one, and the next reader cannot tell which they are holding. The remaining option — a repair that does nothing, so the condition stays outstanding — is worse than either, because an outstanding repair makes the health check exit non-zero on every run, and a permanently red check is one people learn to scroll past. A report that never gates anything is the only version of this that survives contact with a real repository.
The third decision was how far to parse. A strict reader would check that the six labels appear in order, that each is one line, and that none is empty. The parser instead finds the heading and looks for each label anywhere inside that section, checking neither order nor length nor contents. The condition worth reporting is an item with no case at all, which was thirteen of eighteen on the project that prompted this; an item with the right six labels in the wrong order is fine, and flagging it would train people to ignore the report before it ever caught the thing it exists for.
The fourth was the one the incoming item deliberately left open: how to tell work that is parked from work that was dropped, given that both currently move to the same directory. The item named two candidates and asked for whichever had less new surface. Neither was taken. A sixth status directory has to be threaded through the status list, the projection configuration, the layout migration, browse, the entry point's counts and the website, and every count in between. A new frontmatter field depends on the extension-field mechanism and hands the framework one more thing to validate. What shipped adds nothing that was not already present: the history list each item already carries may hold a reason, and a move to hold must supply one. History already lives in the frontmatter, so the reason is visible to the cheap frontmatter-only listing without opening a single body — and status still comes from the directory, so no second representation of status was created. That last point is not a detail. A second place recording status is the one extension this layout can never accept, because a wrong status then looks exactly like a right one.
The fifth decision was to make the follow-up cap a number in an instruction rather than a mechanism. Nothing counts the items a retrospective queues, and nothing will. The cap on lessons has been a sentence since it shipped and it works; adding a counter would mean the completion flow reads the roadmap, which is a new coupling in the one place this project has learned to keep quiet. The same reasoning applies to merge-first, which could have been a similarity search over open items and is instead one sentence telling the agent to look at a list it is already reading.
One thing is worth stating because no code exists for it. Three of the four rules are prose in shipped skills, and nothing at runtime observes whether they were followed. That is not an oversight to be closed later: the enforcement available is the instruction itself, and the tests pin that the instruction is present, so deleting a rule fails the suite even though breaking one does not. A reviewer looking for the mechanism is entitled to know it was a choice, and to know that the alternative on offer was the trap this release exists to avoid.
How AIDLC solves it
Finishing a piece of work stops automatically creating more of it.
The framework has always capped what an instance learns — the retrospective distills zero to three lessons and says an empty distillation is a valid outcome. It capped nothing about what an instance queues. So closing something reliably added something, and a backlog built that way cannot be drained. On the first external project using AIDLC, items appeared in the inbox at the rate instances closed: nine on one day, five on another, five, eight. Of the eighteen sitting there on 2026-09-23, triage kept three. Four were findings about areas an item already owned — one of them literally asked somebody to triage another item. Thirteen had no executive summary at all, so none of the thirteen could be decided about from the top of the file; each one needed its case re-derived before anybody could say yes or no to it.
Three rules and one detector now stand between a finished instance and a bigger roadmap.
The six-line executive summary binds every path into the inbox, not just the capture flow.
It was already the rule in the roadmap skill and it was already followed there. What went
wrong is that a second path existed and was never told: an instance capturing something
mid-phase reads one paragraph in the overview skill, and that paragraph required nothing. Both
paths now carry the rule. Not yet decided. remains a valid value for any label — an admitted
gap is worth more than an invented figure, because the next reader can tell the difference —
but an absent section is no longer a valid item.
A report-tier check names the items that fall short. aidlc doctor lists every inbox item
with no summary, or with labels missing, and says which labels. It writes nothing, under any
flag, and it never touches doctor's exit code. Both of those are deliberate. The only available
repair is writing six lines of judgement about somebody else's idea, and an invented case reads
exactly like a real one; a no-op repair at confirm tier would instead make the health check
permanently red over one thin item, which is how people learn to ignore it. The check looks at
the inbox only — the other four statuses have already been decided about — and stays silent on
items whose frontmatter will not parse, because a different check already reports those by
name.
A retrospective queues at most two follow-up items. Ranked, with zero stated as a valid outcome, in the same shape as the cap on lessons that has worked since it shipped. Everything past the cap goes into the instance's own record under a heading marking it observed and not queued — findable, attributable, and costing nobody a triage decision. The cap is on queued items, not on observations: a retro that noticed nine things writes nine and queues two. And before writing a new item at all, the agent checks whether an open item already owns the area, and appends a dated section to that item if one does.
Parked work is now distinguishable from dropped work, and this was the one question the
roadmap item deliberately left open. It offered two candidates — a new status directory, or a
new frontmatter field — and asked for whichever had the least new surface. Neither won. A sixth
directory would have to be threaded through the status list, the projection config, the layout
migration, browse, the entry point's counts and the website; a new field needs the extension-field
mechanism and gives the framework something else to validate. What shipped instead adds nothing
that was not already there: a history entry may carry a reason, and a move to hold must supply
one, prefixed parked — or declined — . History already sits in the frontmatter, so browse
reads the reason without opening a body and the frontmatter-only rule that keeps browse cheap
survives untouched. Status still comes from the directory, so no second representation of status
was created — the one extension the layout can never allow.
That unblocks the concrete cost the item cited. Agreed work gated on something that does not exist yet can now be parked with its trigger written down, instead of sitting in the backlog where it counts as ready. Two of that project's eleven "ready" items were in exactly that state, kept there because dropping was moving to hold and a test asserted the owner had not been dropped.
The reason is emitted as a double-quoted scalar, unconditionally. That is the same rule this project learned the hard way about item titles, one level down: the history entry is a YAML flow mapping, so an unquoted reason containing a comma or a colon ends the entry early and takes the whole item's frontmatter with it — and a parked reason is the string most likely to contain a comma. Five of this project's first forty-nine items went invisible for up to ten days to the unquoted-title version of that bug.
Nothing here adds a phase, a gate, or anything that blocks starting or finishing work. An inbox size limit that refused to start an instance was explicitly out of scope: it would have been the eighth feature whose whole job is watching the lifecycle, taxing every future instance to police the previous seven. Success is measured once, by hand, on the named project — items queued per close at most two, instance-written items without a summary zero, and an inbox no larger after triage than before. No counter ships.