Single Loop Architecture
The foundational AI agent loop pattern with core components and real implementations.
What Is a Single Loop Architecture?
The single loop architecture is the foundational building block of all agent loop systems. It represents the simplest possible pattern: one agent processing one task through a complete cycle of planning, executing, verifying, and iterating. Despite its simplicity, understanding this pattern thoroughly is essential because all more complex architectures -- nested, multi-agent, and hierarchical -- are built by composing and extending this basic unit.
The evidence for this convergence is visible across every major tool and framework. Claude Code (github.com/anthropics/claude-code) runs a tool-calling loop until the task is done. Aider (github.com/paul-gauthier/aider, 30K+ stars) implements a git-first loop that reads code, generates edits, applies patches, and verifies with tests. Codex CLI (github.com/openai/codex) wraps the same pattern with scheduling and sub-agent support. The syntax differs across tools, but the architecture is identical.
As the Oracle Developers blog confirms: "Despite differences in SDK design, nomenclature, and architectural philosophy, every major AI organisation has converged on the same underlying execution pattern." Six major companies -- OpenAI, Anthropic, Google, Microsoft, Meta, and LangChain -- all build on this single loop foundation.
The Basic Loop: Task, Plan, Execute, Verify, Iterate
The single loop architecture operates across four core phases, repeated until the task is complete:
+----------------------------------------------------------+
| Single Loop |
| |
| +--------+ +--------+ +----------+ +---------+ |
| | Task | -> | Plan | -> | Execute | -> | Verify | |
| | Queue | | | | | | | |
| +--------+ +--------+ +----------+ +----+----+ |
| ^ | |
| | v |
| | +-----------+ |
| +-----------------------------------| Feedback | |
| | Channel | |
| +-----------+ |
+----------------------------------------------------------+
1. Task Queue: The Entry Point
The task queue holds work to be processed. In its simplest form, it is a list of tasks defined in a JSON file. In production systems, it can be a message queue, a database table, or a git-tracked file.
2. Plan: Decide What to Do
The LLM processes the task context and available tools, then decides on an action plan. For simple tasks, planning is implicit -- the model reasons and acts directly. For complex tasks, planning can be an explicit step where the model generates a structured plan before execution.
LangChain's LLMCompiler (github.com/langchain-ai/langchain) implements an explicit planning phase: the planner generates a directed acyclic graph of tasks with dependency tracking, enabling parallel execution. The original paper (Kim et al., ICML 2024) reports a 3.6x speedup over sequential ReAct-style execution.
3. Execute: Take Action
The agent performs the planned action -- calling a tool, modifying a file, running a command, querying an API. This is where the agent's tools give it the power to act in the real world.
4. Verify: Check the Result
The output is evaluated against defined criteria. This is what makes the loop closed rather than open. The verification step determines whether the task is complete or needs another iteration.
Core Components
The Executor
The executor is the agent that performs the actual work. It receives a task and returns a result. Here is a concrete implementation modeled after how Claude Code and Aider process tasks:
from dataclasses import dataclass
@dataclass
class Task:
id: str
description: str
acceptance_criteria: list[str]
max_iterations: int = 10
token_budget: int = 50000
@dataclass
class Result:
task_id: str
output: str
tokens_used: int
metadata: dict
class Executor:
"""The agent that executes tasks."""
def __init__(self, model, tools):
self.model = model
self.tools = {t.name: t for t in tools}
def execute(self, task: Task, feedback: str = None) -> Result:
messages = [
{"role": "system", "content": self._build_system_prompt(task)},
{"role": "user", "content": task.description}
]
if feedback:
messages.append({
"role": "user",
"content": f"Previous attempt feedback: {feedback}"
})
total_tokens = 0
while True:
response = self.model.generate(messages, tools=self.tools)
total_tokens += response.tokens
if not response.tool_calls:
return Result(
task_id=task.id,
output=response.content,
tokens_used=total_tokens,
metadata={"iterations": response.iteration}
)
# Execute tool calls
for tool_call in response.tool_calls:
tool = self.tools[tool_call.name]
result = tool.execute(**tool_call.arguments)
messages.append({"role": "tool", "content": result})
The Evaluator
The evaluator judges the quality of the executor's output against acceptance criteria. This is the verifier component that closes the loop. Production agent frameworks vary in how they implement evaluation:
@dataclass
class Feedback:
passed: bool
score: float # 0.0 to 1.0
details: dict
suggestions: list[str]
class Evaluator:
"""Evaluates output against acceptance criteria."""
def __init__(self, checks: list[str] = None, judge_model=None):
self.checks = checks or []
self.judge_model = judge_model
def evaluate(self, task: Task, result: Result) -> Feedback:
# Run deterministic checks first
check_results = {}
for check in self.checks:
exit_code = subprocess.run(
check, shell=True, capture_output=True
).returncode
check_results[check] = exit_code == 0
all_checks_passed = all(check_results.values())
failed_checks = [c for c, passed in check_results.items() if not passed]
if not all_checks_passed:
return Feedback(
passed=False,
score=0.0,
details=check_results,
suggestions=[f"Fix: {c}" for c in failed_checks]
)
# Optional LLM judge for semantic quality
if self.judge_model:
judge_result = self.judge_model.evaluate(
task.description, result.output, task.acceptance_criteria
)
return Feedback(
passed=judge_result.score >= 0.9,
score=judge_result.score,
details={"checks": check_results, "judge": judge_result.details},
suggestions=judge_result.suggestions
)
return Feedback(
passed=True,
score=1.0,
details={"checks": check_results},
suggestions=[]
)
Aider (github.com/paul-gauthier/aider) provides a clear real-world evaluator pattern. Aider runs your test suite after every edit cycle. If tests fail, the failure output becomes the feedback that feeds the next iteration. This deterministic check approach -- run a command, check exit code -- is the most reliable evaluator for coding tasks.
SWE-Agent (github.com/princeton-nlp/SWE-Agent, 15K+ stars) takes a more sophisticated approach with its research-oriented evaluator, using specialized monologue agents that track context and explicitly verify that file modifications match the stated intent before accepting a result.
The Feedback Channel
from enum import Enum
class LoopAction(Enum):
COMPLETE = "complete" # Task passed, done
RETRY = "retry" # Task failed, try again with feedback
ACCEPT_BEST_EFFORT = "best_effort" # Max iterations, return best result
HALT_FATAL = "halt" # Unrecoverable error
class FeedbackChannel:
"""Routes the system based on evaluation results."""
def route(self, task: Task, feedback: Feedback,
iteration: int, scores: list[float]) -> LoopAction:
if feedback.passed:
return LoopAction.COMPLETE
if iteration >= task.max_iterations:
return LoopAction.ACCEPT_BEST_EFFORT
# Detect divergence -- scores trending downward
if len(scores) >= 3:
recent = scores[-3:]
if all(recent[i] > recent[i+1] for i in range(2)):
return LoopAction.ACCEPT_BEST_EFFORT
return LoopAction.RETRY
The divergence detection here is critical. Without it, an agent can enter an infinite retry loop that burns tokens without making progress. Context overflow is another common failure mode: each iteration adds feedback to the context, and without a mechanism to compact or reset, the loop eventually exceeds the context window and produces garbage.
The State Manager
The state manager persists state across iterations and enables crash recovery. As discussed in State Persistence, state lives outside the model's context window.
class StateManager:
"""Manages loop state across iterations and sessions."""
def __init__(self, state_file: str = "loop_state.json"):
self.state_file = state_file
def save(self, loop_id: str, state: dict):
with open(self.state_file, "w") as f:
json.dump({"loop_id": loop_id, **state}, f, indent=2)
def load(self, loop_id: str) -> dict | None:
try:
with open(self.state_file) as f:
data = json.load(f)
return data if data.get("loop_id") == loop_id else None
except FileNotFoundError:
return None
Claude Code uses CLAUDE.md files as its primary state persistence mechanism. Project-level knowledge, conventions, and constraints are stored in markdown files that the agent reads at the start of every loop iteration. This is an implementation of the InfoQ context engineering principle of compression -- distilling accumulated knowledge into a compact form that fits within context limits.
Complete Single Loop Implementation
Putting the components together:
class SingleLoop:
"""A complete single-loop agent architecture."""
def __init__(self, executor, evaluator, feedback_channel, state_manager):
self.executor = executor
self.evaluator = evaluator
self.feedback_channel = feedback_channel
self.state_manager = state_manager
def run(self, task: Task) -> Result:
# Attempt to resume from existing state
existing = self.state_manager.load(task.id)
if existing and not existing.get("is_complete"):
iteration = existing["iteration"]
best_result = existing.get("best_result")
scores = existing.get("scores", [])
last_feedback = existing.get("last_feedback")
else:
iteration = 0
best_result = None
scores = []
last_feedback = None
while True:
# Execute the task
result = self.executor.execute(task, last_feedback)
# Evaluate the result
feedback = self.evaluator.evaluate(task, result)
scores.append(feedback.score)
# Track the best result across iterations
if best_result is None or feedback.score > max(scores[:-1], default=0):
best_result = result
# Route based on feedback
action = self.feedback_channel.route(
task, feedback, iteration, scores
)
# Persist state after every iteration
self.state_manager.save(task.id, {
"iteration": iteration,
"scores": scores,
"best_result": best_result,
"last_feedback": feedback.details,
"is_complete": action == LoopAction.COMPLETE
})
if action == LoopAction.COMPLETE:
return result
elif action in (LoopAction.ACCEPT_BEST_EFFORT, LoopAction.HALT_FATAL):
return best_result
else:
iteration += 1
last_feedback = str(feedback.suggestions)
Real-World Single Loop Implementations
Claude Code: The Reference Implementation
Claude Code (github.com/anthropics/claude-code, docs at code.claude.com/docs) is the most well-documented example of a single loop architecture in production. While it supports multi-agent and nested patterns, its core operation is a single loop:
while not done:
response = call_llm(messages)
if response has tool_calls:
results = execute_tools(response.tool_calls)
messages.append(results)
else:
done = True
return response
The key features of Claude Code's single loop:
A concrete CLAUDE.md configuration that feeds Claude Code's single loop:
# Project: auth-service
## Tech Stack
- Python 3.12, FastAPI, PostgreSQL
- Tests: pytest with async fixtures
## Conventions
- All endpoints return `{status, data, error}` format
- Use pydantic models for all request/response schemas
- Run tests with: `pytest tests/ -v`
## Quality Gate
- All tests must pass before marking a task complete
- Type check with: `mypy src/`
This file acts as the state persistence layer. Every loop iteration reads these constraints and applies them during execution and verification.
Aider: Git-First Single Loop
Aider (github.com/paul-gauthier/aider, 30K+ stars) implements the single loop with a git-centric approach. Every iteration produces a commit, and if tests fail, the commit output becomes the feedback for the next iteration:
# Start Aider with Claude 3.5 Sonnet and auto-commit
aider --model claude-3.5-sonnet --auto-commits --yes-always
# Or with OpenAI's model
aider --model gpt-4o --message-template "Follow existing patterns. Run tests after each change."
Codex CLI: Single Loop with Sub-Agents
Codex CLI (github.com/openai/codex) implements the same pattern with additional surface features:
- Tool-calling loop via Codex SDK: The core loop calls tools until the task is complete
- Automations: Scheduling layer that triggers the loop on a cadence
- Sub-agents: TOML-defined agents in
.codex/agents/for specialized roles within the loop - Skills: Named, reusable instruction sets that the loop can invoke
# Run Codex with a task
codex "implement the user authentication module"
# Run with a specific agent
codex --agent backend-expert "optimize database queries"
LangChain: Single Loop via StateGraph
LangChain (github.com/langchain-ai/langchain) compiles the agent loop into a StateGraph that manages the while loop internally. The loop invokes the LLM, evaluates tool calls, executes them, appends results to the message state, and repeats until the model returns a final response or the recursion limit is reached.
LangChain adds middleware hooks at every stage: before_model, after_model, modify_model_request. This allows layering behavior on top of the loop without modifying the loop itself -- the same pattern that made web frameworks powerful.
Cursor and Windsurf: IDE-Integrated Single Loops
Cursor (cursor.com) and Windsurf (windsurf.ai, by Codeium) embed single loops directly into the IDE. Cursor's Agent mode processes multi-file editing tasks in a loop, with up to 8 parallel agents in Cursor 2.0. Windsurf's Cascade agent provides multi-step execution within the same loop paradigm.
- Use Chat mode first to understand the codebase (implicit planning)
- Switch to Agent mode for multi-file changes (execution)
- Verify after each agent run with manual review (verification)
- Decompose large tasks into smaller ones to avoid context overflow
This chat-then-agent pattern is a practical application of the single loop's plan-execute-verify structure.
Configuration Parameters
Every single loop needs explicit configuration. These are not optional for production systems.
| Parameter | Description | Recommended Default |
|---|---|---|
| max_iterations | Hard cap on loop cycles | 10 for simple tasks, 50 for complex |
| token_budget | Maximum tokens per run | Align with your cost tolerance |
| time_limit | Wall-clock timeout | 5-30 minutes depending on task complexity |
| quality_threshold | Score required to accept output | 0.9 for critical tasks, 0.7 for drafts |
| backoff_base | Initial retry delay (seconds) | 1.0 |
| backoff_max | Maximum retry delay (seconds) | 60.0 |
When to Use Single vs More Complex Architectures
| Factor | Single Loop | Nested Loop | Multi-Agent |
|---|---|---|---|
| Task complexity | Simple, well-defined | Hierarchical sub-tasks | Multi-domain expertise needed |
| Dependencies | None or minimal | Sequential sub-tasks | Independent sub-tasks |
| Agent expertise | General-purpose | Same agent, different levels | Different models for different roles |
| Verification | Single evaluator | Per-level evaluators | Specialized verifiers per role |
| Scalability | Vertical (better agent) | Vertical (deeper reasoning) | Horizontal (more agents) |
| Debugging | Easy | Complex | Very complex |
| Overhead | Minimal | Moderate | High (more tokens, coordination) |
| When to use | Most coding tasks | Tasks with clear sub-steps | Research, multi-domain problems |
The guidance from every major AI company is consistent: start with the simplest loop that works. Anthropic's published guidance states: "Start with the simplest architecture that solves the problem. Introduce the agent loop only when iterative reasoning and adaptive tool use are required." OpenAI's guidance mirrors this.
Move to Nested Loop Architecture when the task has clear sub-steps that each warrant their own quality control loop. Move to Multi-Agent Loop when different expertise is genuinely needed and the coordination overhead is justified. Frameworks like CrewAI (github.com/crewAIInc/crewAI) and AutoGen (github.com/microsoft/autogen) provide multi-agent orchestration for those scenarios.
The Plan-Execute-Verify-Replan Framework
A more formal variant of the single loop is the Plan-Execute-Verify-Replan framework, described in research on Verified Multi-Agent Orchestration (VMAO). This explicitly structures the loop into four named phases:
- Plan: Decompose the objective into discrete subtasks
- Execute: Work through each subtask
- Verify: Evaluate the output against acceptance criteria
- Replan: If verification fails, adjust the plan based on what was learned
This framework adds an explicit replanning step that the basic single loop handles implicitly. It is useful when the cost of replanning is high and you want to make the reasoning about plan changes explicit and auditable.
Best Practices
-
Start simple, add complexity only when measured: A single loop handles most coding tasks. Do not add nested loops or multi-agent coordination unless you can measure the improvement. Aider, Claude Code, and Cursor all succeed with single loops for the majority of coding workflows.
-
Define clear evaluation criteria before implementing the executor: The verifier determines when the loop stops. Design it first. In Claude Code, this means writing test commands in your
CLAUDE.mdbefore starting work. -
Always retain the best result: Even when retrying, keep the best attempt as a fallback. This prevents a bad iteration from destroying good work.
-
Log every iteration: Task ID, iteration number, score, action taken, tokens consumed. This enables debugging, optimization, and cost tracking.
-
Make components injectable: Design the executor, evaluator, and feedback channel as swappable components so you can mix and match without changing loop logic. This is what makes the Cline plugin (github.com/cline/cline, which supports MCP protocol) flexible -- it can swap between different executor backends.
-
Separate the maker from the checker: Use a different model or agent for evaluation than for generation. This prevents the agent from confirming its own biases.
-
Persist state to disk: The model forgets between iterations and sessions. The filesystem does not. Claude Code's
CLAUDE.mdpattern, Aider's git history, and the state manager pattern above are all implementations of this principle.
Next Steps
- For hierarchical task patterns within a single loop, see Task Loop
- For multi-level decomposition, see Multi-level Task Loop
- For when a single loop is not enough, see Nested Loop Architecture
- For coordinating multiple agents, see Multi-Agent Loop