Skip to main content

Team Maturity Explains the Friction, the Foundation Predicts the House of Cards

In the age of LLMs, the work that matters at your job is no longer adopting the tools. It is raising the velocity at which your team can ship features without turning the software it builds into a house of cards. When two teams hold the same tools and get wildly different outcomes, the first thing to study is the team itself, and Bruce Tuckman’s model of group maturity will explain a large share of the gap. But it will not explain all of it, because team maturity governs how easily a team absorbs a new practice, not whether the software that practice produces stays standing. Maturity is necessary and it is not sufficient, and most organizations are diagnosing only half the problem.

The Real Job Is Velocity Without Debt
#

Raw velocity stopped being interesting the year a model could draft a feature in an afternoon. Anyone can go fast now. The constraint that separates serious teams from reckless ones is sustainable velocity, the ability to ship quickly and also keep the system survivable as it grows.

The distinction is not new, and AI did not invent it. The DORA research program spent years measuring engineering organizations along two independent axes, throughput and stability, and its central finding was that you have to track both because they are not the same thing. Throughput is lead time and deployment frequency, how fast work gets out. Stability is change failure rate and time to restore, how often what you ship breaks and how long it takes to recover when it does. A team can score high on throughput and low on stability, and that team is not fast. It is a house of cards being shuffled quickly, and the bill comes due as rework, outages, and a codebase nobody is willing to touch.

LLMs supercharge the throughput axis for free. They do nothing for the stability axis unless your engineering system is built to hold them accountable. So the whole question of which teams ship effectively with LLMs collapses into a narrower one: which teams can absorb the throughput multiplier without their stability metrics collapsing? That is a property of the team and a property of the code, and the team part is only half of it.

Why Tuckman Explains So Much of the Adoption Gap
#

Before moving to the other reasons, it is worth being realistic about how much Tuckman’s stages of group development actually explain, because the answer is a lot.

A team in the performing stage adopts a new AI practice with low friction for reasons that are structural, not cultural. Its members have already negotiated how decisions get made, so “which code review workflow do we use now that the model writes most of the code?” gets resolved in one meeting instead of three. Its conventions are settled and shared, so when an engineer introduces a prompt template or a skill, the rest of the team can tell whether it fits the way they already work. It has the psychological safety that Project Aristotle identified as the strongest predictor of team effectiveness, which means an engineer can say “the model’s output is wrong here and I do not understand why” without that admission costing them status.

A team still in forming or storming has none of these assets, and so it pays the adoption tax on every change. As I argued in When Engineers Disagree on Best Practices, a team that has not yet built a repeatable process for resolving disagreements will relitigate the same practice debate over and over, and the debate is rarely about the practice. It is a proxy for unresolved questions about whose judgment the team trusts. LLM adoption surfaces a dozen of these questions at once, because it touches review, testing, specification, ownership, and onboarding simultaneously. A performing team processes all of that in the background. A storming team drowns in it.

So if your observation is “some teams picked up the new AI workflows easily and others fought about it for two quarters,” Tuckman is very likely your explanation. Team maturity is the dominant predictor of adoption friction, and adoption friction is the dominant predictor of whether a team even gets to the starting line.

What maturity does not predict is what happens after the team starts shipping.

The House of Cards Is Predicted by the Foundation
#

Here is the gap maturity cannot close. A mature, high-trust, psychologically safe team sitting on top of a brittle codebase with no tests and no specification discipline will still ship a house of cards. It will just do so with impressive cohesion, minimal interpersonal drama, and a strong retrospective culture. The team dynamics are good and the software is still wrong, because the things that decide whether generated code stays standing are properties of the engineering system, and those properties vary somewhat independently of how well the team gets along.

If you want to know why two mature teams with the same tools ship software of very different durability, look at the foundation. The factors below are not equal in importance. Two of them, verification and codebase health, decide whether the software stands up at all, and the other four only improve the quality of what gets built. No amount of strength in the four can compensate for weakness in the two, because the two are what keep the structure standing. For shipping without a house of cards, verification and codebase health outrank team maturity, and the rest exist to support them.

The two factors that outrank maturity
#

These are the structural ones. A team weak on either of them ships a house of cards regardless of how mature it is, and a team strong on both can survive even rough team dynamics. Everything else in the foundation eventually feeds into one of these two.

Verification is the throttle
#

Once code writes itself, the bottleneck moves to checking whether the code is correct, and that move is the central claim of The Shifting Bottleneck. Verification is where the stability axis lives, and it is why two teams with identical throughput can have radically different change failure rates.

The team with a fast, trustworthy test suite, a CI pipeline that catches real regressions, and a short feedback loop can let the model generate aggressively, because it can verify cheaply. Every generation is a hypothesis and the test suite is the experiment, and the cost of a wrong generation is seconds. The team that verifies by reading the diff, or by running the feature once in a staging environment, cannot afford to let the model run. It hits a ceiling where the human reviewer becomes the bottleneck, and either it slows down to stay safe or it speeds up and ships unverified code.

The difference between those two teams is the DORA stability axis measured in engineering practice. The team that ships safely with LLMs is usually not the team with the best prompters. It is the team with the best test suite and the fastest signal, because that team can convert the throughput multiplier into stable throughput instead of into rework. Investing in verification infrastructure is now the highest-return thing a team can do to raise its LLM shipping velocity, which is a counterintuitive claim only if you are still measuring velocity as lines produced rather than features landed without rollback.

The codebase is the model’s context
#

The other structural factor is that the LLM does not generate code in a vacuum. It generates code as a continuation of the context it is given, and the largest piece of context is the codebase itself. A clean, well-factored codebase with clear naming, consistent patterns, and a single way of doing each thing is excellent context, and the model faithfully reproduces its conventions. A tangled codebase with five competing styles, dead abstractions, and comments that contradict the code is terrible context, and the model faithfully extends the mess. As I argued in The Importance of Context When Interacting with LLMs, the context is the entire mechanism by which a frozen set of weights produces behavior relevant to your situation, and the codebase is the part of the context you control.

This is why the same model, the same prompt, and the same engineer produce different quality output on different codebases. The codebase is doing most of the work, and a codebase that is already a house of cards is a context that asks the model to build more cards. You cannot hand an LLM a cathedral built on sand and get back a cathedral built on bedrock; you get back a taller pile of sand. The teams shipping safely are, more often than they realize, the teams whose codebase was already safe to extend, and the LLM is merely making that pre-existing health visible at higher speed.

The factors that strengthen the foundation
#

The next four factors do not replace the first two. They decide how much wrong output the verification layer has to catch, and how high the ceiling on the best possible generation sits. A team that is strong here and weak on verification still ships a house of cards, just a slightly smaller one. A team that is weak here and strong on verification stays safe, but slowly, because its loop drowns in bad output it has to reject.

Specification discipline separates amplifiers from noise
#

An LLM is a multiplier on the quality of the instructions it receives, which means the teams that win are the ones that produce high-quality instructions at scale. This is specification, and it is the skill that Software Engineering Teams in the Age of AI names as the highest-leverage capability in the current era.

A team that writes a precise specification before it prompts, one that defines what done means, which invariants must hold, and which edge cases matter, gets an LLM that behaves like an effective pair programmer. A team that prompts first and specifies never gets a hallucination engine that produces plausible code solving the wrong problem, and the wrongness is often invisible until production. Specification is also where the human bottleneck genuinely lives now, because writing a precise spec is hard cognitive work that the model cannot do for you until you have done the thinking it depends on. Teams that institutionalize specification, through templates, through review of the spec before the implementation, through a skill that enforces the steps, pull away from teams that treat the prompt as the spec, and they pull away fast.

Conventions have to be written down to be inherited
#

A performing team has settled conventions, and that is exactly what makes the team mature. But settled conventions that live only inside the senior engineers’ heads are invisible to two important workers: the new hire, and the LLM. Neither of them received the osmosis.

The teams that ship consistent, safe output at scale are the ones that have externalized their conventions into a form the model actually reads. That means lint rules the CI enforces, architecture decision records that capture why a choice was made, contribution guides that name the patterns to reuse, and, most powerfully, skills that encode a team’s process as executable steps the agent follows on every run. A convention in a head is advice the model will ignore. A convention in a skill or a lint rule is a constraint the model has to satisfy.

This is the mechanism by which a mature team scales its maturity into the model. A storming team that somehow wrote its conventions down would get more out of the LLM than a performing team that left them tacit, which is a real inversion and a useful diagnostic. If your team is mature and your LLM output is still inconsistent, the conventions are probably in the wrong place. They are in people, and they need to be in files.

Review is a backstop, not the mechanism
#

The instinct when a tool speeds up code production is to use it to speed up code review, and on this point the instinct is closer to right than wrong, for a reason that is easy to miss.

Code review is a one-time signal. It catches what one reviewer notices, once, on the diff in front of them, while they happen to be alert. A specification prevents the whole class of issue from reaching implementation, and a test catches the same bug on every future run, for as long as the codebase exists. As Rethinking Code Review in the Age of LLMs argues, an hour spent improving the specification outranks an hour spent reviewing the output, because the specification compounds and the review does not. For the purpose of shipping without a house of cards, specification is the structural lever, and review is the non-compounding backstop behind it.

The backstop still has a job, but it is narrower than the one review used to claim. With generated code the reviewer is the only brain in the loop, so a light, intent-focused check still catches the occasional wrong assumption before it ships. What it does not do is scale. If the model produces ten changes a day, ten hours of human review is not a process that survives, and the right response to a repeated class of review comment is to encode it as an automated gate so it never depends on a human noticing again. The teams that ship safely let the model handle style, keep a thin intent check as the backstop, and spend the freed review hours writing better specifications. The teams that ship a house of cards inverted this, keeping review heavy while starving the specification that actually prevents the cards.

Domain depth and the discipline to build less
#

Both of the following are judgments the model cannot make for you: what the software should do, and whether it should exist.

On the first judgment, the model produces code that is technically correct and strategically wrong, faster than ever, when nobody on the team deeply understands the business context. Deciding whether a feature should exist, and what form it should take, is the part of the pipeline AI cannot do, and it is the bottleneck the throughput multiplier pushes you into, exactly as the shifting bottleneck predicts. A team with deep domain ownership extends its system coherently. A team spread thin across too many concerns generates five services where one would do, and each of them is a card.

On the second judgment, cheap implementation makes overbuilding the default temptation. Every generated feature is surface area for bugs, cognitive load, and future constraints, and the cost of maintaining a feature never approached zero the way the cost of writing it did. The team with the discipline to say “we do not need this, ship the smaller thing” survives longer than the team that ships everything the model can draft, and that discipline is a product judgment that maturity does not produce on its own. It comes from somewhere else, usually from someone at the table who has been burned by feature bloat before and is willing to be the friction.

How Maturity and Foundation Interact
#

The fair synthesis is that these two layers reinforce each other, and the most effective teams are strong on both, but they fail in characteristically different ways.

A performing team on a clean foundation with strong tests and written conventions is the team that wins this era. It absorbs new AI practices without friction, and when it ships, the foundation catches what the model gets wrong. A performing team on a brittle foundation hits a wall it cannot see, because the team dynamics are good and so nobody is arguing, and the stability metrics degrade quietly until a production incident makes them visible. A storming team on a clean foundation still wastes most of its energy on the wrong fights, but its code tends to survive the fights because the foundation holds. A storming team on a brittle foundation fails loudly and fast, which is at least easy to diagnose.

The trap is to read every symptom as the layer you already know how to fix. Engineering management tends to diagnose everything as team dynamics, because that is the toolkit it has, and so it sends a brittle-foundation team to a retrospective when what it needs is a test suite. Engineering teams tend to diagnose everything as tooling, because that is the toolkit they have, and so they adopt a new model when what they need is a written convention. The binding constraint is usually the layer you are not looking at, and maturity is very good at hiding problems in the foundation because a mature team does not complain about them until they break.

What to Do Next
#

If you lead a team and want to know whether you are shipping features or shipping cards, a few concrete moves separate the diagnosis from the guesswork.

Measure both DORA axes, not just throughput. If your deployment frequency is rising and your change failure rate or time to restore is rising with it, you are not getting faster. You are getting more volatile, and the LLM is the reason. The stability metrics are the house-of-cards indicator, and they are free to collect.

Treat the codebase as context and pay down the part the model keeps getting wrong. If the LLM consistently produces bad output in one module, that module is bad context, and the fix is to refactor the module, not to write a longer prompt. The model is telling you where your code is incoherent, because incoherent code is exactly what it reproduces worst.

Write your conventions down somewhere the model reads them. A skill, a contribution guide, a lint rule, an architecture decision record, anything that moves a standard out of a head and into the execution path. The test is whether a new engineer and a fresh agent both produce work that matches the team’s patterns on day one without being told.

Keep a thin, intent-level review as a backstop and let the model own style. If your review comments are still about formatting, you are spending human attention on the part the model already fixed. Reallocate those hours into specification, which is the lever that compounds.

Write the specification before the prompt, every time. The spec is the highest-leverage artifact in the pipeline now, and the team that treats it as optional is the team whose LLM output drifts toward plausible-and-wrong.

The Team That Wins
#

The teams that win this era are easy to misread. They look like the most mature teams, and they often are mature, but the maturity is doing a specific job. It is letting them adopt new practices without bleeding energy, so that they can spend that energy on the foundation that actually decides whether the software stands up.

Maturity is how you remove the friction of getting started. The codebase, the tests, the specifications, the written conventions, and the intent-level review are how you keep the result from collapsing under its own weight. Study your teams, because Tuckman will tell you a lot. Then study the code they are standing on, because that is what determines whether the velocity they have earned is velocity they get to keep.

See also
#

References
#


The Pull Request Queue Outgrew You: A Triage Layer for Open Source Maintainers

Open source always had a queue problem. For most of its history the threat was volume: more pull requests than a maintainer could read, arriving faster than one unpaid person could clear. The pull request queue scales with the project’s popularity, and a single maintainer’s attention does not scale with anything at all.

That is still true, and it is no longer the worst of it. The character of the queue changed. More than one in five code reviews on GitHub now involve an agent, and Copilot’s automated review alone has run more than sixty million times, growing tenfold in under a year, so the machine-generated pull request is no longer a fringe of the queue. It is becoming the norm, and a machine-generated pull request is a different kind of problem than a human one. It looks correct, because plausibility is exactly what a language model optimizes for, and it may be subtly, uniformly wrong in ways that read as confident and clean. There is usually no author who thought about each line, so there is no intent for you to leverage as context, only output you have to verify from scratch. And almost never does the contributor tell you which model produced the code, what prompt generated it, or whether a human ever read it before it landed in your queue. The flood is not just bigger now. It is full of plausible code with no provenance, and plausible, unprovenanced code is the hardest thing in the world to triage by reading it.

The instinct of a responsible maintainer is to read every pull request, carefully, and reply thoughtfully. That instinct is exactly what kills the project. Every half-hour spent reverse-engineering an AI-generated PR that looks reasonable until the third function is a half-hour not spent on the architectural change that keeps the library alive. The queue grows while you sleep, and while you are polite, and while you are giving a stranger’s model the benefit of the doubt.

You cannot review your way out of this, for the same reason a team engineer cannot: per-unit review scales linearly with your hours, and the entropy is produced faster than you can read it. The difference is that on a team you can hire. In open source, you are usually alone, unpaid, and tired, and the code you are being asked to vet no longer carries the reasoning of the person who submitted it. The only way out is to stop reviewing everything and start triaging everything, so that your scarce attention lands only on the pull requests that deserve it.

Triage Before Review
#

Review answers the question, “is this code correct?” Triage answers a cheaper question that comes first: “does this pull request deserve my attention at all, and if so, how much?”

Most maintainers fuse the two. They open a pull request, start reading the diff, and only then discover that it is stale, that it conflicts with main, that it has no tests, that it touches a file nobody asked it to touch, or that it is the fourth duplicate of a request they already declined. Every one of those discoveries was free to make before reading a single line of code, and making them up front is the entire difference between a queue you manage and a queue that manages you.

A triage layer is the set of automated signals that answer the cheap questions before you ever open the diff. Does it still apply? Does it still merge? Is it small or sprawling? Is it risky or routine? Who is it from? Does it match the conventions the project already requires? Each of these is a machine-checkable property, and a property a machine can check is attention you never have to spend again. This is the same move The Codebase Gardener makes for a team codebase: encode the standard where every change is forced to pass through it, instead of carrying it in your head as a review habit.

The goal of the triage layer is not to merge everything automatically. It is to make the queue sortable, so that when you sit down with your limited hour, you are looking at the five pull requests that matter, ranked, instead of the fifty that arrived in the order they happened to come in.

Plausibility Is the Danger, Provenance Is the Missing Signal
#

Understanding why the modern queue is dangerous is what defines the triage layer, because AI-generated code breaks the assumptions review used to rest on.

When a human wrote the pull request, you and the author shared a mental model. You could trust that the choices in the diff were deliberate, even imperfect ones, and a gap between your expectation and the code was an interesting signal, because it represented two human understandings of the same problem meeting. When a model wrote the code, there is no shared mental model, and there is no author who deliberated. The code is the output of a pattern-matching process, internally coherent, consistent with nothing around it, and wrong about the domain in ways that look exactly like correctness. This is the argument from Rethinking Code Review in the Age of LLMs, and it lands hardest in open source, where the reviewer and the “author” have never spoken.

What makes this crisis specific is the provenance gap. Contributors almost never disclose that the code is generated, let alone which model generated it, what prompt produced it, or whether a human checked the output before opening the pull request. Without provenance, you cannot assess risk. A fifty-line patch from a contributor who tested it by hand and a fifty-line patch a model hallucinated in three seconds look identical in the diff, and the diff is all you have. Plausible code with no origin story is the default input now, and it forces a worst-case assumption on every pull request: treat it as unverified until something proves otherwise.

The 2026 data shows how invisible this is by default. A census of 180 million repositories found that the obvious signal, a bot account, recovers only about three percent of the commits AI coding agents actually produced, so the overwhelming majority of machine-generated code reaches you with no detectable fingerprint. And the studies that watched what reviewers did with it found the same pattern: agent-generated pull requests carry more redundancy and technical debt than human ones, yet reviewers express more positive sentiment toward them, and the majority of AI-coauthored pull requests merge with no explicit human review at all. Plausibility is doing exactly the work of hiding the problem.

The implication for triage is concrete. First, the durable defense against plausible code is not a sharper opinion but a machine-checkable gate, because opinion-based review loses against code that was designed to look right. Tests, static analysis, type checking, reproducible builds: these operate on what the code does, not on how it reads, and they do not get fooled by confident prose. Second, the absence of provenance is itself a routing signal. A large, high-churn, undocumented pull request from an unknown contributor should default to high-risk, not because the contributor is malicious but because you have no evidence to assign it anything lower. Third, the one piece of information that would most improve your triage, which model and what prompt, is the one nobody is giving you, which means the cheapest high-leverage intervention available is to start asking for it.

Let Automation Carry the Logistics
#

The first tier of triage is pure logistics, and all of it is solved. You should not be tracking any of this by hand, and in 2026 you increasingly do not even have to build it yourself. The most consequential shift is that GitHub started shipping these gates at the platform level, because the flood crossed a threshold unpaid maintainers could not hold: per-user caps on open pull requests, pull-request archiving, and “smarter bypass” signals based on account age and merge history. The stated rationale is the one this article rests on: the cost to create a change has fallen below the cost to review it. When the host carries the load, your job narrows to the gates the platform cannot generalize.

Mark and close the stale, on a clock that forces a decision. A pull request that has had no activity for twenty-eight days is not waiting for you. It is rotting, and rot is contagious, because a queue full of stale PRs signals to new contributors that nobody is home. actions/stale marks a pull request stale after twenty-eight days, posts a warning, and closes it seven days later if the contributor does not respond. Twenty-eight is also the ceiling on a healthy open pull request, not just the stale timer. If a PR has been open more than twenty-eight days and is not merged, the right answer is almost never “keep waiting,” it is one of two things: break it into smaller pieces that can each land on their own, or reject it. A large PR that lingers is usually a PR that was too big to review in the first place, dressed up as a PR that is waiting for a reply. This is not cruelty. Letting a contributor’s work sit unread for a year is cruelty. A fast, automatic close with a clear “reopen if you are still interested” is a kindness, and it is a kindness that costs you nothing.

Surface the conflicting. A pull request that no longer merges cleanly is a pull request that is wasting your attention, because until the contributor rebases, your review is provisional. eps1lon/actions-label-merge-conflict adds a merge-conflict label the moment a PR falls behind main, and removes it the moment it merges again. Now you can filter label:-merge-conflict and only review work that is actually ready. The contributor also gets a clear, automated nudge that rebase is needed, without you having to be the one to say it.

Label by size. The blast radius of a change is the single best predictor of how much attention it deserves, a point I made in The Merge Gate: a README typo and a schema migration are both pull requests, and they do not need the same gate. A size action tags every PR with size/S through size/XL based on diffstat. Small changes become candidates for fast-track; large ones become candidates for “please split this.”

Label by area. actions/labeler tags a pull request based on which files it touches, so you can route area/ci, area/docs, area/security to the right context, or skip the areas you are not the expert in.

Flag the first-timers. Label first-time contributors explicitly. Not so you can be suspicious of them, but so you can be generous with them, because a good first review is how a first-time contributor becomes a second-time contributor, and a second-time contributor is how a project outlives its original maintainer.

Enforce the conventions you already require. If your project requires a linked issue, a semantic title, a signed commit, a test for every new function, enforce each one with a check that runs on open. amannn/action-semantic-pull-request is one example, but the specific tool matters less than the principle: anything you find yourself typing in review comments repeatedly belongs in a check that fails the build. A review comment is a standard you enforce only when you are awake. A failing check is a standard that runs forever.

Gate at the trust boundary, before the diff. The bluntest triage signal is whether you have any reason to trust the contributor at all, and in 2026 the canonical implementation of that idea is mitchellh/vouch, built for the Ghostty terminal against a wave of AI slop. It auto-closes pull requests from unvouched contributors and routes them through a vouching issue, so the queue you actually read is the queue from people who have earned a hearing. It is a strong filter, and a truthful one to use carefully: the cost is friction for legitimate newcomers, which is why the vouching path has to be a real door, not a wall.

Each of these is a small automation, and none of them review code. Together they collapse the queue from “everything that arrived” to “everything that is ready, relevant, and sized.” That is most of the battle, and it cost you zero hours of reading diffs.

Demand Provenance
#

The single most valuable triage signal is the one the current ecosystem refuses to provide, which means the maintainer has to require it.

Add two fields to your pull request template, and make them hard to skip. Was this change generated or significantly assisted by an AI tool? If so, which model, and what was the prompt or task description? A checkbox and a free-text line are enough. You do not need a policy on whether AI contributions are welcome; you need the data to triage them on their actual risk rather than on a guess.

This is no longer hypothetical. In 2026, rust-lang wired an AI policy into its contributing guide and pull request template, and it is not alone: scipy asks for the model name, qemu requires code provenance, Ghostty ships an AI_POLICY file, and the Linux kernel has long held the submitter responsible for attesting to AI-generated code. The convention is fragmenting into a field of per-project rules, which is messy, but the direction is clear, and the closest thing to a shared format, declare-ai’s provenance file, is emerging for exactly this gap. The platform will not hand you this signal for free. Everything GitHub has shipped for maintainers, per-user caps, archiving, smarter bypass signals, operates on volume and account history, and none of it tells you which pull requests a model wrote. If you want provenance, you have to ask for it in the template.

Label from the answer. human-authored, ai-assisted, ai-generated, and a tag for the model when it is disclosed. Now provenance becomes a filter and a routing input instead of a mystery. A human-authored patch from a known contributor with passing tests can travel the fast lane. An ai-generated patch with no model disclosed and no linked issue starts one gate further back, by default, because it carries the provenance risk this era is defined by.

Be explicit that disclosure is not a penalty. The penalty is discovery, when you eventually realize a PR was generated and the contributor hid it, because at that point the trust that makes open source work is gone and the PR is closed on principle. Disclosure is what lets a generated contribution compete for your attention on its merits. Concealment is what makes every generated contribution read as an attempt to slip something past you.

One caution, because the obvious next step is to reach for cryptographic attestation: signed provenance is not the same as trustworthy provenance. In 2026, cryptographically valid supply-chain attestations were produced for malicious packages, which means a signature confirms a chain of custody, not that the code is safe. Demand disclosure, but verify the code on its own terms, not on the strength of the attestation alone.

A maintainer cannot triage what they cannot see, and in a queue full of plausible code, the origin of the code is the first thing they need to see.

Let the LLM Do the First Pass
#

Once the logistics are handled, the remaining question is the one automation traditionally could not answer: is the code itself any good. That used to require a human, because reading a diff and reasoning about its consequences was exactly the task machines could not do. It is not anymore. An LLM will not review a pull request as well as you would, but it will review it in seconds, on every pull request, at three in the morning, and it will produce a structured signal you can sort and filter on. That is a different value than correctness, and for triage it is the value that matters.

The 2026 evidence is now strong enough to separate the hype from the result. On the hype side, independent benchmarks are blunt about the ceiling: across eight frontier models, reviewers catch only fifteen to thirty-one percent of the issues humans flag, and adding more context makes them worse, not better, while the first independent cross-vendor ranking puts the best tool at an F1 near fifty-one. An LLM review is a noisy signal, and treating it as a verdict is the mistake. On the result side, the largest production deployment of the year ran a hundred and thirty thousand review passes across five thousand repositories, and engineers reached for the human “break glass” override on six tenths of one percent of merge requests. That number is the point: the system ran unattended on the easy tier not because the model was a great reviewer, but because it was a great sorter, tiering by risk and handing a coordinator a structured finding to deduplicate and rank.

Run an LLM review on every pull request when it opens and on every push that updates it. Give it your evaluation criteria, explicitly, the same checklist you would walk through mentally if you opened the diff yourself. Does it add a new dependency? Does it change a public interface? Does it touch security-sensitive code paths? Does it introduce backward/forward-incompatible behavior? Are there tests for the new behavior? Does it match the naming and structural conventions of the surrounding code?

Ask for the output as structured data, not prose. A risk score and a confidence score, say on a one-to-five scale, a one-line summary, and a short list of specific findings. Then parse those fields and turn them into labels. risk:low, risk:medium, risk:high. confidence:high, confidence:low. needs-human-review when the model is unsure or when the risk is high. auto-merge-candidate when the risk is low, the confidence is high, the size is small, the tests pass, and the contributor is trusted.

Now your queue is sorted by signal instead of by arrival time. The pull requests that are low-risk and high-confidence can be merged on green, or queued for a single glance, because the LLM has already done the scanning work you would have done anyway. The pull requests that are high-risk, or where the model is uncertain, rise to the top of your attention with the findings already attached. You are no longer choosing what to read. You are confirming or rejecting a hypothesis the triage layer has already formed.

Wire those scores back into the logistics layer, so a pull request that fails the review does not just sit and wait for you. When confidence drops below three or risk climbs above three, post a comment that tells the author the specific findings they need to address, and let the stale action treat that as the notice that starts the closing clock. The contributor gets a concrete path to merge instead of silence, and if they do not take it the pull request closes itself, decided by the standard you encoded rather than by your mood on a given Thursday.

A few cautions, because this is the part people get wrong.

The LLM review is advisory, not authoritative. It hallucinates, it misses subtle bugs, and it is confidently wrong in exactly the register that makes you want to trust it. Never wire it to merge on its own verdict for anything that crosses a trust boundary, changes a public contract, or is hard to undo. Use it to route attention, not to replace it, and reserve the replaced attention for the small, reversible, low-risk changes where being wrong is cheap to fix. This is the same risk-based gating The Merge Gate argues for: the unit of gating is the blast radius of the change, not the existence of the pull request.

The deepest caution is specific to this era, and you have to internalize it before trusting any automated score. When an LLM reviews code that an LLM wrote, the reviewer shares the generator’s blind spots. Both are statistical models trained on overlapping corpora, and a mistake plausible enough for one model to make is often plausible enough for the other to overlook. Two models agreeing that “this looks fine” is a weaker signal than either model issuing that verdict alone, and if the reviewer and the generator come from the same model family the agreement proves almost nothing.

In 2026 this stopped being a conjecture. Recursive self-training studies show that an AI reviewer gating its own output collapses into a rubber-stamp regime, where acceptance scores rise while correctness falls, and that only model-independent checks, compilation, types, tests, slow the collapse without stopping it. The one direct measurement of the effect found that heterogeneous pairs, a Claude reviewer over Codex output, flag a defect sixty-nine percent of the time, where homogeneous pairs flag it only fifty-three percent of the time. Same-family agreement is measurably weaker than cross-family agreement, which is the empirical form of the warning above.

