Mark Emelianov
EN

Medium · Ai Engineering, Software Development · Draft

The Four-Loop Pattern: Running Autonomous Coding Agents Without Drift

How to keep AI engineering disciplined when the agents work faster than you can review them — including when the author of the discipline drifts from it.

First published on Medium on 12 May 2026; last edited on 13 May 2026. Read it on Medium. 17 min read.

How to keep AI engineering disciplined when the agents work faster than you can review them — including when the author of the discipline drifts from it.

This is an exploratory article, not a prescription. Its value is not that the Four-Loop Pattern is already proven, but that it gives language to a real failure mode: autonomous agents can be busy, correct locally, and still not move the product forward.

If you’ve spent more than a week running coding agents on a real codebase, you’ve probably hit the same wall I did. The agents are productive — they ship commits, they pass CI, they update docs. The repo grows. Tests get added. New mechanisms get built.

Four robots in blue, purple, orange and green swirls around an infinity-shaped loop, each with a different tool: cube, magnifying glass, tablet, chart.

And then you step back, look at your product, and realize: nothing actually got better.

The agents are busy. The output is real. But the user-facing experience is roughly where it was a month ago. The codebase is bigger, the doctrine is thicker, the meta-architecture is more elegant — and the actual feature you wanted to ship still doesn’t work end-to-end.

This article describes the pattern I’ve converged on for running autonomous coding agents in a way that actually moves the product forward. It’s not a silver bullet. It’s a discipline. And — important — it’s a discipline I caught myself violating multiple times within the same session that produced this article. That’s part of what’s in here too: the failure modes that survive even after you write the rules.

The failure mode

The pathology has a name: drifting-neutral fires. A “fire” is a single execution of an agent loop. A drifting-neutral fire is one where the agent does work, ships commits, passes CI, and changes nothing the user can see or measure.

Common variants:

Each of these feels productive because the agent IS shipping commits. The fire is “successful” in the trivial sense — it didn’t crash, CI was green, the diff merged. But the platform’s measurable state didn’t change. Multiply by 50 fires per week and you have an expensive treadmill.

"The failure mode: drifting-neutral fires": doctrine inflation, stub proliferation, mechanism without measurement, plan thrashing; the antidote is convergence.

The discipline below exists to make this failure mode impossible to hide.

The four-loop architecture

Refuse the “one big agent” pattern. A single loop trying to do everything (implementation, review, monitoring, user testing) drifts because the priorities compete and there’s no separation of concerns to enforce.

Four loops, each with a closed responsibility:

Each loop runs in its own session. They communicate through the file system (git for shared state; per-loop directive files for handoffs). They do not call each other directly.

The orthogonality matters. The implementer cannot rubber-stamp itself; the reviewer cannot do its own implementation; the analyst cannot ship the changes it proposes; the tester cannot fix what it grades. Each loop is forced to use the other loops’ outputs.

Four-loop architecture: implementer (60 min), reviewer (30), analyst (90), user-shaped tester (4 h), each with a "cannot" list, sharing state via files.

This is the same separation a healthy human engineering team has — developer, reviewer, QA, ops — codified for agents. The loops are not “more flexible” than humans. They’re more rigid. That’s the point.

The convergence law

Parallelism without progress is worse than no agents at all. To get progress, a hard rule:

Every fire ends in one of three states: CONVERGENT (shipped a measurable platform improvement with before/after evidence), EXPLICITLY-NEUTRAL (deliberately did nothing, with reason surfaced), or REGRESSIVE (made something worse — requires immediate revert plus a written lesson). A fire that ran, did things, but produced no measurable change is DRIFTING-NEUTRAL and is a failure mode the reviewer loop flags as a discipline violation.

“Measurable” is load-bearing. It needs a canonical state matrix every loop reads on cold start: feature-area grades, success rates, queue depths, deploy lag — whatever your product’s primary axes are. A fire is convergent if some cell in that matrix improved AND no cell worsened. Improvement must be supported by before/after evidence in the PR body.

Three robots on paths: green convergent with a rising chart, amber explicitly neutral with a notebook, red regressive with a warning; a state matrix in front.

In practice:

