ReAct Loops & Autonomous Cognitive Architectures
Master the Reasoning + Acting (ReAct) cycle, Plan-and-Solve architectures, scratchpad memory maintenance, and deterministic loop breakout conditions.
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:
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
- Unbounded Recursion: Never launch an agent without hard iteration caps () and token budgets.CODE / PROMPT
max_iterations=8
- Missing Observation Validation: Always validate tool outputs against Pydantic schemas before feeding them back into the scratchpad.
- No Breakout Signal: Always provide explicit guidance in the system prompt on when to terminate the loop.
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.