This is why provenance matters at the review layer as well: feed the disclosed model and prompt into the reviewer’s context so it can target the failure modes that model is known for, rather than re-reading the diff through the same lens that produced it. And it is why, for ai-generated pull requests, you must discount the confidence score and default toward needs-human-review unless the change is independently verified by something that does not share the blind spot: a passing test, a type checker, a reproducible build, a specification the code is checked against. The LLM review is one signal in the triage layer, never the only one, and against generated code its job is to surface hypotheses for a human or a gate to confirm, not to pronounce the code correct.

Calibrate by reviewing the reviewer. For your first month, read the LLM’s review on every pull request you also review yourself, and keep a tally of where it was right, where it was wrong, and where it missed something you caught. That tally tells you which criteria to strengthen in the prompt and which labels to distrust. An uncalibrated LLM gate is a liability. A calibrated one is a second pair of eyes that never gets tired, and that gets more accurate every time you adjust the criteria.

Post the rationale, not just the label. Have the action leave the summary and findings as a comment on the pull request. The contributor sees what was flagged, the maintainer sees why a label was applied, and the verdict is auditable rather than a black box. Transparency is what keeps an automated review from feeling like a gatekeeping robot, and it is what lets a contributor fix the problem before you ever have to look.

Route by Risk, Not by Arrival
#

Once the triage layer is producing labels, the routing writes itself, and it should match the risk profile of each change rather than the order it was submitted. The tiering is no longer theoretical: the production system cited above classifies every change into trivial, lite, or full tiers and spends twenty cents of review on a typo fix where it spends a dollar sixty-eight on a sprawling one, because the gate a change deserves is a function of its blast radius, not its existence.

Low risk, high confidence, small, passing tests: auto-merge on green, or batch them into a single weekly pass where you glance and click. High risk, or low confidence, or large, or crossing a security boundary: hold for human review, and review those first. AI-generated with no model disclosed, or no linked issue, or no tests: default to the human queue until a test or a specification proves it, regardless of how small it looks, because small and plausible is exactly the profile of a subtle bug. First-time contributor: prioritize the response, because the speed of your first reply decides whether they come back. Conflicting: invisible until rebased. Stale: closed.

Within the human-review queue, attack the oldest first. A pull request that has waited the longest is the one closest to going stale, and clearing it, by merging or by closing, is what keeps the queue from accumulating a tail that nobody will ever reach, so weight your attention toward age, not toward whatever happened to land on top today.

This is the open source version of the argument from The Merge Gate: treating every pull request as needing the same gate is a failure to think about risk, and most pull requests do not need a human at all. The maintainer who wins is not the one who reads the most diffs. The maintainer who wins is the one whose queue has been pre-sorted so that the diffs they do read are the only ones that ever needed a human.

The Contributor Relationship Is the Hidden Triage
#

There is a layer underneath all of this, and it is the one maintainers most often neglect, because it is not technical. Most of the pain of an overflowing queue is not the code. It is the guilt of unanswered contributors, the dread of opening the tab, and the slow resentment of work that is supposed to be voluntary but has started to feel like a debt.

Automation is part of the answer, but so is setting expectations, because a contributor who knows what to expect does not require a personal reply to stay patient.

Write a CONTRIBUTING.md that says what you will and will not accept, what a good pull request looks like, and how long response takes. Use a pull request template that asks for the linked issue, the motivation, and the test. Publish a response-time norm, even a truthful one: “I review pull requests on Thursdays.” State it, link it in every template, and let the automation reinforce it. Predictability is a contribution, and a maintainer who responds every Thursday is more sustainable than one who responds in a burst and then disappears for three months.

The economics underneath this are shifting, slowly. The clearest 2026 voice on maintainer sustainability argues that the polite channels, sponsorship and pledge drives, have failed, and that maintainers should take open source work on company time rather than donate their evenings, because attention donated after hours is attention that does not scale. The funding is real but thin: sovereign and industry programs disbursed millions to individual projects this year, with a sovereign fund investing over a million euros in a single project and the open source pledge setting a two-thousand-dollar-per-engineer floor, yet no 2026 survey has measured whether maintainer burnout actually fell. Treat the automation and the expectations as the structure that carries the weight, and the funding as the still-insufficient subsidy.

And learn to close fast. A fast, clear “no, and here is why” is a gift. It respects the contributor’s time, it keeps the queue fair, and it is almost always kinder than a silence that stretches into a year. The maintainer’s fear of seeming ungrateful is what swells the queue past recoverability. A no is not ungrateful. A no is an answer, and an answer is all a contributor is waiting for.

What to Do Next
#

You do not need to build the whole layer at once, and you should not try. Pick the single thing that is costing you the most attention right now and automate that one.

Add actions/stale and let it start closing the pull requests you were never going to get to. Add eps1lon/actions-label-merge-conflict and stop looking at diffs that are not ready to merge. Add a size labeler and a path-based labeler and make the queue sortable. Write the CONTRIBUTING.md you have been meaning to write, and add the two provenance fields, AI-assisted yes or no, and which model, to the pull request template, so every contribution arrives with the one piece of context this era hides by default. If the flood is mostly from contributors you have no reason to trust, add a trust gate like vouch before any of the rest, because closing unvouched pull requests up front is the single largest reduction in queue size available to you.

Then, and only then, wire in the LLM first-pass review. Start it in shadow mode, posting its summary as a comment without applying any labels, and read along with it for a month. When you trust its risk and confidence calls, turn the labels on. When you trust the labels, let the lowest-risk, highest-confidence, smallest changes merge on green. Each step is a slice of attention you stop spending by hand and start spending on the pull requests that actually need a human.

A sustainable open source project is not one where the maintainer reads everything. It is one where the maintainer has built a triage layer good enough that almost nothing needs to reach them unread, and what does reach them is exactly what was worth their time. Build that, one automation at a time, and the queue stops being the thing that owns you.

See also
#

  • The Codebase Gardener - the team-codebase version of the same argument: encode the standard where work is forced to pass through it, instead of carrying it as a per-PR review habit
  • The Merge Gate - the case for gating on the blast radius of the change rather than on the existence of a pull request, which is the principle behind risk-based triage routing
  • Rethinking Code Review in the Age of LLMs - why an automated first pass plus a precise specification outperforms a tired human scanning a diff, the premise the LLM review layer stands on
  • Defects Flow Downstream, Fixes Must Flow Upstream - why repeated review comments signal a missing check rather than a missing reviewer, the root of “encode it, do not retype it”

References
#


Learn the Foundation, Not the Syntax: Why Low-Level Languages Still Matter When the Machine Writes the Code

The question gets asked as a fork in the road: either drill new developers on low-level languages until they can write a kernel from memory, or accept that writing code is finished and retrain everyone into prompt-wielding product managers. It is a false dichotomy, and both branches are wrong for the same reason. They both confuse the surface of programming with the thing programming was always meant to teach, and that thing is now the only part AI cannot do for you.

Two Errors, Shared Confusion
#

The “teach them everything” camp treats low-level fluency as a production skill. It points at manual memory management, pointer arithmetic, and hand-rolled data structures as the price of admission to the profession, and it is right that these were once essential. It is wrong that they still are, as production. Writing C by hand stopped being the bottleneck the year a model could write it, read it, and port it faster than a careful senior could, and the market has already priced that in.

The “writing code is over” camp takes the same observation and overruns with it. If production is automated, the argument goes, the developer’s job becomes specification and orchestration, and the foundation the code runs on is somebody else’s problem, probably the machine’s. This is the AI-maxxing error applied to education, and it is the more dangerous of the two, because it feels like foresight while quietly removing the one capability that becomes scarcer and more valuable exactly as production gets cheap: the mental model of how the system actually behaves.

Both camps make the same mistake the resistor and the maximalist make in AI-Maxxing and Resistance Are the Same Mistake: they argue about how much low-level to use instead of asking what low-level is for. The answer to that question dissolves the debate.

What Low-Level Actually Teaches, and Why It Compounds
#

Strip away the syntax drills and the “implement linked lists in C” hazing, and a low-level language is a teaching apparatus for a small number of durable mental models. None of them are about the language. They are about the machine the language sits on top of, and they are exactly the models that become structural once you can no longer trust the code you are reading.

A model of execution. What lives in memory, what gets allocated where, what a call frame is, what happens when a function returns. This is not trivia. It is the difference between an engineer who can read a stack trace and one who can only read an error message, and the generated code that breaks in 2026 breaks in ways that only the first engineer can diagnose.

A model of cost. Big-O is taught in school and forgotten because it is inert until you have felt a cache miss, an allocation storm, or an N-plus-one query at the boundary between the ORM and the database. Low-level work is the cheapest known way to make that cost felt in the body, and once it is felt it transfers to every higher language you will ever touch. When the model produces plausible code that is also quietly quadratic across a network boundary, the person with a cost model catches it and the person without ships it.

A model of failure. Low-level code fails loudly: a segfault, a leak, a data race, a corrupted pointer. The cause is concrete and the lesson sticks. High-level and generated code fails softly, at the seams between abstractions, and the softness is the hazard, because soft failures train nothing and accumulate until they become outages. The engineer who learned on hard failures can debug the soft ones. The reverse is not true.

And, most importantly, a model of where the abstractions leak. Every stack you will ever work on is a tower of abstractions, and every one of them leaks under stress: the ORM leaks into SQL, the garbage collector leaks into latency, the container leaks into the kernel, the model’s confidence leaks into a hallucinated API call. When a leak surfaces in production, the person who can see through the abstraction to the layer underneath is the person who fixes it. Everyone else files a ticket and waits.

These four models are not production skills. They are not even, strictly, low-level skills. They are durable skills, in the sense Keeping Up With AI Is a Losing Strategy draws between the ephemeral and the durable: they do not decay across model generations, and they make every other thing you do, including supervising a model, more effective. That is the entire case for low-level in one sentence: it is the most efficient known way to build the models that do not depreciate.

Why “Writing Code Is Over” Is the Dangerous Half
#

Here is the asymmetry that settles which error matters more.

Forgetting low-level syntax is a recoverable error. The developer who never memorized the C calling conventions can look them up, or ask the model, the day they need them, and the cost is a few minutes.

Losing the mental model is not recoverable in the moment you need it. When the generated code is failing in production at three in the morning, there is no time to develop an intuition for memory layout, and the model that wrote the code is the same model confidently misdiagnosing it. You can build a mental model with an LLM as a tutor, over time, the same way you can build one with a good textbook or a patient colleague; the tool is not the obstacle, the hours of deliberate study are. What you cannot do is prompt one into existence under time pressure, and using the model to debug its own output already presupposes the very model it would take weeks to grow.

This is the shifting bottleneck wearing its sharpest face. Production was the bottom of the stack, and automating it moved the constraint up to verification, and verification is precisely the layer that demands the foundation knowledge the “code is over” camp wants to skip. The preparation that says “we will not need this because writing is automated” is the preparation that makes you unable to do the job writing’s automation created. You are optimizing away the exact layer the bottleneck landed on.

There is a near-term counter-argument worth taking seriously: that verification gets automated too, and then specification, and so on up the stack. Even granting that, the same framework says the bottleneck just moves to deciding what to build and whether what was built is correct, which still requires understanding systems deeply. I cannot find a version of this future in which understanding the foundation stops compounding, only versions in which the surface syntax stops mattering. Those are different claims, and the debate quietly collapses them into one.

The Onboarding Hole the Tools Opened
#

There is a structural reason this question is urgent now, and it is not philosophical. For most of the profession, a developer built their mental model of systems the only way the model can be built: by struggling with code that broke, reading core dumps, profiling slow paths, and fixing real failures under real pressure, repeatedly, for years. The struggle was the curriculum, and it was free, because it was simply the job.

The tools have quietly removed the struggle, and with it, the curriculum. An engineer who starts today can ship a feature without ever reading the code the tool wrote, without ever opening a profiler, without ever needing to understand why the first version was slow, because the tool never produced a slow first version for them to fix. The onboarding path that used to build that underlying mental model now bypasses it, and the onboarding paradox in Software Engineering Teams in the Age of AI is the downstream symptom: juniors ship faster and understand less, and the understanding gap is invisible until something breaks.

So the question is not whether to teach low-level. It is whether to teach it deliberately, because the accidental curriculum that used to teach it for free has been automated away. A generation that learns to prompt before it learns how a machine actually executes will be fluent at the surface and hollow at the foundation, and the hollowness will only become visible at the moment it becomes expensive, in production, at three in the morning, with no model able to help.

The Synthesis: Read the Foundation, Don’t Write It
#

The resolution is not a midpoint between the two camps. It is a different axis entirely.

Stop teaching new developers to produce low-level code as if they would ship it. Manual memory management as a daily craft, pointer arithmetic as a drill, hand-rolled allocators as a rite of passage: these are depreciating production skills, and spending years on them is the two-year test failing in slow motion. The surface area of low-level is large and mostly irrelevant to the work most developers will actually do, and Brooks’s old split between accidental and essential complexity still maps onto it cleanly: the syntax and the boilerplate are accidental, and the accident is exactly what the model now absorbs.

Do teach them to read the foundation. Read a stack trace down to the frame that matters. Read a flame graph and point at the function that is eating the budget. Read a heap profile, an strace, a slow query log, a core dump. Read the source of the standard library they use every day, at least once, until the abstraction stops being magic. Reading is cheaper than writing, it transfers to every language and every model generation, and it builds exactly the four models above without demanding the years of production fluency the old curriculum required.

The rule of thumb is blunt and useful: enough low-level to debug, not enough to ship. A few focused weeks of C or Rust, or even a careful tour through how the managed language you already use actually executes, is enough to install the models for a working lifetime, provided the engineer keeps reading systems instead of reading only diffs. A career of writing C, in 2026, is overkill for most roles and a misallocation of the time that should be going into domain depth and judgment.

And for the small fraction of work that genuinely lives at the foundation, embedded, kernels, databases, runtimes, high-frequency paths, the calculus flips and fluency is still required. The point is not that nobody should write low-level code. The point is that “should every new developer learn to write low-level code” is the wrong question, asked about the wrong layer, and the answer, which is “no, but every developer should learn to read the machine,” is what the two camps keep talking past.

What to Do Next
#

If you hire or mentor new developers, stop using “do you know C” as a proxy for anything. It measures syntax, and syntax is cheap.

