teachyou.ai academy
← All posts
AI AgentsReActLLM orchestrationagent architecturetool use

Agent Planning Algorithms: ReAct, Plan-and-Execute, Tree of Thoughts

Pramod Dutta · Jul 8, 2026 · 12 min read

Agent planning is the part of an AI agent that decides what to do next: which tool to call, whether to revise the approach, and when the task is done. Most production agents use one of three patterns: ReAct (reason, act, observe, repeat), Plan-and-Execute (write a full plan, then run it step by step), or Tree of Thoughts (explore multiple reasoning branches and pick the best one). Each trades off latency, cost, and reliability differently, and picking the wrong one for your task is the single most common reason agents feel flaky in production.

This article walks through all three with working code, tells you when each one breaks down, and shows a decision framework you can apply to your own agent instead of copying whatever pattern is trending.

What "agent planning" actually means

An LLM by itself just predicts the next token. An agent wraps that model in a loop: give it a goal, let it choose actions (usually tool calls), feed the results back in, and repeat until the goal is met or a stop condition fires. The planning algorithm is the shape of that loop.

There are three decisions every planning algorithm has to make:

  • When does the model plan relative to acting? Before every single action (ReAct), once up front (Plan-and-Execute), or across many parallel hypotheses (Tree of Thoughts).
  • How much does the model see of its own past reasoning? A running transcript, a fixed plan with checkboxes, or a tree of partial solutions it can backtrack through.
  • What happens when a step fails? Re-reason immediately, replan the remaining steps, or discard that branch and try another.

Get this right and the agent recovers from bad tool output, avoids infinite loops, and stops when it should. Get it wrong and you get agents that call the same tool five times, hallucinate a "done" state, or burn tokens exploring dead ends.

ReAct: reason, act, observe, repeat

ReAct (Reason + Act) interleaves thinking and doing at every step. The model produces a short thought, picks one action, observes the result, and loops. There's no upfront plan, just continuous re-evaluation.

This is the default pattern in most agent frameworks (LangGraph's create_react_agent, the Claude Agent SDK's tool loop, OpenAI's function-calling loop) because it maps directly onto how chat models already work: message in, message out, tool call in between.

A minimal ReAct loop, framework-free, using the Anthropic Python SDK:

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "search_docs",
        "description": "Search internal documentation",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    }
]

def search_docs(query: str) -> str:
    # stand-in for a real retrieval call
    return f"3 docs found for '{query}'"

messages = [{"role": "user", "content": "How do we rotate API keys?"}]

for _ in range(6):  # hard cap prevents runaway loops
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    messages.append({"role": "assistant", "content": response.content})

    if response.stop_reason != "tool_use":
        break  # model produced a final answer

    tool_results = []
    for block in response.content:
        if block.type == "tool_use" and block.name == "search_docs":
            result = search_docs(**block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": result,
            })
    messages.append({"role": "user", "content": tool_results})

final_text = next(b.text for b in response.content if b.type == "text")
print(final_text)

The loop has one job: keep passing the growing message history back to the model until it stops requesting tools. The for _ in range(6) cap is not optional. Without a hard iteration limit, a ReAct agent that gets a confusing tool result can call the same tool repeatedly, and you'll pay for every round trip.

Where ReAct wins: short tasks with a handful of tool calls, tasks where each step's outcome genuinely changes what should happen next (debugging, research, customer support lookups), and anything where you want the agent visibly reasoning about intermediate results rather than blindly executing a script.

Where ReAct breaks down: long multi-step workflows. Because the model re-plans from scratch at every step, it has no persistent sense of "step 4 of 9." It can lose track of the overall goal, especially past 10-15 tool calls, because the context window fills with observation noise and the original objective gets diluted. It's also the most token-expensive pattern per unit of work, since every step re-sends the full history.

Plan-and-Execute: plan once, run the steps

Plan-and-Execute splits the job into two phases. A planner model writes out an ordered list of steps up front. An executor (often a separate, cheaper model or the same model in a different mode) works through that list, calling tools as needed, without re-deriving the plan each time. If a step fails or the world changes, a replanner revises the remaining steps, not the whole thing.

This is closer to how a human tackles a project: write a checklist, then execute it, adjusting only when something on the checklist turns out to be wrong.

import anthropic
import json

client = anthropic.Anthropic()

