The Spec-Plan-Build-Verify-Review Loop: An Operator's Manual for Coding Agents

Back to blog
Mehran Mozaffari·

What I Actually Do Now When I Sit Down to Code

The shift that the skills map forced on me was less about learning new tools and more about abandoning a deeply ingrained habit. For twenty years, "coding" meant opening a file and starting to type. The cursor was the interface. Now the cursor is almost never where the work happens. The work happens in the conversations around the cursor — the spec I write before the agent touches anything, the plan I prune before it executes, the review I conduct after it claims victory.

I've come to think of the five phases not as a taxonomy to memorize but as a set of verb-driven gates. Spec is not a document; it's the act of forcing ambiguity into determinism. Plan is not a checklist; it's the act of pruning a dependency graph until every node is atomic enough to roll back independently. Build is not generation; it's context provisioning under strict budget. Verify is not "run tests"; it's establishing ground truth that the agent cannot game. Review is not code reading; it's architectural drift detection at the speed of human comprehension.

The false dichotomy I keep hearing — that the skills map is somehow anti-autonomy, that it's a retreat from letting agents do real work — is backwards. What the map actually does is make autonomy safe to scale. An agent left to its own devices will happily churn through 40 steps of self-correction on a flaky test, burning tokens and destabilizing your git history. The map doesn't reduce the agent's power; it defines what persists across its work. The spec persists. The plan persists. The test skeletons persist. The agent's output becomes replaceable, because the structure around it is not.

That's the real mental model shift. Before, I asked "what did the agent generate?" Now I ask "what did I leave behind that survives the agent?" The code is ephemeral. The contract is not.

Specification: Turning a Sentence into a Contract

The spec phase is where I've seen the most dramatic improvements in agent reliability, and it's also the phase most people skip because it feels like overhead. The mechanism is straightforward: a vague request gives the model an enormous search space. "Add user authentication" could mean anything from a stub function to a full OAuth flow with refresh tokens and session revocation. Every possible interpretation is a valid completion of the prompt, and the model will pick one — probably the most statistically common one, not the one in your head.

A good spec shrinks that space to a single corridor. When I write a spec for an agent, I'm not writing a prose description of what I want. I'm writing something closer to a compiler input. I define the schema explicitly — what fields exist, what types they have, what constraints apply. I define the interface contract — what functions or endpoints the agent must expose, what the return shape is, what errors are possible. I define the boundary conditions — what the system is explicitly not supposed to do, what input ranges are invalid, what side effects are forbidden.

The critical distinction I learned to make is between a statement of intent and enumerable acceptance criteria. "The user should be able to reset their password" is intent. "Given a valid email, the system sends a reset link within 2 seconds and records a password_reset_requested event" is an acceptance criterion. The former leaves the agent room to hallucinate; the latter gives it a test it can actually write against.

Where this gets more nuanced is designing specs that are agent-shaped. A good spec for an LLM-based agent is invariant-inclusive — it spells out the things that must never change, because agents will silently violate constraints when they fade out of context. It's test-anchorable — every acceptance criterion should map to a test skeleton that already exists, so verification is immediate rather than deferred. And it's budget-informed — I mean this in two senses. First, the spec should imply a scope small enough that the resulting diff fits in a single review session. Second, the spec should anticipate the token cost of the agent's exploration, because a vague spec causes the agent to read half the codebase before deciding what to do.

stateDiagram-v2
    [*] --> AmbiguousAsk
    AmbiguousAsk --> SpecAuthored: Engineer writes explicit schema, boundaries, test skeletons
    SpecAuthored --> SpecReady: Guard on completeness (criteria enumerable, interfaces defined, tests exist)
    
    SpecReady --> Execution: Agent proceeds within bounded search space
    
    AmbiguousAsk --> Hallucinating: Detection - spec allows multiple interpretations, no test anchors
    Hallucinating --> SpecAuthored: Return to author iteration
    
    SpecReady --> OverSpecified: Detection - acceptance criteria exceed review budget, latency trap
    OverSpecified --> SpecAuthored: Prune scope before execution

The hallucinating state isn't something that happens during execution — it's something I've learned to detect at spec time. If I can't enumerate the acceptance criteria, the agent will fill the gap with its own assumptions. That's not a bug; it's how the model works. The over-specified trap is subtler. A spec that's too comprehensive becomes a latency bomb — the agent spends forever reading files it doesn't need, and the resulting diff is too large to review carefully. I've learned to treat spec writing as a pruning exercise, not a completeness exercise. The goal is not to specify everything; it's to specify enough that there's only one way to interpret the ask.

Planning: Pruning the Dependency Graph Before the Agent Acts