Instead, hand them a deliberately broken program, one with a memory or concurrency bug hidden behind a clean high-level interface, and watch what they do. The ones who can form a hypothesis about the layer underneath are the ones who can supervise a machine. The ones who can only describe the symptom to the model and accept its first confident answer are the ones who will ship that bug to production and then be unable to explain it.

If you are a new developer yourself, do not let the tools talk you out of the foundation. Generate the boilerplate, take the shortcut, and then, separately, on your own time, read the source of something you depend on until you can explain how it actually works. The generation is free. The understanding is not, and it is the only part of this profession that the next ten years will reward more, not less.

The debate between “learn everything low-level” and “writing code is dead” is two ways of staring at the surface. The surface is going away. The foundation is not. Prepare accordingly.

See also
#

References
#

  • Spolsky, “The Law of Leaky Abstractions” - the original framing for why every abstraction eventually fails at the layer underneath, which is where foundation knowledge pays
  • Wikipedia, “Theory of Constraints” - the framework for why automating code production relocates rather than removes the bottleneck, landing it on verification
  • Wikipedia, “No Silver Bullet” - Brooks’s split between accidental complexity (the syntax and boilerplate AI now handles) and essential complexity (the mental model of the problem it cannot)
  • Wikipedia, “Accidental complexity” - the distinction that lets you sort low-level trivia, which is accidental and depreciating, from low-level mental models, which are essential and compounding
  • Wikipedia, “Vibe coding” - the extreme of the “writing code is over” posture, used here as the steelman argued against rather than a strawman
  • Willison, “Vibe coding” - a practitioner’s account of what you can and cannot safely delegate, and why supervision still requires understanding the output

What I've built and what I need: June 2026

Over the month since May, the agents repository evolved from building the SDLC pipeline into a mature, self-improving system. The biggest shift was adding verification and consistency tooling, introducing stateful SDLC tracking, and standardizing conventions across the skill library.

What I Have Been Working On
#

Added verification and consistency tooling. The latter half of the month focused on checking the system’s own work. validate-pr runs runtime validation of PRs, checking out a branch, building it, and proving every claim in the PR description through execution. verify-pr performs static code inspection covering quality, correctness, and architecture alignment. backpropagate-sdlc walks the SDLC artifact chain in reverse, from code back to issue, to verify end-to-end traceability and detect drift accumulated across phases. sync-repository ensures the entire repository stays consistent across SDLC, code, tests, documentation, types, and observability. improve-autonomy assesses what would be needed to run a session fully autonomously end to end.

Overhauled the SDLC infrastructure. The old bootstrap-sdlc was replaced by sync-sdlc, which reconciles the codebase with the .sdlc/ directory instead of just bootstrapping it. A new .sdlc/state.yml file in worktrees tracks the current phase, GitHub reference, and feature, giving skills persistent context across invocations. The sdlc-status skill was extracted and heavily improved through multiple iterations, adding vocabulary tooltips, open questions, and a footer. Observability and telemetry phases were added to the SDLC pipeline, extending coverage from needs assessment through deployment and retrospective. qualify-issue drives a multi-round Q&A loop to gather enough information before requirements. create-feasibility / review-feasibility and create-existing-solutions / review-existing-solutions assess viability and survey prior art before committing to a build. fix-issue orchestrates a bug fix end to end by delegating to reproduce-issue, create-implementation, and create-pr. Post-merge coverage extends through deploy-pr and observe-production, which deploy changes and verify SLOs and error rates in production. Production health is planned from the start with create-observability / review-observability / audit-observability, which define logging, metrics, tracing, and alerting for each feature. create-telemetry / review-telemetry define the analytics events for each feature. Cross-skill conventions were consolidated into a shared references file, and per-repository AGENTS.md overrides can now be stored under repositories/ for project-specific instructions. SDLC artifacts produced by the skills across projects I contribute to or monitor are tracked in a dedicated sdlc repository. That repository includes automation to generate static HTML status pages from the output of the sdlc-status skill. Each project’s artifacts are reviewed weekly by sync-sdlc to detect and correct drift.

Shipped 7 maintenance skills for code quality. A new “maintenance” skill category was added covering security auditing, code duplication detection, type gap analysis, churn analysis, and related diagnostics. These were integrated into the SDLC pipeline as a structured diagnose-harden-clean-document workflow, giving every feature a code health pass alongside the existing requirements-to-learnings flow.

Polished PR and issue workflows. Issue triage gained duplicate detection and smarter label management, and triage-issue was split out from triage-issues for single-issue versus batch processing. The ghx CLI was introduced and integrated into the review and feedback skills. It adds inline PR review comments, pending reviews, and comment stashing that the standard gh CLI does not support. Reversibility checks were added across review-* skills, and forward compatibility checks were introduced. create-issue had its duplicate checking restored and time estimates improved. A new automate-session skill was added to identify repetitive workflows worth encoding as skills.

Added new lifecycle and utility skills. create-needs-assessment and review-needs-assessment evaluate whether a feature addresses a genuine need before investing in feasibility or requirements. research-article maps the state of the art before writing, producing a research brief with organized sources. setup-docs-site scaffolds a MkDocs documentation site with a GitHub Actions publish workflow. vacation-handoff generates a pre-leave handoff covering deadlines, on-call coverage, and in-flight work. audit-attention analyzes how time splits between compounding and depreciating activities using the two-year test, suggesting what to delegate versus protect. create-message improves outgoing messages by removing negative tone and adding actionable suggestions. summarize-meeting produces a structured meeting summary from a transcript file. review-skills and compare-skills audit the skill library itself for duplicates, broken references, orphaned skills, and practices worth adopting from other libraries.

Closed three of last month’s needs. Replying to inline PR comments, listed as a need in May, is now fully integrated into the review and feedback skills via ghx. Incremental PR description updates, also listed in May, are now handled by the update-pr-description skill, which makes minimal adjustments to the existing description after new commits rather than regenerating from scratch. Bug reproduction now triggers automatically when the “bug” label is added to an issue via reproduce-issue, though commenting results back on the issue is still unreliable.

What I Currently Need
#

From last month, the following are still needed in priority order:

The new needs for this month:

Skill usage tracking. I started using agentsview, which lets you filter conversation logs on terms. Given a list of skill names over time, it is possible to filter on those names as long as they were not expanded. OpenCode only expands slash commands if they start a message, so prefixing them with a space prevents auto-expansion and would allow tracking, but that is an ugly hack that needs a cleaner solution.

Contextual Slack support. slack-cached can already read Slack threads, and given the right working directory it may have enough relevant context to help users better than generic Q&A pairs. What remains is wiring it into an actual support workflow.

Automate issue triage follow-up conversations. The triage-issues skill currently sends a single message when an issue is received. If the user replies, no further LLM interaction happens. The triage loop should continue the conversation until the issue is fully qualified.

Reliable bug reproduction comments. The reproduce-issue skill now triggers automatically when the “bug” label is added to an issue, but commenting the reproduction results back on the issue is unreliable and needs to be stabilized.

Introduce loops to automate skill usage. As described in Loops as Files, loops provide a scheduling layer that can run skills on a recurring basis without manual triggering. Wiring skills into loops would enable workflows like automated issue triage, weekly repository syncs, and scheduled PR reviews to run unattended.

Explore routines, habits, and event-driven skill triggering. Beyond loops, I want to investigate routines and event-driven triggers that fire skills when the right conditions occur. As the skill library grows past 130 skills, some are never used simply because nothing is wired to invoke them. Connecting those skills to the right trigger or schedule would unlock work that currently goes undone.

Improve the SDLC status report. As I work on large features, iterating involves back and forth between SDLC stages that requires changes to be backported and then forward propagated. The sdlc-status report needs better content and visualization to make this flow visible and navigable.

Reduce manual testing and validation. I spend too much time manually testing and validating that a feature works as expected. The improve-autonomy skill should help close this gap, but the challenge is behavioral: I instinctively revert to doing it myself instead of immediately asking how an agent could do what I am about to do.

Rework llm-augmented-workflows for flexibility. llm-augmented-workflows needs to be reworked to handle feature and bug fix requests flows more flexibly, and to support other harnesses beyond Claude Code such as OpenCode and Pi.


AI-Maxxing and Resistance Are the Same Mistake: Optimize Attention, Not AI Quantity

Two camps have hardened around LLMs, and they sound like opposites. One wants AI in everything, automating every task that can be automated, measuring success by how little the human touches. The other wants AI held at arm’s length, preserving the craft, the understanding, and the roles that made the field what it is. They are not opposites; they are the same mistake pointed in different directions, and the mistake is optimizing how much AI to use instead of where to spend the attention AI frees up.

Two Stances, One Error
#

AI-maxxing treats the fraction of work delegated to a model as a score to be maximized. If a task can be automated, it should be, and the human’s remaining job is to orchestrate ever more automation. Its logical endpoint is the self-evolving codebase, the LLM agent company, the one-person team driving a dozen agents in parallel, none of them jokes and all of them early sketches of a real stance.

Resistance treats the fraction of work kept in human hands as a value to be defended. If a task can be done by hand, it should be, because doing it by hand is what builds the skill and the identity that make the work worth doing. Its logical endpoint is the engineer in 2026 still typing boilerplate from memory and calling it craft.

Each camp carries a real insight. The AI-maxxer is right that production has stopped being the bottleneck, and that clinging to manual production is no longer rational. The resistor is right that not everything delegable should be delegated, because delegation can erode the very capability that makes delegation safe. Both are right about the other’s blind spot and wrong about their own, because neither is asking the question that actually decides the outcome.

The Question Neither Camp Asks
#

The question that matters is not “how much AI should I use?” It is “of the things I do, which are depreciating and which are compounding, and does AI move each one in the right direction?”

A depreciating activity is one whose value drops as the environment changes. Typing boilerplate, memorizing an API, writing the fifth CRUD endpoint of the week: these were valuable when production was scarce, and they are melting in value every month that models get better. A compounding activity is one whose value rises the more of it you do, and that feeds back into everything else. Understanding a domain deeply, holding taste about what to build, judging whether a piece of code solves the right problem: these do not decay, and they make every other thing you do more effective.

The right stance falls out of this distinction immediately. Delegate depreciating activities ruthlessly, and protect compounding activities ferociously.

The AI-maxxer breaks this by applying the delegation rule to everything, including the compounding activities, and slowly hollows out the judgment layer that makes the delegation produce anything worth having. The resistor breaks it by applying the protection rule to everything, including the depreciating activities, and slowly mortgages the future to preserve a capability the market no longer rewards.

Why AI-Maxxing Is the More Insidious Error
#

Here is the asymmetry that lifts the argument out of a polite “both sides” essay.

Resistance is a recoverable error. The resistor falls behind, notices eventually, and can adopt the tools later. The depreciating skills they protected are still useful during the transition, and the AI is still there, waiting. The cost is lost time, which is real but bounded.

AI-maxxing is an insidious error, because it feels like winning the whole time. The output keeps flowing. The pull requests keep landing. The agents keep producing plausible, confident, well-formatted work, and nothing in the loop tells you that the judgment layer underneath has been quietly atrophying.

Delegation is not free even when the model is free. Every time you delegate a compounding activity, deciding what to build, evaluating whether the generated code is correct, choosing between two architectures, you forgo the practice that built the judgment you would need to evaluate the delegation. The AI-maxxer assumes judgment persists without exercise. It does not. A mind that never decides what to build loses the ability to tell whether what was built is worth shipping, and no model substitutes for that loss, because using the model well already presupposes it.

This atrophy is the hole I pointed at in The Shifting Bottleneck: when you automate a layer, the bottleneck moves up to a more judgment-heavy layer, it does not disappear. The difference is that the bottleneck article described the move as something that happens to the system. AI-maxxing is what happens when you mistake the move for a disappearance, and convince yourself there is no bottleneck left worth staffing.

Why Resistance Is Wrong, but Less Dangerously
#

The resistor’s error is the mirror image, and it deserves to be named fairly.

Most of what the resistor calls craft is just production, and production has stopped being the scarce thing. Writing code by hand does not teach the domain faster than reading code, including generated code, with intent. Memorizing an API does not make you a better architect; it makes you a faster typist, for an API that will be deprecated in eighteen months. Protecting a depreciating activity in the name of craft is not craft; it is nostalgia with a deadline.

The legitimate kernel inside resistance is the fear that delegation erodes capability. That fear is correct, and the AI-maxxer should borrow it wholesale. But the answer is not to refuse delegation across the board. The answer is to refuse it selectively, at exactly the compounding activities where the erosion matters, and to embrace it everywhere else so aggressively that you buy back the time to do the compounding work properly.

The resistor who types their own boilerplate to “stay sharp” ends up with less time for the deep domain work that would actually keep them sharp, and they are protecting the wrong layer. The AI-maxxer who delegates the deep domain work to “focus on orchestration” ends up with nothing left to orchestrate well, because orchestration without domain depth is just queue management.

The Test That Settles It
#

For any activity on your plate, ask one question. If I let the model do this for the next two years, will the me that emerges be more valuable, or less, than the me that kept doing it by hand?

Boilerplate, scaffolding, routine tests, formatting, summarizing a thread, drafting a first pass at a known pattern: delegate all of it, and the you that emerges after two years is more valuable, because you spent those two years on something else that compounded. Deciding what to build, judging whether a design is right, reading a hard paper with the intent of being able to teach it, debugging a subtle failure by reasoning about the system: keep these, even when a model offers to do them, because the two years of practice is the entire asset.

Notice that the test is about the allocation of your attention, not the quantity of AI you use. An engineer who delegates ninety percent of their work to a model and spends the freed time going deeper on the remaining ten percent is not the AI-maxxer this essay argues against. They are doing it right, and the high delegation ratio is a symptom of having correctly identified their compounding layer, not a target they are optimizing for. The number to watch is not how much you delegate; it is how much of your remaining time lands on compounding work.

The Boundary Moves, and That Is the Hard Part
#

The complication, and the reason the sorting is not a one-time exercise, is that the line between depreciating and compounding is not fixed.

A thing that was compounding yesterday can become depreciating tomorrow. Writing SQL by hand was once a compounding skill, a path to a deep understanding of how the data informs the business; today the model writes the query and the deep part is knowing what to ask it for and whether the result is truthful. Reading logs was once a compounding skill; increasingly the model triages them and the deep part is deciding which anomalies matter. Each shift in capability redraws the line, and the stance that worked last year can become either error in the next.

The moving boundary is why neither camp has a stable answer. The AI-maxxer’s “delegate everything” is wrong because some of what they delegate is still compounding, and the resistor’s “do everything by hand” is wrong because some of what they protect has already stopped compounding. The only stable skill is the meta-skill of repeatedly telling the two apart, and that meta-skill is itself compounding, which is the strongest case I can make for spending attention on it.

