~/articles/react-reflexion-planning-patterns
◆◆Intermediate

ReAct, Reflexion, and Planning Patterns

How ReAct, Reflexion, Plan-and-Execute, and LATS differ as agent planning strategies — with honest benchmarks, failure modes, and a decision framework for production.

15 min readupdated 2026-07-02Ironclad Academy
// DEPTH
the full breakdown — requirements, capacity, evolution, trade-offs

Your agent demo worked beautifully in the sales meeting. It navigated the web, used three tools, and answered the question in under 30 seconds. In production two months later, the same task pattern is failing one in three times — sometimes looping on a tool error, sometimes producing an answer that ignores a key observation it just received, sometimes just stopping early and claiming it's done. Nobody changed the model. Nobody changed the tools. The failure rate crept up as task complexity increased.

The culprit is usually not the model — it's the planning pattern. Most demo agents run ad-hoc loops with no explicit strategy. That works at 2-3 step tasks. It degrades quickly beyond that.

What a planning pattern actually does

Before examining specific patterns, it helps to pin down what we mean. A planning pattern is the structure that governs the agent's decision-making across multiple steps: how it decides what to do next, when it calls tools vs reasons, how it incorporates observations, and when it stops.

The simplest possible agent has no pattern — it asks the model to "do the task" and hopes the model figures out the steps internally. This works for one-shot tasks. It fails for multi-step tasks because the model has no mechanism to inspect intermediate results and adjust course.

Every planning pattern is a different answer to these four questions:

  1. Does the agent plan upfront or decide step-by-step?
  2. Does reasoning happen before each action, after, or both?
  3. How are observations incorporated into future reasoning?
  4. What terminates the loop?

ReAct: thought, action, observation

ReAct — from the 2022 paper "ReAct: Synergizing Reasoning and Acting in Language Models" — is the pattern that most agent frameworks implement as their default. The premise is simple: interleave the model's chain-of-thought with tool calls in the same context.

sequenceDiagram
    participant M as Model
    participant T as Tools

    M->>M: Thought: I need to find the current price of X
    M->>T: Action: search("X price today")
    T-->>M: Observation: X is priced at $142.30 as of 2026-07-01
    M->>M: Thought: Now I need to compare this with Y
    M->>T: Action: search("Y price today")
    T-->>M: Observation: Y is priced at $98.10
    M->>M: Thought: X is 45% more expensive than Y. I have what I need.
    M->>M: Final answer: X costs $44.20 more than Y (45.1% premium).

The key insight is that the observation from each tool call flows directly into the next thought. The model can change direction based on what it learns — if the search returns unexpected information, the next thought can account for it. This is what separates ReAct from purely action-based agents that don't reason between steps.

The original paper reported 71% success on ALFWorld (household navigation tasks) versus 45% for an action-only ablation — and 34 points absolute over the best imitation-learning baseline. On HotpotQA (multi-hop question answering), ReAct alone roughly matched chain-of-thought; the gains came from combining the two with self-consistency. These numbers are worth holding loosely — they were measured with PaLM 540B on specific benchmarks, and absolute performance has shifted significantly with more capable models. The relative principle holds: reasoning before each action helps, especially when tasks require adapting to unexpected observations.

Here's what ReAct looks like in code using the Anthropic SDK:

import anthropic

client = anthropic.Anthropic()

def run_react_agent(task: str, tools: list[dict], max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": task}]
    
    for step in range(max_steps):
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )
        
        # Check termination
        if response.stop_reason == "end_turn":
            # Extract the final text response
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text
            return "No answer produced."
        
        if response.stop_reason != "tool_use":
            raise ValueError(f"Unexpected stop reason: {response.stop_reason}")
        
        # Append assistant's response (may include thinking + tool calls)
        messages.append({"role": "assistant", "content": response.content})
        
        # Execute all tool calls and collect results
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)  # your dispatch
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(result),
                })
        
        messages.append({"role": "user", "content": tool_results})
    
    return f"Reached max steps ({max_steps}) without completing the task."

