/pattern/react/

01 · Single AgentReason+ActThought-Action-Observation Loop

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.

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.