← AI Agents Architecture & Patterns
🤖 AI Agents

Agent Reflection and Self-Correction Loops

Source: mortalapps.com
TL;DR
  • Agent reflection is an architectural pattern where an LLM evaluates and refines its own generated outputs against explicit criteria before final delivery.
  • It solves the problem of silent failures, hallucinations, and formatting violations in complex multi-step generation tasks.
  • In production, it acts as an automated quality gate, shifting error correction from runtime client-side failures to internal agent iterations.
  • Implementing this pattern requires strict max-iteration guards and deterministic scoring thresholds to prevent runaway token costs and infinite loops.

Why This Matters

In production multi-agent systems, relying on a single-pass LLM generation often leads to subtle, silent failures. An ai agent self correction loop addresses this vulnerability by introducing an explicit evaluation phase where the model critiques its own output against a set of deterministic or heuristic constraints. This pattern was invented because even state-of-the-art models struggle to plan, execute, and verify complex outputs simultaneously within a single forward pass. By decoupling generation from evaluation, we allow the model to allocate dedicated compute and context to error detection, significantly improving accuracy.

If you ignore this pattern in production, your system will suffer from high failure rates in downstream tasks that depend on strict schemas, valid code, or factual accuracy. This leads to broken APIs, database constraint violations, and degraded user trust. Use an AI agent self-correction loop when the cost of an incorrect output is high, and when the output can be programmatically or semantically validated. For low-latency, low-stakes interactions (such as simple chat routing or basic summarization), the added latency and token cost of a reflection loop outweigh its benefits. In those scenarios, simpler patterns like schema-enforced structured outputs or direct single-pass generation are preferred. However, for complex workflows like code generation, multi-step planning, or agentic RAG, self-correction is a critical reliability layer.

Core Concepts

To build reliable self-correction loops, you must understand the core components and terms that govern this architectural pattern:

  • Generator Node: The component responsible for producing the initial draft, solution, or structured output based on the user prompt and context.
  • Reflector/Critic Node: The component that evaluates the Generator's output against specific criteria, generating structured feedback, error logs, or a pass/fail score.
  • Self-Correction Loop: The iterative execution cycle where the Generator refines its output based on the Critic's feedback until a stopping condition is met.
  • Max-Iteration Guard (Loop Budget): A hard deterministic limit on the number of reflection cycles to prevent infinite loops and runaway token consumption.
  • Scoring Threshold: The quantitative or qualitative benchmark that an output must meet during evaluation to bypass further reflection.
  • Deterministic vs. Semantic Validation: Deterministic validation uses code (e.g., Pydantic, unit tests, compilers) to verify outputs, while semantic validation uses an LLM-as-a-judge to evaluate quality, tone, or alignment.

How It Works

The self-correction loop operates as a stateful, iterative pipeline. The complete internal lifecycle consists of the following phases:

Phase 1: Initial Generation

The process begins when the Generator Node receives the user input and system instructions. It produces the first draft of the output (e.g., a block of code, a structured JSON object, or a multi-step plan).

Phase 2: Validation and Critique

The generated output is routed directly to the Validator/Critic Node. This node executes two types of checks:

  1. Programmatic Validation: The system runs fast, deterministic checks such as JSON schema validation, regex matching, or code compilation.
  2. Semantic Validation: If programmatic checks pass, an LLM-as-a-judge evaluates the output against qualitative rubrics (e.g., factual accuracy, completeness, tone).

Phase 3: Score Assessment and Routing

The Validator outputs a structured validation report containing a boolean status, a numeric score, and a list of specific errors. The system compares this report against the pre-defined scoring threshold. If the output passes, the loop terminates immediately, and the final result is returned to the client.

Phase 4: Feedback Synthesis and Iteration

If the output fails validation, the Critic synthesizes the errors into a structured feedback prompt. This prompt details exactly what failed, why it failed, and how to correct it. The Generator receives its previous output, the critic's feedback, and the original prompt, then produces an updated draft.

Phase 5: Guard Check and Fallback

