/pattern/react/

01 · Single AgentReason+ActThought-Action-Observation LoopTask-Oriented Agent

ReAct.
Think, act, look, think again.

The agent alternates iteratively between a reasoning step and a tool call until the goal is achieved. It observes the result of the action and derives the next step from it.

When to reach for it

  • The task requires tool use and the exact path cannot be planned in advance.
  • The agent must react adaptively to tool results.
  • Adaptivity matters more than minimizing LLM calls.

When it backfires

  • The plan is known in advance — use Plan-and-Execute.
  • Costs per LLM call are strictly limited.
  • The task is pure text generation without external data.

The tradeoff

High adaptability is gained at the expense of a significantly higher token and call volume per step.

The mental model

A loop you can draw on a napkin.

ReAct isn't an architecture — it's a posture. Three moments repeated until the task looks done. Watch the loop run; it's the entire pattern.

ThinkActObserve
Walk it through

A real run, step by step.

Task"Has our biggest competitor announced a new tariff?"
1 / 7
Thought 1"Big question, no plan yet. Let me grab the competitor's pricing page first."
Action 1fetch_page(url="acmecorp.com/pricing")
Observation 1Page lists a tier called "Pro" at €29 — but no published date is visible.
Thought 2"The price is new to me. I need the announcement date — try press releases."
Action 2web_search("AcmeCorp new tier announcement 2026")
Observation 2Press release dated 14 April 2026 confirms the launch.
Answer"AcmeCorp launched a Pro tier at €29 on April 14, 2026."
In code

The loop is already built in.

LangGraphpython
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o")
graph = create_react_agent(
    model,
    tools=[web_search, fetch_page],
    prompt="Answer with tool-use.",
)

# Built-in loop: model -> tools -> model -> ... -> END
# `recursion_limit` is your guard against runaway loops.
result = graph.invoke(
    {"messages": [("user", query)]},
    config={"recursion_limit": 10},
)
Pitfalls

Three ways this pattern will hurt you.

Unbounded reasoning

The model keeps reasoning, never decides to answer. A budget on iterations isn't optional — it's the loop's terminator.

Fix · Hard max_turns or recursion_limit. Treat the limit as expected, not exceptional.

Token blow-up from history

Every iteration appends to the message history. Run 8 turns of tool use and you're carrying 8 observations into every subsequent call.

Fix · Summarise old observations, or use a scratchpad pattern (Working Memory) to keep only the relevant slice.

Hallucinated tool calls

The model invents a tool name or fakes a parameter the schema doesn't allow. Without validation, you'll execute the wrong thing — or crash on it.

Fix · Schema-enforce every tool call. Treat the model's JSON like any other untrusted input.

Threat exposure

What this pattern exposes

Adopting this pattern opens these attack surfaces. Each links to its entry in the threat model.

Full threat model →
Framework support

Where ReAct is native.

LangGraphcreate_react_agent prebuiltNative
OpenAI Agents SDKdefault agent loopNative
CrewAIagent runtime defaultNative
Google ADKtool-calling agentsNative
Microsoft Agent FrameworkReAct primitivesNative

Search

Search patterns, frameworks, and pages.