The plan phase is where the architect/worker split becomes real. When I use a model like o3 for planning and a cheaper model for synthesis, the division of labor isn't just about cost — it's about role clarity. The planner is doing something fundamentally different from the builder: it's reasoning about dependencies, atomicity, and rollback paths. It's producing a graph, not code.

What I'm looking for when the planner returns its proposed dependency graph is not completeness but granularity. The failure mode I've seen most often is an over-conjoined graph where every step depends on the step before it, and a single failed node poisons everything downstream. If step 4 of 7 fails and every subsequent step was built assuming step 4 succeeded, the agent has to either re-plan from scratch or attempt a messy partial rollback. That's where the churn and git history noise come from.

My gate on the plan is straightforward. I ask: can I roll back each node independently? If the answer is no, the node needs to be split. I prune branches that the plan includes but that aren't strictly necessary for the current scope. I enforce an atomic step budget — each node should map to a diff small enough to review in one pass. The build model then executes toward a single scoped node, not toward the whole feature. After each node, I check the actual diff line count against my budget. If it exceeds the bound, I force a re-plan rather than letting the agent plow forward.

flowchart TD
    A[User prompt] --> B[Plan model - e.g., o3 - proposes dependency graph DAG with atomic steps 1..N]
    B --> C{Human gate: review & prune}
    C -->|Re-plan when failed node| B
    C -->|Prune irrelevant branches, split too-large nodes, enforce rollback-ability| D[Build model - cheaper/faster - executes toward a single scoped node]
    D --> E{Check git diff line count}
    E -->|If > budget, force re-plan| B
    E -->|Within budget| F[Proceed to next node until complete]

The graph review itself is the highest-leverage human activity in the entire workflow. A good planner will propose a reasonable DAG, but it doesn't know your codebase's history, your team's conventions, or which parts of the legacy system are fragile. It doesn't know that the "clean" refactor it suggested will touch the exact module that three other agents are currently modifying. That's why the human gate matters — not to second-guess the planner's logic, but to inject the constraints the planner can't see.

I've also learned that the plan phase is where I should be most aggressive about removing work. The planner optimizes for completing the request, not for minimal scope. If I don't prune aggressively, the agent will happily introduce six helper functions to do something that one existing utility already handles. The plan is the last cheap moment to fix that. Once the build model starts executing, everything gets more expensive.

Context Provisioning and Agent-Ready Codebases

The build phase is where the difference between a merely functional agent setup and a genuinely productive one becomes visible. I've found that the actual bottleneck in this phase is never the model's ability to generate code — it's the quality of context I'm providing. The variable that dominates is not prompt engineering in the traditional sense; it's repository architecture and the discipline of context provisioning.

When I configure an agent environment, two things matter: how instructions are organized and how the codebase itself is structured. For instructions, I avoid monolithic root-level config files. A single AGENTS.md at the repository root might work for a small project, but once you're past a few thousand lines, the file becomes a rules dumping ground. The failure mode is attention diffusion — the agent simply stops following negative constraints buried under six screens of instructions. I've watched it violate "do not modify the database schema without a migration script" because that constraint was in a section the model never re-read.

My approach is hierarchical and directory-scoped. Each directory has its own small instruction file that contains only the rules relevant to that part of the system. The root config handles global conventions; each child directory handles its local invariants. This mimics how I'd structure norms in a human team — the people working on the auth module care about auth security rules, not the styling conventions of the frontend.

For the codebase itself, I invest in what I call "agent-symmetric" structure. If the module boundaries are clean and the interfaces are explicit, the agent can read two files and understand what it needs to change. If the codebase is tangled and the dependencies are implicit, the agent has to pull in a dozen files to understand the impact of its edit — and that's when context overflow happens.

Here's how the different agent archetypes actually handle context provisioning:

Archetype Context source How it's sliced Token efficiency Context overflow behavior
Skills Map (structured) AGENTS.md hierarchy, schema contracts, test skeletons Directory-scoped rules, explicit bounds via atomic plan nodes High — minimal repo reading per task Plan gate forces re-spec before overflow
CLI loop (Claude Code, Aider) CLAUDE.md, git status, ripgrep results, AST parsing Agent-initiated tool calls, incremental file reads Medium — tool truncation, token budget burns fast Degraded instruction adherence, stale variables
IDE-native (Cursor, Windsurf) .cursorrules, active tabs, vector index, LSP diagnostics Localized by active file and symbol references Medium — relies on editor state, not explicit structure Silently drops constraints, uses deprecated internals
Sandbox (Devin, OpenHands) Full VM filesystem, persistent terminal, browser state Entire repo available, agent chooses what to read Low — massive context window saturation "Lost in the middle" — critical instructions vanish

