Types of AI Agents
In the last chapter, we defined what an agent is and walked through the fundamental agent loop. But saying "AI agent" is a bit like saying "vehicle" - technically accurate, but it covers everything from a skateboard to a 747. The architecture you choose for your agent matters enormously. Pick the wrong one and you'll either over-engineer a simple problem or under-engineer a complex one.
This chapter maps the landscape. We'll cover six major types of agents, show you when each one shines, and help you choose the right architecture for your specific use case.
1. Reactive Agents
The simplest possible agent. A reactive agent has no memory, no internal model, and no planning. It takes in the current input and immediately produces an action. Stimulus in, response out.
Think of it like a thermostat: the temperature drops below 68, the heater turns on. The temperature rises above 72, the heater turns off. No memory of past temperatures. No prediction of future ones. Just react to right now.
When to use reactive agents
- Simple automation tasks with clear trigger-response mappings
- High-speed decision making where latency matters more than sophistication
- Environments that are fully observable (you can see everything you need in the current input)
Code example
class ReactiveAgent:
"""A simple reactive agent that responds based on keyword matching."""
def __init__(self):
self.rules = {
"price": self.check_price,
"weather": self.check_weather,
"news": self.fetch_news,
}
def act(self, user_input: str) -> str:
"""React to the current input. No memory, no planning."""
user_input_lower = user_input.lower()
for keyword, handler in self.rules.items():
if keyword in user_input_lower:
return handler(user_input)
return "I don't know how to handle that request."
def check_price(self, query: str) -> str:
# Call a pricing API
return fetch_stock_price(extract_ticker(query))
def check_weather(self, query: str) -> str:
return fetch_weather(extract_location(query))
def fetch_news(self, query: str) -> str:
return search_news(extract_topic(query))
This agent doesn't remember that you asked about Apple's stock price 30 seconds ago. Every request is a blank slate.
You might think reactive agents are too simple to be useful. But many production systems are essentially reactive agents - Slack bots that respond to commands, webhook handlers that triage alerts, email filters that auto-categorize messages. Sometimes the skateboard really is the right vehicle.
2. Model-Based (State-Based) Agents
Model-based agents maintain an internal representation of the world - a mental model. They remember things. They track how the world changes over time. This lets them handle situations where the current input alone isn't enough to make a good decision.
Think of a GPS navigation agent. It doesn't just know your current location - it knows where you started, where you're going, what roads you've already tried, and what traffic looks like. It maintains a model of the relevant world.
How they differ from reactive agents
A reactive customer support agent sees: "My order is late." It responds with a canned shipping-delay message.
A model-based agent sees the same message but also knows: this customer has contacted us three times about this order, the order has been delayed twice, and the customer is a premium subscriber. Its response accounts for all of that context.
Code example
class ModelBasedAgent:
"""An agent that maintains internal state about the world."""
def __init__(self):
self.world_model = {
"customer_history": {},
"active_orders": {},
"system_status": {},
}
def update_model(self, observation: dict):
"""Update internal world model with new observation."""
if "customer_id" in observation:
cid = observation["customer_id"]
if cid not in self.world_model["customer_history"]:
self.world_model["customer_history"][cid] = []
self.world_model["customer_history"][cid].append(observation)
def act(self, user_input: str, context: dict) -> str:
"""Decide action based on current input AND world model."""
self.update_model(context)
customer_id = context.get("customer_id")
history = self.world_model["customer_history"].get(customer_id, [])
contact_count = len(history)
if contact_count >= 3:
return self.escalate_to_human(customer_id)
elif context.get("is_premium"):
return self.prioritized_response(user_input, history)
else:
return self.standard_response(user_input)
The crucial addition is self.world_model. The agent builds up knowledge over time and uses it to make better decisions.
3. Goal-Based Agents
Now we're getting into the territory where things feel genuinely "agentic." A goal-based agent doesn't just react to the present or track the past - it plans toward a future state. It has a goal, and it figures out the sequence of actions needed to achieve it.
This is where the LLM's reasoning ability becomes central. The agent needs to:
- Understand the goal
- Assess the current state
- Identify the gap between current state and goal
- Generate a plan to close that gap
- Execute the plan step by step
- Replan if something goes wrong
Example: a research agent
Goal: "Write a comprehensive summary of the latest developments in quantum computing."
The agent might plan:
- Search for recent quantum computing news (last 3 months)
- Identify the top 5 most significant developments
- For each development, find the original source/paper
- Extract key technical details from each source
- Synthesize into a coherent summary with citations
- Review for accuracy and completeness
No one told it those steps. It derived them from the goal.
Goal-based agents are the sweet spot for most production use cases in 2025. They're sophisticated enough to handle complex tasks, but the architecture is still simple enough to debug and reason about. If you're building your first "real" agent, start here.
4. Utility-Based Agents
A goal-based agent asks: "Does this action move me toward my goal?" A utility-based agent asks: "Of all the actions that move me toward my goal, which one is best?"
The difference is optimization. Utility-based agents assign scores to potential actions and pick the highest-scoring one. This matters when there are multiple valid paths and you care about quality, efficiency, cost, or risk.
Example: a travel-booking agent
A goal-based agent might book the first flight it finds that meets the criteria. A utility-based agent evaluates multiple options:
def score_flight(flight: dict, preferences: dict) -> float:
"""Score a flight option based on multiple utility dimensions."""
score = 0.0
# Price utility (lower is better, normalized)
price_score = 1.0 - (flight["price"] / preferences["max_price"])
score += price_score * preferences["price_weight"] # e.g., 0.4
# Duration utility (shorter is better)
duration_score = 1.0 - (flight["duration_hrs"] / 24.0)
score += duration_score * preferences["duration_weight"] # e.g., 0.3
# Departure time utility (closer to preferred time is better)
time_diff = abs(flight["departure_hour"] - preferences["preferred_hour"])
time_score = 1.0 - (time_diff / 12.0)
score += time_score * preferences["time_weight"] # e.g., 0.2
# Airline preference
if flight["airline"] in preferences["preferred_airlines"]:
score += 0.1
return score
The agent searches for flights, scores each one, and picks the winner. If the user says "I care most about price," the weights shift. If they say "I need to arrive by 2 PM," the scoring function adapts.
When utility-based agents shine
- Multi-criteria decision making (buying decisions, resource allocation)
- Situations where "good enough" isn't enough - you need optimal
- Competitive environments where the best action depends on evaluating alternatives
5. Learning Agents
Every agent type we've covered so far has a fixed strategy. A learning agent improves over time based on experience. It has four conceptual components:
- Performance element - decides what action to take (this is the agent itself)
- Critic - evaluates how well the agent is doing based on feedback
- Learning element - modifies the performance element based on the critic's feedback
- Problem generator - suggests exploratory actions to discover new knowledge
In the LLM agent world, learning typically happens through:
- In-context learning - Including successful (and failed) past trajectories in the prompt so the LLM can learn from examples within a session.
- Fine-tuning - Periodically retraining the model on successful agent trajectories.
- Memory systems - Storing lessons learned in a retrievable knowledge base that the agent consults before acting.
- Reflexion - Having the agent explicitly critique its own performance and generate improvement strategies.
class LearningAgent:
"""An agent that stores and retrieves lessons from past episodes."""
def __init__(self, base_agent, memory_store):
self.agent = base_agent
self.memory = memory_store # vector DB or similar
def act(self, goal: str) -> str:
# Retrieve relevant past experiences
past_lessons = self.memory.search(goal, top_k=3)
# Augment the goal with learned context
enhanced_prompt = f"""Goal: {goal}
Lessons from similar past tasks:
{self.format_lessons(past_lessons)}
Apply these lessons to avoid past mistakes and use proven strategies."""
result = self.agent.run(enhanced_prompt)
# After completing, reflect and store new lessons
reflection = self.reflect(goal, result)
self.memory.store(reflection)
return result
def reflect(self, goal: str, result: str) -> str:
"""Generate a lesson from this episode."""
return llm.chat(f"""
Task: {goal}
Result: {result}
What worked well? What should be done differently next time?
Summarize in 2-3 bullet points.
""")
Learning agents sound amazing in theory, but they have a bootstrapping problem: they need to fail before they can learn, and failures in production can be costly. Use learning agents for low-stakes tasks first, and always have a fallback to non-learning behavior.
6. Hierarchical Agents
Hierarchical agents are agents that manage other agents. There's a "manager" agent that breaks down a high-level goal into subtasks, delegates each subtask to a specialized "worker" agent, and aggregates the results.
This maps naturally to how human organizations work. A CEO doesn't write code - they set strategy and delegate. A VP breaks strategy into projects. A team lead breaks projects into tasks. Individual contributors execute.
Architecture
┌─────────────────────────────┐
│ ORCHESTRATOR AGENT │
│ (understands the big │
│ picture, delegates) │
└──────────┬──────────────────┘
│
┌─────┼──────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Research│ │Writing │ │Code │
│Agent │ │Agent │ │Agent │
└────────┘ └────────┘ └────────┘
When to use hierarchical agents
- Complex tasks that require multiple distinct skill sets (research + writing + coding)
- Tasks where parallelism helps (researching 5 topics simultaneously)
- When different subtasks need different models (a cheap fast model for simple lookups, a powerful expensive model for synthesis)
When NOT to use them
- Simple tasks that one agent can handle - adding a manager agent just adds latency and cost
- When the subtask boundaries aren't clear (the manager will struggle to decompose the work)
- When subtask results are highly interdependent (coordination overhead exceeds the benefit)
Comparison Table
Here's how all six types stack up:
| Type | Complexity | Memory | Planning | Learning | Best For |
|---|---|---|---|---|---|
| Reactive | Very Low | None | None | None | Simple triggers, fast responses |
| Model-Based | Low-Medium | Yes (internal state) | Minimal | None | Context-dependent decisions |
| Goal-Based | Medium | Yes | Yes (plan toward goal) | None | Most general-purpose agent tasks |
| Utility-Based | Medium-High | Yes | Yes (with optimization) | None | Multi-criteria optimization |
| Learning | High | Yes (+ long-term) | Yes | Yes | Repeated tasks that improve over time |
| Hierarchical | Very High | Per-agent | Multi-level | Optional | Complex, multi-skill-set tasks |
Single-Agent vs. Multi-Agent Systems
A question that comes up early in every agent project: should I use one agent or multiple?
Single-agent: when to use it
- The task has a clear, linear workflow - search, then process, then output
- One model can handle all required reasoning - you don't need specialized expertise
- Simplicity matters - debugging one agent is dramatically easier than debugging three
- Latency is critical - multi-agent systems have coordination overhead
Multi-agent: when to use it
- The task has distinct phases that require different expertise - a research phase and a coding phase and a review phase
- You want separation of concerns - each agent has a focused system prompt and toolset
- Subtasks can run in parallel for speed
- You need checks and balances - one agent writes, another reviews
The industry trend in 2025 is moving away from complex multi-agent frameworks and toward simpler, more controllable architectures. Many teams that started with multi-agent systems have migrated back to a single agent with a well-designed tool set. Don't add agents until a single agent's limitations are clearly the bottleneck.
Real-World Architecture Mapping
Which products use which architectures? Here's a rough mapping based on publicly available information:
| Product | Primary Architecture | Notes |
|---|---|---|
| Claude Code | Goal-based, single agent | Clear goal (user task), plans, uses tools, iterates |
| Cursor | Goal-based with utility scoring | Ranks code completion candidates by likelihood |
| Perplexity | Goal-based with learning | Learns citation patterns, plans search queries |
| Devin | Hierarchical (planner + executor) | High-level planning agent delegates to coding sub-agents |
| AutoGPT | Goal-based (original), learning (later) | Added memory/reflection in later versions |
| CrewAI apps | Hierarchical (multi-agent) | Explicitly designed for agent teams |
| Customer support bots | Model-based (most), reactive (simple ones) | Track conversation state and customer history |
| Trading bots | Utility-based | Score opportunities by expected return, risk, liquidity |
How to Choose: A Decision Flowchart
Walk through these questions in order:
Q1: Does the task require memory of past interactions or context?
- No → Reactive agent might be enough
- Yes → Continue
Q2: Does the task require planning - figuring out a sequence of steps to reach a goal?
- No → Model-based agent (maintains state but follows fixed logic)
- Yes → Continue
Q3: Are there multiple valid paths and do you need to pick the best one?
- No → Goal-based agent (plan and execute toward the goal)
- Yes → Utility-based agent (score and rank alternatives)
Q4: Will the agent perform this task repeatedly and should it improve over time?
- Yes → Add learning capabilities to your chosen architecture
Q5: Does the task require multiple distinct skill sets that one prompt/toolset can't cover?
- Yes → Consider a hierarchical / multi-agent architecture
- No → Stay single-agent
Most teams should start with a goal-based single agent and only add complexity when they hit clear limitations. Premature architecture is as dangerous as premature optimization.
Architectural Patterns in 2025
Beyond the theoretical agent types, several practical architectural patterns have emerged in the agent-building community. These are the "design patterns" of the agent world.
ReAct (Reasoning + Acting)
The most common pattern. The agent alternates between thinking (reasoning about what to do) and acting (calling a tool). Each thought-action pair is followed by an observation (tool result), which feeds into the next thought.
Thought: I need to find the current stock price of NVIDIA.
Action: web_search("NVIDIA stock price today")
Observation: NVIDIA (NVDA) is trading at $847.23, up 3.2%...
Thought: Now I have the price. The user also asked about market cap.
Action: web_search("NVIDIA market cap 2025")
Observation: NVIDIA's market cap is approximately $2.1 trillion...
Thought: I have both pieces of information. Let me compose the answer.
Answer: NVIDIA is currently trading at $847.23 with a market cap of ~$2.1T...
Pros: Simple, transparent, easy to debug (you can read the trace). Cons: Can be slow (serial execution), may get stuck in loops.
Plan-and-Execute
The agent first creates a complete plan, then executes each step. If a step fails, it replans.
Plan:
1. Search for quarterly revenue data for Apple (last 4 quarters)
2. Search for quarterly revenue data for Microsoft (last 4 quarters)
3. Create a comparison table
4. Analyze trends and write summary
Executing step 1...
Executing step 2...
[Step 2 failed - search returned no results]
Replanning from step 2...
Revised step 2: Search for "Microsoft quarterly earnings 2024 2025"
Executing revised step 2...
Pros: More structured than ReAct, handles complex multi-step tasks well. Cons: Upfront planning can be wrong; replanning adds complexity.
LATS (Language Agent Tree Search)
Inspired by Monte Carlo Tree Search (used in game-playing AI). The agent explores multiple possible action paths, evaluates them, and backtracks when a path looks unpromising. Think of it as "try multiple approaches and keep the best one."
Pros: Finds better solutions for complex problems. Cons: Expensive (multiple LLM calls per step), complex to implement.
Reflexion
After completing a task, the agent critiques its own performance and stores the lessons. On the next similar task, it retrieves and applies those lessons. This is the "learning agent" pattern made concrete.
Task result: [agent's output]
Self-reflection: "I spent too many steps searching for information that
was available in the initial context. Next time, I should read the full
input before searching. Also, my final summary was too long - the user
asked for a brief answer."
Stored lesson: "Read full context before searching. Match output length
to request."
Pros: Genuine improvement over time. Cons: Requires persistent storage, reflection quality varies.
Building a Goal-Based Agent with Planning
Let's make this concrete with a more complete code example. Here's a goal-based agent that creates and follows an explicit plan:
import json
from dataclasses import dataclass
@dataclass
class Plan:
steps: list[str]
current_step: int = 0
class GoalBasedAgent:
def __init__(self, llm, tools: dict):
self.llm = llm
self.tools = tools
def create_plan(self, goal: str) -> Plan:
"""Ask the LLM to decompose the goal into steps."""
response = self.llm.chat(
f"""Break this goal into 3-7 concrete steps.
Return as a JSON array of strings.
Goal: {goal}"""
)
steps = json.loads(response)
return Plan(steps=steps)
def execute_step(self, step: str, context: str) -> str:
"""Execute a single step, possibly using tools."""
response = self.llm.chat(
messages=[
{"role": "system", "content": f"Context so far: {context}"},
{"role": "user", "content": f"Execute this step: {step}"},
],
tools=self.tools,
)
if response.tool_call:
tool_fn = self.tools[response.tool_call.name]
return tool_fn(**response.tool_call.arguments)
return response.content
def run(self, goal: str) -> str:
"""Run the full goal-based agent loop."""
plan = self.create_plan(goal)
context_parts = []
print(f"Plan ({len(plan.steps)} steps):")
for i, step in enumerate(plan.steps):
print(f" {i+1}. {step}")
for i, step in enumerate(plan.steps):
print(f"\nExecuting step {i+1}: {step}")
result = self.execute_step(step, "\n".join(context_parts))
context_parts.append(f"Step {i+1} result: {result}")
plan.current_step = i + 1
# Final synthesis
return self.llm.chat(
f"""Based on these results, provide the final answer to: {goal}
{chr(10).join(context_parts)}"""
)
This is a practical starting point. In Chapter 3, we'll build something similar with real tools and a working LLM integration.
What's Next
You now have a map of the agent landscape - from simple reactive bots to sophisticated hierarchical systems. You know which architecture fits which problem, and you've seen how the commercial products you use implement these patterns.
But maps are useless if you never go anywhere. In Chapter 3, we're going to build an agent from scratch. No frameworks, no abstractions - just Python, API calls, and the patterns you've learned here. You'll have a working research agent by the end that can search the web, extract information, and write summaries.
Let's build.