Overview of Agent Architectures
The architecture of an AI agent determines how it reasons, plans, and executes tasks. Choosing the right architecture is one of the most important design decisions when building agent systems. Each pattern trades off between reliability, flexibility, cost, and latency.
In this lesson, we'll explore the four dominant agent architectures used in production systems today: ReAct, Plan-and-Execute, Multi-Agent, and Tool-Use patterns. Understanding these patterns will help you design agents that are both powerful and reliable.
Architecture Selection Guide
- ReAct: Best for tasks requiring interleaved reasoning and action, like research and analysis
- Plan-and-Execute: Best for complex tasks that benefit from upfront planning before execution
- Multi-Agent: Best for tasks requiring diverse expertise or parallel workstreams
- Tool-Use: Best for straightforward tasks where the LLM just needs access to external capabilities
1. ReAct (Reasoning + Acting)
The ReAct pattern (Yao et al., 2022) interleaves reasoning traces with actions. The agent thinks about what to do, takes an action, observes the result, then thinks again. This creates a tight feedback loop where reasoning informs action and action results inform further reasoning.
ReAct Loop
| Step | Component | Description |
|---|---|---|
| 1 | Thought | The agent reasons about the current state and what action to take |
| 2 | Action | The agent selects and executes a tool or function |
| 3 | Observation | The agent receives and processes the action's result |
| 4 | Repeat | Loop until the task is complete or a stop condition is met |
// ReAct agent implementation with LangChain
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { Calculator } from "@langchain/community/tools/calculator";
const model = new ChatAnthropic({
model: "claude-sonnet-4-20250514",
temperature: 0,
});
const tools = [
new TavilySearchResults({ maxResults: 3 }),
new Calculator(),
];
// Create a ReAct agent with LangGraph
const agent = createReactAgent({
llm: model,
tools: tools,
});
// Run the agent
const result = await agent.invoke({
messages: [
{
role: "user",
content: "What is the population of Tokyo and what is it divided by 3?",
},
],
});
console.log(result.messages[result.messages.length - 1].content);
# ReAct agent implementation with LangChain Python
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.tools import DuckDuckGoSearchRun
model = ChatAnthropic(model="claude-sonnet-4-20250514", temperature=0)
tools = [
TavilySearchResults(max_results=3),
DuckDuckGoSearchRun(),
]
# Create a ReAct agent
agent = create_react_agent(model, tools)
# Run the agent
result = agent.invoke({
"messages": [
{"role": "user", "content": "Research the latest AI agent frameworks in 2026"}
]
})
print(result["messages"][-1].content)
2. Plan-and-Execute
The Plan-and-Execute pattern separates planning from execution. First, the agent creates a detailed plan with discrete steps. Then it executes each step, optionally re-planning if circumstances change. This is particularly effective for complex, multi-step tasks.
// Plan-and-Execute pattern
import { ChatAnthropic } from "@langchain/anthropic";
const planner = new ChatAnthropic({ model: "claude-sonnet-4-20250514" });
const executor = new ChatAnthropic({ model: "claude-sonnet-4-20250514" });
interface Plan {
steps: string[];
currentStep: number;
}
async function planAndExecute(task: string): Promise<string> {
// Phase 1: Planning
const planResponse = await planner.invoke([
{
role: "system",
content: `Create a step-by-step plan to accomplish the task.
Return a JSON array of steps. Each step should be a clear, actionable instruction.`,
},
{ role: "user", content: task },
]);
const plan: Plan = {
steps: JSON.parse(planResponse.content as string),
currentStep: 0,
};
// Phase 2: Execution
const results: string[] = [];
for (const step of plan.steps) {
const execResponse = await executor.invoke([
{
role: "system",
content: `Execute this step. Previous results: ${results.join("\n")}`,
},
{ role: "user", content: step },
]);
results.push(execResponse.content as string);
plan.currentStep++;
}
// Phase 3: Synthesize
return results.join("\n\n");
}
# Plan-and-Execute pattern in Python
from langchain_anthropic import ChatAnthropic
import json
planner = ChatAnthropic(model="claude-sonnet-4-20250514")
executor = ChatAnthropic(model="claude-sonnet-4-20250514")
async def plan_and_execute(task: str) -> str:
# Phase 1: Create a plan
plan_response = planner.invoke([
{"role": "system", "content": """Create a step-by-step plan.
Return a JSON array of actionable steps."""},
{"role": "user", "content": task}
])
steps = json.loads(plan_response.content)
# Phase 2: Execute each step
results = []
for step in steps:
exec_response = executor.invoke([
{"role": "system", "content": f"Execute this step. Context: {results}"},
{"role": "user", "content": step}
])
results.append(exec_response.content)
# Phase 3: Synthesize results
return "\n\n".join(results)
3. Multi-Agent Architecture
In multi-agent systems, multiple specialized agents collaborate to solve a problem. Each agent has a specific role, tools, and expertise. A supervisor or orchestrator coordinates the agents, routing tasks and aggregating results.
# Multi-agent system with LangGraph
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-sonnet-4-20250514")
# Define specialized agents as nodes
def researcher(state: MessagesState):
"""Agent specialized in research and information gathering."""
response = model.invoke([
{"role": "system", "content": "You are a research specialist. Find accurate information."},
*state["messages"]
])
return {"messages": [response]}
def analyst(state: MessagesState):
"""Agent specialized in data analysis and insights."""
response = model.invoke([
{"role": "system", "content": "You are a data analyst. Analyze the information provided."},
*state["messages"]
])
return {"messages": [response]}
def writer(state: MessagesState):
"""Agent specialized in writing clear reports."""
response = model.invoke([
{"role": "system", "content": "You are a writer. Create a clear, well-structured report."},
*state["messages"]
])
return {"messages": [response]}
# Build the multi-agent graph
graph = StateGraph(MessagesState)
graph.add_node("researcher", researcher)
graph.add_node("analyst", analyst)
graph.add_node("writer", writer)
graph.add_edge(START, "researcher")
graph.add_edge("researcher", "analyst")
graph.add_edge("analyst", "writer")
graph.add_edge("writer", END)
app = graph.compile()
result = app.invoke({"messages": [{"role": "user", "content": "Analyze AI agent trends"}]})
4. Tool-Use Pattern
The simplest agent architecture is the tool-use pattern, where an LLM is given access to a set of tools and decides which ones to call based on the user's request. Modern LLMs like Claude and GPT-4 have native function calling support that makes this pattern reliable and easy to implement.
// Tool-use pattern with Anthropic's native function calling
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object" as const,
properties: {
location: { type: "string", description: "City name" },
},
required: ["location"],
},
},
{
name: "search_database",
description: "Search the product database",
input_schema: {
type: "object" as const,
properties: {
query: { type: "string", description: "Search query" },
limit: { type: "number", description: "Max results" },
},
required: ["query"],
},
},
];
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
tools: tools,
messages: [
{ role: "user", content: "What's the weather in San Francisco?" },
],
});
// Process tool use blocks
for (const block of response.content) {
if (block.type === "tool_use") {
console.log(`Tool: ${block.name}, Input: ${JSON.stringify(block.input)}`);
}
}
Choosing the Right Architecture
Architecture Decision Matrix
| Factor | ReAct | Plan-Execute | Multi-Agent | Tool-Use |
|---|---|---|---|---|
| Complexity | Medium | High | Very High | Low |
| Latency | Medium | High | Variable | Low |
| Cost | Medium | High | High | Low |
| Reliability | Good | Good | Variable | High |
| Flexibility | High | Very High | Very High | Medium |
Best Practices
- Start Simple: Begin with tool-use pattern and add complexity only when needed
- Set Iteration Limits: Always cap the number of agent loop iterations to prevent runaway costs
- Add Observability: Log every thought, action, and observation for debugging
- Handle Failures: Implement retry logic and graceful degradation for tool failures
- Test with Diverse Inputs: Agent behavior can vary dramatically with different prompts
Summary
Agent architectures range from simple tool-use patterns to complex multi-agent systems. The key is matching the architecture to your use case — simpler is almost always better unless the task genuinely requires the additional complexity. In the following lessons, we'll dive deep into each pattern with production-ready implementations.