Notice the max_steps guard. This is not optional. Without a hard cap, a model in a confusing tool-error loop will happily consume your entire token budget. The agent loop article covers iteration caps in depth; the short version is that every production ReAct agent needs one.

One pattern worth calling out: when you get multiple tool_use blocks in a single response, execute them concurrently and send all the results back in a single tool_result message. Sequential execution when parallelism is available is a common performance mistake — see tool-use round trips for the full mechanics.

Reflexion: learning from failure without fine-tuning

ReAct adapts within a single episode. But if the task fails, the next attempt starts from scratch with no memory of what went wrong. Reflexion (Shinn et al., 2023) fixes this by adding an episodic memory of verbal lessons.

The architecture has three components: an actor (the ReAct agent), an evaluator (a separate model or heuristic that scores outcomes), and a self-reflection mechanism that converts failure signals into natural-language lessons.

flowchart LR
    TASK[Task] --> ACTOR[Actor: ReAct loop]
    ACTOR --> EVAL[Evaluator: did it succeed?]
    EVAL -->|"success"| DONE[Final answer]
    EVAL -->|"failure"| REFLECT[Self-reflection: write lesson]
    REFLECT --> MEM[Episodic memory buffer]
    MEM -->|"prepend lessons to context"| ACTOR

    style REFLECT fill:#a855f7,color:#fff
    style MEM fill:#0e7490,color:#fff
    style DONE fill:#15803d,color:#fff

The lesson lives in a verbal episodic buffer — a rolling list of natural-language failure notes prepended to the agent's context on the next attempt. Something like:

Previous attempts:
- Attempt 1: Failed because I searched for the product by name, but the database 
  uses SKU codes. Next time, call get_product_id() first to resolve the name to an SKU,
  then use that in subsequent queries.

This is what makes Reflexion interesting: it learns without gradient updates. The agent literally reads its own post-mortem and incorporates it into the next run. Across HumanEval, AlfWorld, and HotPotQA, Reflexion has shown 10-20 percentage point gains over one-shot ReAct in the original evaluation — again, relative ordering matters more than absolute numbers as models have improved.

The critical dependency is the evaluator. If the evaluator can't reliably detect failures, the self-reflection step produces lessons that describe why a success was actually a failure, poisoning the episodic buffer. For coding tasks, this is tractable: run the tests, check whether they pass. For open-ended tasks (writing, research synthesis), you need an LLM judge, and LLM judges have well-documented biases — position bias, length bias, agreeableness bias — with error rates above 50% in some configurations. See agent evaluation for why this is a harder problem than it appears.

The cost math is also punishing. Each retry episode runs the full ReAct loop plus the evaluator plus the reflection step. A task that takes 3 attempts and ~5,000 tokens per episode costs ~16,000-18,000 tokens total. That's fine for a high-value task. It's not fine if your agent has a 50% first-attempt success rate on every task in a high-volume pipeline.

Plan-and-Execute: separate the planner from the doer

ReAct decides what to do next at every step. Plan-and-Execute (Wang et al., 2023) separates this into two phases: a planner call that produces a complete step list upfront, and executor calls that carry out each step.

flowchart TD
    TASK[Task] --> PLANNER["Planner: decompose into steps\n(single LLM call)"]
    PLANNER --> STEPS["Step list:\n1. Get user profile\n2. Fetch recent orders\n3. Check inventory\n4. Generate recommendation"]
    STEPS --> E1[Executor: Step 1]
    STEPS --> E2[Executor: Step 2]
    STEPS --> E3[Executor: Step 3]
    E1 --> COMBINE[Combine results]
    E2 --> COMBINE
    E3 --> COMBINE
    COMBINE --> E4[Executor: Step 4]
    E4 --> DONE[Final answer]

    style PLANNER fill:#00e5ff,color:#0a0a0f
    style COMBINE fill:#0e7490,color:#fff

The appeal is twofold. First, predictability: you can show the plan to a human or a guardrail system before executing anything irreversible. Second, parallelism: independent steps can run concurrently, which cuts wall-clock latency on tasks with multiple independent lookups. For a task requiring five independent API calls, Plan-and-Execute guarantees they can all run at once across the plan's independent branches; ReAct gets that parallelism only when the model happens to batch the calls into a single response — otherwise each step waits for the previous observation.