The reviewer loop enforces this on every PR. PRs that don’t fit one of the three states get REQUEST_CHANGES. The author has to either find the measurable improvement, reclassify as neutral with reason, or revert.

This rule alone catches roughly 80% of the drift problem. The rest needs more.

The pay-down rule

Rules accumulate. Each new rule a future agent must read is a tax on every future fire’s context budget. So:

Adding a new doctrine document, agent definition, backlog item, or process step REQUIRES retiring at least one existing one (delete it, fold it into another, or archive it). Net complexity cannot grow without explicit operator approval. Exception: if the new item replaces multiple old items via convergence, that’s net-negative and acceptable.

This is the rule I personally violate the most. It’s hard to delete things you wrote. But the alternative is a doctrine doc that takes 30 minutes to read at the start of every session — which means agents skim it, which means they don’t follow it, which means they drift.

A balance scale reading "Net complexity must not grow" between an Add bin of new doctrine and backlog and a Retire bin; a reviewer checklist ends in Approve.

The reviewer loop checks every PR: did it add new doctrine files or backlog items? If yes, did it also retire something of equivalent weight? If not, REQUEST_CHANGES with: “convergence-discipline: this PR adds N new doctrine items but retires 0. Either retire one or document why net-add is justified by a measurable improvement landing in this PR.”

The no-stub-shipping rule

Convergence-discipline catches per-fire drift. It doesn’t catch per-artifact drift. A single fire can ship 10 artifacts where 3 work and 7 are stubs, count as “convergent” in aggregate, and quietly grow the platform’s stub count.

Per-artifact rule:

Every artifact you ship — endpoint, screen, widget, capability, agent definition, manual section, anything — MUST be in one of three states: WORKING (verified end-to-end with test, smoke output, or screenshot in the PR body), READY-TO-WORK (gap explicitly named with completion item queued in the same PR), or STUB-WITH-QUEUE (explicit status:stub marker plus cited completion item plus operator-readable explanation when reached). STUB-WITHOUT-QUEUE is a discipline violation.

The classic violation: shipping a screen with a “Queue unavailable — Retry” widget. The widget’s shape is fine; the data behind it doesn’t exist; no completion item is queued. The user hits a wall with no explanation and no path forward.

Robots on a conveyor sort artifacts into Working, Ready-to-work, Stub-with-queue, and a bin for Stub-without-queue, which is labelled a discipline violation.

A subtler violation: shipping a docs file with markdown links to sibling files that don’t exist. The link target should be a stub-with-queue file but it isn’t even that — it’s a void. The reviewer’s link-checker fails CI; the deploy gate refuses; the platform stalls. I shipped this exact failure mode in the session that produced this article — and the analyst loop caught it within hours. More on that below.

The universal cold-start protocol

Each loop, at the start of every fire, runs the same six probes before doing any work:

Plus a seventh in the monitoring loop:

  1. Probe rate-limit headroom. How much of the current billing window is consumed? What’s the burn rate?

After the probes, each loop applies its response rules in rationale-anchored priority order: reachability before correctness, deploy gate before content, master CI before backlog, backlog before new work, platform metric before doctrine.

Cold-start protocol run before any work: seven probes from sync to trunk to rate-limit headroom, plus a five-step response priority order.

This sounds bureaucratic. It’s the opposite — it removes the agent’s freedom to choose poorly. A loop confronted with “deploy gate refused, four PRs unreviewed, CI red, backlog growing, lowest-grade feature area at F” no longer has to think about priority. The protocol decides: deploy gate first.

Thirteen robustness invariants

State-probing protocols are brittle by default. Probes time out, files are missing, networks fail. So:

"13 robustness invariants" grid: hard timeouts, fallback modes, lease-based concurrency, audit log, verification after action, cost cap, worktree isolation.

I-13 is the one I added after this session. The other twelve I had right. The collision pattern I-13 prevents is described in the “What still breaks this pattern” section below.

The model and thinking-mode matrix

Modern coding-agent platforms let you choose model size AND thinking-mode (normal vs extended-thinking with explicit reasoning budget). The cost spread between cheapest and most expensive is roughly 60×. If every activity defaults to “the smartest model with deepest thinking,” you burn money on routine work that doesn’t benefit from depth.