What to Do Next
#

Stop measuring yourself by how much AI you use, in either direction. Run the inventory instead. List the activities that fill your week, and mark each one depreciating or compounding, using the two-year test. Then push hard on both ends: delegate the depreciating ones as aggressively as you can, and block off time for the compounding ones so that the time you bought back actually lands on them.

Do not be surprised if the result is a higher delegation ratio than the resistor would tolerate and a lower one than the AI-maxxer would brag about. That is what getting it right looks like. The ratio is an output, not a target.

And watch the boundary. Re-run the inventory every few months, because the model’s growth will have moved it, and an activity that was worth keeping may now be worth delegating, or, more dangerously, the reverse. The most expensive mistake in either direction is the one you keep making because you sorted the list once and never looked again.

The AI-maxxer and the resistor are each certain they have found the answer, and that certainty is the real cost. The correct stance is uncomfortable: delegate like a maximalist, protect like a minimalist, and never stop asking which is which.

See also
#

References
#

  • Goodhart’s law - why maximizing a proxy (fraction of work delegated) undermines the outcome the proxy stood for
  • Theory of constraints - the framework for why removing one bottleneck reveals the next, and why delegation never eliminates the judgment layer
  • Automation bias - the tendency to over-trust automated output, the failure mode that lets AI-maxxing feel like winning while judgment atrophies
  • Satisficing - settling for “good enough” outputs, the silent cost of delegating compounding activities to a system that optimizes plausibility

Keeping Up With AI Is a Losing Strategy

Every week brings a new frontier model, a new agent framework, a new benchmark, and a new wave of posts explaining why this one changes everything. The production of AI-related information is growing exponentially. My ability to consume it is not, and neither is yours. Keeping up is the wrong goal, and chasing it will quietly consume the time and attention you need to actually use any of it.

The Asymmetry
#

Information overload is not new. What is new is the asymmetry between production and consumption in a domain that is itself being transformed by the technology it covers.

A human reads at roughly the same rate today as a generation ago: a few hundred words per minute, a handful of long-form pieces per day, a finite number of hours in the week. Meanwhile the supply of AI content is compounding. arXiv’s machine learning categories add thousands of papers every month, with submission volumes climbing year over year. Model providers ship flagship releases on a monthly cadence where they used to ship annually. And now a meaningful fraction of that content is itself written, summarized, or generated by LLMs, which closes the loop: AI produces content about AI faster than humans can read content about AI.

The loop is structurally different from “there is a lot to read.” The half-life of any specific fact is shrinking while the rate at which new facts arrive is rising. Both halves of the ratio are moving against you at once.

Why “Just Keep Up” Fails
#

The instinctive response to a firehose is to read faster, subscribe to more newsletters, and carve out more time. I tried all three. Reading faster works for a linear stream. The strategy collapses against an exponential one.

Every new source you add also adds its own outgoing links, its own recommendations, its own “you should also read this.” The graph of things you could read grows faster than the list of things you have read. You are not behind because you are undisciplined. You are behind because the target is moving faster than your means of reaching it.

The cost is not only time. Constant context-switching between model announcements, framework launches, and benchmark debates produces the same attention residue and decision fatigue that come from managing too many concurrent tasks. The result is a state of feeling informed without being informed, a steady hum of awareness with no depth underneath it. You know the names of ten new models and cannot explain how any of them differ in a way that matters.

The Reframe: Optimize for Signal, Not Coverage
#

Coverage is impossible, so stop measuring yourself against it. The objective is not to read everything. The objective is to make good decisions about what to read, what to build, and what to ignore.

Making those decisions is a filtering problem, not a reading problem. And filters are something you can design, tune, and improve, unlike your reading speed.

Separate the Durable From the Ephemeral
#

Most of what feels urgent in the AI cycle is ephemeral. A specific model’s benchmark numbers, a specific launch event, a specific viral demo: these date within weeks. A surprising amount is durable. The mechanics of attention, the basics of context and prompting, the theory of constraints in a pipeline, the economic profile of inference costs, the difference between evaluation and vibes: these barely move across model generations.

When you are time-constrained, overweight the durable. Read one paper that teaches you a concept you will reuse for years over ten posts that summarize this week’s leaderboard. The ephemeral items are not worthless, but they should be consumed on demand, when a specific decision requires them, not on a schedule driven by fear.

Build a Funnel, Not a List
#

A reading list is a flat pile of undifferentiated “should read.” A funnel is a staged filter that gets cheaper at each stage.

At the top, cast a wide net through a small number of trusted curators and aggregators, and let them do the first pass. At the middle, use titles and one-paragraph summaries to decide what earns a closer look. At the bottom, read the few items that survive, deeply. Most items should die at the top of the funnel, never reaching your full attention. The goal of a funnel is to throw almost everything away, confidently and without guilt.

This is the same principle behind supervising many agents at once: you cannot hold it all in working memory, so you externalize state, standardize summaries, and let most of the system run without your involvement (see Managing Many Concurrent LLM Agent Sessions).

Use AI to Compress the Stream to Your Bandwidth
#

The same technology producing the flood is also the best tool for surviving it.

Have an LLM summarize a forty-page paper into the three things you actually need. Have it compare two model release notes and tell you what differs in a way that affects your work. Have it triage a week of submissions and rank them against your stated interests. Used this way, the LLM is a lossy compressor tuned to your specific decision bandwidth.

The risk is that summaries carry the summarizer’s blind spots. Mitigate it by going to primary sources for anything you intend to act on, and by keeping the durable layer of your understanding built from full readings, not compressions. Summaries are for triage and breadth; depth still requires reading the original.

Consume on Demand, Not on Schedule
#

Push consumption, reading whatever arrived in your feed today, optimizes for the producer’s schedule. Pull consumption, reading what a current problem requires, optimizes for yours.

Most of what you “need to know” about a new model or tool only matters once you have a task that touches it. Until then, the information is inventory. When the task arrives, the information is usually still there, and a focused hour of just-in-time learning will beat ten hours of distracted pre-reading. Knowing where to find something when you need it is a more durable skill than knowing it now.

This is uncomfortable, because it requires trusting that you will not miss something critical. You will miss some things. You will also miss things by spending your day reading instead of doing, and those misses are silent.

Pick a Few Sources and Defend Them
#

There are more newsletters, podcasts, and feed channels than any person can sample, and most of them recycle the same primary sources. Find the small set that consistently adds signal rather than volume, and defend that set against additions. Every new subscription should displace an existing one, not stack on top of it.

The same applies to people. A few colleagues or communities that share your context will filter better than any algorithm, because they know what you already know and what you are trying to do.

The Skill That Compounds
#

Fighting the firehose makes it easy to miss what actually compounds.

Your current knowledge of AI is a depreciating asset. Specific model behaviors, tool APIs, and leaderboard rankings decay within months. What does not depreciate is the rate at which you can learn a new tool when you need it, the quality of your filter, and your grasp of the durable layer underneath the releases. These compound, and they are the only things in this cycle that do.

A developer who has internalized the theory of constraints, the structure of an LLM pipeline, and the difference between evaluation and marketing can pick up this week’s framework in an afternoon. A developer who chased every release for two years but never built that foundation knows a hundred tool names and cannot reason about any of them. The first developer is robust to the pace of change. The second is a hostage to it.

The Bottleneck Moves, Again
#

The deeper pattern is the one described in The Shifting Bottleneck. When code generation stopped being the bottleneck, review became it. When review is automated, specification and judgment become it.

Information consumption has its own version. Once reading everything stops being possible, triage becomes the bottleneck. Once triage is delegated to AI, choosing what to care about, at the level of goals and taste, becomes the bottleneck. Each time you solve one layer, the constraint moves up to a more human, more judgment-heavy layer. The judgment layer is where the durable work sits, and it is also the layer least helped by reading faster.

What to Actually Do
#

Stop trying to keep up. Pick a small set of high-signal sources and ignore the rest. Overweight durable concepts over ephemeral releases. Build a funnel that throws most things away. Use LLMs to compress the stream to your bandwidth, then read primary sources for anything you will act on. Switch from push to pull: learn on demand, when a real task requires it. And spend the time you reclaim on the layer that compounds, your filter, your learning rate, and your judgment.

You will miss things. So will the person who reads all day, and they will also have missed the chance to build anything with what they read.

See also
#

References
#


Loops as Files: The Scheduling Layer Skills Forgot

Skills as files solved one half of the automation problem. They told the model how to do a task, versioned and reviewable, loaded into context on demand. They did not solve the other half. They told the model nothing about when to run. A skill is inert until something invokes it, and in practice that something is almost always a human typing a command.

That leaves the most experienced agent in your system doing nothing until you remember to ask it. It also leaves every event in your environment, the issue that was just opened, the Slack thread that just heated up, the dependency that just shipped a security patch, waiting for a human to notice and forward it to the right skill. The human has become the cron.

The fix is the same form as the fix for the variance problem in Bringing Everyone to the Same Level: take the invisible process out of someone’s head and make it a file. Except the process to extract now is not “how do I triage an issue,” it is “when do I triage issues, and what triggers that decision.” That belongs in a file too, and that file is a loop.

The Asymmetry Between How and When
#

Skills files normalized a useful idea. The prompt is the asset, the asset is text, text is versioned in git, and versioned text is reviewed, diffed, shared, and reused like code. The same model that made skills valuable applies cleanly to the trigger layer, but the trigger layer is still being treated as plumbing. It lives in crontabs nobody reads, in GitHub Action YAMLs that drift away from the skills they invoke, in shell scripts that bake agent invocations into system paths.

The result is that the two halves of an autonomous workflow live in different worlds. The prompt is curated. The trigger is improvised. When the prompt changes, the trigger does not, and when the trigger breaks, nobody who understands the prompt finds out until the agent has been silent for a week.

The schedule is part of the behavior. A skill that runs on /deploy and the same skill that runs at 02:00 every Tuesday are not the same skill. They have different blast radius, different cost profile, and different failure modes, and they deserve to be specified, reviewed, and owned together.

What a Loop File Is
#

A loop file is a markdown document with two parts. The frontmatter declares when it runs and under what constraints. The body declares what it does, almost always by composing skills that already exist.

In form it is the closest existing analog to a GitHub Actions workflow file, except the unit of work is not a shell command, it is an agent invocation with a loaded skill.

A minimal example:

---
name: hourly-issue-triage
description: Triage new issues as they appear, every hour.
on:
  schedule:
    cron: "0 * * * *"
skills: [triage-issue]
budget:
  max-runs-per-day: 24
  max-cost-usd-per-run: 0.50
---

# Hourly Issue Triage

For every issue opened since the last run, invoke `/triage-issue`.
Defer to the skill for all classification logic.
Stop when the budget for this run is exhausted and resume on the next tick.

That is the whole artifact. The skill owns the how. The loop owns the when, the how-often, the how-much, and the what-to-do-when-it-breaks. Each concern is in its own file, each file is reviewable, and changes to one do not silently invalidate the other.

The Frontmatter Is the Contract
#

The reason the frontmatter matters more than the body is that the frontmatter is the part the runtime actually parses. It is the contract between the human writing the loop and the system executing it.

A skill with no frontmatter is still useful as a document. A loop with no frontmatter is a skill with no trigger. So the fields that go in the frontmatter are not cosmetic metadata, they are the trigger specification, and they should be designed the way any interface is designed, with the smallest set of concepts that covers the realistic workload.

The field I want to anchor on is on, borrowed deliberately from GitHub Actions because the mental model is already widely understood. on says what causes this loop to fire, and its value can be one or many of three things.

Time
#

on:
  schedule:
    cron: "0 * * * *"

Cron is the obvious first case, and there is no reason to invent a new syntax for it. Cron is ugly, but it is universally understood, parseable, and already supported by every scheduler the reader is likely to have. Most loops that need to run on a clock are well expressed as a single cron expression, and loops that need more complex recurrence can compose multiple loop files.

A secondary form worth supporting is an interval, for cases where wall-clock alignment does not matter:

on:
  interval:
    every: 15m

Interval is easier to read and easier to distribute across a fleet, since N agents with every: 15m will naturally desynchronize in a way that cron will not.

Events
#

Time is the easy case. The interesting case is event-driven, which is where the word “loop” undersells what is going on. A loop that fires on a schedule is just a cron job with a markdown file on top. A loop that fires on an event is something genuinely new: a versioned, reviewable handler for things that happen in the world.

on:
  events:
    - type: github.issues.opened
      repo: "owner/repo"
      filter: "labels.length == 0"

    - type: slack.mention
      channels: ["#support"]
      filter: "text contains 'sev1'"

    - type: github.pull_request.opened
      repo: "owner/repo"
      filter: "author.trust < 'trusted'"

    - type: file.changed
      paths: ["pyproject.toml", "uv.lock"]

    - type: metric.threshold
      metric: api.error_rate
      window: 5m
      op: ">"
      value: 0.01

The pattern is the same in every case: a typed event source, an optional scope, and an optional filter expression. The types themselves are namespaced by source (github.*, slack.*, file.*, metric.*) so that adding a new integration is additive rather than a schema change.

A single loop can declare multiple events, and the runtime treats them as a logical OR. That covers the common case of “I want this skill to fire on a cron or when a human pokes it,” which is exactly the pattern of most operational loops.

Webhooks
#

The third trigger is a catch-all for events the runtime cannot subscribe to directly. A webhook is an event source the runtime exposes rather than consumes.

on:
  webhook:
    path: /loops/deploy-staging
    secret: ${DEPLOY_WEBHOOK_SECRET}

Anything that can hit an HTTP endpoint, from a monitoring tool to a ChatOps button to a physical device, can now trigger a skill, and the access control lives next to the prompt it gates.

The Frontmatter That Prevents the Loop From Eating Your Wallet
#

Time and events say when to start. The loop also needs to say when to stop, and this is the part that existing skill files do not need and loop files cannot live without.

budget:
  max-runs-per-day: 24
  max-cost-usd-per-run: 0.50
  max-concurrent: 1
concurrency: cancel-previous  # cancel-previous | queue | drop
timeout: 10m
on-failure: alert      # alert | retry | escalate | ignore
escalation:
  skill: notify-on-call
  after: 3 failures

I argued in The Self-Evolving Repository that cost runaway is one of the defining failure modes of autonomous systems, and a loop without a budget is one bad while True away from being a case study. Every loop file should be able to answer three questions without ambiguity: how often can this run, how much can each run spend, and what happens when it fails. If a loop cannot answer those questions, the runtime should refuse to start it.

