The orchestrator that drafts its own backlog¶
If you are new to building this way, you probably picture a linter as one thing: a check that either passes or fails a pull request. Green, your code merges. Red, you go fix it. That model is correct for most of the checks in this repo -- and it is exactly wrong for a second, quieter class of check that does something stranger.
When one of these checks notices that the codebase has drifted -- a file has bloated into a god file, a near-copy of an existing helper has crept in, a hand-rolled tally could be a standard-library Counter -- it does not fix the code. It also does not fail the pull request that introduced the drift. It writes a brand-new ticket, drops it into the backlog, and lets the merge proceed. The orchestrator grows its own to-do list.
This post is about that move: the drift tripwires and the periodic code-health passes draft a remediation ticket instead of repairing the drift in the run that spotted it. It is a small idea with a surprisingly large payoff, and -- because I would rather you trust the rest of this thread -- one honest catch I will not bury.
A linter here is not always a yes/no gate¶
The companion post Review surfaces and the AI-first guardrails lays out the review stage in full, but here is the one distinction you need for this post. The project's review preflight is an ordered list of named check verbs in the review configuration, sorted into buckets. Two of them matter here:
review.always_checks-- the unconditional wall. Every ticket, every time.lint,test, the ASCII check, the bug-fix gates. Any one of them going red blocks the merge. This is the pass/fail linter you already have a mental model for.review.soft_checks-- checks that report drift but do not block the merge on their own.
That second bucket is the interesting one. A soft check is allowed to notice something is wrong and let the code ship anyway. If it cannot fail the build, what does it do with what it noticed? It drafts a ticket.
Three tripwires, registered as soft checks¶
Open the review configuration and find the review.soft_checks line. Three of its entries are the drift tripwires this post is about:
lint-blast-radius-drift-- watches for structural drift: a module that grew into a god file, a hub that too many other files now import, a constant set scattered across a module. Backed by the project scripts; the whole-repo version is the finder verb the project dev wrapper.lint-dedup-drift-- watches for a changed file that adds a clone or orphans a symbol across the warn band of the duplication threshold table. Backed by the project scripts; the finder is the project dev wrapper.lint-library-replacement-drift-- watches for hand-rolled backend Python that a trusted library could carry instead. Backed by the project scripts; the finder is the project dev wrapper.
All three are real entries in the review.soft_checks string today. None of them can fail a merge. And all three, when they fire, do the same thing: they write a state: draft ticket into the backlog describing exactly the drift they saw, and they leave the code alone.
The move: draft, do not fix¶
Here is a real example. When lint-blast-radius-drift decided that an integration test file had crossed the size band, it did not refactor the file. It dropped this ticket into the backlog -- the backlog -- and the ticket's own front matter tells you everything about the design:
state: draft source: lint-blast-radius-drift 2026-06-26 priority: P3 group: blast-radius-review tags: [refactor] bug_heuristic: false
The body says it plainly: "Keeping the ticket as a draft makes the signal durable without launching autonomous refactor work until a human confirms it." That is the whole philosophy in one sentence. The tripwire's job is to capture the drift the moment it lands, as a self-contained, auditable artifact -- not to act on it.
Two things make this work and keep it from becoming noise:
- The drift is captured decoupled from the pull request that caused it. The merge that introduced the god file does not have to also fix the god file. The fix becomes its own narrow ticket, scheduled whenever a human wants it, reviewed on its own merits.
- The tripwire checks the existing backlog and the archive before it drafts. It will not re-file a candidate that already has a ticket, so the same review running ten times does not manufacture ten copies of the same to-do.
The drift soft checks are the always-on, per-review half. The other half is the periodic code-health passes, documented in the review-rule docs. That doc is the registry: it pairs each drift surface (blast-radius, library-replacement, dedup) with its review spec, its finder verb, and a one-line description of what the pass triages. On a phase cadence, the workflow runs the whole-repo finder for each registered surface, triages the ranked ledger against the surface spec, and -- the recurring move again -- "seed[s] narrow remediation tickets for human promotion." Same instinct as the soft check, run at whole-repo scale: find the drift, write a ticket, hand it back.
What the worked tickets look like¶
You can read the accumulated backlog as a fossil record of drift the system noticed and filed against itself. Three naming families, all real on the current tree:
blast-radius-drift-*-- one ticket per structural candidate that crossed a band, e.g.blast-radius-drift-tests-integration-test-server-ai-integration-py-god-file-78be59f6acin the backlog, and dozens of archived siblings such asblast-radius-drift-src-server-room-py-god-file-3a8c640ad9andblast-radius-drift-src-engine-base-py-high-fan-in-hub-7c1d81cb32. The slug encodes the file path and the smell, so the ticket is self-documenting before you open it.dedup-*-- one ticket per duplication or dead-code candidate.dedup-werewolf-dawn-payload-fixturesis a current confirmed one; the archive holdsdedup-unified-home-start-request,dedup-room-bluff-post-action-tail, and the triage seeddedup-first-remediation-pass. The duplication side of this machine gets its own deep dive in Trimming the duplication an AI cannot see, which walks the finder, the soft check, and the human-gated remediation pass end to end.library-replace-*-- one ticket per hand-rolled snippet a library could replace, e.g.library-replace-holdem-equity-outcome-counterin the backlog andlibrary-replace-protocol-join-pydantic-modelin the archive.
Every one of these started life as a state: draft stub the system wrote about itself, sourced either from a drift soft check (source: lint-<surface>-drift <date>) or a periodic pass (source: Phase N <surface> pass <date>).
Why draft-then-fix is the right shape¶
The drafting half is deliberately the easy half. The hard half -- actually doing the refactor -- is gated behind a protocol, because a careless cleanup can do real damage. the review-rule docs is the spec for what the dedup pass triages, and its remediation protocol is the model for why drafting and fixing are separated.
For a de-duplication: write a golden or characterization test on real server-shaped fixtures first, prove it passes on both copies, then collapse the clones to a single source of truth with no compatibility shim, then run the golden test again -- equal output is the proof the change preserved behavior. For a deletion: prove the target unreachable against the dynamic-dispatch allowlist, then use the full suite as the equivalence oracle. That is a careful, behavior-preserving, golden-test-first procedure -- exactly the kind of work you do not want an agent improvising inside an unrelated pull request at 2am. Drafting the ticket lets the system notice drift cheaply and instantly, while reserving the expensive, risky fix for a focused run a human chose to schedule.
The result is a system that accumulates its own backlog of small, golden-test-first refactors: auditable (each is a committed artifact with a source: line), schedulable (each is an independent ticket), and decoupled from the change that triggered it.
The honest edge¶
A self-drafted backlog is not free autonomy, and selling it as such would undercut everything else in this thread.
Someone still has to triage and prioritize those tickets. Look again at that front matter: state: draft. A drafted ticket does not run. Before any of these become autonomous refactor work, a human has to promote it to state: confirmed -- which is a human deciding this drift is worth paying down now. The orchestrator is very good at filling a to-do list and has no opinion at all about which items on it matter. The mechanism shifts when drift is captured (immediately, mechanically, as an artifact) -- not who decides it is worth fixing (still a person).
And a tripwire that fires too eagerly does not save work; it manufactures it. A backlog full of low-value draft tickets is its own kind of mess -- noise a human now has to read past to find the drift that actually matters. The project tunes against this in places: lint-dedup-drift, for instance, only acts on candidates whose codemap maturity: is stable, deliberately exempting experimental and in-progress code, because ahead-of-time work that nothing imports yet looks exactly like dead code to a static scan. That tuning is a constant maintenance cost, and getting it wrong in the eager direction turns a helpful instrument into a spam generator.
So the honest framing is the same one the rest of this thread keeps landing on: this is an instrumented development system, not a self-driving one. The drift tripwires and periodic passes are a genuinely useful instrument -- they give the project a memory for drift that no single context window has, and they write that memory down the instant it forms. But the backlog they grow is a queue of suggestions, not a queue of decisions. The orchestrator drafts its own to-do list. It still needs you to decide what is actually worth doing.
Or: subscribe for more posts on building autonomous AI development infrastructure.