Agentic Reasoning Patterns — From ReAct to Hierarchical Planning in Production Systems
The days of cobbling together agent systems with ad-hoc prompts and prayer are ending. Just as the Gang of Four's design patterns transformed object-oriented programming from chaotic experimentation into disciplined engineering, a parallel revolution is sweeping through agentic AI development. The January 2026 paper "Architecting Agentic Communities using Design Patterns" cataloged 45+ distinct patterns across reasoning, memory, and coordination—giving us, for the first time, a shared vocabulary for discussing what actually makes agents work. If you're still building agents by intuition alone, you're leaving significant reliability and performance on the table.
The research consensus emerging from 2026 is clear: production-grade agents don't rely on single reasoning approaches. They compose multiple patterns—ReAct cycles nested within hierarchical plans, memory augmentation feeding into both—creating systems that are more than the sum of their parts. The Agentic Frameworks for Reasoning Tasks study demonstrated that single patterns plateau around 67% task completion on complex reasoning benchmarks, while thoughtful composition pushes past 82%. Understanding these patterns isn't academic—it's the difference between agents that demo well and agents that ship.
This article dives deep into the three foundational reasoning patterns—ReAct, Memory-Augmented, and Hierarchical Planning—and shows you exactly how to compose them in LangGraph. We'll move past the conceptual and into the mechanical: state schemas, routing logic, failure modes, and a complete runnable implementation.
Core Pattern #1: ReAct — Reasoning-Action Cycles in Practice
The ReAct pattern—Reasoning plus Acting—represents perhaps the most fundamental shift in how we build agents. At its core, ReAct implements interleaved thought-action-observation loops: the agent explicitly reasons about its current state, selects an action (typically a tool call), observes the result, and then reasons again. This cycle continues until the agent determines the task is complete or reaches a termination condition.
What distinguishes ReAct as an "Agentic AI" pattern rather than a simple "LLM Agent" pattern is the autonomous determination of which actions to take based on observations. The agent isn't following a predefined workflow—it's dynamically deciding what to do next based on what it's learned. This autonomy is precisely what makes ReAct powerful and precisely what makes it dangerous in production.
The implementation anatomy breaks down into three distinct components. First, thought traces as explicit state: rather than letting reasoning happen implicitly in the model's hidden representations, ReAct externalizes it. The agent generates a "Thought:" prefix that captures its current understanding and intent. Second, action selection as tool binding: the thought leads to an explicit "Action:" that maps to a tool invocation with specific parameters. Third, observation parsing as state updates: the tool's output becomes an "Observation:" that feeds back into the next reasoning cycle.
Common failure modes are well-documented but still catch teams by surprise. Reasoning drift occurs when thought traces become increasingly repetitive or circular, often indicating the agent has lost track of its objective. Action stuttering manifests as the same tool being called repeatedly with identical or near-identical parameters—the agent is stuck in a local minimum. Observation blindness happens when the agent generates new thoughts that completely ignore the tool results it just received, often because the context window is saturated or the observation was poorly formatted.
Production hardening requires explicit countermeasures. Thought budgets cap the number of reasoning cycles (typically 5-7 for most tasks, rarely exceeding 10). Action deduplication tracks recent tool calls and flags or blocks repeated identical invocations. Observation summarization compresses long traces to preserve context window space for fresh reasoning. The LangGraph framework provides native support for these patterns through its state management and conditional routing capabilities.
Core Pattern #2: Memory-Augmented Agents — Beyond Conversation History
Memory-Augmented agents learn from interactions to improve future performance—a capability that transforms agents from stateless executors into systems that genuinely get better over time. This pattern operates distinctly from simple conversation history; it involves deliberate storage, retrieval, and application of learned information across sessions and tasks.
The 2026 research on agentic frameworks identifies three critical memory integration points. Pre-planning retrieval queries memory before the agent begins work, surfacing relevant past experiences, user preferences, or domain knowledge that should inform the approach. Mid-execution reference allows the agent to consult memory during reasoning cycles—"Have I seen this error before? What worked last time?" Post-task consolidation extracts lessons learned and stores them for future use, completing the learning loop.
The Memory as Action paradigm represents a crucial architectural decision. Rather than treating memory operations as implicit system behavior, modern agent frameworks increasingly expose memory operations—store, retrieve, update, forget—as first-class agent actions. This means the agent explicitly decides when to save information, what queries to run against its memory, and even when to deprecate outdated knowledge. The agent becomes responsible for its own learning, not just its immediate task execution.
Trade-off analysis reveals the hidden costs of memory augmentation. Memory hit rate measures how often retrieved memories are actually relevant—low hit rates mean you're burning context window tokens on noise. Retrieval latency adds directly to response time; embedding lookups and vector searches aren't free. Context window consumption is the silent killer—rich memory retrieval can consume 30-40% of your available context before the agent even begins reasoning about the current task.
When does memory hurt? Cases where accumulated memory introduces noise or outdated context are more common than most teams realize. An agent that "remembers" a deprecated API will confidently use it. An agent that learned workarounds for a bug that's since been fixed will apply unnecessary complexity. Memory requires curation, and autonomous agents that can't distinguish fresh knowledge from stale knowledge will degrade over time.
Core Pattern #3: Hierarchical Planning — Decomposing Complex Goals
Hierarchical Planning addresses a fundamental limitation of flat reasoning: some tasks are simply too complex to solve in a single ReAct loop. The pattern involves decomposing complex goals into subgoals, delegating execution to specialized processes, and synthesizing results back up the hierarchy.
The critical distinction from simple task decomposition lies in dynamic replanning. A static DAG executor follows predetermined paths regardless of intermediate outcomes. Hierarchical Planning, by contrast, monitors subgoal completion and adjusts the broader plan based on what's learned. If subgoal B reveals that subgoal C is unnecessary, a hierarchical planner adapts. If subgoal A fails in an unexpected way, the planner can reformulate subsequent steps or escalate.
Planning depth trade-offs are well-studied in the 2026 agentic frameworks research. Shallow plans (2-3 levels) execute quickly but may miss important subtleties in complex tasks. Deep plans (5+ levels) capture more nuance but introduce substantial overhead—each planning level requires LLM calls, and errors compound across levels. The research finding is clear: most production systems cap at 4 levels because planning overhead becomes dominant cost beyond 7 levels. The time spent planning exceeds the time saved by better execution.
Integration with ReAct creates powerful hybrid systems. Rather than choosing between planning and reactive execution, successful implementations use ReAct cycles within each planning level while maintaining hierarchical structure. The planner decomposes the goal into subgoals; each subgoal is executed via ReAct loops; observations from execution feed back into the planner for potential replanning. This combination—"hybrid reasoning strategies" in the research terminology—consistently outperforms pure approaches.
Failure recovery in hierarchical systems requires careful design. Subgoal failure propagation determines how a failed subgoal affects the broader plan—does it block the parent goal, trigger replanning, or get marked as optional? Replanning triggers define when the system should abandon its current plan and start fresh versus attempting local repairs. Graceful degradation ensures that partial success is captured even when full completion isn't possible.
Hands-On: Code Walkthrough
Let's build a three-pattern agent in LangGraph that composes ReAct, Memory-Augmented, and Hierarchical Planning. This research assistant plans multi-step investigations, reasons through each step with tool access, and learns from past queries to improve future performance.
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_core.tools import tool
import operator
import json
# State schema separating planning state, reasoning traces, and memory references
class AgentState(TypedDict):
# Hierarchical planning state
goal: str
plan: list[dict] # List of subgoals with status
current_subgoal_index: int
planning_depth: int
# ReAct reasoning state
messages: Annotated[list, add_messages]
thought_count: int
max_thoughts: int # Thought budget for ReAct loops
recent_actions: list[str] # For action deduplication
# Memory-augmented state
memory_context: str # Retrieved memories for current task
memories_to_store: list[dict] # Pending memory writes
# Meta state for pattern routing
task_complexity: Literal["simple", "moderate", "complex"]
pattern_trace: list[str] # Which patterns contributed to decisions
# Initialize the LLM - using Claude for strong reasoning
llm = ChatAnthropic(model="claude-sonnet-4-20250514", temperature=0)
# Define research tools for the ReAct pattern
@tool
def search_papers(query: str) -> str:
"""Search academic papers on a topic. Returns summaries of relevant papers."""
# Simulated - in production, connect to Semantic Scholar, arXiv, etc.
return f"Found 3 papers on '{query}': [Paper summaries would appear here]"
@tool
def search_documentation(query: str) -> str:
"""Search technical documentation for APIs and frameworks."""
return f"Documentation results for '{query}': [Docs would appear here]"
@tool
def analyze_code(code_snippet: str) -> str:
"""Analyze a code snippet for patterns, issues, or improvements."""
return f"Analysis of code: [Analysis would appear here]"
tools = [search_papers, search_documentation, analyze_code]
llm_with_tools = llm.bind_tools(tools)
# Memory operations as first-class actions
@tool
def store_memory(key: str, content: str, memory_type: str) -> str:
"""Store information for future retrieval.
memory_type: 'fact', 'procedure', 'preference', or 'lesson'"""
return f"Stored memory '{key}' of type '{memory_type}'"
@tool
def query_memory(query: str) -> str:
"""Retrieve relevant memories based on semantic query."""
# Simulated - in production, vector store retrieval
return f"Retrieved memories relevant to '{query}': [Memories would appear here]"
memory_tools = [store_memory, query_memory]
llm_with_memory = llm.bind_tools(tools + memory_tools)
# Pattern-specific node: Hierarchical Planning
def plan_decompose(state: AgentState) -> AgentState:
"""Decompose the goal into subgoals with hierarchical structure."""
# Track pattern usage for observability
state["pattern_trace"].append("hierarchical_planning:decompose")
planning_prompt = f"""You are a research planning agent. Decompose this goal into
2-4 concrete subgoals. Each subgoal should be independently executable.
Goal: {state['goal']}
Previously retrieved context from memory:
{state['memory_context']}
Return a JSON array of subgoals, each with:
- "description": what to accomplish
- "status": "pending"
- "estimated_complexity": "simple" | "moderate" | "complex"
- "dependencies": list of subgoal indices this depends on
"""
response = llm.invoke([SystemMessage(content=planning_prompt)])
# Parse the plan (with error handling in production)
try:
plan = json.loads(response.content)
except json.JSONDecodeError:
# Fallback: single subgoal matching the original goal
plan = [{"description": state["goal"], "status": "pending",
"estimated_complexity": "moderate", "dependencies": []}]
return {
**state,
"plan": plan,
"current_subgoal_index": 0,
"planning_depth": state.get("planning_depth", 0) + 1
}
# Pattern-specific node: ReAct reasoning cycle
def reason_act_observe(state: AgentState) -> AgentState:
"""Execute one ReAct cycle: think, act, observe."""
state["pattern_trace"].append("react:cycle")
# Check thought budget
if state["thought_count"] >= state["max_thoughts"]:
return {**state, "messages": state["messages"] + [
AIMessage(content="Thought budget exhausted. Summarizing findings...")
]}
current_subgoal = state["plan"][state["current_subgoal_index"]]
react_prompt = f"""You are executing a ReAct reasoning loop.
Current subgoal: {current_subgoal['description']}
Memory context: {state['memory_context']}
Recent actions taken (avoid repetition): {state['recent_actions'][-3:]}
Think step by step:
1. What do I know so far from observations?
2. What information am I still missing?
3. What action should I take next?
If the subgoal is complete, respond with "SUBGOAL_COMPLETE: [summary]"
Otherwise, call the appropriate tool.
"""
messages = state["messages"] + [SystemMessage(content=react_prompt)]
response = llm_with_memory.invoke(messages)
# Track the action for deduplication
action_signature = str(response.tool_calls) if response.tool_calls else "reasoning_only"
# Check for action stuttering (same action 3+ times)
if state["recent_actions"][-2:].count(action_signature) >= 2:
state["pattern_trace"].append("react:stutter_detected")
response = AIMessage(content="Detected repeated actions. Reconsidering approach...")
return {
**state,
"messages": state["messages"] + [response],
"thought_count": state["thought_count"] + 1,
"recent_actions": state["recent_actions"] + [action_signature]
}
# Pattern-specific node: Memory query (pre-planning retrieval)
def memory_query(state: AgentState) -> AgentState:
"""Query memory for relevant context before planning or execution."""
state["pattern_trace"].append("memory:pre_retrieval")
# Construct semantic query from current goal/subgoal
query_target = state.get("goal", "")
if state.get("plan") and state["current_subgoal_index"] < len(state["plan"]):
query_target = state["plan"][state["current_subgoal_index"]]["description"]
# Simulated memory retrieval - in production, use vector store
retrieved_context = f"Relevant past experiences for '{query_target}': [Retrieved memories]"
return {
**state,
"memory_context": retrieved_context
}
# Pattern-specific node: Memory store (post-task consolidation)
def memory_store(state: AgentState) -> AgentState:
"""Consolidate learnings from completed subgoal into memory."""
state["pattern_trace"].append("memory:consolidation")
if not state.get("plan"):
return state
current_subgoal = state["plan"][state["current_subgoal_index"]]
consolidation_prompt = f"""Review the execution of this subgoal and extract
key learnings worth remembering for future tasks.
Subgoal: {current_subgoal['description']}
Execution trace: {[m.content[:200] for m in state['messages'][-5:]]}
What lessons, facts, or procedures should be stored for future reference?
"""
response = llm.invoke([SystemMessage(content=consolidation_prompt)])
new_memory = {
"subgoal": current_subgoal["description"],
"learnings": response.content,
"timestamp": "2026-07-13" # In production, use actual timestamp
}
return {
**state,
"memories_to_store": state.get("memories_to_store", []) + [new_memory]
}
# Routing logic: determine which pattern to invoke based on state
def route_by_state(state: AgentState) -> str:
"""Conditional routing based on task complexity and current progress."""
# If no plan exists, start with memory retrieval then planning
if not state.get("plan"):
if not state.get("memory_context"):
return "memory_query"
return "plan_decompose"
# Check if current subgoal is complete
current_subgoal = state["plan"][state["current_subgoal_index"]]
last_message = state["messages"][-1] if state["messages"] else None
if last_message and "SUBGOAL_COMPLETE" in str(last_message.content):
# Mark subgoal complete and consolidate memory
current_subgoal["status"] = "complete"
# Move to next subgoal or finish
if state["current_subgoal_index"] < len(state["plan"]) - 1:
return "memory_store" # Consolidate before moving on
return "end"
# Check if we need to replan (too many failed attempts)
if state["thought_count"] > state["max_thoughts"] * 0.8:
state["pattern_trace"].append("routing:replan_considered")
# Could trigger replanning here for complex failures
# Default: continue ReAct cycle for current subgoal
return "reason_act_observe"
def advance_subgoal(state: AgentState) -> AgentState:
"""Advance to the next subgoal after memory consolidation."""
return {
**state,
"current_subgoal_index": state["current_subgoal_index"] + 1,
"thought_count": 0, # Reset thought budget for new subgoal
"recent_actions": [], # Clear action history
"memory_context": "" # Will be refreshed by memory_query
}
# Build the composed graph
def build_research_agent() -> StateGraph:
"""Construct the three-pattern agent graph."""
graph = StateGraph(AgentState)
# Add pattern-specific nodes
graph.add_node("memory_query", memory_query)
graph.add_node("plan_decompose", plan_decompose)
graph.add_node("reason_act_observe", reason_act_observe)
graph.add_node("memory_store", memory_store)
graph.add_node("advance_subgoal", advance_subgoal)
# Entry point: always start with memory retrieval
graph.add_edge(START, "memory_query")
# Memory query leads to planning if no plan exists
graph.add_conditional_edges(
"memory_query",
lambda s: "plan_decompose" if not s.get("plan") else "reason_act_observe",
{"plan_decompose": "plan_decompose", "reason_act_observe": "reason_act_observe"}
)
# Planning leads to ReAct execution
graph.add_edge("plan_decompose", "reason_act_observe")
# ReAct cycles with conditional exit
graph.add_conditional_edges(
"reason_act_observe",
route_by_state,
{
"reason_act_observe": "reason_act_observe",
"memory_store": "memory_store",
"memory_query": "memory_query",
"plan_decompose": "plan_decompose",
"end": END
}
)
# Memory store leads to advancing subgoal
graph.add_edge("memory_store", "advance_subgoal")
# After advancing, query memory for new context
graph.add_edge("advance_subgoal", "memory_query")
return graph.compile()
# Usage example with observability
if __name__ == "__main__":
agent = build_research_agent()
initial_state: AgentState = {
"goal": "Compare ReAct and Chain-of-Thought prompting for code generation tasks",
"plan": [],
"current_subgoal_index": 0,
"planning_depth": 0,
"messages": [],
"thought_count": 0,
"max_thoughts": 7, # Thought budget per subgoal
"recent_actions": [],
"memory_context": "",
"memories_to_store": [],
"task_complexity": "moderate",
"pattern_trace": []
}
# Execute with streaming for observability
for step in agent.stream(initial_state):
node_name = list(step.keys())[0]
state = step[node_name]
print(f"\n=== {node_name} ===")
print(f"Pattern trace: {state.get('pattern_trace', [])[-3:]}")
print(f"Thought count: {state.get('thought_count', 0)}/{state.get('max_thoughts', 7)}")
The code above demonstrates several key architectural decisions. The AgentState TypedDict cleanly separates concerns—planning state, reasoning traces, and memory references each have their own fields, making the graph easier to debug and extend. The pattern_trace field provides observability into which patterns contributed to each decision, essential for debugging in LangSmith.
Notice how the routing function route_by_state implements the pattern composition logic. It checks for plan existence, subgoal completion, and thought budget exhaustion to determine which pattern to invoke next. This is the "sequential composition" approach—plan first, then execute via ReAct, with memory operations at key integration points.
Pattern Composition: The 2026 Research Consensus
The Agentic Frameworks for Reasoning Tasks study crystallized what practitioners had been discovering empirically: single patterns plateau, and composition unlocks the next performance tier. Their benchmarks showed ReAct alone achieving 67% task completion on complex reasoning tasks, rising to 82% when combined with memory patterns and hierarchical planning.
Three composition strategies dominate the research literature. Sequential composition (plan → execute) is what we implemented above—hierarchical planning produces a structure that ReAct cycles then fill in. Nested composition embeds one pattern within another's nodes—for example, using ReAct cycles within each planning decision to gather information before committing to subgoals. Parallel composition runs multiple reasoning strategies simultaneously and uses voting or critic agents to select the best output.
The Critic-Actor meta-pattern deserves special attention. This approach uses one agent pattern to evaluate another's outputs—a planning agent that critiques a ReAct agent's proposed actions before allowing them, or a memory-augmented critic that checks whether proposed plans align with past successful approaches. The STEM Agent architecture demonstrates this with its self-adapting evaluation loops.
Reflexion integration takes composition further by implementing self-improvement loops that modify pattern parameters based on task outcomes. If ReAct cycles consistently hit thought budgets on certain task types, a Reflexion layer can learn to increase the budget or trigger earlier replanning. This meta-learning over pattern configurations represents the frontier of agent development.
Anti-patterns discovered through large-scale studies include memory-before-planning, which retrieves context before understanding what context is actually needed, resulting in irrelevant or distracting information. Over-hierarchical designs spend more time planning than executing, particularly problematic when planning overhead exceeds 40% of total execution time. LangGraph's StateGraph natively supports pattern composition through its subgraph and conditional routing features, while alternatives often require custom orchestration layers.
What This Means for Your Stack
Pattern selection should follow a clear heuristic based on task autonomy requirements. Low autonomy tasks—structured data extraction, validation, simple retrieval—benefit from Structured Output and Validation patterns, not full ReAct loops. The overhead isn't worth it. High autonomy tasks—open-ended research, complex debugging, multi-step investigations—justify the ReAct + Memory + Planning composition. The best AI agent frameworks in 2026 support both modes without forcing you into one approach.
The migration path for existing systems follows a proven trajectory. Start with ReAct alone, validating that your tools and observation parsing work correctly. Add memory when you see repeated tasks that could benefit from learned context—but measure memory hit rate before committing. Add hierarchical planning when task complexity exceeds what single-level reasoning can handle, typically indicated by thought budget exhaustion becoming common.
Observability requirements differ by pattern. ReAct needs thought trace visibility and action frequency monitoring. Memory needs retrieval quality metrics and staleness tracking. Hierarchical planning needs subgoal completion rates and replanning frequency. LangSmith already supports custom annotations; pattern-specific trace categories are reportedly coming Q3 2026.
Cost implications are non-trivial. Hierarchical planning multiplies LLM calls—a 4-level plan with 3 subgoals per level means 40+ planning calls before execution even begins. Memory retrieval adds 100-500ms latency per lookup depending on your vector store. Budget your patterns based on task value: high-stakes tasks justify composition overhead; routine tasks should use minimal patterns.
Testing strategy must address pattern interactions. Unit test individual patterns with mocked dependencies—verify ReAct handles observation blindness, verify memory retrieval degrades gracefully with empty stores, verify planning caps at maximum depth. Integration test compositions to catch emergent failures—patterns that work individually can interfere when combined. The research community has developed MemBench and SkillBench for regression testing; adopt similar benchmark-driven testing for your specific domain.
When should you avoid patterns entirely? Simple retrieval tasks don't need reasoning loops. Deterministic workflows with known branching don't need planning. Latency-critical paths (sub-second requirements) often can't afford pattern overhead. Not every agent needs to be agentic—sometimes a well-tuned prompt and a single LLM call is the right answer. The socio-technical analysis of agentic systems emphasizes that pattern complexity should match problem complexity, not exceed it.
What to Build This Week
Project: Build a Pattern-Instrumented Research Assistant
Take the code from the walkthrough and extend it with full pattern observability. Your goals:
Instrument pattern transitions: Log every time the router switches between patterns, including the state that triggered the switch. Output should show the pattern sequence for any query:
memory_query → plan_decompose → reason_act_observe × 4 → memory_store → advance_subgoal → memory_query → reason_act_observe × 2 → endImplement pattern metrics: Track thought budget utilization per subgoal, memory hit rate (how often retrieved memories appear in subsequent reasoning), and planning overhead ratio (planning time / total time).
Add a failure injection mode: Randomly fail tool calls or return unhelpful observations. Observe how your pattern composition handles degraded inputs. Does it replan? Hit thought budgets? Fall into action stuttering?
Connect to real tools: Replace the simulated tools with actual API calls—arXiv API for paper search, your codebase for documentation search. See how real-world latency and result variability affect pattern behavior.
The goal isn't a production-ready assistant—it's building intuition for how these patterns interact under realistic conditions. The teams shipping reliable agents in 2026 are the ones who've internalized these failure modes through hands-on experimentation, not just reading about them.
Sources
- LangGraph: Agent Orchestration Framework for Reliable AI Agents
- langchain-ai/langgraph: Build resilient agents - GitHub
- Agentic Frameworks for Reasoning Tasks: An Empirical Study - arXiv
- Architecting Agentic Communities using Design Patterns
- Socio-technical aspects of Agentic AI - arXiv
- STEM Agent: A Self-Adapting, Tool-Enabled, Extensible Architecture for Multi-Protocol AI Agent Systems
- A Large-Scale Study on the Development and Issues of Multi-Agent AI Systems
- The best AI agent frameworks in 2026 - LangChain
This is part of the **Agentic Engineering Weekly* series — a deep-dive every Monday into the frameworks,
patterns, and techniques shaping the next generation of AI systems.*
Follow the Agentic Engineering Weekly series on Dev.to to catch every edition.
Building something agentic? Drop a comment — I'd love to feature reader projects.
Top comments (0)