The activity-to-model mapping:

Model and thinking-mode matrix: nine activities mapped to model size, thinking mode, reasoning budget and cost; default is small with normal thinking.

Default: smaller model + normal thinking. Escalate when depth genuinely helps. Never default to “biggest + deepest just in case” — the biggest cost line in any agent system is subagents inheriting the parent’s model when they should override to something cheaper.

The agent-vs-operator boundary

The most important rule: agents do not silently transfer work to the human operator.

When an autonomous coding system can’t do something — domain expertise, market positioning, real-user testing, business model decisions — there’s a temptation to write “operator should…” into the docs. That’s not honest. It’s offloading the hard parts at exactly the time when the operator is least equipped to take them on (early in the product’s life, when there’s no MVP yet).

What agents WILL deliver: lifting feature grades from broken to working; design polish; competitive teardowns from public sources; integration depth; pre-customer UX testing (via the user-shaped tester loop); onboarding flows; reporting features.

What only the operator can decide: strategic priority (which feature matters most for the market they know); business model; final aesthetic preference between two compliant designs; explicit bypass authorization (when an agent says “this test is a flake, can I bypass”); reclassification of priority levels.

Real customer testing is DEFERRED, not transferred. Bringing real users in pre-MVP burns goodwill on a broken product. The user-shaped tester loop substitutes until MVP threshold is reached. Then real users come in to find what the simulated tester missed.

Agent versus operator boundary: agents deliver fixes, polish, teardowns and UX testing; only the operator decides strategy, business model and bypasses.

This boundary is the one operators most need to enforce. Without it, the agent system becomes a thing that demands operator attention rather than a thing that does work.

The single canonical state file

All four loops, all the discipline rules, all the protocols — they’re useless if there’s no shared definition of “what’s the platform’s state right now.”

One file. Append-only trend table. Each row a snapshot in time. Columns are the metrics the product cares about: feature grades by area, success rates, blocker counts, queue depths.

Every loop reads this file on cold start. Convergent fires update it. Drifting-neutral fires don’t. After a week or two, the trend in this file IS the answer to “are the agents producing convergence.”

One canonical state.md as an append-only trend table read and written by all four loops, with convergent, drifting-neutral and convergence-stalled outcomes.

If 7 consecutive snapshots show no movement, the reviewer loop surfaces “convergence stalled — pause loops, meta-conversation needed.” The file is the safety valve that catches the case where every individual fire claims to be productive but the aggregate isn’t.

What still breaks this pattern

This is the section the original draft of this article didn’t have. It cost me a session of drift to add it.

Author drift

The person (or agent) who writes a rule routinely violates it before the ink dries. I shipped a per-artifact discipline that said “every artifact ships WORKING, READY-TO-WORK, or STUB-WITH-QUEUE” — and in the same session I shipped a documentation tree with 16 dangling markdown links that fit none of those states. The analyst loop caught it within hours. I was the author of the rule that flagged my own violation.

The implication: self-enforcement of discipline does not work. The reviewer and analyst loops are not nice-to-have; they are the only thing that catches author drift. Build them first. The rules you write yourself will not bind you without external enforcement.

Shared-working-tree collisions

If you run all four loops out of the same physical directory, they’ll collide on every git operation. One loop does git checkout <branch> and the other loop's git status suddenly reports a different reality. Half-merged commits get attributed to the wrong session. Untracked files appear and disappear depending on which branch the most recent loop checked out.

The fix is invariant I-13 above: each loop operates in its own git worktree. Separate working directory, shared .git database. The git worktree add command is one line and it fixes the entire class of failure permanently.

I learned this by colliding repeatedly with my own analyst loop while running an operator-direct main session in the same directory.

Launcher pitfalls

Three traps you’ll hit before the loops even start firing.

All three of these I hit in one session. None of them are exotic — they’re load-bearing for any Windows-host setup. None of them are documented in agent-platform README files. Budget a day to find them.

The cost-vs-value disconnect

The same session that produced this article cost roughly $157 in API-equivalent compute (the actual subscription is fixed; the dollar figure is what the same work would cost on pay-as-you-go). The session produced nine merged pull requests of doctrine, infrastructure, and process. It produced zero user-visible product improvements. The product-fix I committed early in the session never reached production because the deploy gate refused — due to 15 failing tests plus the 16 dangling markdown links I shipped.

This is the cost-vs-value disconnect named directly. The discipline I describe in this article was built up across that exact session. The platform’s measurable state did not improve. The infrastructure for future sessions to improve it now exists — and you should be honest with yourself (and your operator) when that’s the trade you made.

"What still breaks this pattern": author drift, working-tree collisions, launcher traps, and a ~$157 session that merged 9 PRs with zero product improvement.

The convergence-discipline rule, applied retroactively, would have flagged the entire session as DRIFTING-NEUTRAL. That’s the rule working as intended. It also tells you something uncomfortable: a session built to produce a discipline article cannot easily also produce product progress, because every minute spent on doctrine is a minute not spent on the feature. Plan accordingly.

What this WON’T do

Honest boundaries on what the discipline can and can’t deliver:

"What this won't do": the discipline lifts features from broken to working, but cannot reach top grade, set strategy, find real customer pain or win the market.

Naming what won’t happen is part of the discipline. Without it, operators expect more than the system can deliver and lose trust when it under-delivers.

What it costs

A four-loop session running 8 hours per day at matrix-respecting cadence costs roughly the equivalent of a single mid-tier engineering hire’s tooling budget per month. The actual subscription is fixed; the constraint is rate limits per billing window, not dollars.

Defaulting everything to the largest model with deepest thinking? Roughly 5–7× that. The matrix discipline is the difference between sustainable autonomous engineering and a runaway bill.

What it costs on a per-session basis is more variable. A session that ships pure doctrine — like the one that produced this article — burns budget without producing product improvement. That’s not waste if the doctrine is going to pay back in convergence over future sessions, but it IS waste if the doctrine sits unenforced.

"What it costs" infographic: baseline four-loop session cost, right-sized model versus default-to-biggest at 5-7x, cost split by category, and rules of thumb.

The reviewer loop is what makes doctrine pay back. Without it, doctrine accumulates as dead weight.

The lesson I keep relearning

Every time I add a new rule, agent, or process to this system, I’m adding load to the future. The convergence-discipline rule is the only one that’s net-negative on load — it forces every other addition to pay for itself in measurable improvement.

The pathology I described at the start of this article — “the agents are productive but the product isn’t moving” — is a discipline failure, not a tooling failure. The agents do exactly what you tell them: ship commits, pass CI, update docs. They just weren’t told the commits had to move a measurable axis.

Once you tell them that, with explicit per-fire and per-artifact rules, with a single canonical state file, with four-loop separation of concerns, the honest boundary about what only humans can do, and worktree isolation so they don’t collide — the agents become useful.

Not magical. Useful. Sometimes.

The “sometimes” is the part the original draft of this article missed. The discipline is the target. Even with it written down, even with rules explicit, even with the reviewer loop running, you (and your agents) will drift. The drift gets caught — that’s what the analyst loop is for — but it doesn’t get prevented. The mechanism is self-correcting, not self-preventing.

That distinction matters. The literature on AI engineering tools is full of “magical” claims. The reality is: a small set of disciplined patterns, rigorously enforced by other agents (not by self-discipline), that make autonomous agents do real work most of the time, with caught-and-corrected drift the rest of the time. Everything else is theatre.

A robot holds a tablet checklist (pick backlog item, write code, add tests, update docs, open PR, convergent fire) and gives a thumbs up.

If you’re running coding agents on a real codebase and seeing the failure modes I described, the patterns above might help. They’re not novel; most are codifications of what good engineering teams already do. The novelty is making them load-bearing for autonomous loops where there’s no human in the immediate path to enforce them implicitly — and naming the failure modes that survive the discipline anyway.

Addendum. Cue card

Poster of the Four-Loop Pattern: four loops with responsibilities, shared state folders, three fire outcomes, an example state matrix and the convergence law.

This page is a full copy of the story as published on Medium. Read it on Medium. Illustrations generated with AI.