The concurrency field is the other one that earns its keep early. A loop that runs every five minutes and takes six minutes to finish will, without a concurrency policy, slowly consume every slot the runtime has. cancel-previous, queue, and drop cover the realistic cases, and the right default is cancel-previous for anything stateless.

Loops Compose Skills, They Do Not Replace Them
#

There is a temptation, once you have a loop format, to start inlining the prompt into the loop body. Resist it. A loop file should read like an orchestrator, not like a skill, because it is one.

The body of a loop is typically a short sequence of skill invocations, with just enough glue logic to express ordering, branching, and handoff.

# Nightly Dependency Review

1. `/audit-dependencies` against the current lockfile.
2. If it reports any `severity >= high`, `/create-issue` with the audit output.
3. `/notify-on-call` summarizing counts and the worst offender.

Three skills, one paragraph of orchestration, fully readable. The logic that is hard (what counts as a high-severity vulnerability, how to phrase an issue, who the on-call is) lives in the skills it invokes, where it can be improved independently, tested independently, and reused from other loops.

This also keeps the loop file faithful to its job. A loop file that grows past a screen of text is probably doing the work of a skill and should be split. The same length discipline that keeps skills effective, which I borrowed from the “lost in the middle” argument in Bringing Everyone to the Same Level, applies to loops. If the runtime has to parse a long preamble before it even reaches the trigger, the trigger is no longer the contract.

Why Loops Have to Be Files
#

Every argument for skills as files applies, with minor edits, to loops as files, and one new argument applies only to loops.

Versioning. A trigger change is a behavior change. “Run triage on every new issue” and “run triage on every new issue except those from outside contributors” are different policies with different consequences. A trigger that lives in a crontab or a GitHub Actions YAML that nobody reviews is a policy that nobody reviewed, and reviewing it matters because the consequences of a trigger bug (silently not running, or silently running too often) are usually larger than the consequences of a prompt bug.

Reviewability. A loop file in git means every change goes through a pull request, which means the blast radius of the change is visible to the people who will be paged when it goes wrong. This is the same point I made, about code, in The Codebase Gardener: a standard that lives only in someone’s head is a standard that dies when that person goes on vacation. A loop that lives only in someone’s crontab has the same half-life.

Portability. A loop file describes what should happen, not where it runs. The same file can be executed by a local scheduler during development, by a team-shared runner in production, and by a CI provider that wants to dry-run it on every commit. That portability is what makes loops shareable across teams the way skills are shareable, and it is what makes a “loop library” a coherent concept in a way that a “crontab library” never was.

Diffability. The single most useful property of a file is that you can git blame it. When the on-call gets paged at 03:00 because the deployment loop has been firing every two minutes for an hour, the first question is “who changed the trigger, when, and why,” and the answer should be one git log away, not an archaeological dig through a CI settings UI.

The argument that applies only to loops is co-location with the skill. A skill file and the loop that schedules it are describing two facets of the same behavior, and when they live in the same repository, in the same format, in the same review pipeline, they evolve together. A skill change that should have changed the trigger (say, “this skill is now expensive, run it less often”) is a change that can actually be made in the same pull request by the same person who understood the consequence.

What the Runtime Owes You
#

A loop format without a runtime is a markdown opinion. The runtime is what makes loops safe to leave running, and it owes the operator a small, specific set of behaviors that are not optional.

Idempotency by default. A loop will be double-triggered. The cron will fire twice during a clock skew. The webhook will be retried. The runtime must be willing to run the same loop with the same inputs twice and treat the second run as a no-op, which means the skills it invokes must be idempotent, which is a property worth designing into skills whether they are loop-driven or not.

Per-run state. Every run writes a state file, in the same form I described in Managing Many Concurrent LLM Agent Sessions: what fired, what ran, what it produced, what it cost, what it will do next time. Without this, debugging a misbehaving loop is reading logs, and reading logs is what we used to do before we had files.

A kill switch. There must be one command, one CLI flag, one environment variable, that stops every running loop and disables every scheduled trigger. The cost of not having this is the cost of not having a circuit breaker in your electrical panel.

Observability that is not optional. A loop that fails silently is strictly worse than no loop at all, because no loop at least fails loudly when a human expected it. Every run emits run-started, run-succeeded, run-failed, run-budget-exceeded events, and the runtime ships them to whatever sink the operator already trusts.

Dry run. --dry-run evaluates the trigger, selects the skill, expands the inputs, and prints what it would have done, without invoking the model. This is the cheapest possible way to debug a trigger filter, and it should exist on every loop runner.

What Goes Wrong
#

Loops inherit the failure modes of any autonomous system and add two of their own.

Drift toward the measurable. A loop that fires on github.issues.opened will, over time, optimize the project for issue-driven signals, because those are the signals that cause work to happen. The project stops reacting to anything that does not show up as an event the loop can see. This is Goodhart’s law applied to ops automation, and the defense is the same as in The Self-Evolving Repository: keep a human-edited roadmap, and reserve a small number of loops for periodic “what should we be working on” reflection that is not event-driven.

Loops stepping on loops. Once loops are cheap to write, people write a lot of them. Two loops that both touch the same issue tracker, both with their own opinions about labels and priorities, will quietly fight each other, and the issue tracker will lose. The runtime needs loop-level isolation (separate working directories, separate state files, separate rate limits) and a registry that makes it easy to answer “which loops currently fire on this event,” the same way a codebase makes it easy to answer “which tests currently exercise this function.”

Trigger rot. A loop that worked when it was written will silently stop working when the thing it triggers on changes form, the GitHub webhook payload gains a field, the Slack channel gets renamed, the metric gets relabeled. Loops need the same periodic sweep that code needs, and the sweep is mechanical: for each loop, fire its trigger in dry-run, confirm the skill still runs, retire the loops that nobody owns.

Cost runaway, again. Worth saying twice. An event-driven loop with no budget, attached to a busy event source, is a direct line from “Slack got excited” to “the API bill is four digits.” The budget field in the frontmatter exists for exactly this reason, and the runtime must enforce it hard, not warn softly.

The Naming Question
#

I have used “loop” throughout because it captures the simplest mental model: something that runs again and again. But “loop” undersells the format. A loop that fires only on slack.mention is not really looping. It is reacting.

The more accurate name is probably “routine,” “trigger,” or “automation,” and the reason none of those quite land is that each one emphasizes one of the two trigger classes at the expense of the other. “Loop” emphasizes time. “Trigger” emphasizes events. “Routine” emphasizes the work. The format itself does not care. The on: field is the source of truth, and the file is whatever its trigger says it is.

In practice, teams that adopt this will settle on one word and use it for both, the same way “skill” is now used for files that range from one-line hints to multi-page workflows. The word matters less than the format, and the format is: markdown, frontmatter, on, a budget, a short body.

What to Do Next
#

You do not need a runtime to start.

Find the one task that you do on a cadence and keep doing manually because it is “too small to automate.” Triage the overnight issue queue. Summarize the Slack channel at end of day. Check whether dependencies have shipped patches. That task is your first loop.

Write it as a loop file, even if the runtime that will execute it does not exist yet. Write the frontmatter faithfully, the trigger you want, the budget you would accept, the skill it should invoke. If the skill does not exist yet, write its stub too.

You now have two artifacts that describe the behavior you want, in the same repository, in the same format, reviewable in the same pull request. When a runtime arrives (and several already will, given how cheaply existing coding agents can be wrapped in a scheduler), your loop is ready to drop in.

Then do it again with the next task, and the next. Each loop file is a piece of operational judgment that stops being a private habit and starts being a shared, owned, versioned standard. The team that wins in this era is not the one with the most skills. It is the one whose skills run, on time and on event, without anyone remembering to ask them to.

See also
#

References
#


When a Closed Decision Reopens: Breaking the Scope Relitigation Cycle

A decision reached with your principal did not actually close the question. It closed the question in the room where it was made. When a new manager met the senior leader for the first time, the shared understanding that took weeks to build stayed behind, and the original ambiguity rushed back in to fill the gap. The cycle you are trapped in is not a scope problem. It is a context-transfer problem wearing a scope problem’s clothes.

The Decision Didn’t Reopen. The Context Didn’t Transfer.
#

When leadership says “build this feature” without saying what “this” means, they have not given you a requirement. They have handed you a fog bank, and asked you to find a form inside it. You did the right thing. You built a demo to turn the fog into something people could react to, you sat with the principal, and you negotiated a concrete, buildable target. That work was real, and it was correct.

Then a person who was not in the room walked into a different room with the senior leader, and the target moved.

This feels like betrayal, or like a failure of the new manager, or like leadership changing its mind. It is usually none of those. It is the predictable consequence of a decision that was never written down in a form that could travel. A decision that lives only in the heads of the people who were present is a decision that cannot survive contact with anyone who was absent. Your organization keeps generating absent people: new hires, new managers, new conversations, new quarters. Every unwritten decision has a half-life, and the half-life is exactly as long as it takes for one new person to enter the chain.

The senior leader was never actually aligned with you on a specific scope. They were aligned with a feeling, which is that the feature should be impressive and should exist. The specific, feasible compromise you reached with the principal was never transferred back to the person whose vague wish started the whole thing. So when the topic came up again, the leader reached for the only thing they still held, which was the original wish, unmodified by everything you had since learned was impossible.

You Have Two Problems, Not One
#

The conversation keeps oscillating because two genuinely different questions have been tangled into a single thread, and the two questions keep pulling against each other.

The first question is the north star: what would the ideal version of this feature look like, if the tools caught up and the constraints disappeared? That vision is valuable. It tells you which direction to walk, and it is the thing that excites leadership in the first place.

The second question is the milestone: what will we actually ship this quarter, with the tools we have today? That answer is necessarily smaller, uglier, and more compromised than the north star, and it is the only thing you can actually build.

The cycle happens when leadership talks about the north star and hears your milestone answer as a refusal, and when you talk about the milestone and hear their north star answer as scope creep. You are both correct, about different questions, and the fight is sustained entirely by the fact that nobody has written the two answers on the same page and drawn a line between them.

The fix is to separate the questions, explicitly and in writing. Keep the north star. Document it, reference it, and let it do the work of inspiring people. Then draw a hard line underneath it and write, separately, what you are committing to build now, why that subset and not another, and what specifically would have to change for the next slice to become feasible. The north star is a direction. The milestone is a contract. Conflating them is what makes every meeting feel like the last meeting, again.

“That’s Impossible” Is Not an Answer a Leader Can Process
#

Here is the cruelest part of the situation, and the one most likely to keep you stuck.

When you tell leadership “we can’t build the full vision, the tools aren’t there yet,” you are reporting a technical fact, and you expect it to land as a boundary. It does not. It lands as an opinion, and a suspicious one, because every leader has heard engineers declare things impossible that later turned out to be merely hard. The word “impossible” carries no information a decision-maker can act on. It sounds like reluctance, and so it invites the very relitigation you are trying to end.

The move that actually closes the loop is to convert impossibility into a set of specific, dated, falsifiable claims. Not “we can’t do X,” but “X requires capability Y, which does not exist in our stack; the closest vendor offering is at stage Z; building it ourselves is an estimated N months of work from the platform team, which is not currently funded.” Now the leader is not arguing with your willingness. They are looking at a menu, and the menu has prices on it. They can choose to accept the limit, fund the enabler, or descope, but whatever they choose, they are choosing with information instead of against a vibe.

“Impossible” is a wall. The list of what it would take is a door. Leaders cannot walk through walls, but they will walk through doors all day, and often they will fund the hallway that leads to the next one. This reframing also protects you. The day the tools do catch up, your document already names the capability you were waiting for, and the path to the north star becomes a matter of executing a plan you wrote months ago instead of restarting the conversation from scratch.

A Verbal Decision Is Just an Opinion That Hasn’t Been Overwritten Yet
#

The single highest-leverage thing you can do to stop the back-and-forth is to insist that decisions get written down the moment they are made, in a place that outlives the meeting.

This is what Architecture Decision Records were invented for, as Michael Nygard argued when he proposed the format. The record is a short document that captures the context, the decision, the alternatives considered, and the status, so that a person who arrives later can understand not just what was decided but why. The same discipline applies to scope decisions, which are every bit as consequential as architectural ones. A one-page record that says “we will build this subset, not the full vision, because of these specific constraints, and we agreed this on this date with these people” is worth more than any number of follow-up meetings.

The reason writing matters more than talking is that writing has different physics than speech. A conversation degrades the instant it ends. The memory of it degrades faster, and as it passes through more mouths it distorts, the way the children’s game of telephone turns a clean phrase into nonsense in a few retellings. A document does not degrade. It says the same thing to the new manager on their first day as it said to the principal on the day it was written. It says the same thing to the senior leader in their next one-on-one as it said in the room where the compromise was struck.

If a decision is worth making, it is worth making durable, and a decision is only as durable as the artifact it is recorded in. The cost of writing it down is ten minutes. The cost of not writing it down is the cycle you are currently living in, paid in weekly installments, forever.

The New Manager Is Not the Enemy. They Are a Missing Node.
#

It is easy, and emotionally satisfying, to blame the new manager for reopening the wound. Resist that story. Almost certainly, they walked into their first meeting with the senior leader underprepared, because nobody had handed them the history. They improvised from the leader’s enthusiasm, which pointed at the north star, not at your milestone compromise. That is not sabotage. That is a new person doing their best with the information they were given, which was not enough.

The deeper structural issue is that information in an organization flows along its communication paths, and an org that just gained a new link between the team and the senior leader has, in effect, rewired those paths. A decision that was stable under the old wiring may be unstable under the new wiring, because the new path bypasses the node where the context lived. This is why onboarding a new manager is not a courtesy, it is a defensive necessity. Within their first week, they need to receive, in writing, the current decisions, the reasons behind them, and the constraints that produced them. A new manager who has read the decision records cannot accidentally contradict them, because they know they exist.

There is also a principal-agent dynamic here that is worth naming. The senior leader, the new manager, the principal, and you all have different information, different incentives, and different audiences they are trying to satisfy, the classic pattern of the principal-agent problem. The new manager may be, reasonably, trying to demonstrate value to their new boss, and an enthusiastic “we can do more” lands better in that first meeting than a cautious “actually, we already agreed to less.” They are not wrong to want to look good. The system is wrong to have left them no shared record to align to. Fix the system and the manager will fall into line with it, because the record gives them something safer to bring to the leader than improvisation: a documented plan they can defend.