The token efficiency metric is the one I optimize for most aggressively. A rule of thumb I follow: an instruction file should not exceed virtual memory budget — which I treat as roughly what the agent can hold in context while still maintaining coherence. Beyond that, rule sprawl sets in and adherence decays. Each instruction file gets pruned like any other code artifact. If a rule wasn't needed in the last five sessions, I delete it.

The real payoff here is that a well-structured codebase makes the agent need to read fewer files. When the idioms are consistent and the modules are deep, the agent can operate with a handful of files in context. When the structure is shallow and the coupling is implicit, the agent reads a dozen files and still misses critical constraints. The codebase itself is context — and it's the context you control.

Where the Whole Map Breaks Down: Failures at the System Level

The failure modes of this workflow aren't isolated to any single phase. They're systemic — they emerge from the interaction between the agent, the harness, and the human who set it all up. I want to walk through the ones I've seen most often, because recognizing them early is what separates a productive agent setup from a costly experiment.

The most insidious is context saturation. As repository instructions accumulate — AGENTS.md files, MCP schemas, multi-file source context, iterative conversation turns — token counts scale past the 50k–150k range. What happens then isn't a graceful degradation. The agent's attention diffuses. Negative constraints specifically — the things you said not to do — get shadowed by later chunk injections. I've watched an agent silently revert a schema migration it made two turns earlier, then regenerate a new one based on stale assumptions, because the original constraint was buried under scrollback. The agent produces syntactically valid code that subtly breaks implicit contracts, and nothing flags it until integration.

The flaky harness contamination is a close second. When a verification loop runs against a test suite with non-deterministic tests — a race condition here, a timing-sensitive integration test there — the agent interprets the failure as its own fault. It then makes random destabilizing changes to fix a problem that was never in the code it wrote. I've seen agents rewrite perfectly good business logic, clear caches, and delete files entirely, all because a CI runner was flaky. The loop doesn't know the difference between "your code broke this test" and "the network is slow."

MCP payload floods are a quieter disaster. An agent executing find . or querying a database without a LIMIT clause can blow up its own context window in a single tool call. The conversation state is gone, the agent has no memory of what it was doing, and the loop restarts from scratch. That's expensive. Non-idempotent commands — partial migrations, container spins with no cleanup, overwriting uncommitted files — cause damage that outlasts the session.

The cost blowout is the budget killer. Uncapped multi-turn verification loops on frontier models mean a single prompt can quietly run 30+ iterations, eating API quotas and turning a routine task into a $50 bill. I've learned that the loop limit isn't a luxury; it's a hard requirement.

At the team level, rubber-stamping is the hidden tax. Agents generate 500-line PRs in minutes. Human review speed doesn't scale with them. When verification suites are weak, review becomes theatrical — people approve large diffs they haven't actually traced. Rule sprawl compounds it. Teams append rules to AGENTS.md without curation, creating conflicting instructions that degrade adherence across the board.

Three Ways to Actually Apply This in Your Own Repo

The skills map is a framework, not software. But there are three concrete tools you can build in an afternoon that operationalize its core principles.

A verification guardrail sandbox. Run your CLI agent loop — Claude Code or Aider — inside a Docker container with a read-only mounted test suite directory. The agent's shell commands execute there, but it can't mutate the tests. Add a counter that kills the process after four failed test runs and exits non-zero. The failure mode to watch: the agent tries to run tests through a different binary, or demands write access to a lockfile that happens to live in the read-only directory. Log stderr carefully. Did the agent try a legitimate alternative interpreter, or was it attempting to bypass the sandbox entirely? That distinction tells you how far the agent is willing to push to game the verification loop.

A spec-to-acceptance-criteria linter. Write a script that parses a proposed /spec — or any markdown spec — and counts whether every named acceptance criterion has at least one observable assertion that could anchor a test. If criteria lack test anchors, reject the spec and demand rework before /plan begins. This gate connects the /spec phase directly to /verify, ensuring you never generate code that has no way to prove correctness. The false-positive risk is real: a deliberately vague exploratory spec will get rejected. Add an escape hatch flag for "exploratory spike" and treat it as noisy data — if you're using it often, you're not really spec-driven.

A hierarchical AGENTS.md health checker. Build a tool that reads all instruction files recursively, estimates token count for each, and flags when the loaded file set exceeds a 20k-token budget. Then propose which rules could move to a subdirectory. This connects directly to the instruction-collision failure mode. The trap: the tool may suggest moving a global architecture rule into a subdirectory, which causes drift if that rule was meant to apply everywhere. Recommend a "global vs. local" flag in the rule text so the tool (and the agent) can distinguish intent. The health checker should sit alongside an MCP server that reports the file tree — context hygiene is always partial when you can't see the full context surface.

Resources

Updated 2026-09-03 by Mehran Mozaffari.

Related posts