Skip to content

ReAct Agent Pattern: Reasoning + Acting Loop #153

@yasha-dev1

Description

@yasha-dev1

Overview

The ReAct (Reasoning + Acting) pattern is an agent architecture that alternates between explicit reasoning steps and targeted tool calls. The agent generates reasoning traces (thoughts) before each action, making the decision-making process more transparent and explainable. This pattern is particularly effective for complex multi-step tasks requiring explicit reasoning.

How It Works

The ReAct agent follows a cyclical pattern:

  1. Thought: LLM generates reasoning about what to do next
  2. Action: LLM decides which tool to call with what parameters
  3. Observation: Tool result is fed back to the LLM
  4. Repeat until reaching a Final Answer
Question: What is the weather in Paris and London?

Thought: I need to check the weather in Paris first
Action: weather_api
Action Input: {"city": "Paris"}
Observation: Paris - 18°C, Sunny

Thought: Now I need to check London
Action: weather_api
Action Input: {"city": "London"}
Observation: London - 15°C, Cloudy

Thought: I have both weather reports, I can now answer
Final Answer: Paris is 18°C and sunny, London is 15°C and cloudy

Reference Implementations

Proposed PyWorkflow Implementation

from pyworkflow import workflow, step, agent
from pyworkflow.agents import ReActAgent, Tool

# Define tools
@step()
async def search(query: str) -> str:
    """Search the web for information."""
    return await search_api.query(query)

@step()
async def calculator(expression: str) -> float:
    """Evaluate mathematical expressions."""
    return eval(expression)

# Create ReAct agent
@agent(
    pattern="react",
    model="claude-sonnet-4-5-20250929",
    tools=[search, calculator],
    max_iterations=10,
)
async def research_agent(query: str):
    """
    Research agent with explicit reasoning traces.
    
    The agent will:
    1. Generate thoughts about the query
    2. Decide which tools to use
    3. Observe results
    4. Repeat until confident in answer
    """
    pass  # Agent loop handled by framework

# Use the agent
result = await research_agent.run(
    "What is the population of Paris multiplied by 2?"
)
print(result.answer)
print(result.reasoning_trace)  # Full thought-action-observation log

Event Types

ReAct agents record these events in PyWorkflow's event log:

  1. AGENT_STARTED - Agent execution begins

    {"run_id": "abc123", "query": "...", "tools": ["search", "calculator"]}
  2. AGENT_THOUGHT - LLM generates reasoning

    {"thought": "I need to search for Paris population first", "iteration": 1}
  3. AGENT_ACTION - Tool call initiated

    {"action": "search", "action_input": {"query": "Paris population 2026"}, "iteration": 1}
  4. AGENT_OBSERVATION - Tool result received

    {"observation": "Paris population is 2.1M", "iteration": 1}
  5. AGENT_FINAL_ANSWER - Agent reaches conclusion

    {"answer": "4.2 million", "total_iterations": 3}
  6. AGENT_COMPLETED / AGENT_FAILED - Execution ends

Implementation Details

Prompt Structure

The ReAct prompt template should follow this format:

Answer the following question as best you can. You have access to the following tools:

{tool_descriptions}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {query}
Thought:

Event Replay

During replay, the agent should:

  1. Skip LLM calls for already-recorded thoughts/actions
  2. Return cached observations from event log
  3. Fast-forward to the last iteration before suspension
  4. Continue reasoning from that point

PyWorkflow Integration

# Internal implementation (pyworkflow/agents/react.py)
class ReActAgent:
    async def run(self, query: str):
        ctx = get_context()
        iteration = 0
        
        while iteration < self.max_iterations:
            # Check if thought exists in event log (replay mode)
            cached_thought = ctx.get_event(EventType.AGENT_THOUGHT, iteration=iteration)
            
            if cached_thought:
                thought = cached_thought.data["thought"]
            else:
                # Generate new thought
                thought = await self._generate_thought(query, history)
                await ctx.record_event(EventType.AGENT_THOUGHT, {"thought": thought, "iteration": iteration})
            
            # Similar pattern for action and observation
            if "Final Answer:" in thought:
                return self._extract_answer(thought)
            
            action, action_input = self._parse_action(thought)
            
            # Execute tool as workflow step (durable, retryable)
            observation = await self._execute_tool(action, action_input)
            
            await ctx.record_event(EventType.AGENT_OBSERVATION, {"observation": observation, "iteration": iteration})
            
            iteration += 1
        
        raise MaxIterationsExceeded()

Trade-offs

Pros

  • Explainability: Explicit reasoning traces make decisions transparent
  • Debuggability: Easy to see where agent went wrong
  • Human-in-the-loop: Can intervene at thought/action boundaries
  • Educational: Great for understanding agent behavior
  • Better for complex reasoning: LLM can "think out loud" before acting

Cons

  • ~40% slower: Extra LLM tokens for thoughts vs direct tool calling
  • Higher cost: More tokens generated per query
  • Verbosity: Reasoning traces can be lengthy
  • Prompt engineering: Requires careful prompt design to avoid loops
  • Token limits: Long conversations may exceed context window

Comparison to Tool-Calling Agents

Aspect ReAct Tool-Calling
Speed Slower (reasoning overhead) ~40% faster
Cost Higher (more tokens) Lower
Explainability High (visible reasoning) Low (black box)
Use Case Complex reasoning tasks Straightforward tool use

Related Issues

  • #[ISSUE_2] - Tool-Calling Agent (simpler, faster alternative)
  • #[ISSUE_3] - Plan-and-Execute Agent (different planning strategy)
  • #[ISSUE_4] - Router/Dispatcher Agent (meta-routing to ReAct agents)

References

Implementation Checklist

  • Create pyworkflow/agents/react.py with ReActAgent class
  • Add ReAct prompt templates
  • Implement thought-action-observation loop
  • Add event types: AGENT_THOUGHT, AGENT_ACTION, AGENT_OBSERVATION
  • Implement event replay for agent state
  • Add max_iterations safeguard
  • Create @agent(pattern="react") decorator
  • Add tests for ReAct agent execution
  • Add tests for event replay
  • Document ReAct agent in examples/
  • Add integration tests with mock LLM

Metadata

Metadata

Assignees

No one assigned

    Labels

    agentsAI Agent module (pyworkflow_agents)enhancementNew feature or requestfeatureFeature to be implemented

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions