From Vibe Coding to Spec-Driven Development to Loop Engineering
Every team that builds seriously with AI walks the same road, usually without noticing. It starts with a chat window and a good feeling, and — if the work survives contact with production — it ends somewhere far more disciplined. This is a field note on that road: the three stages we see teams pass through, what each one is genuinely good at, and the specific failure that forces the jump to the next.
We are writing it down because the stages are easy to confuse. "Vibe coding," "spec-driven development," and "loop engineering" get used loosely, as vibes themselves. They are not interchangeable moods. They are three different answers to one question — who is responsible for correctness, and how is it checked? — and the answer changes as the stakes rise.
The short version
| Stage | The unit of work | Correctness comes from | Breaks when |
|---|---|---|---|
| Vibe coding | A prompt | The human eyeballing the output | You can't hold the whole system in your head |
| Spec-driven development | A specification | The spec + acceptance tests | The spec is right but the run is flaky |
| Loop engineering | A loop | The system verifying and correcting itself | — (this is where production lives) |
Each row is a reaction to the row above it. Read top to bottom, it is a story about moving responsibility for correctness out of the human's head and into an artifact, then into a system.
Stage one: vibe coding
Vibe coding is building by feel. You describe what you want in natural language, the model produces something, you look at it, and you react. If it looks right, you keep it. If it looks wrong, you nudge. The loop is fast, tactile, and — this is the important part — the only verifier is you, looking at the screen.
It is genuinely great. For a prototype, a throwaway script, a UI you can see with your own eyes, or the first exploratory hour of any project, nothing beats it. The feedback is immediate and the cost of being wrong is a re-roll.
Here is the shape of it:
You: "Build a page that lists our open roles from the careers API."
Model: <200 lines of code>
You: *scrolls, runs it* "Close. Make the cards clickable and sort by team."
Model: <revised code>
You: *looks* "Ship it."
The reason it works is also the reason it doesn't scale: verification is implicit and human. You are the test suite. That is fine when "correct" means "looks right in the browser" and the whole thing fits on one screen. It falls apart the moment correctness depends on something you can't see at a glance — a concurrency edge case, a compliance rule, a contract with another service, a behavior that only shows up on the 10,000th request.
The tell that you've outgrown vibe coding: you find yourself re-checking the same things by hand every time you make a change, because you no longer trust that a small edit didn't break something out of view. That manual re-checking is a test suite — you just haven't written it down yet.
Stage two: spec-driven development
Spec-driven development moves the source of truth out of the conversation and into a document. Instead of steering the model turn by turn, you write down what "correct" means — the behavior, the constraints, the acceptance criteria — and the model implements against that. The spec becomes the artifact you review and version; the code becomes a build output of it.
The mental shift is subtle but total. In vibe coding you review code. In spec-driven development you review the spec, and you let tests review the code.
A spec doesn't have to be heavy. Often it's a short, precise document:
## Feature: careers listing
### Behavior
- Fetch open roles from GET /api/roles.
- Show role title, team, location, and an apply link.
- Sort by team (A→Z), then by title (A→Z).
### Constraints
- Roles with `status != "open"` MUST NOT be shown.
- If the API is unreachable, render the last cached response and a stale badge.
### Acceptance
- [ ] A closed role in the fixture never appears in the DOM.
- [ ] With the API mocked to 500, the stale badge renders.
- [ ] Sorting is stable and matches the fixture snapshot.
Now correctness lives in two places you can actually point at: the constraints (what must be true) and the acceptance tests (how we'll know). The model can generate the implementation, regenerate it, or a different model can — and the tests decide whether the result is acceptable. The human reviews intent, not line-by-line syntax.
This is a large step up, and for a great deal of software it is the right and final stop. It gives you determinism where you need it: the same spec produces a result that either passes the acceptance criteria or doesn't.
So why is there a stage three? Because specs describe a destination, not a journey. A spec tells you what a correct result looks like. It says nothing about what to do when the run that's supposed to produce that result is non-deterministic — which is exactly the situation once an AI agent is doing the work at runtime, in the wild, against inputs you didn't write fixtures for.
Stage three: loop engineering
Loop engineering is what you build when the doer is itself an AI system operating without you in the chair. The spec is still there — you still need to know what correct means — but now the thing that checks the work, catches the mistakes, and tries again has to be part of the system, because you're asleep or handling ten thousand concurrent runs and cannot be the verifier.
The unit of work is no longer a prompt or a spec. It's a loop: generate a candidate, verify it against the spec, and if it fails, feed the failure back in and correct. The loop runs until the work passes or a budget is exhausted.
Written as a control loop, the idea is almost boring — which is the point:
def run(task, spec, max_attempts=4):
feedback = None
for attempt in range(max_attempts):
candidate = agent.generate(task, spec, prior_feedback=feedback)
report = verify(candidate, spec.criteria) # tests, evals, checks
if report.passed:
return candidate
feedback = report.failures # what went wrong, precisely
escalate(task, feedback) # hand to a human, with context
Everything hard about loop engineering is hidden inside verify and feedback.
The loop is trivial. Making the verifier trustworthy is the entire discipline:
- The verifier has to be real. Assertions, type checks, and tests are the cheap layer. Above them sit evals — graded checks on fuzzy outputs where there is no single right answer, only better and worse. A loop is only as honest as its weakest check; a verifier that rubber-stamps is worse than none, because it manufactures false confidence at scale.
- Feedback has to be actionable. "Failed" teaches the model nothing. "Role
r_912had statusclosedbut appeared in the output at index 3" tells it exactly what to fix. The quality of the feedback string is, in practice, the quality of the loop. - The loop needs a budget and an exit. Real loops cost money and can thrash. Bounded attempts, then a clean escalation to a human with the full trace, is not a failure mode — it's the design.
This is the stage where the earlier two are absorbed rather than discarded. The
spec supplies the criteria the verifier checks against. The vibe-coding instinct
— generate fast, react to what you see — becomes the inner generate step,
except the thing reacting is now the verifier instead of your eyes.
Why this is the stage that ships. The industry's open secret is that most AI projects never reach production — not because the models are weak, but because they're wrapped like demos: one shot, no verifier, a human implicitly in the loop who quietly isn't there at 3 a.m. Loop engineering is what a demo becomes when you make the verification explicit and the human optional.
How we think about it at CatalEx
Running agents in production is loop engineering as a full-time job. The platform-level features we talk about — agents that are self-correcting (they catch and fix their own mistakes mid-run), self-metriced (they pick and track the criteria they're judged on), and self-evolving (they get better from each outcome instead of plateauing on day one) — are all names for parts of a well-built loop:
- Self-correcting is the
fail → diagnose → regenerateedge, made reliable. - Self-metriced is the agent participating in defining
spec.criteria, so the verifier isn't frozen at whatever we guessed on day one. - Self-evolving is what happens when the feedback from thousands of loops feeds back into how the next generation is produced.
None of it is exotic once you see it as one control loop drawn at different altitudes. The demo is the inner box. The spec is the criteria. The loop is the product.
Where you actually are
You don't pick a stage as an identity; you pick it per piece of work, honestly.
- Exploring, prototyping, building something you can see? Vibe code it. Speed is the whole point, and you are a perfectly good verifier for things that fit on a screen.
- Building something that has to keep being correct as it changes, with more than one person or model touching it? Write the spec. Move correctness out of your head and into acceptance criteria.
- Putting an AI system in the position of doing the work unattended, at scale, against inputs you didn't anticipate? Engineer the loop. Make verification and correction part of the system, because you won't be there to be the test.
The mistake is not being at an early stage. Vibe coding a prototype is correct. The mistake is staying at a stage after the work has quietly moved past it — shipping vibe-coded code as if it were verified, or shipping a spec as if the run that fulfills it were deterministic. Each stage exists to fix the specific way the previous one lies to you once the stakes go up.
Know which lie you can currently afford. Build the next loop before you need it.
Written by CatalEx Engineering. We build the AI operating layer for AI-native companies — one platform to build, deploy, and run AI agents in production. More at catalex.co.