Agent architecture patterns compared
Three patterns dominate production agent design today: ReAct, Plan-Execute, and Tree-of-Thought. Each solves a different problem. Picking the wrong one costs you in latency, money, or reliability before you understand why.
This article gives you a practical comparison so you can make the call fast when designing a new agent.
ReAct
ReAct (Reasoning + Acting) interleaves thought steps with tool calls in a single loop. The model reasons about what to do, calls a tool, observes the result, reasons again, calls another tool, and eventually produces a final answer.
Thought: I need to look up the user's account status
Action: get_account(user_id="u_123")
Observation: {"status": "active", "plan": "pro"}
Thought: Now I need to check their recent invoices
Action: list_invoices(user_id="u_123", limit=3)
Observation: [{"id": "inv_1", "amount": 49, "status": "paid"}, ...]
Thought: I have enough to answer
Final Answer: Your account is active on the Pro plan. Your last invoice was $49 and has been paid.
When it works well:
- Short-horizon tasks (2–6 tool calls)
- Unpredictable branching where the next action depends on the previous result
- Conversational agents where the user can steer mid-loop
- Exploratory tasks where you do not know the path ahead of time
When it breaks:
- Long-horizon tasks with many steps — the context window fills with intermediate traces
- Tasks that require parallel work — ReAct is inherently sequential
- Production systems where every intermediate step costs tokens you do not need
Token cost: High relative to the work done, because every thought and observation stays in the context window.
Latency: Directly proportional to the number of tool calls. Each call waits for the previous result.
Reliability: Good when the task is short. Degrades as chain length grows — each step is another chance for the model to reason incorrectly.
Complexity: Low. You can implement a working ReAct loop in under 100 lines of Python using any provider SDK.
Plan-Execute
Plan-Execute separates planning from execution. The model first produces a structured plan (a list of steps), then a separate executor runs those steps, optionally with a different model or set of tools.
# Phase 1: Planner
plan = planner_llm.complete(
"Create a step-by-step plan to process this support ticket: {ticket}"
)
# Returns:
# [
# {"step": 1, "action": "classify_ticket", "input": "..."},
# {"step": 2, "action": "lookup_customer", "input": "..."},
# {"step": 3, "action": "draft_response", "input": "..."},
# ]
# Phase 2: Executor
for step in plan.steps:
result = executor.run(step)
plan.record(step, result)
When it works well:
- Tasks where the path is mostly predictable from the starting state
- Long multi-step workflows where you want to audit or modify the plan before execution
- Systems that need a human-in-the-loop review point between planning and acting
- Workflows where some steps can run in parallel (the executor can parallelize)
When it breaks:
- Tasks with high environmental uncertainty — the plan becomes stale after the first unexpected result
- Short tasks where the planning overhead outweighs the benefit
- Dynamic environments where step outputs change what subsequent steps should do
Token cost: Moderate. Planning is a single LLM call. Execution calls are focused and carry less context than ReAct traces.
Latency: Planning adds upfront latency. Execution can be fast if steps run in parallel.
Reliability: Higher than ReAct for predictable workflows. Lower for adaptive ones — the executor does not replan when it hits unexpected results unless you add a replanning loop.
Complexity: Medium. You need a plan schema, a planner prompt, an executor loop, and a decision about when to replan.
Tree-of-Thought
Tree-of-Thought (ToT) generates multiple reasoning branches and evaluates them before committing to a path. Instead of a single linear chain, the model explores several options, scores each, and picks the best continuation.
# Generate candidate next steps
candidates = [
llm.complete(f"{state}
Option A:"),
llm.complete(f"{state}
Option B:"),
llm.complete(f"{state}
Option C:"),
]
# Score each candidate
scores = [evaluator.score(c) for c in candidates]
# Pick the best and continue from there
best = candidates[scores.index(max(scores))]
When it works well:
- Tasks that resemble search problems — writing, code generation, mathematical reasoning
- Cases where local optima are a real risk (picking the first plausible path leads to dead ends)
- Offline batch tasks where you can afford multiple LLM calls per decision point
- Quality-critical work where you are willing to pay for better outputs
When it breaks:
- Latency-sensitive applications — branching multiplies your LLM calls
- Tasks without a clear scoring function — you need a way to evaluate candidates
- Most real-time conversational agents
Token cost: High. You are paying for multiple completions at each branch point.
Latency: High. Multiple LLM calls per step. Not suitable for interactive use.
Reliability: High for the task types it fits. The branching and evaluation naturally finds better paths.
Complexity: High. You need candidate generation, a scoring/evaluation mechanism, and a search strategy (breadth-first, depth-first, beam search).
Decision matrix
| Dimension | ReAct | Plan-Execute | Tree-of-Thought |
|---|---|---|---|
| Latency | Medium (sequential) | Low-Medium (parallel exec) | High (multiple branches) |
| Token cost per task | High | Medium | Very high |
| Reliability (short tasks) | High | High | High |
| Reliability (long tasks) | Degrades | Good | Good |
| Parallelism support | No | Yes | Yes (per branch) |
| Human review point | Hard | Natural | Hard |
| Implementation effort | Low | Medium | High |
| Good for real-time | Yes | Yes | No |
| Good for quality-critical offline work | No | Sometimes | Yes |
How to choose
Start with ReAct if:
- The task has 6 or fewer tool calls
- The path depends heavily on intermediate results
- You are prototyping and want something running fast
Use Plan-Execute if:
- The task is well-defined and the steps are mostly predictable
- You want a human review point before execution
- You need parallel step execution
- Long-horizon multi-step tasks are the norm
Use Tree-of-Thought if:
- Output quality is more important than latency or cost
- The task resembles a search problem (many local optima)
- You are running offline batch work
- You have a reliable scoring function
Hybrid patterns are common in production. A Plan-Execute outer loop with a ReAct inner executor for individual steps is a practical middle ground. The planner breaks the task into chunks; each chunk runs a short ReAct loop.
Practical notes for production
- Instrument every pattern the same way. Log the full trace — thoughts, tool calls, observations, scores — regardless of pattern. The patterns differ in structure but share the same debugging needs.
- Token cost adds up faster than you expect. A Plan-Execute agent with a 10-step plan and 3 tool calls per step is 30+ LLM calls before counting retries. Model your cost before committing.
- Reliability degrades with context window pressure. For ReAct especially, prune intermediate results or summarize early observations before they crowd out later reasoning.
- The executor in Plan-Execute does not need to be the same model as the planner. Use a cheaper, faster model for execution steps that just need tool routing. Save your best model for planning and synthesis.