Before starting another iteration, the system increments the iteration counter. If the counter exceeds the max-iteration guard, the loop breaks to prevent an infinite cycle. The system then executes a fallback mechanism, such as returning the highest-scoring draft or raising a system exception.

Architecture

The architecture consists of four primary components: the State Manager, the Generator, the Validator/Critic, and the Router.

The State Manager maintains the execution context, including the original prompt, the current draft, the iteration count, and the history of critiques. Execution starts when the State Manager receives a request and invokes the Generator. The Generator produces a draft and writes it to the State Manager.

The State Manager then routes the draft to the Validator/Critic. The Validator executes programmatic tests (such as schema validation or code execution) or semantic evaluations (LLM-as-a-judge). The Validator outputs a validation report containing a boolean status, a numeric score, and a list of specific errors.

This report flows to the Router. The Router checks two conditions: did the validation pass, and has the iteration count reached the max-iteration limit? If validation passes, the Router sends the final output to the client. If validation fails and the limit is not reached, the Router increments the iteration count in the State Manager and routes the draft and critique back to the Generator for refinement. If the iteration limit is reached without a passing score, the Router diverts the execution to a Fallback Handler, which logs a warning and returns either the highest-scoring draft or raises a system exception.

Critique Prompt Design: Structuring the Feedback Loop

The success of a self-correction loop depends heavily on the quality of the feedback provided to the Generator. Generic prompts like "This is incorrect, please fix it" often cause the model to oscillate or repeat the same mistake. To write effective critique prompts, you must provide structured, actionable feedback.

Use structured XML or JSON to isolate feedback components. The feedback should include:

  • The exact error location: Line numbers, JSON keys, or paragraph sections.
  • The validation rule violated: The specific constraint or rubric item that failed.
  • The observed vs. expected behavior: What the model produced versus what was required.
  • Constructive suggestions: Guidance on how to resolve the issue without giving away the complete solution.
<critique>
  <error_type>SchemaViolation</error_type>
  <location>$.billing_address.zip_code</location>
  <violation>Expected a 5-digit string, received a 9-digit integer.</violation>
  <remedy>Convert the zip_code to a string and ensure it is exactly 5 digits.</remedy>
</critique>

Setting Scoring Thresholds and Validation Metrics

Validation must be divided into deterministic and semantic layers. Deterministic validation should always run first because it is fast and cheap. If a JSON payload fails schema validation, there is no need to run an expensive LLM-as-a-judge step.

For semantic validation, establish a clear rubric with quantitative scoring (e.g., 1 to 5). Define explicit criteria for each score level:

  • Score 5: Fully accurate, complete, and adheres to all stylistic guidelines.
  • Score 4: Accurate and complete, but minor stylistic improvements are possible.
  • Score 3: Mostly accurate, but contains minor omissions or formatting issues.
  • Score 1-2: Critical inaccuracies, hallucinations, or major structural failures.

Set your scoring threshold based on your application's tolerance for error. For mission-critical tasks, require a perfect score (5/5) or zero critical errors. For creative tasks, a lower threshold (>= 4/5) may be acceptable to preserve variety.

Loop Budgets and Preventing Infinite Reflection

Without strict controls, self-correction loops can become infinite. This happens when a model fixes one error but introduces another, or when it oscillates between two incorrect states. To prevent this, you must implement a strict loop budget:

  1. Max-Iteration Guard: Set a hard limit on the number of reflection cycles (typically 3 to 5). If the agent cannot resolve the issue within this budget, terminate the loop.
  2. Oscillation Detection: Maintain a hash history of previous outputs. If the Generator produces an output that matches a previous state, break the loop immediately to prevent repetitive cycles.
  3. Context Window Management: Do not append the entire history of drafts and critiques to the chat history. This causes quadratic token growth and degrades prompt adherence. Instead, keep only the original prompt, the current draft, and the immediate critique in the context window.

Tradeoff Analysis: Latency vs. Accuracy

Implementing a self-correction loop introduces a significant latency penalty. Each iteration requires a full round-trip to the LLM, meaning a loop with 3 iterations can take 4 times longer than a single-pass generation.

