intermediatearchitecturesingle-looparchitecturetask-queueexecutor

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.

  1. Use Chat mode first to understand the codebase (implicit planning)
  2. Switch to Agent mode for multi-file changes (execution)
  3. Verify after each agent run with manual review (verification)
  4. 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.

ParameterDescriptionRecommended Default
max_iterationsHard cap on loop cycles10 for simple tasks, 50 for complex
token_budgetMaximum tokens per runAlign with your cost tolerance
time_limitWall-clock timeout5-30 minutes depending on task complexity
quality_thresholdScore required to accept output0.9 for critical tasks, 0.7 for drafts
backoff_baseInitial retry delay (seconds)1.0
backoff_maxMaximum retry delay (seconds)60.0

When to Use Single vs More Complex Architectures

FactorSingle LoopNested LoopMulti-Agent
Task complexitySimple, well-definedHierarchical sub-tasksMulti-domain expertise needed
DependenciesNone or minimalSequential sub-tasksIndependent sub-tasks
Agent expertiseGeneral-purposeSame agent, different levelsDifferent models for different roles
VerificationSingle evaluatorPer-level evaluatorsSpecialized verifiers per role
ScalabilityVertical (better agent)Vertical (deeper reasoning)Horizontal (more agents)
DebuggingEasyComplexVery complex
OverheadMinimalModerateHigh (more tokens, coordination)
When to useMost coding tasksTasks with clear sub-stepsResearch, 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:

  1. Plan: Decompose the objective into discrete subtasks
  2. Execute: Work through each subtask
  3. Verify: Evaluate the output against acceptance criteria
  4. 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

  1. 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.

  2. 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.md before starting work.

  3. Always retain the best result: Even when retrying, keep the best attempt as a fallback. This prevents a bad iteration from destroying good work.

  4. Log every iteration: Task ID, iteration number, score, action taken, tokens consumed. This enables debugging, optimization, and cost tracking.

  5. 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.

  6. 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.

  7. Persist state to disk: The model forgets between iterations and sessions. The filesystem does not. Claude Code's CLAUDE.md pattern, Aider's git history, and the state manager pattern above are all implementations of this principle.

Next Steps