agent-loop
Why AI agents fail on the second edit, and how validation loops fix it
AI coding agents fail on sequential edits because they lack validation checkpoints between changes, causing cascading errors that compound across multi-step modifications.

Most AI coding agents can handle a single-file change. Ask them to "add a TypeScript interface for User" and they'll probably get it right. But ask them to then "now use that interface in the auth service and update the tests" and watch the wheels come off. The second edit breaks imports, introduces type mismatches, or silently reverts parts of the first change. The problem isn't the model's intelligence—it's that multi-step editing without validation is flying blind.
When an agent makes sequential edits to a codebase, each change creates new constraints that the next edit must respect. Without checkpoints between steps, the agent has no signal that it's drifting off course until you run the code yourself and find it doesn't compile. By then, you're three edits deep into a broken state, and the agent's context is polluted with its own mistakes.
Why sequential edits accumulate errors
Every code change shifts the ground under the next one. Add a new parameter to a function, and every call site becomes invalid until updated. Rename a module, and import paths break. These cascades are obvious to a human doing incremental compilation, but invisible to an agent generating diffs in isolation.
The typical agent workflow looks like this:
1. Agent reads the user request
2. Agent proposes a diff for file A
3. Diff gets applied to the working tree
4. Agent proposes a diff for file B, based on its internal model of what file A now contains
5. Repeat until the task seems complete
The flaw is step 4. The agent's "internal model" of the codebase is just its prediction of what the files should look like. If the diff in step 2 had a subtle error—say, it added import { User } but the actual export is export type User—the agent doesn't know. It proceeds with step 5 assuming the import is valid, and generates file B's changes accordingly. When file B tries to use User as a runtime value instead of a type, the TypeScript compiler chokes, but the agent has already moved on.
This compounds. By edit three or four, the agent is hallucinating code that depends on features that were never successfully added. You end up with a diff that looks coherent in isolation but is riddled with cross-file inconsistencies.
What validation loops actually do
A validation loop means: after every edit, compile and lint before proceeding. If the build fails, that signal goes back to the agent immediately, along with the error output. The agent then has a choice: fix the error in the file it just changed, or roll back and try a different approach.
Here's the same workflow with validation:
1. Agent reads the user request
2. Agent proposes a diff for file A
3. Diff gets applied
4. Run compiler/linter. If errors, send output to agent and loop back to step 2
5. Agent proposes a diff for file B, now working from a known-good state
6. Run compiler/linter. If errors, send output to agent and fix or retry
The difference is that the agent never moves forward from a broken state. Each edit is a checkpoint. If file A's change introduced a type error, the agent sees error TS2345: Argument of type 'string' is not assignable to parameter of type 'number' before it touches file B. It can fix the parameter type in file A and re-submit. Only after file A passes validation does the agent move to the next file.
This isn't just about catching bugs—it's about keeping the agent's context grounded in reality. The compiler output is an objective fact, not a prediction. The agent doesn't have to guess whether its last edit was correct; the build tools tell it.
The retry budget: failing forward intelligently
Validation loops prevent blind forward progress, but they also require a strategy for how many times an agent should retry a failing edit before giving up or escalating. We've found that a fixed retry budget per edit step works well: allow 2-3 attempts to fix a compilation error, then either skip that file or surface the failure to the user.
Without a budget, an agent can get stuck in a loop where it repeatedly tries and fails the same fix, burning tokens and user patience. With a budget, the agent makes a bounded number of attempts, and if it can't resolve the error, it moves on with a clear acknowledgment of what's broken. The user then sees something like:
✓ Updated src/models/user.ts
✗ Could not resolve type error in src/services/auth.ts after 3 attempts:
Property 'username' does not exist on type 'User'
✓ Updated src/services/email.ts
This is far more useful than silently committing broken code and hoping the user won't notice. It also creates an audit trail: you know exactly which edit failed validation and what the error was, so you can either fix it manually or refine the agent's prompt.
Small diffs reduce validation overhead
One objection to validation loops is that they sound slow. Compiling after every single file change seems wasteful if you're editing ten files in a refactor. But the overhead is manageable if your diffs are small.
Goatfied's agent loop explicitly targets small, reversible edits. Instead of generating a single massive diff that touches fifteen files, the agent plans discrete steps: "add interface," "update function signature," "fix call sites." Each step is a few lines of change. Compiling a few lines is fast—especially with incremental compilers like TypeScript's tsc --incremental or Rust's cargo check. The validation step typically adds a second or two per edit, which is negligible compared to the time saved by not debugging a broken multi-file refactor later.
Smaller diffs also make retry loops cheaper. If an edit fails validation, the agent only has to revisit a focused change, not untangle a sprawling diff. It's easier to fix "this one import is wrong" than "somewhere in these 200 lines, there's a type mismatch."
Constraints as guide rails, not roadblocks
The validation loop is part of a larger pattern: using constraints to guide the agent toward correct code, not just plausible code. The compiler is one constraint. Linters are another. Tests are a third. Each constraint narrows the space of acceptable edits.
For example, if the task is "add a new API endpoint," the validation loop might look like:
1. Add route definition → compile → pass
2. Add handler function → compile → pass
3. Add request schema → compile, lint → fail due to unused import → fix → pass
4. Add unit test → run test suite → fail because mock data is incomplete → fix → pass
At each step, the agent gets concrete feedback about what's wrong. The test failure tells it exactly which assertion failed and why. The linter tells it which line has the unused import. These aren't vague hints—they're actionable errors that point directly to the fix.
This is why we call it a "guide rail." The constraints don't prevent the agent from making changes; they prevent it from making changes that break invariants. The agent still has creative freedom in how it implements the feature, but it can't violate type safety, code style, or test coverage requirements.
When validation isn't enough
Validation loops catch syntactic and type-level errors, but they don't catch logic bugs. If the agent writes a function that compiles, passes linting, and even passes a shallow test, but has an off-by-one error or incorrect business logic, the validation loop won't flag it. That's where test coverage and human review come in.
The goal isn't to make agents infallible—it's to make their failures visible and recoverable. A validation loop ensures that the agent doesn't compound errors across multiple edits. It keeps the codebase in a known-good state at every checkpoint. But the human still owns the final decision: does this change actually do what I want?
In practice, we've found that validation loops eliminate about 70-80% of the "it doesn't compile" failures that plague multi-step agent edits. The remaining 20-30% are either logic errors that require tests, or edge cases that need human judgment. That's a worthwhile trade.
Audit trails for multi-step changes
One underrated benefit of validation loops is the audit trail. When each edit is a discrete commit or checkpoint, and each checkpoint includes the validation result, you can trace exactly how the agent arrived at the final state. If something goes wrong, you don't have to reverse-engineer a tangled diff—you can step back through the validated edits and see where the logic diverged from your intent.
This is especially valuable in regulated environments or team workflows where code changes need to be reviewable. A sequence of small, validated diffs is far easier to review than a single 500-line pull request with no intermediate states. You can see the agent's reasoning step-by-step, and approve or reject individual edits rather than the whole batch.