To balance this tradeoff, consider a hybrid model architecture. Use a smaller, faster model (e.g., GPT-4o-mini or Claude 3.5 Haiku) for the initial generation and programmatic validation. If programmatic validation fails, use the same fast model for the first correction attempt. Only escalate to a larger, more capable model (e.g., GPT-4o or Claude 3.5 Sonnet) for semantic critique or if the fast model fails to resolve the issue after the first iteration.

Code Example

A complete, production-ready Python implementation of an agent self-correction loop using Pydantic for structured validation and a mock LLM client with retry guards.
Python
import os
import logging
from typing import Dict, Any, Tuple, Optional
from pydantic import BaseModel, Field, ValidationError

# Configure logging for production observability
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("SelfCorrectionLoop")

class GeneratedCode(BaseModel):
    language: str = Field(..., description="The programming language used.")
    code: str = Field(..., description="The executable code block.")
    unit_tests: str = Field(..., description="Unit tests verifying the code.")

class ValidationReport(BaseModel):
    is_valid: bool
    score: float = Field(..., ge=0.0, le=1.0)
    feedback: Optional[str] = None

class ReflectionAgent:
    def __init__(self, max_iterations: int = 3, target_score: float = 0.9):
        self.max_iterations = max_iterations
        self.target_score = target_score
        # Ensure API keys are loaded from environment variables
        self.api_key = os.environ.get("OPENAI_API_KEY")
        if not self.api_key:
            logger.warning("OPENAI_API_KEY not found in environment variables. Running in mock mode.")

    def _generate_draft(self, prompt: str, feedback: Optional[str] = None) -> Dict[str, Any]:
        """Simulates LLM generation call. In production, replace with actual LLM API call."""
        logger.info("Generating draft...")
        if not feedback:
            # First attempt: contains a deliberate syntax error for demonstration
            return {
                "language": "python",
                "code": "def add_numbers(a, b):
    return a + b",
                "unit_tests": "assert add_numbers(2, '3') == 5"  # Type mismatch error
            }
        else:
            # Corrected attempt based on feedback
            logger.info("Applying feedback to generate corrected draft...")
            return {
                "language": "python",
                "code": "def add_numbers(a, b):
    return int(a) + int(b)",
                "unit_tests": "assert add_numbers(2, '3') == 5"
            }

    def _validate_output(self, draft: Dict[str, Any]) -> ValidationReport:
        """Validates the draft programmatically and semantically."""
        logger.info("Validating draft...")
        try:
            # Step 1: Deterministic Schema Validation
            validated_data = GeneratedCode(**draft)
            
            # Step 2: Programmatic Execution Validation (Mocked unit test run)
            if "int(a)" not in validated_data.code and "'3'" in validated_data.unit_tests:
                return ValidationReport(
                    is_valid=False,
                    score=0.4,
                    feedback="Unit test failed: TypeError: unsupported operand type(s) for +: 'int' and 'str'. Please cast inputs to integers."
                )
            
            return ValidationReport(is_valid=True, score=1.0, feedback="All checks passed.")
            
        except ValidationError as e:
            return ValidationReport(
                is_valid=False,
                score=0.0,
                feedback=f"Schema validation failed: {str(e)}"
            )

    def execute(self, prompt: str) -> Tuple[Dict[str, Any], ValidationReport]:
        """Executes the self-correction loop with max-iteration guards."""
        current_prompt = prompt
        current_feedback = None
        best_draft = None
        best_report = ValidationReport(is_valid=False, score=0.0, feedback="No runs executed.")

        for iteration in range(1, self.max_iterations + 1):
            logger.info(f"Starting loop iteration {iteration}/{self.max_iterations}")
            
            # Generate draft
            draft = self._generate_draft(current_prompt, current_feedback)
            
            # Validate draft
            report = self._validate_output(draft)
            
            # Track best output in case we exhaust our budget
            if report.score > best_report.score:
                best_draft = draft
                best_report = report

            # Check stopping conditions
            if report.is_valid and report.score >= self.target_score:
                logger.info(f"Target score met on iteration {iteration}. Terminating loop.")
                return draft, report

            logger.warning(f"Iteration {iteration} failed validation. Score: {report.score}. Feedback: {report.feedback}")
            current_feedback = report.feedback

        logger.error("Max iterations reached without meeting target score. Returning best available draft.")
        return best_draft, best_report

if __name__ == "__main__":
    agent = ReflectionAgent(max_iterations=3, target_score=0.9)
    final_output, final_report = agent.execute("Write a Python function to add two numbers, handling string inputs.")
    print(f"
Final Result (Valid: {final_report.is_valid}):")
    print(f"Code: {final_output['code']}")
    print(f"Feedback: {final_report.feedback}")
Expected Output
2026-03-30 12:00:00,000 - INFO - Starting loop iteration 1/3
2026-03-30 12:00:00,001 - INFO - Generating draft...
2026-03-30 12:00:00,002 - INFO - Validating draft...
2026-03-30 12:00:00,003 - WARNING - Iteration 1 failed validation. Score: 0.4. Feedback: Unit test failed: TypeError: unsupported operand type(s) for +: 'int' and 'str'. Please cast inputs to integers.
2026-03-30 12:00:00,004 - INFO - Starting loop iteration 2/3
2026-03-30 12:00:00,005 - INFO - Generating draft...
2026-03-30 12:00:00,006 - INFO - Applying feedback to generate corrected draft...
2026-03-30 12:00:00,007 - INFO - Validating draft...
2026-03-30 12:00:00,008 - INFO - Target score met on iteration 2. Terminating loop.

Final Result (Valid: True):
Code: def add_numbers(a, b):
    return int(a) + int(b)
Feedback: All checks passed.

Key Takeaways

Decoupling generation from evaluation is essential for correcting complex, multi-step LLM errors.
Programmatic, deterministic validators should always run before expensive, semantic LLM-as-a-judge evaluations.
A hard max-iteration guard (typically 3-5 cycles) is mandatory to prevent infinite loops and runaway token costs.
Critique prompts must provide structured, actionable feedback (e.g., line-by-line errors) rather than generic correction requests.
Context window management is critical; keeping the entire iteration history leads to quadratic token growth and prompt degradation.
Graceful fallbacks must be defined to handle cases where the reflection loop exhausts its budget without passing validation.

Frequently Asked Questions

What is the difference between agent reflection and standard retries? +
Standard retries simply execute the same request again, hoping for a non-deterministic improvement. Agent reflection analyzes the specific failure, generates structured feedback, and passes that feedback to the generator to guide targeted corrections.
When should I avoid using a self-correction loop? +
Avoid self-correction loops for low-latency, low-stakes tasks (e.g., simple chat routing or basic summarization) where the added latency and token cost outweigh the benefits of minor quality improvements.
How do I prevent my agent from getting stuck in an infinite reflection loop? +
Implement a hard max-iteration guard (typically 3-5 cycles) and state oscillation detection, which hashes previous outputs to break the loop if the agent begins repeating itself.
Can I use a smaller, cheaper model for the critic node? +
Yes. A hybrid architecture often uses a smaller, faster model for initial generation and programmatic validation, only escalating to a larger, more capable model for complex semantic critiques.
How does agent reflection differ from Self-RAG? +
Self-RAG is a specific application of reflection focused on evaluating retrieved documents and generation alignment, whereas agent reflection is a general architectural pattern applicable to code generation, planning, and structured outputs.
What happens if the critic itself makes a mistake or hallucinates? +
If the critic hallucinates, the generator may receive incorrect feedback, leading to poor corrections. This is mitigated by using highly structured critique rubrics, deterministic validators, or a more capable model for the critic.
How do I handle state management across multiple reflection iterations? +
Use a centralized State Manager (backed by Redis or PostgreSQL) to track the original prompt, current draft, iteration count, and critique history, ensuring the loop remains stateless and horizontally scalable.
What is state oscillation in reflection loops, and how do I detect it? +
State oscillation occurs when an agent alternates between two incorrect states (e.g., fixing error A but introducing error B, then fixing B but reintroducing A). Detect it by maintaining a hash history of previous drafts and breaking the loop if a duplicate hash is found.