v2.1.0: the fleet learns to read its own comments
Released 2026-09-05. A release about the auto-dev fleet finding out how often it had been answering questions it never actually looked into.
What we were trying to fix
Running the backlog down with parallel workers kept producing the same puzzling report: an issue would come back marked unplanned, and the plan would be sitting right there on the issue, in a comment. survey.sh only ever read the issue body. Older issues, and every issue seeded after the fact, carry their plan as a comment instead, so the survey was calling them empty and the fleet was skipping them.
Once we pulled on that thread, the same shape showed up twice more. The survey pages through issue comments with a cap, and when it hit the cap it reported “no plan” with exactly the confidence it used when it had read everything. And workers, dispatched into whatever checkout they woke up in, were free to write outside the area they had been given, so the non-overlapping areas the orchestrator hands out were a promise nothing enforced.
Three bugs, one root cause: the fleet was confident about things it had not looked at.
What we decided
Fix all three at the level of the thing that was blind, not at the level of the symptom.
survey.sh now reads a plan wherever it lives, body or comment (#343). The comment cap stays, but a survey that stopped at the cap now says so instead of guessing, so a bounded read reports as bounded rather than as absent (#426, landed as #435). And workers are dispatched with isolation: "worktree", which gives each one its own git worktree before its prompt ever runs, so area isolation became a property of the dispatch rather than an instruction a worker might read loosely (#412).
The other half of the release is the kit pointing its own instruments back at itself. review-sessions (#397) reads the transcripts of past runs, finds the failures the kit itself caused, clusters them by root cause, drops what main already fixed, and files what earns an issue. deliver-issue (#396) chains create-issue, implement-issue and merge-pr for a single item, each phase in a fresh sub-agent, which is the same context-cost argument the fleet had already made for splitting implement from merge. And setup-repo (#400) took over the four repository surfaces a public repo gets judged by: description, homepage, topics, and the Pages source. That last one is why this journal has a site to live on at all.
What got cut
We did not remove the comment cap. #435 bounds the blind spot and reports it honestly; the cap itself is still there, and an issue with a very long comment thread can still be surveyed short. Bounding was cheap and correct, removing was neither, so we shipped the bound.
We also kept the new inline-fix carve-out (#410) deliberately narrow. A worker that trips over something adjacent may fix it in place only when the fix is local (every file it touches is already in the diff) and small (no new file, no behaviour the spec did not already cover). Everything else gets filed. The tempting version of that rule, “fix it if it is clearly a regression”, was written and then rejected: it is the version that turns every pull request into an unbounded one.
What bit us
Mostly our own parsers, and mostly in the same way: reading text a shade too literally.
plan-freshness.sh read the words “none expected” as a file path and reported it missing (#403), then read a wrapped **Files:** field as though the wrap were the end of the line (#419). create-issue matched a flag name inside an ordinary word in the idea text and treated it as a flag (#404). The tracked-exec-globs pathspecs, the header describing them, and the code reading them had drifted into three different answers (#384).
One more is worth remembering. A worker confined to a worktree can be handed a guard script whose path resolves outside it, and be refused. It has nobody to ask. So we wrote the fallback down (copy the guards into the worktree, run them from there, and say so in the report) rather than leaving each worker to improvise around a refused guard, because improvising around a guard is the one failure that guard exists to prevent (#414).