The weakness is brittleness. The planner generates a plan based on the task description and its priors, without seeing any intermediate results. If step 2's output makes step 3's approach invalid — say, the user has no order history, so "fetch recent orders" returns empty, and the recommendation strategy should change — the executor dutifully runs step 3 as planned and generates a generic recommendation that ignores what it just learned. Fixing this requires a re-planning step, which adds latency and complexity, partly defeating the efficiency gain.

ReWOO (Xu et al., 2023) is a variant that takes the single-pass planning further: it generates the entire plan in one forward pass, referencing future tool outputs as placeholder variables (#E1, #E2) that workers later fill in with real results before a solver combines them. This is even cheaper (roughly 0.4× the tokens of full ReAct) but even more brittle — the plan cannot adapt to what those results actually contain. It suits well-constrained tasks where tool outputs are predictable and the planning LLM has seen similar tasks before.

In practice, the boundary between Plan-and-Execute and ReAct has blurred. LangGraph's graph-based state machines can model both: define explicit planning and execution nodes, route conditionally based on execution outcomes, and re-plan when observations deviate significantly from expectations. This hybrid is what most serious 2026 agent deployments use for complex workflows.

LATS: tree search over actions

LATS (Language Agent Tree Search, Zhou et al., 2023) applies Monte Carlo Tree Search to the agent action space. Instead of committing to one action at each step, it expands multiple candidate actions, simulates each to completion, scores the results, and backpropagates the scores to select the best path.

flowchart TD
    ROOT[Initial state] --> A1[Action A]
    ROOT --> A2[Action B]
    ROOT --> A3[Action C]
    A1 --> A1a[A then X]
    A1 --> A1b[A then Y]
    A2 --> A2a[B then X]
    A2 --> A2b[B then Y]
    A1a -.->|"score: 0.92"| ROOT
    A1b -.->|"score: 0.41"| ROOT
    A2a -.->|"score: 0.67"| ROOT
    A3 -.->|"pruned"| ROOT

    style ROOT fill:#00e5ff,color:#0a0a0f
    style A1a fill:#15803d,color:#fff
    style A3 fill:#555,color:#fff

The result: on HumanEval coding benchmarks, LATS achieved 94.4% with GPT-4, versus ~80% for standard greedy ReAct. On programming tasks with a well-defined correctness signal (test suites), the combination of exploration and value estimation finds solutions that greedy search misses.

The cost is steep. With a branching factor of 3 and depth of 5, you're expanding up to 3^5 = 243 nodes. In practice, pruning and early termination reduce this substantially, but a realistic LATS run on a 10-step task with tight pruning still costs 15,000-25,000 tokens — 3-5× a single ReAct run. This is acceptable when the task has a quantifiable value (generating a production-ready function that saves an engineer hours) and the action space is discrete enough to make the search tractable.

Tree of Thoughts is the simpler relative: BFS or DFS over intermediate reasoning steps, without calling external tools. It stays inside the model context and is useful for logic puzzles, planning in text, and creative brainstorming where you want to evaluate multiple reasoning paths before committing. It is not an agent pattern in the tool-calling sense — it doesn't interact with the environment between tree nodes.

{ "type": "error-compound", "accuracy": 0.85, "steps": 10, "title": "Compounding error at 85% per-step accuracy" }

What breaks

Every planning pattern degrades in specific ways. Knowing which failure mode corresponds to which pattern is more useful than knowing the benchmark numbers.

ReAct failure: reasoning drift. The thought before each action is cheap — and on long tasks, cheap reasoning degrades. The model's thoughts gradually lose coherence — the context fills with prior steps, the most relevant instructions drift toward the middle of the window, and the reasoning starts to optimize for whatever the recent observations said rather than the original task. This is the "lost-in-the-middle" problem applied to agent traces. Mitigations: keep task instructions at the top and bottom of context, compress old observations, cap context length explicitly. See context window management for agents.

ReAct failure: tool error loops. The model gets a tool error, writes a thought about it, calls the same tool again with slightly different parameters, gets the same error, and repeats. Without a circuit breaker on the specific tool, this loop continues until the iteration cap fires. Fix: track per-tool consecutive failure counts, surface the count to the model's context, and terminate or escalate after 2-3 failures.

Reflexion failure: poisoned memory. The evaluator incorrectly scores a correct answer as wrong (false negative). The reflection step produces a lesson based on a misdiagnosis. On the next attempt, the agent explicitly avoids the approach that actually worked. This is hard to debug because the lesson text looks plausible. Defense: require the evaluator to show its reasoning, spot-check evaluator outputs against human labels during development.

Plan-and-Execute failure: stale plan. The most common failure in practice. The planner generates a sequence of steps that assumes an intermediate result that doesn't materialize. Downstream steps proceed with wrong assumptions and produce a confident-sounding but wrong final answer. The model doesn't know the plan is invalid because it didn't receive the feedback that would have changed the plan. Defense: add a "plan validation" step after each execution block that checks whether the current results are consistent with the plan's assumptions; re-plan if not.

LATS failure: value function collapse. The scoring function that evaluates terminal states must distinguish near-correct from correct. For coding tasks, test suites work well. For open-ended tasks, LLM-based value functions inherit all the biases of LLM-as-judge. When the value function is noisy, MCTS converges to local optima rather than global ones — you get a confidently-selected but mediocre path. Defense: use multiple scoring criteria, average them, and treat LATS as unsuitable for tasks without a reliable terminal evaluator.

Universal failure: compounding error. This is strategy-agnostic. At 85% per-step accuracy — a reasonable number for a well-prompted modern model on a moderately hard task — 10 steps yields 0.85^10 ≈ 20% end-to-end success. Planning patterns change where errors concentrate, not whether they compound. Run the math in the other direction: >80% end-to-end success on a 5-7 step task requires ~96-97% per-step reliability (0.97^7 ≈ 0.81); at 85% per-step, anything beyond a single step already drops below 80%. That is why production agents cap depth aggressively.

{ "type": "agent-loop", "scenario": "stuck-loop", "title": "What a stalled ReAct loop looks like" }

Comparing the patterns in production

DimensionReActReflexionPlan-and-ExecuteLATS
Token cost1× baseline2-4× (retries)~0.5×3-10×
Observation-adaptivityHighHigh (within episode)LowMedium (tree explores)
First-attempt successModerateModerateModerate to highHigh
Multi-attempt successN/AHigh (with good evaluator)N/AHigh
Requires external evaluatorNoYesNoYes (for value function)
Parallelism potentialLowLowHighMedium
DebuggabilityHigh (linear trace)MediumHigh (visible plan)Low (tree logs)
Production maturityVery highMediumHighLow

The framework support picture: LangGraph can model all four patterns through its graph primitives and supports stateful re-planning. The OpenAI Agents SDK uses ReAct by default with explicit handoffs. CrewAI's sequential and hierarchical process types map roughly to Plan-and-Execute topologies. LATS is mostly a research framework — you'll find it in academic codebases but rarely in production agent stacks.

The decision in practice

The right pattern for most production agents is ReAct with a hard iteration cap and per-tool circuit breakers. It's the simplest, the most observable (linear trace you can log and replay), and well-supported by every major framework. Most of the time, the bottleneck is not the planning pattern — it's the quality of the tool schemas, the quality of the system prompt, or the task complexity itself. See designing tool schemas before assuming you need a more sophisticated planning architecture.

Graduate to Plan-and-Execute when:

  • Tasks are decomposable into independent parallel subtasks.
  • You need a human-reviewable step list before execution.
  • Token cost is a hard constraint and task structure is predictable.

Consider Reflexion when:

  • You have reliable automated evaluation (unit tests, structured graders).
  • The task is high-value enough to justify multiple retry episodes.
  • First-attempt failure rate is high and the failures have analyzable patterns.

Reach for LATS only when:

  • You have a reliable terminal evaluator (e.g., test suite for code).
  • The action space is discrete and bounded.
  • The task value is high enough to justify 5-10× the token cost.
  • You've already exhausted simpler patterns.

The multi-agent dimension complicates this slightly. When you have an orchestrator routing to specialized worker agents, the orchestrator's planning pattern and the worker agents' inner loops can be different. An orchestrator doing Plan-and-Execute (decompose and dispatch) with workers running ReAct (adapt within their subtask) is a common and effective hybrid. Multi-agent orchestration patterns covers the composition question in depth.

One piece of production advice that doesn't fit neatly into the pattern taxonomy: regardless of which planning approach you use, make the agent's reasoning visible to your observability stack. Log every thought, every tool call input and output, every observation. When a task fails in production, you need to see the full trace to distinguish "the planning pattern made a wrong turn" from "the tool returned bad data" from "the model hallucinated a tool call that doesn't exist." The pattern itself is a small part of the debugging story — the trace is the whole story.

The field has converged on ReAct as the default loop for good reason: it's predictable, auditable, and handles the majority of agentic tasks that reach production. The more exotic strategies earn their token cost only in narrow domains where you've already pushed the simpler approach to its ceiling.

// FAQ

Frequently asked questions

What is the ReAct pattern in AI agents?

ReAct (Reasoning + Acting) interleaves the model's chain-of-thought with tool calls in a single context stream: the model writes a thought, issues an action, observes the result, then writes the next thought. This interleaving lets observations inform subsequent reasoning steps. On ALFWorld, ReAct reached 71% success versus 45% for an action-only ablation in the original 2022 paper (Yao et al., evaluated with PaLM 540B) — and 34 points absolute over the best imitation-learning baseline. Most modern agent frameworks implement some variant of ReAct as their default inner loop.

How does Reflexion improve on ReAct?

Reflexion adds a self-evaluation layer after each episode: a separate evaluator scores the outcome, and the agent writes a natural-language "lesson learned" that gets prepended to the next episode attempt. This verbal episodic buffer, rather than gradient updates, is what makes the improvement possible without fine-tuning. Across HumanEval, AlfWorld, and HotPotQA, Reflexion has shown 10-20 percentage point gains over one-shot ReAct — but the gains are episodic (each retry sees the lessons) and require a reliable evaluator to catch failures in the first place.

When should I use Plan-and-Execute instead of ReAct?

Use Plan-and-Execute when the task structure is predictable and you need to parallelize subtask execution — for example, fetching data from five independent APIs before combining results. ReAct adapts better when intermediate observations change what steps are needed. Plan-and-Execute is cheaper (planning once vs reasoning at every step) but brittle: if a subtask's result makes earlier steps invalid, the planner must re-plan from scratch. For tasks with strong observation-dependence, ReAct's per-step reasoning flexibility outweighs the extra token cost.

What is LATS and when is it worth the cost?

LATS (Language Agent Tree Search) runs Monte Carlo Tree Search over the agent action space: expand multiple candidate next-actions, simulate each to a terminal state, backpropagate the scores, and return the best path. It achieved 94.4% on HumanEval with GPT-4 — significantly above greedy ReAct — but at 3-5x the token cost of a standard agent run. LATS is warranted for high-value decisions with a small, discrete action space (code generation, theorem proving), not for open-ended tasks where the tree fans out uncontrollably.

What is Tree of Thoughts and how does it differ from LATS?

Tree of Thoughts (ToT) applies BFS or DFS over intermediate reasoning steps, not over external actions. It is a pure reasoning strategy that stays within the model context and does not invoke real tools. LATS extends this idea to agents with tool calls and uses Monte Carlo rollouts with environment feedback. In practice, ToT is used for standalone reasoning tasks (puzzles, planning in text); LATS is used when the agent interacts with an external environment and needs to value paths based on real tool outcomes.

Does compounding error still affect ReAct agents?

Yes. If each ReAct step succeeds with 85% reliability, a 10-step task succeeds end-to-end with only 0.85^10 ≈ 20% probability. Planning patterns do not eliminate this — they change where errors concentrate. Plan-and-Execute front-loads risk into the planning step; LATS distributes it across tree branches. The practical mitigation is to cap task depth (under 10 steps for most production tasks), add per-step validation, and surface human checkpoints before irreversible actions.

// RELATED

You may also like