A Playbook for Breaking the Cycle
#

When you find yourself back at the start of a scope conversation you thought was over, do the following, in order.

First, stop re-arguing the scope in the meeting where it resurfaced. Relitigating under pressure produces worse decisions than the original ones, and it taxes the same finite decision-making energy that you need for the work itself. Buy time. Say that you want to make sure the conversation reflects everything the team has already learned, and that you will come back with a written summary. That sentence is free, and it breaks the spiral.

Second, write the decision record, if it does not already exist. Context, the north star, the agreed milestone, the specific technical constraints that forced the compromise, the people who agreed, and the date. This is the artifact that should have existed all along, and creating it now is not bureaucratic theater, it is the wall that holds up everything that follows.

Third, convert every “we can’t” into a priced option. For each piece of the vision that is out of reach, write what it would take to reach it: which capability, which team, which rough timeline, which dependency. Hand leadership a menu, not a wall.

Fourth, brief the new manager before the next senior-leader conversation. Walk them through the record in person, make sure they understand the constraints, and explicitly invite them to bring their own objections now rather than in the room with the leader. Make them a co-author of the record, and they will defend it instead of overturning it.

Fifth, re-present to the senior leader, on your terms, with the record in hand. Acknowledge the north star openly, because dismissing the vision is what makes leaders dig in. Then show the milestone, show the priced options, and ask for a single explicit decision: accept the current scope, fund an enabler, or descope. Whatever they choose, write the new decision down before anyone leaves the conversation. A meeting that ends without a written decision is a meeting that will have to be held again.

Finally, schedule the next scope review in advance, on a date you control, instead of waiting for the topic to erupt on its own. Scope conversations are not avoidable, and they are not the enemy. The enemy is surprise relitigation, the version that ambushes you in someone else’s calendar. A standing quarterly review where the north star, the milestone, and the priced options are revisited together turns the cycle into a rhythm, and a rhythm is something a team can plan around.

The Real Lesson
#

You cannot build something that is impossible today, and you should not promise that you can. But you also cannot expect a vague wish, transmitted through a changing cast of people, to ever stop generating the scope conversation you dread. The way out is not to win the argument one more time, harder. It is to change the medium in which the argument happens. Write the vision. Write the milestone. Write the prices of the gaps between them. Put all of it where the next person can find it. The conversation that reopens next month will reopen against a record instead of against your memory, and records win those fights.

Decisions that are written, priced, and shared do not need to be relitigated, because they travel to the rooms you are not in and make your case for you.

See also
#

References
#


The Codebase Gardener: Keeping a Codebase Sane When Teammates and Their Agents Pull in Every Direction

You cannot review your way to a sane codebase when the code is being produced faster than you can read it. This is the new arithmetic of the LLM era, and it is brutal for the one engineer on the team who cares about consistency. The strategy that actually works is to stop fighting at the pull request and start fighting at the layer everyone, and every agent, is forced to pass through.

The Math Changed Under Us
#

For most of software history, one careful engineer could hold a codebase together by reviewing most of what landed in it. The volume was human. A teammate produced code at roughly the rate you could read it, and a determined reviewer could keep the drift in check. That equilibrium is gone, and two things killed it.

First, throughput. A teammate driving an LLM agent now opens in an afternoon the pull requests that used to take a week. The unit of work did not change; the rate at which units appear did.

Second, plurality. Each teammate may be driving a different agent, loaded with different skills, different conventions, a different mental picture of what “good” means for this codebase. The codebase does not converge on a single style through argument anymore. It fragments into as many dialects as there are agents producing it, and each dialect looks plausible, because plausibility is exactly what an LLM optimizes for. A function can be internally coherent, consistent with nothing around it, and wrong about the domain all at once.

You, alone, reading diffs, are now the slowest station in a pipeline that was designed to outrun you. Per-unit review scales linearly with your hours, and the entropy is being produced exponentially. No amount of discipline closes that gap.

Reviewing Harder Is the Trap
#

The instinct of the engineer who cares is to review more, longer, more strictly. This is exactly the wrong move, for reasons I laid out in Rethinking Code Review in the Age of LLMs. Every hour you spend line-editing someone else’s agent output is an hour the next three pull requests go unread. You become the bottleneck, and bottleneck reviewers do not stay bottleneck reviewers for long. Teammates start tagging each other for approvals, slicing changes to slip under your threshold, or simply waiting you out until you tire.

You end up exhausted, quietly resented, and still losing ground to the codebase. The worst part is that the work feels virtuous while you do it. Catching a misnamed variable in review feels like defending the standard. It is not. It is fighting a single ember while the forest is on fire.

Review is a per-unit activity, and per-unit activity cannot match entropy that is being produced faster than you can read it. If your entire strategy is “I will catch the problems at review,” the strategy has already lost. The question is not how to review better. The question is how to make most of the review unnecessary.

Move the Leverage From Review to Infrastructure
#

The way one person defends a codebase against many producers is to stop operating on the units and start operating on the system that produces them. This is the theory of constraints applied to code quality: if you cannot keep up at the review station, do not add more reviewers. Change the station.

Concretely, it means every standard you care about has to live in one of the few places work is forced to pass through, rather than in your head or in your review comments. There are only a handful of such places, and together they are the entire leverage surface available to a single engineer.

The skill and rule files every agent loads at the start of a task. The CI gates every pull request must clear before it can merge. The templates and scaffolds every new module begins from. The default configuration the linters, formatters, and analyzers ship with. The hooks that run on commit, on push, and on merge.

If your standard is encoded in any of these, it executes on every change, forever, without you being present. If it lives only in your review comments, it executes only when you are awake, looking, and willing to argue about it. That is the whole difference between a standard and an opinion. A standard runs whether or not anyone agrees with it. An opinion dies the moment you go on vacation.

This is the same point made, from the team’s perspective, in Bringing Everyone to the Same Level: the senior engineer’s edge was always a process they ran in their head, and a process in a head does not scale. Here the stakes are higher, because you are not trying to lift the team to your level. You are trying to hold the line while the team is actively, if innocently, pulling away from it. The mechanism is the same. Encode the process, and let the encoding do the defending.

Make the Right Thing the Path of Least Resistance
#

Most code in a codebase is the way it is because that was the easiest thing to type at the time. This is a feature, not a bug, for someone trying to steer a codebase alone. If the easiest path also happens to be the correct path, conformity is free, and nobody has to be persuaded of anything.

This is the paved road idea, and it is the single highest-leverage intervention available to a solo defender. You do not get compliance by arguing for it. You get it by making the compliant thing the thing that happens when nobody is thinking.

Ship a project template that already has the test harness, the lint config, the migration format, and the observability scaffolding wired in, and a teammate who spins up a new service produces conforming code by default, without ever reading your standards document. Wire the migration linter into CI so that a non-backward-compatible migration fails to merge, and the question of whether backward compatibility is “our standard” stops being a question at all. Friction for the wrong thing, zero friction for the right thing. That asymmetry does more work than any amount of documentation, because it operates on the path of least resistance rather than against it.

The flip side matters just as much. Remove friction from the behavior you want. If you want small, reversible changes, make small changes trivially easy to merge. If reviewing is expensive, people batch, and batching is exactly what produces the large, entangled, unreviewable pull requests that defeat you. Tax what you want less of, and subsidize what you want more of, and do both in the tooling rather than in the standup.

Invariants, Not Preferences
#

Most review wars never end because most of what people fight over are preferences. Tabs versus spaces. Single exit versus early return. One assertion per test. Preferences are arguable, and in an LLM-saturated codebase, arguable means unwinnable, because the other engineer’s agent was told the exact opposite of what yours was told, with equal confidence.

The move is to convert as many preferences as possible into invariants before they can become arguments. An invariant is a property the system will enforce whether or not anyone agrees with it. Database migrations must be backward compatible. New dependencies require an audit record. Public API changes require a feature flag. Cyclomatic complexity above a threshold fails the build. These are not opinions. They are machine-checkable constraints, and a constraint that a machine checks is a constraint you never have to argue about again.

Every preference you promote to an invariant is one fewer conversation you have to win, and one fewer place the codebase can drift while you are not looking. Reserve your remaining human attention for the small set of things that genuinely cannot be encoded: taste, architectural direction, whether a feature should exist at all. Everything else should be a gate or a default, silently doing the work you used to do by hand.

Garden in Sweeps, Not in Diffs
#

Per-PR review is not the only way to fight decay, and in this era it is no longer even the best way. Some drift will always get through, because no gate is perfect and because some of what rots a codebase is not visible in any single diff. Naming conventions slide. Duplication accretes. A module that was clean in isolation becomes a knot once three teammates have each extended it in a different direction.

The answer is to operate the Boy Scout rule at the codebase level rather than at the commit level: run regular maintenance sweeps instead of trying to catch everything one pull request at a time. Once a week, run the dead-code analysis. Run the duplication detector and look at the new clusters. Pull the complexity trend report and see which functions crossed a threshold this week. Skim a list of newly added dependencies.

A sweep lets you fix the eighty percent of drift that no individual pull request would have surfaced, and it lets you fix it in a way that does not require winning an argument on each one. You are not blocking anyone’s work. You are tidying, in batches, on your own time, against objective signals from the tools. The gardener does not follow every leaf as it falls. The gardener rakes.

This is also the frank acknowledgment that some entropy is the cost of speed, and that the goal is not zero drift. The goal is a drift rate low enough that your weekly sweep runs net positive, so that over months the codebase gets cleaner rather than dirtier even as it grows. A codebase that gets slowly cleaner under load is a codebase you are successfully defending. A codebase that gets slowly dirtier no matter how hard you review is one you are losing, and the review is not the fix.

Write the Decisions That Outlast You
#

One of the cheapest, highest-leverage things a solo defender can do is write things down in a form that survives without them. Architecture decision records, one page each, capturing what was decided, what alternatives were rejected, and why. A short conventions document that is explicitly the source of truth the lint config is derived from. A “why this exists” header on the modules most likely to be misunderstood.

The point of writing it down is not to win today’s argument. Today’s argument will be re-litigated regardless, because the engineer on the other side, or their agent, has not read it. The point is to ensure that the same argument does not have to be re-won from scratch every time, by you, in real time.

A decision that lives in a file is a decision the next agent can be pointed at. A decision that lives only in your head dies the moment you switch teams, or take a week off, or simply get tired of explaining it for the fiftieth time. When your defenses are encoded as files, the codebase keeps its structure without you holding it together. When they are encoded only as your vigilance, the codebase is one two-week vacation away from drift you will spend a month undoing.

Pick the Battles That Are Actually One-Way Doors
#

You cannot hold every line, and trying to is the fastest route to burnout. The discipline is to decide, in advance, which fights are worth your scarce attention, and the useful frame is the distinction between one-way and two-way doors, which I wrote about in When Engineers Disagree on Best Practices.

A one-way door is a decision that is hard or impossible to reverse. Choosing a primary database. Committing to a service boundary that will be expensive to move later. Adopting a framework that will permeate every file. Dropping a database column. These deserve your full attention, and as the defender of the codebase these are where you should spend it. Block them, slow them, write the decision record, make the team justify the trade-off.

A two-way door is a decision you can undo in an afternoon. A naming convention. A helper function in the wrong package. A test that could have been structured better. Let these go, or fix them in a sweep later. The solo defender who treats every diff as a one-way door exhausts themselves on reversible things and has nothing left for the decisions that actually compound.

This is also the cure for the resentment that otherwise eats this role alive. You will see things every day that are not how you would have done them. Most of them do not matter. Learn to feel the one-way doors in your stomach and let the rest pass, and you will last long enough to actually defend the things that count.

The Real Limit
#

Sometimes the team will not align, and no amount of infrastructure will fully save you. A teammate may insist on driving their own agent with their own skills, their own conventions, their own picture of the codebase, and treat your paved road as a suggestion rather than a default. A manager may value shipping velocity over every standard you have encoded, and quietly override the gates that matter.

At that point your job is no longer to win. It is to make the cost of the drift visible. Keep the trend reports. Keep the complexity numbers. Keep the record of which decisions were one-way doors that got walked through without the decision being made. Do this not to build a case against anyone, but because the most powerful thing a defender can produce, when defense fails, is a clear record of what was lost and when, so that the next attempt at sanity starts from evidence rather than from vibes.

And know when to stop defending a particular front. A codebase is not worth your health, and a team that has decided, collectively, to let the codebase rot will rot it with or without you. Your leverage is highest at the start of a codebase’s life and lowest once the rot is structural. If you have encoded what you can, written down what you know, and the drift is still winning, the rational move is to spend less energy fighting and more energy deciding whether this is still the codebase you want to be responsible for.

What to Do Next
#

You do not need permission, and you do not need a migration. Pick the single standard that is being violated most often, the one that costs you the most review time, and convert it from a review comment into a gate. A lint rule. A CI check. A line in the skill file every agent loads. Run it on every pull request, including the ones you do not personally review.

Then do it again, next week, with the next most expensive standard. Each conversion is a piece of vigilance that stops being yours and starts being the system’s. Over a quarter, the surface you have to defend manually shrinks to the small set of genuinely judgment-laden calls, and the volume of entropy you face stops mattering quite so much, because most of it is being caught upstream of you, by machinery that does not tire.

The solo defender who wins is not the one who reviews the most. The solo defender who wins is the one who has made themselves, slowly and deliberately, the part of the pipeline that is no longer strictly necessary.

A sane codebase in this era is not one that a heroic reviewer holds together by force of attention. It is one whose standards have been pushed so far upstream, into the skills and gates and defaults and templates, that the code arrives mostly correct, and the reviewer is left doing the small amount of work that only a human can do. Build that, one encoded standard at a time, and the direction the rest of the team is pulling in starts to matter a great deal less.

See also
#

References
#

  • Wikipedia, “Theory of Constraints” – Goldratt’s framing for why you change the bottleneck station rather than adding effort at it, the basis for moving leverage from review to infrastructure
  • Wikipedia, “Paved road” – the principle of making the compliant path the easiest path, which lets a single engineer steer a codebase without persuading anyone
  • Wikipedia, “Technical debt” – the metaphor for accumulated drift that a weekly gardening sweep is meant to keep net-negative
  • Wikipedia, “Broken windows theory” – why visible decay accelerates further decay, and why keeping the drift rate below the sweep rate matters disproportionately
  • tomzx/agents – a working library of the skill files and review gates that implement the infrastructure-first defense described here

The Self-Evolving Repository: Automating a GitHub Project End to End with LLMs

