jnachi
Learning Hub
Agentic AI & RAG8 min readAdvanced

ReAct Loops & Autonomous Cognitive Architectures

Master the Reasoning + Acting (ReAct) cycle, Plan-and-Solve architectures, scratchpad memory maintenance, and deterministic loop breakout conditions.

Works with:LangChain / LangGraphOpenAI Tool CallingClaude Tool UsePython 3.12+

Key Takeaways

  • The ReAct pattern interleaves Thought (reasoning), Action (tool call), and Observation (environment feedback) until a definitive Final Answer is synthesized
  • Unconstrained agent loops risk infinite recursion; production agents mandate max-iteration caps, timeout limits, and error backoff hooks
  • Plan-and-Solve decomposes complex, non-linear enterprise goals into DAG sub-tasks before executing individual tool actions
  • Scratchpad memory holds intermediate reasoning states without polluting the permanent conversation context window

The Diagnostic Context

Autonomous AI agents differ fundamentally from static chatbots: they actively interrogate external environments, invoke APIs, observe real outputs, self-correct after failures, and iterate until reaching a goal.

The Core Technique

The ReAct Execution Loop in Python

The canonical ReAct pattern follows a strict state transition:

PYTHON
from typing import TypedDict, Annotated, Sequence
import operator

class AgentState(TypedDict):
    task: str
    scratchpad: list[str]
    iteration: int
    max_iterations: int
    is_complete: bool
    final_answer: str | None

def react_cycle(state: AgentState, llm, tools_map) -> AgentState:
    if state["iteration"] >= state["max_iterations"]:
        state["is_complete"] = True
        state["final_answer"] = "Error: Maximum iteration limit reached without convergence."
        return state

    # 1. Thought step: LLM analyzes history & decides next tool
    prompt = f"Goal: {state['task']}\nScratchpad:\n" + "\n".join(state["scratchpad"])
    response = llm.invoke(prompt)
    
    if "FINAL ANSWER:" in response.content:
        state["final_answer"] = response.content.split("FINAL ANSWER:")[1].strip()
        state["is_complete"] = True
        return state
        
    # 2. Action step: Parse tool name and arguments
    tool_name, tool_args = parse_action(response.content)
    
    # 3. Observation step: Execute tool in sandbox
    tool_fn = tools_map.get(tool_name)
    observation = tool_fn(**tool_args) if tool_fn else f"Error: Tool {tool_name} not found."
    
    state["scratchpad"].append(f"Thought: {response.content}")
    state["scratchpad"].append(f"Observation: {observation}")
    state["iteration"] += 1
    return state

Key Anti-Patterns in Agent Design

  1. Unbounded Recursion: Never launch an agent without hard iteration caps (
    CODE / PROMPT
    max_iterations=8
    ) and token budgets.
  2. Missing Observation Validation: Always validate tool outputs against Pydantic schemas before feeding them back into the scratchpad.
  3. No Breakout Signal: Always provide explicit guidance in the system prompt on when to terminate the loop.
5-Minute Activation Challenge

Try This Right Now

Define a ReAct system prompt that requires the model to format its reasoning as: Thought: <step analysis> Action: <tool_name>[<json_args>] Observation: <environment result> Final Answer: <terminal output> Test it with a multi-step currency conversion and tax calculation query!

Tip: Knowledge only becomes capability once you run the prompt yourself.

Comprehension Check

Test Your Instincts (1 Questions)

1

What is the primary purpose of the "Observation" step in a ReAct loop?