Skip to main content
← Back to Blog
AI & Technology

You're Not a Developer Anymore, You're Middleware

Vibe coding keeps you inside the loop as a copy-paste router between the terminal and the chat window. Loop-driven coding puts a deterministic test gate in your place, and moves you from in the loop to on it.

Vibe CodingLoop-Driven DevelopmentHuman-on-the-LoopAI Coding AgentsTest-Driven DevelopmentSubagentsDeveloper Workflow

There's a specific moment where vibe coding stops feeling like magic.

You've prompted, the model has written, you've pasted, you've run it, it's failed. You copy the terminal output back into the chat. It apologises, tries again, and touches two files you never asked it to go near. You paste the next error. Somewhere around the fifteenth round trip you realise something uncomfortable: you're not architecting anything. You're a message queue with a keyboard.

That's the wall. And it isn't a model intelligence problem. The model that's failing your fifteenth retry is the same model that nailed the first three. What changed is that the task got big enough that the interaction model broke.

Standard vibe coding keeps you in the loop. Every iteration needs you to route an error from a terminal into a chat window. You are the slowest, most expensive component in a system that otherwise runs at machine speed.

The fix isn't a better model or a cleverer prompt. It's putting a deterministic gate where you currently sit: a test runner the model can't argue with, wired into an automated retry loop, with a hard cap on attempts.

What is the difference between human-in-the-loop and human-on-the-loop coding?

This is the whole argument, so I'll be blunt about it.

In the loop: you set the task, wait for tokens, run the command, read the output, decide what to feed back, repeat. Your attention is pinned to every iteration. Fifteen retries costs you fifteen context switches.

On the loop: you set the task, define what "done" looks like in a way a machine can check, trigger the run, and go do something else. You come back to a git diff and a pass or fail. Fifteen retries costs you nothing, because you weren't there for fourteen of them.

The mechanism that gets you from one to the other is not a smarter prompt. It's a deterministic gate. When you ask a model in chat "are you sure this works?", it re-reads its own output and tells you yes, because self-reflection on text is not the same thing as execution. When the same model is wired into a terminal, it can't argue with 1 test failed. The compiler doesn't care how confident the explanation was. This is the same underlying problem I've written about before: LLM outputs are inconsistent by default, and the answer is always external validation rather than asking the model to check itself.

So you stop reviewing prose and start enforcing reality.

Do subagents solve the same problem as loops?

This is the obvious objection and it deserves a straight answer, because the old way of prompting genuinely does feel like yesterday to me now, and I want to be precise about why rather than just asserting that loops are newer.

Subagents are useful. Five workers exploring five files beats one worker doing it serially, and for breadth-first work like "find every place we call this deprecated method" the fan-out is exactly right.

But look at what got parallelised. You multiplied generation. Verification stayed exactly where it was, which is with you, reading a synthesis and deciding whether to believe it. The orchestrator's confidence in its subagents' output is the same self-reflection problem one layer up: nothing in that hierarchy hit a compiler. It's a hierarchy of assertions.

Subagents make the model faster at producing things you still have to check. Loops make the checking automatic. Those are different problems, and only one of them was ever your bottleneck.

Two honest caveats. Serious agent harnesses do run tests as part of their tool use, so the strong version of this claim is unfair to the good ones. And the two approaches compose well: fan out the generation, then gate the whole result on one deterministic check. The thing I'd actually argue against is subagents without a verification gate, which is most of what people are running.

How do you set up a loop-driven coding workflow?

You need three things, and you need all three or the loop doesn't close:

  1. A goal a machine can evaluate. Not "make the login page better." A failing test, a type check, a lint rule. Something with an exit code.
  2. An execution tool. Claude Code, Cursor CLI, whatever agent can actually write to disk rather than print suggestions.
  3. Ground truth. The test runner, the compiler, the linter. Raw stdout and stderr, fed straight back.

Then: generate, execute, intercept, feed back, retry. Cap it, and commit on green.

You do not need a framework for this. A bash script is enough to prove the concept to yourself:

#!/bin/bash
# vibe-loop.sh - run AI edits until tests pass or the retry cap is hit

MAX_RETRIES=5
COUNT=0
TASK_PROMPT=$1

echo "Starting loop: $TASK_PROMPT"

while [ $COUNT -lt $MAX_RETRIES ]; do
    ((COUNT++))
    echo "Iteration $COUNT of $MAX_RETRIES"

    if [ -f "last_error.log" ]; then
        ai-cli run "Fix the code for task '$TASK_PROMPT' using this error log: $(cat last_error.log)"
    else
        ai-cli run "$TASK_PROMPT"
    fi

    npm test > last_error.log 2>&1
    TEST_EXIT_CODE=$?

    if [ $TEST_EXIT_CODE -eq 0 ]; then
        echo "Passed on iteration $COUNT."
        rm -f last_error.log
        git add .
        git commit -m "feat: $TASK_PROMPT (auto-completed by vibe-loop)"
        exit 0
    fi

    echo "Failed. Retrying with updated stack trace."
done

echo "Hit max retries ($MAX_RETRIES) without passing. Check last_error.log."
exit 1

That's the entire idea. Everything else is refinement.

What are the rules for running an AI coding loop safely?

Start from a clean working directory, always. When a loop goes down a rabbit hole it goes properly down. git reset --hard is the safety net and it only works if you had somewhere clean to reset to.

Cap the iterations hard. Three to five. This is the rule people ignore and then regret. If the model can't fix a test in five attempts, attempt six will not save you. The failure is upstream: your spec was ambiguous, or your test is checking the wrong thing. More tokens won't fix an underspecified goal, they'll just spend money confirming it.

Keep the feedback loop fast. Vitest, Bun, a targeted Jest spec. If your verification step takes five minutes, a five-iteration loop is a twenty-five minute wait and the whole ergonomic advantage evaporates. Do not feed a full end-to-end suite into a micro-loop.

Pass raw output, not summaries. Don't have the model explain the error to itself. Stdout and stderr, unformatted. Stack traces are the cleanest signal an LLM gets when debugging, and every layer of paraphrase between the failure and the model loses information.

When does loop-driven vibe coding not work?

I'd rather you find these out from a blog post than from a burned afternoon.

It's only as good as your test suite, which means it's useless on most legacy code. The loop's entire value comes from the ground truth gate. If your codebase has thin coverage, the gate passes on code that's wrong, and the model will happily commit it with a confident message. On a mature codebase with no tests, loop-driven coding isn't an improvement, it's an automated way to introduce regressions faster.

The hard work moves, it doesn't disappear. Writing a test that genuinely captures "the feature works" is the actual engineering. Loops don't do that part. They just execute against it relentlessly once you have. If you were hoping to skip specification, this makes it worse, not better, because now ambiguity gets amplified across five iterations instead of caught on the first.

Failed loops aren't neutral. A model that can't solve something in five tries doesn't sit still. It refactors adjacent code, adds defensive branches, weakens assertions. You can come back to a diff that's much larger and much worse than where you started. This is why the clean-directory rule matters more than it sounds.

Token cost is real and it's front-loaded on failure. The runs that succeed on iteration one are cheap. The runs that burn all five and fail are the expensive ones, and they're the ones that produced nothing. Budget accordingly, and be aware that a loop wired into bloated tooling compounds the problem, which is a large part of why MCP servers overcharge your LLM.

What loop-driven coding does to your job

Standard vibe coding is talking to an AI while you write software.

Loop-driven vibe coding is building a system where the AI and your test runner talk to each other, and you find out how it went afterwards.

The interesting part isn't that it's faster, though it is. It's what it does to your job. You stop being the person who decides what the next line of code should be, and you become the person who decides what "correct" means and whether the thing that came back qualifies. That's a genuinely different skill, and it's a more valuable one.

It also means you have nowhere to hide when the spec was vague. Which, honestly, is the point.

Share
Link copied