agent-loop
Agent-Loop playbook #1: practical Goatfied tactics for shipping PRs
Goatfied's agent loop runs compilation, linting, and tests on every AI edit before PR creation, eliminating broken imports and type errors that waste review time.
Most teams lose an hour or more per AI-generated PR chasing down broken imports, mismatched types, or test suites that fail in CI after passing locally. The culprit isn't the language model—it's the gap between generation and verification. Goatfied's agent loop closes that gap by treating every edit as a candidate that must survive compilation, linting, and tests before reaching your review queue. This playbook walks through five tactical patterns we've refined for teams shipping their first production PRs through the loop.
Start with a narrow constraint file
The loop's power lives in the constraint step—the moment between planning and editing where you tell the agent exactly what it can and cannot touch. A common mistake is leaving constraints too broad ("fix the API layer") or skipping them entirely, which hands the agent a blank check to rewrite half your codebase.
Instead, write a `.goatfied/constraints.yaml` that specifies allowed file patterns and forbidden zones:
edit:
allow:
- src/api/handlers/*.ts
- src/api/handlers/**/*.test.ts
deny:
- src/api/handlers/legacy/*
- src/core/**
validation:
require:
- npm run typecheck
- npm run lint -- --fix
- npm test -- --testPathPattern=api/handlers
This tells the agent: touch only the handlers directory and their tests, stay out of legacy code, and every proposed diff must pass TypeScript, ESLint, and the relevant test suite. The result is smaller, reversible changesets that fail fast when they break something.
Narrow constraints also make validation cheaper. Running your entire test suite on every iteration burns time; scoping validation to affected modules keeps the loop tight.
Anchor plans to reproducible steps
When the agent plans a change, it writes a natural-language outline of what it intends to do. Vague plans ("improve error handling") lead to vague edits. Effective plans break work into granular, observable steps:
- Add `ApiError` class extending `Error` with `statusCode` and `context` fields
- Update `userHandler.ts` to throw `ApiError` instead of generic `Error`
- Add middleware `errorLogger` to capture and format `ApiError` instances
- Write integration test confirming 400/500 responses include `context`
Each step maps to a verifiable change. The agent can complete step one, run validation, and proceed only if types compile and existing tests still pass. If step two breaks something, the loop retries that step without discarding the working foundation from step one.
This incremental approach mirrors how senior engineers refactor: small moves, continuous validation, easy rollback. It also surfaces problems early—if the agent can't complete step one without breaking tests, you learn that before it rewrites three more files.
Use validation gates as design feedback
The validate step isn't just a safety net; it's a forcing function for better architecture. If the agent repeatedly fails `npm run typecheck` because your types are tangled, that's a signal to refactor your type boundaries before continuing.
Real example: an agent tried to add a new field to a request handler, which required updating a deeply nested shared interface used across eight modules. The loop failed validation on the first attempt because changing the interface broke four unrelated handlers. Instead of having the agent patch all eight call sites, the engineer revised the constraint file to allow touching the shared interface module, then rewrote the interface to use optional fields and helper functions. The agent's next iteration passed on the first try because the architecture supported extension.
This dynamic teaches you where your codebase resists change. Every validation failure is a code smell detector. Over time, you'll find yourself writing code that the loop can modify cleanly—loose coupling, narrow interfaces, comprehensive tests—because those qualities make the agent faster.
Commit small diffs, review often
Goatfied's loop produces diffs of 10–50 lines by design, not because of arbitrary limits but because that's the size that survives validation reliably. When you queue a PR, resist the urge to let the agent "finish everything" before you look at it. Instead, treat each validated diff as a checkpoint.
A practical rhythm:
1. Agent completes one planned step and passes validation
2. You review the diff in the Goatfied UI or local Git log
3. If it's correct, approve and let the loop continue to the next step
4. If it's wrong, reject and refine the plan or constraints
This micro-review cadence catches misunderstandings early. Maybe the agent interpreted "add logging" as "log every function call" when you meant "log errors only." Spotting that after ten lines is cheap; spotting it after three hundred lines is expensive.
The loop's retry mechanism makes this safe. If a step fails validation, the agent doesn't thrash—it re-plans based on the error output, adjusts the edit, and tries again. The constraint file ensures it can't accidentally "fix" the failure by editing unrelated code.
Chain plans for multi-stage work
Some changes genuinely require multiple phases: refactoring a module's internals, then updating its consumers, then adding new behavior on top. The loop handles this through plan chaining—explicit sequencing of distinct plans where each plan's completion becomes a constraint for the next.
Example workflow for migrating a deprecated API:
**Plan 1: Internals**
Constraint: `src/api/v2/` only
Steps: Implement new endpoint logic, add tests, confirm all green
Validation: Full test suite for v2 module
**Plan 2: Consumers**
Constraint: `src/services/` only
Steps: Update service layer to call v2 endpoint, remove v1 calls
Validation: Integration tests confirming services work with v2
**Plan 3: Cleanup**
Constraint: `src/api/v1/deprecated.ts` only
Steps: Delete deprecated endpoint, remove from routing table
Validation: Confirm no references to v1 endpoint remain
Each plan treats the previous plan's output as immutable. Plan 2 can't touch v2 internals because the constraint file forbids it; Plan 3 can't touch services because they're out of scope. This prevents the common failure mode where an agent "helpfully" undoes earlier work while trying to complete later work.
Chaining also clarifies responsibility. If Plan 2 fails validation, you know the problem is in how services consume v2, not in v2 itself. The loop's error output will point to the exact service file and test case, making the fix obvious.
Handle retry loops without panic
Sometimes the agent will hit a validation failure it can't fix alone—maybe your test flakes, or the agent misunderstood a requirement, or the codebase has a legitimate circular dependency the agent can't reason through. The loop will retry a few times, each iteration refining the edit based on the error message. If it still fails, the loop pauses and asks for human input.
This is the intended behavior, not a bug. The pause is the agent admitting "I need more information." Your move:
- Check the error output. Often it's a missing import or typo the agent can fix if you adjust the constraint to allow editing a related file.
- Revise the plan. Maybe the agent took the wrong approach entirely, and a quick human sketch of the right approach lets it succeed on the next attempt.
- Make the change manually. If it's truly a three-line fix in an area the agent can't safely touch, just do it yourself and let the loop continue from the new state.
The loop doesn't care who wrote the code; it only cares that the code compiles and passes tests. Mixing human and agent edits within a single PR is normal and often the fastest path forward.
One anti-pattern to avoid: loosening constraints out of frustration. If the agent keeps failing because it wants to edit `src/core/database.ts` and you keep saying no, don't just open up the entire `src/core/` directory. Either refactor to reduce coupling, or make the database change yourself with full context. The constraint file is there to protect you from runaway edits.
Audit trails without extra work
Every step the loop takes—plan, constrain, edit, validate, retry—gets logged with full diffs and validation output. If you're running self-hosted Goatfied, these logs live in `.goatfied/runs/` as structured JSON. On managed Goatfied, they're in your project dashboard.
This audit trail isn't just for debugging; it's documentation of intent. Six months later, when someone asks "why did we switch from REST to gRPC here?" you can pull up the run log and see the exact plan, the constraint that scoped it, and the validation results that proved it worked. No archeology required.
For teams in regulated environments, the logs also provide compliance evidence: you can show that every code change passed automated tests before merge, that changes were scoped to approved modules, and that the system rejected edits that violated architectural rules. The loop's structure makes compliance a byproduct of normal work, not an extra burden.
---
These six tactics—narrow constraints, granular plans, validation as feedback, small diffs, plan chaining, and confident retry handling—form the foundation of effective agent-loop usage. The goal isn't to hand your codebase to an AI and walk away; it's to establish a tight collaboration loop where the agent handles tedious edits and you steer architecture and review output. Mastering this balance is how teams ship faster without sacrificing quality.
Related reading
- [The Goatfied agent loop: how we ship code that actually compiles first try](/blog/goatfied-agent-loop-compiles-first-try)
- [Agent-Loop playbook #9: practical Goatfied tactics for shipping PRs](/blog/goatfied-agent-loop-playbook-9)
