Best Practices & The Art of Loop Engineering
Verification criteria, convergence strategies, state management, and loop testing from production systems.
At its core, an agent is just a model calling tools in a loop until it's done. The simplicity of this statement belies the skill required to design loops that converge reliably, handle errors gracefully, and produce consistent results. This guide distills best practices from real production systems — including Claude Code, Aider (30K+ GitHub stars), SWE-Agent (15K+ stars), and enterprise deployments documented across the Chinese AI engineering community.
Principle 1: Design Verifiable Goals
The most important aspect of loop engineering is defining goals that can be mechanically verified. If you cannot write a test or automated check that determines whether the goal is met, the loop cannot know when to stop.
Good Goals (Verifiable)
- "All tests in
src/__tests__/pass" - "ESLint reports zero errors on lint"
- "The API returns HTTP 200 on all documented endpoints"
- "The build completes with zero warnings"
Bad Goals (Subjective)
- "The code looks cleaner"
- "The performance seems better"
- "The implementation is good enough"
- "The code follows best practices"
Real-World Example: SWE-Agent's Evaluation Loop
SWE-Agent from Princeton NLP exemplifies verifiable goals in practice. Each agent loop resolves a GitHub issue by running pytest against a repository and checking whether the specific test case referenced in the issue passes. The loop terminates when the test suite exits with code 0 — a clear, binary signal that the goal is met. No subjective judgment is involved.
Practice: Write the Verification First
Before defining the loop, write the verification check:
def is_goal_met():
"""Return True if the loop's goal has been achieved."""
result = subprocess.run(
["npm", "test", "--", "--json"],
capture_output=True, text=True
)
report = json.loads(result.stdout)
return report["success"] and report["numFailedTests"] == 0
If you cannot write this function, your goal is not specific enough.
Principle 2: Design for Convergence
A well-designed loop should converge — getting closer to the goal with each iteration. If the loop oscillates (making and undoing changes), it is poorly designed.
Convergence Strategies
1. Monotonic Progress: Each iteration should only add or improve, never regress.
Iteration 1: Fix 3 of 10 failing tests
Iteration 2: Fix 3 more (6/10 passing)
Iteration 3: Fix 2 more (8/10 passing)
Iteration 4: Fix 2 more (10/10 passing) -> Done
2. Decreasing Error Count: Track and report the error count per iteration. It should trend toward zero.
3. Bounded Exploration: If an approach has not worked after N attempts, try something fundamentally different — do not keep retrying the same fix.
Real-World Example: Aider's Git-First Convergence
Aider (30K+ stars) enforces convergence through its Git-first architecture. Every code change the AI proposes is committed as a Git diff. If a change introduces regressions, Aider can revert to the last good commit and try a different approach. This creates a natural convergence mechanism: the Git history is a monotonically progressing sequence, and any non-convergent change is automatically rolled back.
# Aider enforces convergence via git — each iteration is a commit
aider --model claude-3.5-sonnet src/auth/login.ts
Practice: Log Iteration Progress
[Loop] Iteration 1/20: 3 errors remaining (was 10)
[Loop] Iteration 2/20: 2 errors remaining (was 3)
[Loop] Iteration 3/20: 2 errors remaining (stuck — trying different approach)
[Loop] Iteration 4/20: 1 error remaining (new approach working)
[Loop] Iteration 5/20: 0 errors remaining -> Goal met!
Principle 3: Set Explicit Boundaries
Essential Boundaries
| Boundary | Purpose | Example |
|---|---|---|
| Max iterations | Prevent infinite loops | MAX_ITERATIONS = 20 |
| Max time | Prevent resource exhaustion | TIMEOUT = 300 (5 minutes) |
| Max tokens | Prevent cost overruns | MAX_TOKENS = 100_000 |
| Max errors | Detect stuck loops | If error count unchanged for 3 iterations, escalate |
Real-World Example: Token Budget Discipline
# Layered model strategy for cost-conscious loops
class LoopConfig:
max_iterations: int = 20
max_time_seconds: int = 300
max_tokens: int = 100_000
stuck_threshold: int = 3 # Escalate if no progress for N iterations
# Use Haiku for verification, Sonnet for generation
verification_model: str = "claude-3-haiku-20240307"
generation_model: str = "claude-3.5-sonnet-20241022"
Principle 4: Handle Errors at the Right Level
Error Classification
| Error Type | Recovery Strategy | Example |
|---|---|---|
| Expected | Self-correct within the loop | Test failure -> fix the code |
| Intermittent | Retry with exponential backoff | API timeout -> wait and retry |
| Structural | Escalate to human | Architecture conflict -> ask for guidance |
| Fatal | Stop and report | Disk full, API key invalid -> abort |
Practice: Tiered Error Handling
Level 1 (Auto-retry): Transient failures — retry up to 3 times with backoff
Level 2 (Alternative approach): Same error repeated — try different strategy
Level 3 (Escalate): Fundamental issue — pause loop and notify human
Level 4 (Abort): Unrecoverable — stop the loop and save state
Principle 5: Manage State Carefully
State Management Best Practices
- Checkpoint regularly: Save progress after each successful iteration to disk
- Make state inspectable: Log the full state so humans can audit what happened
- Enable resume: If a loop is interrupted, it should resume from the last checkpoint
- Clean up on completion: Remove temporary files and state artifacts when done
Practice: Use Structured State
{
"loop_id": "refactor-auth-2026-06-25",
"goal": "Migrate auth from JWT to session-based",
"current_iteration": 7,
"max_iterations": 20,
"files_processed": ["login.ts", "register.ts", "middleware.ts"],
"files_remaining": ["logout.ts", "refresh.ts"],
"errors_this_iteration": [],
"last_successful_checkpoint": "iteration-6",
"token_usage": {
"total_input": 45000,
"total_output": 12000,
"budget_remaining": 43000
}
}
Real-World Example: Claude Code's CLAUDE.md as Persistent State
Claude Code uses CLAUDE.md files as a form of persistent state that survives across sessions. A well-maintained CLAUDE.md captures project conventions, architectural decisions, and in-progress work — effectively serving as a checkpoint that any new loop iteration can read. This aligns with the context engineering principle of anchoring: providing stable reference points that ground the agent's understanding across iterations.
# Example CLAUDE.md checkpoint pattern
## Current Task
- Migrating auth from JWT to sessions
- Completed: login.ts, register.ts, middleware.ts
- Remaining: logout.ts, refresh.ts
- Last known error: Session cookie not set on CORS preflight
Principle 6: Keep Humans Informed
Communication Levels
| Level | What to Report | Frequency |
|---|---|---|
| Progress | Iteration count, error trend | Every iteration |
| Milestones | Significant progress | When achieved |
| Blockers | Issues requiring human attention | Immediately |
| Summary | Final result and statistics | On completion |
Practice: Structured Reports
Loop Summary: auth-migration
================================
Duration: 4m 32s
Iterations: 7 / 20 max
Files processed: 5/5
Tests: 42/42 passing
Build: Clean
Changes: 847 lines added, 312 lines removed
Token usage: 57K input / 12K output ($0.19 at Claude 3.5 Sonnet rates)
================================
Principle 7: Optimize Your Prompt Structure
Prompt Structure for Loop Engineering
- Define the goal explicitly — use testable success criteria
- Specify the output format — structured JSON or specific file patterns
- Include verification steps — the agent should run its own checks
- Provide error recovery instructions — what to do when things go wrong
- Set boundaries in the prompt — max files, max iterations, scope limits
Real-World Example: .cursorrules for Convergence
For Cursor users, a well-structured .cursorrules file enforces convergence by bounding the agent's behavior from the start:
# .cursorrules — enforce loop convergence
- Always run tests after modifying code
- If tests fail, read the error message before attempting a fix
- Do not modify more than 3 files per iteration
- Stop and ask if you have attempted the same fix twice without progress
- Commit after each successful test pass
Principle 8: Choose the Right Agent Framework
| Framework | GitHub Stars | Loop Control Model | Best For |
|---|---|---|---|
| LangGraph | 10K+ | Graph-based state machines with explicit edges | Complex workflows with branching logic |
| CrewAI | 30K+ | Role-based task delegation with sequential/hierarchical execution | Team-oriented multi-agent collaboration |
| AutoGen | 40K+ | Conversational multi-agent with flexible message passing | Research and experimentation |
| MetaGPT | 45K+ | Standardized operating procedures (SOPs) as loops | Reproducible software development processes |
For loop engineering specifically, LangGraph offers the most explicit control: each node in the graph is an iteration step, edges define transition logic, and state is passed explicitly between nodes. This makes convergence and boundary enforcement first-class concepts in the architecture.
Key Takeaways
- Verifiable goals are the foundation — if you cannot test it, you cannot loop it
- Convergence design ensures the loop gets closer to the goal each iteration
- Explicit boundaries prevent runaway loops (max iterations, time, tokens)
- Tiered error handling matches recovery strategy to error type
- Structured state enables checkpointing, resumption, and debugging
- Human communication keeps stakeholders informed without requiring constant attention
- Prompt structure directly affects loop consistency across iterations
- Framework choice determines how naturally you can express loop control logic