def make_plan(goal: str) -> list[str]:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": (
                f"Break this goal into 3-6 ordered, concrete steps. "
                f"Return only a JSON array of strings.\n\nGoal: {goal}"
            ),
        }],
    )
    text = resp.content[0].text
    return json.loads(text)

def execute_step(step: str, context: list[str]) -> str:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": (
                f"Prior results:\n{chr(10).join(context)}\n\n"
                f"Now do this step: {step}\n"
                f"Report only the result."
            ),
        }],
    )
    return resp.content[0].text

def run(goal: str):
    plan = make_plan(goal)
    print("Plan:", plan)

    results = []
    for i, step in enumerate(plan):
        outcome = execute_step(step, results)
        results.append(f"Step {i+1} ({step}): {outcome}")

        # cheap replan check: does this outcome invalidate later steps?
        if "blocked" in outcome.lower() or "error" in outcome.lower():
            remaining = plan[i + 1:]
            plan = plan[:i + 1] + make_plan(
                f"Given this happened: {outcome}. Revise these remaining "
                f"steps: {remaining}"
            )

    return results

run("Set up a staging environment that mirrors production")

The key structural difference from ReAct: make_plan runs once (or occasionally, on failure), and execute_step doesn't see the full reasoning history, just prior results. That keeps context small and predictable even for long workflows, because each step only carries forward what it actually needs.

Where Plan-and-Execute wins: workflows with a known shape (deploy pipelines, multi-file code migrations, structured data extraction across many documents). It's also cheaper at scale, because you can run a strong model for planning and a fast, cheap model for the mechanical execution steps. And it's easier to show a user a plan before running it, which matters for anything with side effects.

Where it breaks down: tasks where you genuinely can't know step 3 until you see the result of step 1. If the domain is exploratory (open-ended research, debugging an unfamiliar codebase), a rigid plan gets invalidated constantly and you end up replanning so often it degrades into ReAct anyway, just with more overhead.

Tree of Thoughts: explore, evaluate, backtrack

Tree of Thoughts (ToT) treats problem-solving as search. Instead of committing to one reasoning path, the model generates several candidate next-steps ("thoughts") from the current state, a separate evaluation step scores each one, and the search keeps the promising branches while pruning the rest. It's breadth-first or best-first search over reasoning states, not a single linear chain.

This matters for problems where the first plausible-looking step is often wrong, and you only find out several steps later, so a linear agent (ReAct or Plan-and-Execute) commits too early and can't recover cheaply.

import anthropic

client = anthropic.Anthropic()

def propose_thoughts(problem: str, state: str, n: int = 3) -> list[str]:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=400,
        messages=[{
            "role": "user",
            "content": (
                f"Problem: {problem}\nCurrent partial solution: {state or '(none yet)'}\n"
                f"Propose {n} distinct next steps. One per line, no numbering."
            ),
        }],
    )
    return [l.strip() for l in resp.content[0].text.splitlines() if l.strip()][:n]

def score_state(problem: str, state: str) -> float:
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=10,
        messages=[{
            "role": "user",
            "content": (
                f"Problem: {problem}\nPartial solution: {state}\n"
                f"Rate how promising this path is toward a correct, complete "
                f"solution, 0.0 to 1.0. Reply with only the number."
            ),
        }],
    )
    try:
        return float(resp.content[0].text.strip())
    except ValueError:
        return 0.0

def tree_of_thoughts(problem: str, depth: int = 3, breadth: int = 3, keep: int = 2):
    frontier = [""]  # start with one empty state
    for level in range(depth):
        candidates = []
        for state in frontier:
            for thought in propose_thoughts(problem, state, breadth):
                new_state = f"{state}\n{thought}".strip()
                candidates.append(new_state)

        scored = [(score_state(problem, c), c) for c in candidates]
        scored.sort(key=lambda x: x[0], reverse=True)
        frontier = [state for _, state in scored[:keep]]  # prune

    return frontier[0]

best = tree_of_thoughts("Design a caching strategy for a read-heavy API with occasional bursty writes")
print(best)

The keep parameter is the whole point: at every depth, you generate breadth candidates per surviving state, score them, and discard everything outside the top keep. That's what lets the search recover from a bad early branch instead of being stuck with it, unlike ReAct where a bad early tool call just becomes part of the permanent transcript.

Where ToT wins: puzzles and problems with a real combinatorial structure where local moves can be locally correct but globally wrong (planning game moves, constraint satisfaction, certain classes of code generation where multiple valid approaches exist and only some pan out). It's also useful offline, for generating and picking the best of several candidate solutions before you commit to executing any of them.

