MODULE 5  ·  AI Agents

ReAct Pattern: Think → Act → Observe — The Brain of Every AI Agent

Every AI agent you've used — Cursor, Claude Code, Perplexity, Devin — runs the same pattern under the hood.

📅 Mar 2026
10 min read
🎯 Episode 31 of 98
ReActAgent ArchitectureReasoningPatterns
In this episode

Every AI agent you've ever used — Cursor, Claude Code, Perplexity, Devin — runs the same pattern under the hood.

Think. What should I do next?

Act. Call a tool, execute a function.

Observe. Read the result.

Repeat.

That's ReAct — Reasoning + Acting. Published by Yao et al. in 2022, it became the foundational pattern for AI agents. Before ReAct, LLMs could either reason (chain of thought) or act (tool calling), but not both at the same time. ReAct combined them, and everything changed.

The Problem ReAct Solves

Before ReAct, there were two approaches:

Chain of Thought (Reasoning Only)

Q: How many goals has Messi scored in World Cup finals?

Think: Messi has played in World Cup finals in 2014 and 2022.

Think: In 2014, Argentina lost to Germany. Messi didn't score.

Think: In 2022, Messi scored twice against France.

Answer: 2 goals.

The LLM reasons step by step. But it can't verify anything. If its knowledge is wrong, the reasoning is confidently wrong.

Action Only (Tool Calling Without Reasoning)

Q: How many goals has Messi scored in World Cup finals?

Action: search("Messi World Cup final goals")

Result: "Messi scored 2 goals in the 2022 World Cup final"

Answer: 2 goals.

The LLM calls a tool, but there's no reasoning about why it chose that search query, whether the result is complete, or if it needs to dig deeper. What about the 2014 final?

ReAct: Both Together

Q: How many goals has Messi scored in World Cup finals?

Thought: I need to find Messi's goals across ALL World Cup finals, not just one.

Action: search("Messi goals in all World Cup finals career")

Observation: "2014 final: 0 goals. 2022 final: 2 goals (+ 1 in penalties)."

Thought: So 2 goals in regulation/extra time across both finals.

example
code
Penalties are usually counted separately. I should clarify.

Action: search("do penalty shootout goals count in World Cup stats")

Observation: "Penalty shootout goals are not counted in a player's tournament goal tally."

Thought: So the answer is 2 goals. Both in the 2022 final. None in 2014.

Answer: Messi scored 2 goals in World Cup finals (both in the 2022 final vs France).

See the difference? The LLM reasons about what to search, evaluates the results, identifies gaps, and does follow-up searches. It's not just calling tools — it's thinking about tool results.

The ReAct Loop

Here's the formal pattern:

Input: User's question or task

Loop:

  1. THOUGHT → LLM reasons about current state
example
code
"I need to...", "The result shows...", "I should check..."
  1. ACTION → LLM chooses a tool and provides arguments
example
code
search("query"), calculate("2+2"), read_file("config.py")
  1. OBSERVATION → System executes tool, returns result
example
code
The LLM reads this but doesn't generate it

→ Back to THOUGHT with new information

Exit: When the LLM decides it has enough information → generates final ANSWER

The key insight: thoughts and actions are interleaved. Every action is preceded by reasoning about why that action is the right choice. Every observation is followed by reasoning about what it means.

🔧

Implementation: The Prompt

The ReAct pattern is implemented primarily through the system prompt. Here's a simplified version:

You are a helpful assistant that solves problems step by step.

For each step, use exactly this format:

Thought: [Your reasoning about what to do next]

Action: [tool_name(arguments)]

After you see the result, reason about it:

Thought: [What the result tells you, what to do next]

Action: [next tool call, or provide final answer]

When you have enough information:

Thought: [Summary of findings]

Answer: [Final response to the user]

Available tools:

That's it. The "pattern" is really a prompting strategy combined with tool calling. The LLM generates the thoughts as regular text and the actions as tool calls.

📊

ReAct in Production (Real Code)

Here's how ReAct looks in actual code, not just theory:

python

import openai

import json

snippet
code
client = openai.OpenAI()
system_prompt = """You solve problems step by step.

Before each action, explain your reasoning.

After seeing results, analyze them before your next step.

When done, provide a final answer."""

snippet
code
tools = [
{
        "type": "function",
        "function": {
            "name": "search",
            "description": "Search the web for information",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "Search query"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Evaluate a math expression",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {"type": "string"}
                },
                "required": ["expression"]
            }
        }
    }

]

def execute_tool(name, args):

example
code
if name == "search":
        return web_search(args["query"])  # Your search implementation
    elif name == "calculate":
        return str(eval(args["expression"]))
    return "Unknown tool"
snippet
code
def react_agent(question, max_steps=10):
messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": question}
    ]
for step in range(max_steps):
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools
        )
msg = response.choices[0].message
        messages.append(msg)
# If the model generated text (thought/answer) AND tool calls
        if msg.content:
            print(f"💭 {msg.content}")
if msg.tool_calls:
            for tc in msg.tool_calls:
                name = tc.function.name
                args = json.loads(tc.function.arguments)
                print(f"🔧 {name}({args})")
result = execute_tool(name, args)
                print(f"👁️ {result[:200]}")
messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": result
                })
        else:
            # No tool calls → final answer
            return msg.content
return "Max steps reached"

Run it:

python

snippet
code
answer = react_agent("What's the population of India divided by the area in square km?")

💭 I need to find India's current population and total area.

🔧 search({"query": "India population 2025"})

👁️ India's population is estimated at 1.44 billion as of 2025...

🔧 search({"query": "India total area square kilometers"})

👁️ India has a total area of 3,287,263 square kilometers...

💭 Now I can calculate: 1,440,000,000 / 3,287,263

🔧 calculate({"expression": "1440000000 / 3287263"})

👁️ 438.0

💭 The population density is about 438 people per square km.

→ "India's population density is approximately 438 people per square kilometer..."

Three thoughts, three actions, three observations. That's ReAct in action.

ReAct vs Other Patterns

ReAct isn't the only agent pattern, but it's the most fundamental:

Pattern Description When to Use

ReActThink → Act → Observe loopGeneral-purpose agents, most tasks
Chain of Thought (CoT)Think step by step, no toolsPure reasoning, math, logic
Act-OnlyCall tools without explicit reasoningSimple tool-routing, low latency
Plan-then-ExecuteMake a full plan first, then execute all stepsWell-defined tasks with clear steps
ReflexionReAct + self-critique after each attemptComplex tasks needing error recovery
LATSTree search over ReAct trajectoriesWhen you need to explore multiple paths

Most production agents use ReAct or a variant of it. The "plan-then-execute" pattern is popular for coding agents — plan the changes, then make them all.

💡

Why Thoughts Matter

You might wonder: why not skip the "Thought" step and just let the model call tools directly? Two reasons:

  1. Better Tool Selection

Without reasoning:

User: "Is my flight on time?"

Action: search("flight status") ← Too vague

With reasoning:

User: "Is my flight on time?"

Thought: I need the user's flight number to check status.

example
code
I should ask, or if they mentioned it before, check context.
         They mentioned flight AI-204 earlier.

Action: search("AI-204 flight status today") ← Much better

  1. Error Recovery

Without reasoning:

Action: search("population of Gondor")

Result: "Gondor is a fictional kingdom in Lord of the Rings"

Action: search("Gondor population census") ← Going down a rabbit hole

With reasoning:

Action: search("population of Gondor")

Result: "Gondor is a fictional kingdom in Lord of the Rings"

Thought: Gondor is fictional — it doesn't have a real population.

example
code
I should tell the user this instead of searching further.

Answer: "Gondor is a fictional kingdom from Lord of the Rings —

example
code
it doesn't have a real population figure."

The thought step acts as a circuit breaker — it lets the model realize when it's going down the wrong path and course-correct.

🔬

Common Failure Modes

ReAct isn't perfect. Watch out for:

Thought loops: The model thinks the same thought repeatedly without making progress.

Thought: I should search for more information.

Action: search("topic")

Observation: Same information as before.

Thought: I should search for more information. ← Loop!

Over-thinking: The model generates long, unnecessary thoughts that waste tokens.

Thought: Let me carefully consider all the possible approaches. First,

I could search for X. Or I could search for Y. The advantage of X is...

[500 tokens of deliberation]

Action: search("X") ← Could have just done this immediately

Premature stopping: The model decides it has enough information when it doesn't.

Thought: I found the company's revenue. That's enough.

Answer: "The revenue is $50M."

← But the user asked for PROFIT, not revenue

Fix: Set clear stopping criteria in your prompt. "Only answer when you have ALL the information needed. If in doubt, do one more search."

🛡️

ReAct in the Wild

Every major agent framework implements ReAct under the hood:

LangChain's AgentExecutor — ReAct loop with configurable tools

Claude's tool use — Anthropic's API naturally supports interleaved reasoning + tool calls

OpenAI's Assistants API — ReAct loop managed server-side

AutoGPT — Extended ReAct with memory and self-prompting

CrewAI — Multi-agent ReAct where agents collaborate

When you use any of these, you're using ReAct. The frameworks just handle the loop, error handling, and tool execution for you.

📦

Practical Takeaways

snippet
code
ReAct = Reasoning + Acting — interleave thinking with tool calls for better results

The "Thought" step is critical — it improves tool selection and enables error recovery

It's implemented through prompting — the pattern lives in the system prompt, not in special architecture

Every major agent uses ReAct — it's the foundational pattern behind Cursor, Claude Code, Perplexity

Watch for loops and premature stopping — add max steps and clear stopping criteria

Chain of Thought alone can't verify facts — you need tools for grounding in reality

🚀

What's Next?

Episode 32: Multi-Agent Systems — What happens when one agent isn't enough? Orchestration patterns, handoffs, supervisor agents, and swarm intelligence. When you have multiple agents working together, who's in charge?

← Previous

Ep 30: Tool Calling

Next →

Ep 32: Multi-Agent Systems

Next: Episode 32 — Multi-Agent Systems

One agent is powerful. Multiple agents working together? That's where it gets wild — and where most teams get it wrong.

This is part of a 98-episode series covering AI engineering from tokens to production deployment.

← Previous Ep 30: Tool Calling: How an LLM Actually "Uses" Tools