The question is not whether an LLM can write code for your project. It already can. The question is whether a project can continue to evolve, fix its own bugs, add its own features, and improve its own quality without you ever opening a terminal, writing a prompt, or reviewing a pull request.

This is the self-evolving repository: a GitHub project where every function of the maintainer has been replaced by an automated loop driven by LLMs. No steering. No human-in-the-loop. The maintainer sets it up once, walks away, and the project keeps moving.

I want to be precise about what this means, what it requires, and where it breaks.

What Full Automation Means
#

Most LLM-assisted development today is interactive. A developer describes a task, the LLM writes code, the developer reviews, requests changes, and merges. The developer is the steering wheel. The LLM is the engine.

Full automation removes the steering wheel. The project must decide for itself what to work on, implement the work, verify it, and ship it. The maintainer who set it up does not intervene in the loop.

This is a fundamentally different problem from “use an LLM to write faster code.” It is the problem of encoding every judgment a maintainer makes into a system that can execute that judgment autonomously, reliably, and indefinitely.

A maintainer of an active project does many things. They triage bug reports. They decide which feature requests align with the project’s direction. They prioritize competing work. They review contributions. They fix CI when it breaks. They update dependencies. They write documentation. They make architectural decisions. They decline changes that would make the codebase worse.

Each of these is a decision that currently requires human judgment. Full automation means building a system that makes each of these decisions without the human.

The Autonomous Loop
#

A self-evolving repository runs a continuous loop.

Observe → Decide → Implement → Verify → Ship → Learn

Each stage must be fully automated.

1. Observe
#

The system continuously monitors signals about the project’s state. Open issues and their content. Pull requests from external contributors. CI failures and test results. Dependency vulnerability alerts. User feedback in discussions. Stack Overflow questions mentioning the project. Download and usage statistics. Error reports from production telemetry if the project is a library or service.

These signals are the system’s eyes. Without them, the project is blind to what needs attention. The richer the signal sources, the better the system can prioritize.

A project that only monitors its own issue tracker is operating with tunnel vision. A project that also monitors community discussions, ecosystem health, and downstream breakage has a much fuller picture of where to invest effort.

2. Decide
#

This is the hardest stage, and I will return to it.

Given the observed signals, the system must decide what to work on next. This is not just “pick the highest priority issue.” It is a sequence of judgments: which problems are worth solving, which are urgent, which can wait, which should be declined, which require architectural changes versus incremental fixes.

The decision stage encodes the maintainer’s judgment about what matters. If this encoding is poor, the project will be busy but not productive, shipping changes that do not move the project in a meaningful direction.

3. Implement
#

The system decomposes the chosen work into tasks and generates code. This is the part that LLMs are already good at. Given a clear specification and sufficient context about the codebase, current models can produce working implementations for most routine tasks.

The implementation stage benefits from the same patterns that work in interactive LLM development: competitive generation (multiple attempts, best one selected), iterative refinement (generate, evaluate, regenerate), and specification-driven development (the spec is authored before the code).

The difference is that in the autonomous loop, the specification is also generated by the system, not by a human.

4. Verify
#

Before any change ships, it must pass automated verification. This is the quality gate that replaces human code review.

The verification pipeline should include: the existing test suite, static analysis and linting, security scanning, type checking, and scenario-based validation where an LLM evaluates whether the implementation satisfies the specification.

As I argued in Rethinking Code Review in the Age of LLMs, human code review of LLM-generated code is the lowest-value activity in the pipeline. Automated verification is not just faster, it is more consistent and more reliable for the kinds of checks that matter at this stage.

The critical requirement is that verification criteria are defined before implementation, not after. If the system writes tests to match what it already implemented, the tests prove nothing. The specification and its acceptance criteria must exist first.

5. Ship
#

Verified changes are merged and released. This stage is mostly mechanical: merge the pull request, run the release pipeline, publish the package, update the changelog.

The one non-obvious requirement is a rollback mechanism. If a shipped change introduces a regression that the verification pipeline missed, the system needs to detect the regression in production telemetry and automatically revert. Without this, errors compound.

6. Learn
#

The system records what worked and what did not. Every failed implementation, every reverted change, every missed bug is an input to future decisions.

This learning takes several forms. Failed prompts and their corrections become few-shot examples for future implementations. Bugs that escaped verification become new test cases. Decisions that led to regressions update the prioritization model. Architectural patterns that caused repeated problems become constraints the system avoids.

Over time, the system accumulates institutional knowledge the way a human maintainer does, but in a form that is explicit, versioned, and transferable.

The Direction Problem
#

The hardest problem in a self-evolving repository is not implementation. It is not verification. It is not even CI automation.

It is direction.

When a human maintainer decides what to work on, they are applying judgment that is difficult to articulate. They know the project’s trajectory, the community’s needs, the competitive landscape, and the technical debt that is becoming critical. They have taste. They have a vision for what the project should become.

Encoding this judgment into an autonomous system is the real challenge.

The roadmap as a steering mechanism
#

The maintainer’s initial setup must include a machine-readable roadmap. Not a vague vision statement, but a structured document that defines: the project’s purpose and non-goals, the target user and their needs, the quality bar for changes, the architectural principles that must be respected, and the current priorities ranked by importance.

This document is the project’s constitution. Every autonomous decision is evaluated against it. When the system decides what to work on, it checks the decision against the roadmap. When the system reviews a contribution, it checks whether the contribution moves the project toward a roadmap goal.

The roadmap does not need to be exhaustive. It needs to be sufficient to distinguish work that matters from work that does not. This is a lower bar than most people assume. Most of a maintainer’s triage decisions are not subtle architectural judgments. They are straightforward: this bug affects users, fix it; this feature request is out of scope, decline it; this dependency has a security vulnerability, update it.

Direction from external signals
#

The roadmap provides static direction. External signals provide dynamic direction.

If the system observes that a particular error message is appearing frequently in community discussions, that is a signal to improve the error message or fix the underlying issue. If the system observes that a downstream project broke after a recent release, that is a signal to fix the breaking change. If the system observes that a dependency released a major version, that is a signal to evaluate the migration.

These signals allow the project to evolve in response to its environment without a human pointing the way. The system reacts to what is actually happening rather than to what someone predicted would happen.

The drift problem
#

Signal-driven direction carries a fundamental tension. A self-evolving repository that only reacts to external signals will optimize for whatever those signals measure. If the signals are bug reports, the system will become excellent at fixing bugs and terrible at anything else. If the signals are feature requests, the system will accumulate features and lose coherence.

This is Goodhart’s law applied to software maintenance: when a measure becomes the target of an autonomous system, it ceases to be a good measure.

The roadmap exists to counteract this drift. It is the fixed point that keeps the project aligned with its original purpose even as the system optimizes for observable signals. But the roadmap itself can become stale. A project’s context changes over time. User needs shift. The ecosystem evolves. A roadmap written in January may be wrong by July.

A fully autonomous system needs a mechanism for updating its own roadmap. This is where the direction problem becomes genuinely difficult. Updating the roadmap means making a judgment about what the project should become, and that judgment requires understanding the project’s users, ecosystem, and competitive position at a level that current LLMs can approximate but not fully replicate.

The pragmatic solution is not to solve this perfectly but to bound it. Let the system make small roadmap adjustments based on observed signals, but require large directional changes to go through a human review. This keeps the system autonomous for 95% of decisions while preserving human oversight for the 5% that determine the project’s long-term trajectory.

Quality Without a Human
#

The quality question is the one most people get stuck on. “How do you trust code that no human reviewed?”

The answer is the same one I gave in The Future of Code Review: you do not trust the code, you trust the verification system.

A self-evolving repository needs a verification pipeline that is more rigorous than what most human-maintained projects have today. Not because the code is worse, but because there is no human backstop.

The pipeline should have multiple independent layers, each catching different classes of problems.

Unit and integration tests catch functional regressions. These should be comprehensive and fast. The system should be able to run the full suite in minutes, not hours.

Property-based testing catches edge cases that example-based tests miss. For pure functions and data transformations, property tests can generate thousands of inputs automatically, surfacing bugs that a human would never think to test.

Static analysis catches type errors, security vulnerabilities, and common anti-patterns. Tools like Semgrep, CodeQL, and language-specific analyzers should run on every change.

Mutation testing verifies that the test suite is actually meaningful. If you can mutate the code and the tests still pass, the tests are not testing what you think they are. This is especially important when the tests themselves are LLM-generated.

Scenario-based validation uses an LLM to evaluate whether the implementation satisfies its specification. This is the layer that catches semantic problems that automated tests miss: the code works, but it solves the wrong problem.

Adversarial verification has a separate agent actively try to break the implementation. This agent writes tests designed to fail, probes edge cases, and attempts to find inputs that produce incorrect behavior. It is the automated equivalent of a hostile code reviewer.

The key insight is that each layer should be independent. If the same LLM that wrote the code also wrote the tests and also ran the review, the correlated failure rate is high. The system will make the same mistake in implementation, tests, and review because they all share the same blind spots.

Diversity of verification methods reduces correlated failures. Static analysis does not share blind spots with property testing. Mutation testing does not share blind spots with scenario validation. The layers are strongest when they are structurally different, not just different invocations of the same model.

What Can Go Wrong
#

A self-evolving repository is a complex autonomous system. Complex autonomous systems have failure modes that are hard to predict.

Silent quality decay
#

Silent quality decay is the hardest failure mode to notice. The system ships changes that pass all verification layers but gradually degrade the codebase’s quality. Each individual change is defensible. The cumulative effect is a codebase that is harder to maintain, slower to evolve, and full of subtle interactions that no one understands.

This happens when the verification pipeline measures immediate quality (does this change pass tests?) but not systemic quality (does this change make the codebase healthier?). Metrics like cyclomatic complexity, coupling, and test coverage can help, but they are proxies, not ground truth.

The complexity spiral
#

Without a human saying “this is too complex, simplify it,” the system may accumulate complexity over time. Each new feature adds code. Each bug fix adds special cases. Each dependency update adds compatibility layers. The codebase grows without bound because nothing in the autonomous loop says “stop adding, start removing.”

A self-evolving repository needs an explicit simplification loop. The system should periodically identify the most complex parts of the codebase and attempt to simplify them, even if no bug is forcing the change. This is technical debt reduction as an automated process.

Specification drift
#

When the system writes its own specifications, the specifications can drift from what users actually need. The system optimizes for specifications it can satisfy, not specifications that matter. Over time, the project accumulates well-implemented features that nobody uses.

This is the autonomous version of the problem I described in The Shifting Bottleneck: when implementation becomes free, deciding what to implement becomes the hard part. In a self-evolving repository, the decision problem is fully automated, which means the drift can go unnoticed for longer.

Cascading failures
#

A single bad change that passes verification can break downstream functionality in ways that the verification pipeline does not catch. The system then generates fixes for the symptoms rather than reverting the cause. Each fix introduces new changes, each of which can introduce new problems. The system enters a fix loop, shipping increasingly desperate patches that make the situation worse.

The defense against this is blast radius limitation. Changes should be small, merged incrementally, and monitored for downstream impact. The system should have a threshold: if more than N changes in a row require fixes, stop and flag for human attention rather than continuing to patch.

Cost runaway
#

Every iteration of the autonomous loop costs money: API calls for generation, verification, and decision-making. A system that is stuck in a fix loop, or that is attempting overly ambitious changes, can burn through significant compute before anyone notices.

The system needs explicit budgets. A maximum number of iterations per change. A maximum cost per change. A maximum number of changes per day. When a budget is exceeded, the system pauses and logs the situation rather than continuing to spend.

What the Maintainer Does Once
#

The maintainer’s role in a self-evolving repository is concentrated entirely in the setup phase. Everything that happens after setup is automated.

The setup has five components.

The roadmap. A structured document defining the project’s purpose, non-goals, priorities, quality standards, and architectural principles. This is the single most important artifact. It determines whether the autonomous system makes good decisions or busy decisions.

The verification pipeline. The combination of test suites, static analysis, property tests, mutation tests, scenario validation, and adversarial verification that gates every change. This pipeline must be in place and passing before the autonomous loop starts. If the verification pipeline is weak, the autonomous system will ship low-quality code.

The decision policy. The rules that govern how the system prioritizes work. What signals does it monitor? How does it rank competing tasks? When does it work on bugs versus features versus technical debt versus documentation? This policy encodes the maintainer’s judgment about how to allocate attention.

The guardrails. Budget limits, blast radius constraints, escalation triggers, and rollback mechanisms. These are the safety systems that prevent the autonomous loop from causing damage when something goes wrong.

The learning infrastructure. The mechanisms by which the system records outcomes and feeds them back into future decisions. This includes the few-shot example library, the bug-to-test pipeline, and the decision evaluation framework.

Once these five components are in place, the autonomous loop can run. The maintainer’s ongoing involvement is limited to reviewing escalation reports, the small percentage of decisions that the system flags as requiring human judgment.

When This Makes Sense
#

Not every project should be a self-evolving repository.

This approach makes sense for projects with: clear specifications and measurable quality criteria, active user bases generating feedback signals, maintenance-heavy codebases where most work is bug fixes and incremental improvements, and well-established architectures where most decisions are routine.

It makes less sense for projects that are: in early exploration where direction changes frequently, in domains where correctness is safety-critical, in codebases with poor test coverage or unclear specifications, or where the competitive advantage is design taste that is hard to encode.

The threshold question is simple: can you write down the rules a competent maintainer would follow, well enough that following them mechanically produces good outcomes? If yes, the system can work. If no, the system will be autonomous but not effective.

The Deeper Question
#

A self-evolving repository raises a question that I explored in Scaling the LLM Agent Company: when the system is its prompts, its pipelines, and its proprietary context, what is the project?

The project is no longer its code. The code is a byproduct of the system that generates it. The project is the roadmap, the verification pipeline, the decision policy, the guardrails, and the accumulated learning data. These are the artifacts that determine what the project becomes.

This reframes what it means to maintain a project. The maintainer’s most important work is not writing code, reviewing pull requests, or fixing bugs. It is designing the system that does all of those things autonomously. The maintainer becomes an architect of processes rather than an executor of tasks.

The project that outlives this setup is the one where the autonomous system makes decisions the maintainer would have made, ships quality the maintainer would have approved, and evolves in a direction the maintainer would have chosen.

The question is whether you can encode your judgment well enough to make yourself unnecessary.

If you can, the project evolves without you. If you cannot, no amount of automation will compensate for the missing taste.

See also
#