Where it breaks down: cost. A ToT run with depth=3, breadth=3 makes roughly 9 proposal calls and 9 scoring calls, about 18x the model calls of a single ReAct step. It's rarely worth it for tasks with a single obviously-correct next action (looking up a record, calling a well-defined API), and it doesn't fit naturally into interactive agents that need to call real-world tools with side effects, since you don't want to "explore" three branches that each send an email.

Choosing between them

Use this as a first pass, not a rulebook:

  • Task has fewer than ~10 steps and each step depends on the last one's real-world result: ReAct. This covers most tool-using assistants, support bots, and coding agents doing edit-run-check loops.
  • Task has a known shape, more than ~10 steps, or you want to show the user a plan before running it: Plan-and-Execute. This covers migrations, multi-document pipelines, and anything with expensive or irreversible side effects where a preview matters.
  • Task is a reasoning or search problem where the "right" first move isn't obvious and backtracking is cheap (no side effects): Tree of Thoughts. This covers puzzle-like planning, candidate generation before human review, and offline solution search.

In practice, production systems mix these. A common pattern: Plan-and-Execute for the outer workflow, with each individual step run as a small ReAct loop when that step needs to call tools and adapt to results. Pure ToT is rarer in production because of its cost, but it shows up inside individual planning steps when the stakes of getting that one decision right are high enough to justify the extra model calls.

Guardrails that apply to all three

Regardless of which planning algorithm you pick, a few things prevent the failure modes that make agents unreliable:

  • Always cap iterations or search depth explicitly. Every example above has a hard limit (range(6), depth=3). An LLM will not reliably know when to stop on its own; it needs an external bound.
  • Log every intermediate state. Whatever the model reasons through, whether it's the ReAct transcript, the plan, or the ToT frontier, persist it. When an agent misbehaves in production, this is the only way to tell whether the planning logic or the tool execution was at fault.
  • Separate "decide" from "act" for anything irreversible. If a step sends money, deletes data, or emails a customer, don't let the planning loop execute it directly. Have the planner propose the action and require a separate confirmation step, human or automated, before it runs.
  • Give the model a way to say "stuck." All three patterns can spin: ReAct calling the same tool, Plan-and-Execute replanning in circles, ToT never finding a state above your score threshold. Build an explicit "I cannot complete this, here's why" exit path so failures surface instead of looping silently until the iteration cap kills them.

FAQ

Is ReAct still the right default for most agents in 2026? Yes, for short, tool-heavy, interactive tasks. It's the pattern most agent frameworks implement out of the box, it's the cheapest to reason about, and it matches how conversational tool use already works. Reach for Plan-and-Execute or Tree of Thoughts only when ReAct's specific weaknesses (context bloat over long horizons, no upfront preview, no backtracking) are actually hurting you.

Can I combine Plan-and-Execute with ReAct? Yes, and this is the most common production pattern. Use a planner to produce the ordered steps, then run each step through a small ReAct loop so it can adapt to tool results within that step, without needing to re-derive the entire plan.

Does Tree of Thoughts require a special model or API? No. It's a prompting and orchestration pattern, not a model feature. You need a model that can propose candidate next-steps and a way to score them, either by prompting the same model to self-evaluate (as in the example above) or by using a separate, cheaper model as the scorer to cut cost.

How do I stop a Plan-and-Execute agent from replanning forever? Cap the number of replans, not just the number of steps. If a step fails and triggers a replan, track how many times replanning has fired for the same goal and hand off to a human or fail loudly past a small threshold (2-3 is a reasonable start).

Is Tree of Thoughts worth the extra cost for coding agents? Usually not for routine edits, where the correct next step is fairly obvious and ReAct-style edit-test loops work well. It's worth considering for architecture decisions or algorithm selection, where you want the agent to sketch out two or three approaches and evaluate trade-offs before writing any code, then hand the chosen approach to a normal ReAct or Plan-and-Execute loop for implementation.

What's the simplest way to add planning to an agent that currently just calls tools in a loop? Add a single upfront planning call that returns a short ordered list of steps, store it alongside the conversation, and have the agent reference "which step am I on" in its system prompt. That alone, without a full Plan-and-Execute framework, fixes most of the "agent lost track of the goal" problems that show up in long ReAct loops.