TL;DR
The ReAct agent pattern interleaves a model decision, an external action, and an observation so the next decision can use environmental feedback. ReAct is not a software framework, visible Thought text is not proof of the model's true reasoning, and a tool result is not automatically trustworthy. A production implementation needs structured decisions, server-side policy gates, bounded observations, explicit stop rules, side-effect recovery, and trajectory-level evaluation.
Key Takeaways
- ReAct is a pattern, not a package. The original work used few-shot trajectories containing reasoning traces, actions, and environment observations.
- Tool feedback creates capability and risk. An observation can provide current evidence, return an error, or carry indirect prompt injection.
- Visible reasoning is optional operational data. Record action reasons or summaries when useful, but do not use generated rationales as authorization or compliance evidence.
- The runtime owns control. Tool permissions, argument validation, timeouts, budgets, approvals, retries, and termination must be enforced outside the model.
- Evaluate the whole trajectory. Final-answer quality alone misses unnecessary calls, unsafe attempts, loops, unsupported claims, and ambiguous writes.
What Is the ReAct Agent Pattern?
ReAct, short for Reasoning and Acting, is a prompting and control pattern that interleaves model-generated reasoning traces with task-specific actions and environment observations. Yao et al. introduced it to combine two capabilities that had often been studied separately: reasoning about a task and interacting with an external knowledge source or environment.
The original paper evaluated ReAct on HotpotQA, FEVER, ALFWorld, and WebShop. Its reported gains belong to those models, prompts, tools, datasets, and in-context examples; they do not prove that every modern agent should expose chain-of-thought or that ReAct dominates every workflow.
The naming distinction matters:
ReAct
= a reasoning-and-acting pattern
ReAct agent
= an agent implementation that uses that pattern
Agent framework
= a software library that may implement ReAct plus state,
tools, tracing, persistence, deployment, and other features
A legacy URL or class name may still include framework, but the durable concept is the pattern. Framework-specific classes such as a LangChain ReAct agent are implementations, not the definition.
How Does the ReAct Loop Work?
A ReAct loop lets the model choose an action, lets trusted runtime code execute it, and returns a normalized observation before the model chooses again. The classic paper labels the generated stages Thought, Action, and Observation; a modern runtime may keep internal reasoning hidden and expose only a structured decision plus a short operational summary.
Each boundary has a different owner:
| Boundary | Model may propose | Runtime must enforce |
|---|---|---|
| Decision | answer, tool call, escalation | allowed decision type and output schema |
| Tool | tool name and arguments | identity, authorization, validation, timeout, destination |
| Observation | interpretation of returned data | size, type, provenance, redaction, trust label |
| Retry | another attempt | retry eligibility, backoff, idempotency, attempt budget |
| Finish | final response | evidence, format, policy, and task-success checks |
| Stop | completion or failure | maximum steps, wall time, spend, cancellation, repeated failure |
A Trace Is Evidence About Execution, Not About Hidden Reasoning
A ReAct trace can show which action was requested and which observation was returned, but generated Thought text is not a faithful transcript of internal computation. Chain-of-thought faithfulness research shows that models can use information without reporting it and can produce explanations that do not causally match the answer.
For operations, prefer a bounded event contract:
{
"step": 3,
"decision": "tool_call",
"decisionSummary": "Verify the order state before answering",
"tool": "lookup_order",
"argumentDigest": "sha256:...",
"policyDecision": "allowed",
"observation": {
"status": "ok",
"source": "orders-service",
"bytes": 84
},
"remainingBudget": {
"steps": 2,
"wallTimeMs": 1200
}
}
This event is useful for debugging without claiming that decisionSummary reveals the model's private or complete reasoning. Sensitive prompts, raw tool payloads, credentials, and personal data still need redaction and access control.
ReAct vs CoT, Tool Calling, and Agent Loops
ReAct is one possible policy inside an Agent Loop; it is not synonymous with chain-of-thought, function calling, or the runtime itself.
| Concept | Primary concern | External action required? | Production boundary |
|---|---|---|---|
| Chain-of-Thought prompting | Intermediate generated reasoning text | No | visible rationale may be unfaithful |
| Function calling | Structured tool name and arguments | Yes | schema does not grant permission |
| ReAct pattern | Interleave decision, action, and observation | Usually | observations can mislead or inject instructions |
| Agent Loop | Execute repeated model/tool steps | Optional | budgets, state, retries, cancellation, stop reasons |
| Deterministic workflow | Follow code-defined paths | Optional | less flexible, but easier to test and govern |
Native function calling improves the Action interface because the model can emit a typed tool request rather than text that a regex must parse. It does not make the surrounding loop safe. The application still decides whether that principal may invoke that tool on that object and where data may be sent.
When Should You Use ReAct?
Use ReAct when the next useful step genuinely depends on an observation that cannot be known before execution. Examples include exploratory research, diagnosing a failing system, navigating an unfamiliar codebase, or querying several sources until enough evidence is available.
Prefer a deterministic workflow when:
- the sequence is known in advance;
- every transition has a strict business rule;
- a write must follow a fixed approval chain;
- the task is a bounded transformation;
- a conventional search or single model call meets the quality target.
ReAct trades additional model calls, latency, cost, and failure paths for adaptive control. That trade must be measured against a simpler baseline, not assumed from the task being "multi-step."
A Runnable Structured ReAct Controller
The controller below demonstrates the durable part of ReAct: a policy proposes structured decisions, while trusted code validates and executes tools. It uses only the Python standard library and a read-only mock tool, so the example runs without an API key. In a real application, replace example_policy with a model adapter that returns the same Decision schema.
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional
class DecisionKind(str, Enum):
TOOL = "tool"
FINISH = "finish"
ESCALATE = "escalate"
@dataclass(frozen=True)
class Decision:
kind: DecisionKind
tool: Optional[str] = None
arguments: Dict[str, Any] = field(default_factory=dict)
answer: Optional[str] = None
@dataclass(frozen=True)
class Observation:
tool: str
ok: bool
data: Dict[str, Any]
source: str
@dataclass
class RunState:
goal: str
observations: List[Observation] = field(default_factory=list)
Policy = Callable[[RunState], Decision]
Tool = Callable[[Dict[str, Any]], Observation]
def lookup_order(arguments: Dict[str, Any]) -> Observation:
order_id = arguments.get("order_id")
if not isinstance(order_id, str) or not order_id.startswith("ord_"):
return Observation(
tool="lookup_order",
ok=False,
data={"error": "invalid_order_id"},
source="orders-service",
)
return Observation(
tool="lookup_order",
ok=True,
data={"order_id": order_id, "status": "shipped"},
source="orders-service",
)
TOOLS: Dict[str, Tool] = {"lookup_order": lookup_order}
def run_react(policy: Policy, goal: str, max_steps: int = 4) -> str:
state = RunState(goal=goal)
for _ in range(max_steps):
decision = policy(state)
if decision.kind is DecisionKind.FINISH:
if not decision.answer:
raise ValueError("finish decision requires an answer")
return decision.answer
if decision.kind is DecisionKind.ESCALATE:
raise RuntimeError("run requires human review")
if decision.kind is not DecisionKind.TOOL:
raise ValueError("unsupported decision kind")
if decision.tool not in TOOLS:
raise PermissionError("tool is not allowlisted")
observation = TOOLS[decision.tool](decision.arguments)
state.observations.append(observation)
raise TimeoutError("maximum ReAct steps reached")
def example_policy(state: RunState) -> Decision:
if not state.observations:
return Decision(
kind=DecisionKind.TOOL,
tool="lookup_order",
arguments={"order_id": "ord_123"},
)
latest = state.observations[-1]
if not latest.ok:
return Decision(kind=DecisionKind.ESCALATE)
return Decision(
kind=DecisionKind.FINISH,
answer=(
f"Order {latest.data['order_id']} is "
f"{latest.data['status']}."
),
)
print(run_react(example_policy, "Check order ord_123"))
# Order ord_123 is shipped.
The example deliberately keeps the policy and runtime separate. A model may request lookup_order; it cannot add a new tool, bypass the allowlist, invent an observation, or extend max_steps.
Extending the Controller for Production
Add controls in this order:
- Structured model adapter: validate decision JSON against a versioned schema.
- Principal-aware policy gate: authorize tool, object, arguments, and destination.
- Observation envelope: preserve source, timestamp, schema version, size, and trust label.
- Run budget: enforce steps, tokens, wall time, tool calls, and monetary spend.
- Cancellation: revoke the worker lease before another action begins.
- Final validator: check the result against evidence and task-specific acceptance criteria.
- Durability: checkpoint state separately from external business effects.
Do not add long-term memory or multiple agents until a measured failure requires them. More context and more actors increase the attack surface and make trajectories harder to attribute.
Tool Results Are an Untrusted Observation Channel
Every ReAct observation must be treated as data from a specific source, not as a new instruction or permission. A webpage, email, repository file, MCP result, or OCR transcript can contain indirect prompt injection. Even a trusted service can return stale, malformed, oversized, or cross-tenant data.
Apply controls before returning an observation to the model:
- enforce authentication and object-level authorization at the tool;
- cap bytes, nesting, redirects, rows, and MIME types;
- keep provenance and tenant identity with the payload;
- separate tool data from trusted runtime instructions;
- redact credentials and unnecessary personal data;
- restrict outbound destinations and prevent observations from granting capabilities;
- test multilingual, encoded, delayed, and cross-tool injection chains.
Prompt delimiters and classifiers are useful signals, but they do not create an authorization boundary. The prompt injection defense guide covers the broader threat model.
Retries and External Side Effects
A ReAct agent must not blindly repeat a write after an ambiguous failure. If a payment, email, deployment, deletion, or ticket creation times out, the absence of a response does not prove the effect failed.
For effectful tools, require:
- an application-generated idempotency key;
- a durable effect record with
planned,confirmed,ambiguous, and terminal states; - reconciliation against the system of record before retry;
- approval bound to the exact object, arguments, and destination;
- compensation where a confirmed effect can be reversed;
- fencing or leases so a stale worker cannot continue acting.
The model can propose a retry. The owning service decides whether retry is legal. Checkpointing the conversation or Agent Loop does not provide exactly-once semantics for an external system.
Failure Modes and Controls
| Failure mode | Why it happens | Control |
|---|---|---|
| Repeated action | observation does not change the policy | repeated-call detector and stop reason |
| Hallucinated tool | model invents a name or schema | allowlist and strict decision validation |
| Wrong arguments | task intent is lost during generation | typed schemas, object authorization, clarification |
| Observation injection | returned content contains instructions | provenance, data/instruction separation, least privilege |
| Evidence drift | final answer exceeds observations | claim-to-source validation and abstention |
| Cost explosion | loop continues without measurable progress | step, time, token, tool, and spend budgets |
| Duplicate side effect | timeout is interpreted as failure | idempotency key, effect record, reconciliation |
| False audit confidence | generated Thought sounds plausible | log enforceable events, not inferred mental state |
Retries should be classified by operation. A bounded read may be retryable; a write with unknown outcome must enter reconciliation rather than another model-directed attempt.
How to Evaluate a ReAct Agent
Evaluate ReAct against direct answering and deterministic workflow baselines on the same tasks, tools, model, budget, and infrastructure. The original paper's benchmark results establish the pattern's research value, not a universal production win.
Measure at least:
task_success
unsafe_success
supported_answer_rate
tool_selection_accuracy
invalid_argument_rate
unnecessary_tool_rate
mean_and_p95_steps
repeated_action_rate
recovery_after_tool_error
ambiguous_effect_rate
latency_and_cost_per_success
human_escalation_precision
Use trajectory assertions, not only an LLM judge:
- forbidden tools were never executed;
- tool arguments matched the authorized object;
- every material final claim maps to an observation;
- no observation changed runtime permissions;
- the run stopped with a known reason;
- injected failures produced reconcile, escalate, or bounded retry behavior;
- cancellation prevented later side effects.
Build error slices for stale data, empty results, malformed payloads, permission denial, prompt injection, timeout before commit, timeout after commit, repeated results, and conflicting observations.
Common Questions
Is ReAct still useful with native tool calling?
Yes, but native tool calling changes the implementation. It replaces text such as Action: search[...] with a structured request. ReAct still describes the adaptive loop when the model reads the result and decides what to do next. For a full runtime model, see the Agent Loop guide.
Should a UI display the model's Thought?
Usually not as a claim of faithful reasoning. Show concise action summaries, sources, tool status, approvals, and results that users can verify. A generated rationale may help debugging under access controls, but it can expose sensitive context and may not explain the true cause of a decision.
Does ReAct reduce hallucinations?
It can reduce unsupported guessing when the agent retrieves relevant, correct evidence and uses it properly. It can also amplify bad evidence, stale results, poisoning, or prompt injection. Measure supported-answer rate and retrieval failures instead of treating tool access as a factuality guarantee.
Is ReAct always better than plan-and-execute?
No. ReAct adapts after each observation but incurs sequential latency and repeated model calls. Plan-and-execute can be better when subtasks are known, parallelizable, and cheap to verify. A deterministic workflow is preferable when transitions and approvals must be fixed.
How many steps should a ReAct loop allow?
There is no universal number. Derive the limit from the task state machine, latency and cost budgets, tool risk, and replayed step distribution. Every run still needs a hard maximum, timeout, cancellation path, and repeated-action detector.
Summary
ReAct remains a useful pattern for tasks whose next step depends on new environmental feedback. Its durable contribution is the interleaving of decisions, actions, and observations, not a requirement to expose chain-of-thought or adopt a particular framework.
A production ReAct agent is therefore a controlled Agent Loop: the model proposes, the runtime authorizes, tools execute within bounded capabilities, observations retain provenance, and validators decide whether the run may continue or finish.
Primary Sources
- ReAct: Synergizing Reasoning and Acting in Language Models
- ReAct project page and original examples
- Anthropic: Measuring Faithfulness in Chain-of-Thought Reasoning
- Anthropic: Building Effective Agents
- OWASP LLM01:2025 Prompt Injection