/pattern/reflexion/

01 · Single AgentSelf-ReflectionSelf-CritiqueReflection Loop

Reflexion.
Act. Evaluate. Reflect. Retry.

The agent critically evaluates its own intermediate results and uses this feedback to improve the next steps or output in iterative loops.

When to reach for it

  • Result quality takes precedence over minimal latency.
  • Errors can be identified by the model through targeted self-critique.

When it backfires

  • The model's self-assessment is unreliable.
  • Hard external validations exist.
  • Latency and costs are strictly limited.

The tradeoff

Leads to higher result quality, but incurs additional token costs and can lead to false confidence.

The mental model

A shape you can draw on a napkin.

Act, evaluate, reflect, retry. A self-critique loop bounded by a retry budget.

ActorEvaluator
Walk it through

A real run, step by step.

Task"Write a SQL query that returns last quarter's revenue by region."
1 / 6
Actactor(prompt) → SELECT region, SUM(amount) FROM sales WHERE quarter = 'Q1' GROUP BY region
Evaluateevaluator(query) → ERROR: column 'quarter' does not exist; use 'date' with EXTRACT(QUARTER FROM date)
Reflectreflector(error) → 'I assumed a quarter column exists. I need EXTRACT(QUARTER FROM date) and filter for the prior quarter.'
Retryactor(reflection) → SELECT region, SUM(amount) FROM sales WHERE EXTRACT(QUARTER FROM date) = EXTRACT(QUARTER FROM CURRENT_DATE - INTERVAL '3 months') GROUP BY region
Evaluate 2evaluator(query) → PASS: query executes and returns 4 rows
AnswerSQL query returned. Loop terminated after 2 iterations (budget: 3).
In code

The loop is already built in.

LangGraphpython
from typing import TypedDict
from langgraph.graph import StateGraph, END

class State(TypedDict):
    prompt: str
    attempt: str | None
    evaluation: str | None
    reflection: str | None
    retries: int
    max_retries: int

def attempt(state):
    return {"attempt": actor_llm(state["prompt"])}

def evaluate(state):
    return {"evaluation": evaluator_llm(state["attempt"])}

def reflect(state):
    return {"reflection": reflector_llm(state["evaluation"]), "retries": state["retries"] + 1}

def should_continue(state):
    if "PASS" in state["evaluation"]:
        return "end"
    if state["retries"] >= state["max_retries"]:
        return "end"
    return "retry"

g = StateGraph(State)
g.add_node("attempt", attempt)
g.add_node("evaluate", evaluate)
g.add_node("reflect", reflect)

g.add_edge("attempt", "evaluate")
g.add_conditional_edges("evaluate", should_continue, {
    "retry": "reflect",
    "end": END,
})
g.add_edge("reflect", "attempt")
g.set_entry_point("attempt")

# Initial state includes budget
reflexion = g.compile()
Pitfalls

Two ways this pattern will hurt you.

No retry budget — Reflexion can spiral on an unsolvable task

The loop retries forever on a task that is fundamentally impossible (e.g., querying a non-existent table).

Fix · Hard cap on retries (max_retries). Treat budget exhaustion as an expected outcome, not an exception.

Reflector returns generic 'try again' notes

The reflection is too vague to change the actor's next attempt. The loop converges to the same wrong answer.

Fix · Require the reflector to cite specific errors and propose a concrete change. If it can't, route to human escalation.

Framework support

Where Reflexion is native.

LangGraphNative
Microsoft Agent FrameworkNative
CrewAINative

Search

Search patterns, frameworks, and pages.