Closed-Loop Iteration
Open loop vs closed loop from control theory, convergence criteria, and real examples from production agent systems.
What Is a Closed-Loop System?
A closed-loop system continuously uses its own output as feedback to refine and improve subsequent iterations. This concept, borrowed from control theory, is the foundational architecture behind every reliable AI agent system. In a closed loop, the agent does not just produce output -- it observes the result, evaluates it against criteria, and adjusts its behavior based on the gap between what it produced and what was intended.
Open Loop vs Closed Loop
Understanding the distinction between these two paradigms is foundational to loop engineering.
The Control Theory Analogy
In a closed-loop control system, a sensor measures the system's output, compares it to a reference value (the desired outcome), and feeds the error back to a controller that adjusts the system's input. The system continuously corrects itself toward the target.
In a closed-loop agent system, a verifier measures the agent's output, compares it to acceptance criteria (the desired outcome), and feeds the evaluation back to the agent. The agent continuously corrects itself toward the goal.
| Aspect | Open Loop | Closed Loop |
|---|---|---|
| Feedback | None | Continuous |
| Sensing | No observation of output | Verifier/sensor evaluates output |
| Adaptability | Fixed behavior, no correction | Self-adjusting based on results |
| Error handling | No mechanism to detect or correct errors | Automatic correction via feedback |
| Convergence | Not guaranteed | Designed to converge toward target |
| Complexity | Lower | Higher (verifier, feedback routing) |
| Cost | Lower (no verification overhead) | Higher (verification adds latency and cost) |
| Quality | Depends entirely on initial plan | Improves iteratively |
| Example | Single-pass LLM call | Claude Code /goal with test verification |
The Marketing Reality Check
Many so-called "agentic AI" products are still fundamentally open-loop. They execute a sequence of steps without measuring whether the output meets quality standards. The presence of a while loop does not make a system closed-loop -- what matters is whether the loop contains a feedback mechanism that compares output to intent.
This distinction is critical when evaluating tools. Cline (github.com/cline/cline) supports MCP protocol-based external tool calls but requires explicit verification harnesses to become truly closed-loop. OpenHands (github.com/All-Hands-AI/OpenHands) provides an autonomous coding agent platform, but its reliability depends on how feedback is wired into its execution loops.
The Feedback Mechanism
Feedback is what closes the loop. In AI agent systems, feedback flows through three channels:
1. Deterministic Verification
Tests, type-checkers, linters, and build tools provide ground-truth feedback. This is the highest-reliability feedback channel. Tools like Aider (github.com/paul-gauthier/aider) use git diff and test results as the primary feedback signal in their git-first editing loop.
class DeterministicVerifier:
"""Run tests and checks as the feedback signal."""
def __init__(self, checks: list[str]):
self.checks = checks # ["npm test", "npm run typecheck", "npm run lint"]
def evaluate(self, task, output) -> FeedbackSignal:
results = []
for check in self.checks:
exit_code = subprocess.run(check, shell=True, capture_output=True).returncode
results.append({"check": check, "passed": exit_code == 0})
all_passed = all(r["passed"] for r in results)
failures = [r["check"] for r in results if not r["passed"]]
return FeedbackSignal(
passed=all_passed,
score=1.0 if all_passed else 0.0,
feedback=f"Failed checks: {', '.join(failures)}" if failures else "All checks passed"
)
A real-world example: running Aider with a model and test command wires this loop automatically. The agent edits code, runs tests, and retries if tests fail -- a deterministic closed loop.
aider --model claude-3.5-sonnet --message "Fix the failing auth tests" --auto-test --test-cmd "pytest tests/auth/"
2. External Validation
A separate LLM (or a separate agent) evaluates the output. This catches semantic issues that deterministic checks miss, but carries confirmation bias risk. Claude Code's /goal command uses exactly this pattern: after every turn, a separate, smaller model checks whether the goal has been achieved, ensuring the model that wrote the code is not the one grading it.
class LLMVerifier:
"""Use a separate model as a judge for semantic quality."""
def __init__(self, judge_model, criteria: list[str]):
self.judge_model = judge_model
self.criteria = criteria
def evaluate(self, task, output) -> FeedbackSignal:
prompt = f"""
You are an adversarial reviewer. Evaluate the following output
against these criteria:
{chr(10).join('- ' + c for c in self.criteria)}
Task: {task.description}
Output: {output}
Score each criterion from 0 to 1. Return a JSON object with
scores and a list of specific issues found.
"""
result = self.judge_model.generate(prompt)
return FeedbackSignal.from_json(result)
In Cursor's Agent mode (cursor.com), the multi-file editing agent operates in a similar loop -- it writes code across files, and the built-in verification evaluates whether the changes compile and pass checks. Cursor 2.0 extended this with parallel agents (up to 8) that each maintain their own closed-loop feedback cycles.
3. Environment Observation
The agent observes the real-world effect of its actions. This is the "sensor" in the control theory analogy -- the agent runs the code, hits the API, deploys the change, and observes what actually happens.
SWE-Agent (github.com/princeton-nlp/SWE-Agent, 15K+ stars) demonstrates this pattern in research. The agent is given a GitHub issue, writes a fix, runs tests in an isolated environment, and observes whether the fix resolves the issue -- a closed loop grounded in real environment observation.
class EnvironmentObserver:
"""Observe the real-world effect of an action."""
def observe(self, action: str) -> FeedbackSignal:
execution_result = self.environment.execute(action)
return FeedbackSignal(
passed=execution_result.success,
score=execution_result.success_rate,
feedback=execution_result.output
)
Convergence: When Does a Loop Know It Is Done?
1. Quality Threshold Met
The loop stops when output quality exceeds a predefined threshold. This is the ideal stopping condition -- the task is genuinely complete.
def should_stop_by_quality(feedback: FeedbackSignal, threshold: float = 0.9) -> bool:
return feedback.score >= threshold
2. Maximum Iterations Reached
A hard limit on the number of iterations prevents infinite loops. Every production system needs this guardrail.
def should_stop_by_max_iterations(current: int, maximum: int = 10) -> bool:
return current >= maximum
A real failure case demonstrates why this matters: an agent deployed to scrape a website kept calling a broken tool 400 times in five minutes because it had no hard stopping condition. A maximum iteration limit of three would have prevented the entire failure. Tools like Windsurf's Cascade agent (windsurf.ai) and Codex CLI (github.com/openai/codex) both implement iteration caps as standard safeguards.
3. Convergence Detected
The loop stops when improvement between iterations falls below a minimum delta. The system has plateaued.
def should_stop_by_convergence(
scores: list[float],
min_improvement: float = 0.01,
window: int = 3
) -> bool:
if len(scores) < window:
return False
recent = scores[-window:]
improvements = [recent[i+1] - recent[i] for i in range(len(recent)-1)]
return all(abs(imp) < min_improvement for imp in improvements)
4. Divergence Detected
The loop stops when quality is degrading rather than improving. This catches the failure mode where the agent's corrections make things worse -- one of the "four major failure scenarios" (four major failure scenarios) documented in production agent systems.
def should_stop_by_divergence(scores: list[float], window: int = 3) -> bool:
if len(scores) < window:
return False
recent = scores[-window:]
# Quality is getting worse over recent iterations
return all(recent[i] > recent[i + 1] for i in range(len(recent) - 1))
Multiple Conditions in Practice
Production systems layer multiple conditions together: maximum iteration limits, no-progress detection (exiting when repeated iterations produce no new information), and token/cost budgets as hard guardrails.
class StoppingCondition:
"""Combined stopping conditions for a closed-loop system."""
def __init__(self, quality_threshold=0.9, max_iterations=10,
min_improvement=0.01, token_budget=50000):
self.quality_threshold = quality_threshold
self.max_iterations = max_iterations
self.min_improvement = min_improvement
self.token_budget = token_budget
def should_stop(self, scores: list[float], iteration: int,
tokens_used: int) -> tuple[bool, str]:
# Check quality threshold (ideal stop)
if scores and scores[-1] >= self.quality_threshold:
return True, "quality_threshold_met"
# Check max iterations (safety stop)
if iteration >= self.max_iterations:
return True, "max_iterations_reached"
# Check token budget (cost stop)
# Claude 3.5 Sonnet: $3.00/1M input tokens (Anthropic official pricing)
if tokens_used >= self.token_budget:
return True, "token_budget_exceeded"
# Check divergence (quality getting worse)
if len(scores) >= 3 and should_stop_by_divergence(scores):
return True, "divergence_detected"
# Check convergence (no more improvement)
if len(scores) >= 3 and should_stop_by_convergence(scores):
return True, "convergence_plateau"
return False, "continue"
Quality Metrics for Loop Evaluation
Measuring the effectiveness of a closed-loop system requires tracking multiple dimensions:
| Metric | Definition | Target |
|---|---|---|
| Convergence Rate | Percentage of tasks that reach quality threshold | > 95% |
| Mean Iterations to Converge | Average iterations before stopping | < 3 |
| Quality at Convergence | Final score when loop terminates | > 0.9 |
| Divergence Rate | Percentage of tasks that degrade over iterations | < 2% |
| Time to Convergence | Wall-clock time from start to acceptable output | Minimize |
| Token Efficiency | Total tokens per converged task | Minimize |
| False Positive Rate | Tasks accepted as done but actually incomplete | < 5% |
| Recovery Rate | Failed iterations that eventually converge | > 80% |
Real-World Closed-Loop Systems
Aider's Git-First Verification Loop
Aider (github.com/paul-gauthier/aider, 30K+ stars) implements a deterministic closed loop using git as its feedback mechanism. The agent writes code, stages changes via git, runs tests, and uses test results as the evaluation signal. If tests fail, the failure output routes back into the next iteration's context.
# Run aider with automatic test verification -- a complete closed loop
aider --model claude-3.5-sonnet \
--auto-test \
--test-cmd "pytest tests/ -x" \
--file src/auth/handler.py
The --auto-test flag closes the loop: every edit is verified against tests before the agent proceeds. This is a pure deterministic verification loop, the most reliable feedback channel.
Windsurf's Cascade Agent
Windsurf (windsurf.ai) implements closed-loop iteration through its Cascade agent, which supports multi-step execution with real-time feedback. Cascade observes terminal output, file system changes, and browser behavior as environmental feedback signals, adjusting its actions based on what it observes.
SWE-Agent: Research-Grounded Closed Loops
SWE-Agent (github.com/princeton-nlp/SWE-Agent, 15K+ stars) is a research-oriented software engineering agent that implements closed-loop iteration at the task level. Given a GitHub issue, the agent writes a patch, runs it in an isolated environment, observes test results, and iterates. The research paper documents convergence rates and failure modes systematically, providing empirical data on how many iterations real-world bug fixes require.
Agent Framework Orchestration
Multi-agent frameworks implement closed loops at the coordination level:
- CrewAI (github.com/crewAIInc/crewAI): Team-based multi-agent framework where agents review each other's output, creating inter-agent feedback loops
- LangGraph (github.com/langchain-ai/langgraph): Graph-based agent orchestration with explicit state cycles and conditional edges that implement feedback routing
- MetaGPT (github.com/geekan/MetaGPT, 45K+ stars): Multi-agent framework where different roles (architect, engineer, QA) form a pipeline with built-in verification between stages
Context Engineering for Loop Efficiency
Complete Closed-Loop Implementation
class ClosedLoopAgent:
"""A closed-loop agent with verification and convergence detection."""
def __init__(self, agent, verifier, stopping_condition):
self.agent = agent
self.verifier = verifier
self.stopping_condition = stopping_condition
self.history = []
def run(self, task):
iteration = 0
total_tokens = 0
best_result = None
scores = []
while True:
# Feed previous feedback into the agent
feedback = self.history[-1] if self.history else None
result, tokens = self.agent.execute(task, feedback)
total_tokens += tokens
# Verifier evaluates the output
verification = self.verifier.evaluate(task, result)
scores.append(verification.score)
self.history.append(verification)
# Track best result
if best_result is None or verification.score > max(scores[:-1], default=0):
best_result = result
# Check all stopping conditions
should_stop, reason = self.stopping_condition.should_stop(
scores, iteration, total_tokens
)
if should_stop:
if verification.score >= self.stopping_condition.quality_threshold:
return result # Task genuinely complete
else:
return best_result # Best effort
iteration += 1
Best Practices
- Always define multiple stopping conditions: Never rely on a single criterion. Layer quality threshold, max iterations, convergence detection, and token budget together. The production failure analysis shows that infinite loops and blind retries are the most common and costly failures
- Separate the evaluator from the generator: Using the same model for both preserves biases. Claude Code's
/goalcommand uses a separate model for evaluation. Aider uses deterministic tests as the evaluator. Both approaches avoid confirmation bias - Use deterministic checks whenever possible: Tests, type-checkers, and linters are more reliable than LLM judges. Aider's git-first loop with
--auto-testdemonstrates that deterministic verification is the fastest path to convergence - Apply the layered model strategy: Use larger models for generation and smaller models for verification. This can reduce token consumption significantly, making closed loops economically viable at scale
- Track quality history across iterations: You cannot detect convergence or divergence without historical scores. Every iteration must produce a quantifiable score
- Always return the best result: Even if the loop terminates early, the best attempt found so far may be usable
- Engineer context for the feedback channel: Apply context engineering principles (compression, replacement, retention from the InfoQ framework) to keep feedback signals lean. Irrelevant context in the feedback loop wastes tokens across every iteration
- Prevent runaway loops: Token budgets, time limits, and iteration caps are not optional for production systems. Every documented production failure traces back to missing guardrails
Next Steps
- For error correction within closed loops, see Auto-correction Loop
- For state persistence across iterations, see State Persistence
- For the simplest architecture that implements a closed loop, see Single Loop Architecture