Skip to main content

What are AI Agents?

A Developer Asks Claude to Fix a Bug

It is 11:43 PM. A developer types into their terminal: "Fix the authentication bug in my codebase." They hit Enter and walk away to make coffee.

Over the next four minutes, Claude Code reads 47 files - the auth middleware, the session handler, the JWT validation logic, the test suite, the API routes, the database models. It traces the execution path of a failed login attempt from the HTTP request through three middleware layers down to the database query. It finds the bug: a race condition in the token refresh logic where two simultaneous requests can both generate new tokens, each invalidating the other.

Claude makes 12 edits across 6 files. It updates the token refresh to use a database-level lock, fixes the session handler to check for concurrent refresh attempts, adds a test case that reproduces the race condition, and updates the error messages to be more descriptive. Then it runs the test suite. Three tests fail - not the bug it fixed, but related tests that assumed the old (broken) behavior. It backtracks, reads the test files, understands the test intent, and updates the tests to reflect the correct behavior. The suite passes.

The developer returns with their coffee. The PR is ready.

No human was in the loop between "Fix the authentication bug" and "PR is ready." That is an agent.


:::tip 🎮 Interactive Playground Visualize this concept: Try the ReAct Agent demo on the EngineersOfAI Playground - no code required. :::

Why This Matters

For decades, AI systems could only respond. You asked, they answered. The conversational model - one turn in, one turn out - was the fundamental unit of AI interaction.

This is not how meaningful work gets done. Real work is iterative, exploratory, and requires acting on the world. A programmer does not just think about code - they write it, run it, see what breaks, read the error, think again, write more code. A researcher does not just know facts - they search, read, synthesize, form a hypothesis, search again. A project manager does not just know the project status - they check the tickets, read the comments, send the message, wait, follow up.

Work requires a loop. Language models, until recently, could not loop. They could produce one brilliant response and stop. The moment you needed them to act - to actually do something with that brilliance - you were on your own.

The agent paradigm changes this. An agent is a system that can perceive its environment, reason about what it sees, take action, perceive the result, and repeat. This loop, driven by an LLM as the reasoning engine, is what makes autonomous AI work possible.


Historical Context

The concept of an agent is not new. It has roots in philosophy (intentional systems theory, Daniel Dennett, 1971), artificial intelligence research (Shoham's agent-oriented programming, 1993), and robotics (Brooks' subsumption architecture, 1986). The academic AI community spent the 1990s building software agents - programs that could autonomously pursue goals in an environment.

Those early agents were brittle. They worked in narrow, well-defined domains. They required hand-crafted rules for every situation they might encounter. The moment the environment changed in an unexpected way, they failed.

The missing piece was a general-purpose reasoning engine. Something that could handle novel situations, generate plans, interpret unexpected observations, and recover from errors - without being explicitly programmed for each case.

Large language models are that missing piece.

In 2022, the ReAct paper (Yao et al., Princeton and Google Brain) showed that LLMs could interleave reasoning and acting in a way that dramatically outperformed either reasoning alone or acting alone. In 2023, the first commercially-viable agents appeared: AutoGPT went viral, ChatGPT got plugins, GitHub Copilot started suggesting multi-file edits. By 2024, Devin claimed to complete real software engineering tasks autonomously, Claude Code shipped as a production tool, and every major AI lab had an agent product.

The key insight: LLMs are not just text predictors. They are general-purpose reasoning engines that can plan, use tools, interpret observations, and recover from errors - if you give them the right scaffolding.


The Precise Definition of an Agent

An AI agent is a system that:

  1. Perceives its environment (through tool outputs, memory, context)
  2. Reasons about what it perceives (using an LLM)
  3. Takes action to change its environment (through tool calls)
  4. Repeats this loop autonomously until a goal is achieved or a stopping condition is met

The critical word is autonomously. A system that requires human approval at every step is not an agent - it is a human-in-the-loop system. A system that runs a fixed sequence of steps is not an agent - it is a workflow. An agent dynamically decides what to do next based on what it observes.


The 5 Key Properties of an Agent

Not every system that uses an LLM is an agent. Here are the five properties that distinguish agents from other LLM applications.

1. Goal-Directed

An agent pursues a goal. It has an objective that persists across multiple steps and guides its decisions. "Fix the bug" is a goal. "Answer this question" is not - it is a single-step task.

The goal shapes every decision the agent makes. When it encounters ambiguity (which file to read first?), it resolves that ambiguity by asking: which choice brings me closer to the goal?

2. Environment-Aware

An agent perceives its environment. It knows things about the world beyond what the user told it in the initial message. It can read files, query databases, browse the web, run code, check APIs. It uses these tools to build a model of the current state of the world.

A chatbot that only knows what you told it in the conversation is not environment-aware. An agent that reads your entire codebase before making edits is.

3. Action-Capable

An agent can change its environment. It does not just reason - it acts. It writes files, sends requests, executes code, calls APIs. These actions have real consequences in the world.

This is what separates an agent from a very sophisticated chatbot. The chatbot can tell you how to fix the bug. The agent actually fixes it.

4. Adaptive

An agent responds to what actually happens, not just what it expected to happen. When a tool call fails, it tries a different approach. When an observation is unexpected, it updates its plan. When it discovers new information mid-task, it incorporates it.

This adaptiveness is what makes agents useful for open-ended tasks. If every step was predictable, you would not need an agent - you would write a script.

5. Persistent

An agent maintains state across multiple steps. It remembers what it has done, what it has observed, and what it has learned. This accumulated context is what allows it to make coherent decisions across a long trajectory.

Without persistence, every action would be made in isolation, without awareness of what came before. An agent without memory is an agent that forgets it already tried that approach.


Agent Taxonomy

Not all agents are the same. Here is a practical taxonomy of the agent types you will encounter.

Reactive agents respond to stimuli without maintaining an internal model of the world. They are fast and simple. A rule-based customer service bot is reactive. Most basic tool-using LLM applications are reactive.

Deliberative agents build an internal model of the world and plan before acting. They are slower and more resource-intensive, but better at complex tasks that require foresight. A coding agent that reads all relevant files before making any edits is deliberative.

Hybrid agents combine both: a fast reactive layer handles simple, time-sensitive decisions, while a slow deliberative planner handles complex reasoning. This is how the best production agents work. Claude Code will immediately read a file you reference (reactive) but will deliberate extensively before making changes to core logic (deliberative).

Learning agents update their behavior based on experience. RLHF (Reinforcement Learning from Human Feedback) is the mechanism that makes today's LLMs better at being helpful - it is a form of agent learning. Reflexion agents (Shinn et al., 2023) explicitly reflect on their failures and update their approach within a single run.


Why Agents Are Possible Now: LLMs as the Reasoning Engine

Agents have existed conceptually for decades. What changed in 2022-2023 is that we finally have a reasoning engine capable of powering them.

The old bottleneck was the reasoning component. Classic agents required rule-based planners, hand-crafted world models, and explicit encoding of domain knowledge. This worked for chess (Deep Blue, 1997) because chess has a fixed, well-defined state space. It fails for software engineering because the state space is effectively infinite.

LLMs bypass this bottleneck. They carry a compressed model of the world built from training on the internet. They can reason about novel situations without being explicitly programmed for them. They can interpret unexpected tool outputs, generate recovery plans, and explain their decisions in natural language.

The LLM is the brain. The agent scaffolding provides the eyes, hands, and short-term memory.


The Agent Stack

Every production agent consists of four components:

LLM (Brain): The language model does the reasoning. It reads observations, decides what to do next, generates tool calls, interprets results, and determines when the task is complete. Claude 3.5 Sonnet, GPT-4o, and Gemini 1.5 Pro are the current top choices for agentic reasoning.

Tools (Hands): The mechanisms by which the agent interacts with the world. Reading and writing files. Executing code. Calling REST APIs. Querying databases. Running searches. Sending emails. The richer the tool set, the wider the range of tasks the agent can complete.

Memory (State): What the agent remembers. In the short term, this is the conversation history - the accumulating record of observations, thoughts, and actions. In the long term, this might be a vector database of past experiences, a structured database of learned facts, or a filesystem of artifacts.

Scaffolding (Body): The code that orchestrates everything. The loop that calls the LLM, executes tools, handles errors, manages the context window, decides when to stop, and returns results to the user. This is what you write when you build an agent.


Real-World Agents in Production

These are not toys or demos. These are production systems used by millions of developers.

Claude Code (Anthropic): A coding agent that reads your entire codebase, understands the architecture, makes multi-file edits, runs tests, and iterates until the task is complete. The opening scenario of this lesson was Claude Code.

GitHub Copilot Workspace (GitHub/Microsoft): Given a GitHub issue, Copilot Workspace reads the issue, explores the codebase, creates a plan, implements changes, and opens a PR. Agents working on issues autonomously.

Devin (Cognition): Claimed to be the first "fully autonomous software engineer." Given a task, it sets up its environment, searches for relevant information, writes and tests code, and delivers working software. Its SWE-bench performance (~14% at launch in 2024) was the first credible demonstration that agents could do real software engineering.

ChatGPT with Code Interpreter (OpenAI): Analyzes data, writes Python code, executes it, sees the output, writes more code, generates charts, interprets results. A data analysis agent running in a browser.

Cursor (Anysphere): An IDE where the LLM can read your entire codebase, understand your architecture, make coordinated changes across files, and explain its reasoning. Widely adopted in production engineering teams.


What Agents Can and Cannot Do Today

It is important to be honest about the current state of capability.

What agents do well:

  • Software engineering tasks with well-defined acceptance criteria (tests pass, lint passes)
  • Data analysis and exploration where the goal is insight, not a specific answer
  • Research tasks that require synthesizing information from multiple sources
  • Tasks where iteration and self-correction are acceptable (not time-critical)
  • Tasks within a single, well-understood domain (code, text, data)

What agents struggle with:

  • Tasks requiring precise, reliable execution without errors (a 95% success rate is terrible for most production workflows)
  • Tasks spanning multiple days (context window limits, state management complexity)
  • Tasks requiring nuanced judgment about physical-world consequences
  • Tasks where the cost of a mistake is high (financial transactions, medical decisions)
  • Tasks requiring true novelty - agents recombine knowledge, they do not fundamentally discover

Benchmark context (SWE-bench Verified, 2024-2025):

  • GPT-4 without agents (2023): approximately 2%
  • AutoGPT (2023): approximately 5%
  • Devin (2024): approximately 14%
  • Claude 3.5 Sonnet with scaffolding (2024): approximately 49%
  • Best systems (2025): approximately 55-65%

These numbers mean roughly half of real-world GitHub issues can be resolved autonomously. That is impressive. It also means roughly half cannot. Agents are powerful and limited simultaneously.


A Minimal Agent From Scratch

Here is the simplest possible agent. No frameworks. Just the Anthropic API and a loop.

"""
Minimal AI agent from scratch using the Anthropic API.
Demonstrates the core: tool definition, tool execution, and the agent loop.

Install: pip install anthropic
"""
import anthropic
import json
import subprocess
import os
from typing import Any

client = anthropic.Anthropic()

# ── Tool definitions ──────────────────────────────────────────────────────────
# The description is the most important part - it determines when Claude uses
# each tool. Write descriptions as if explaining to a smart human assistant.

TOOLS = [
{
"name": "read_file",
"description": (
"Read the contents of a file. Use this to examine source code, "
"configuration files, or any text file. Returns the file contents "
"as a string. Prefer this over run_python for reading files."
),
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to read."
}
},
"required": ["path"]
}
},
{
"name": "run_python",
"description": (
"Execute a Python code snippet and return stdout + stderr. "
"Use this to test code, run calculations, verify behavior, "
"or process data. Code runs in a subprocess with a 10s timeout."
),
"input_schema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The Python code to execute."
}
},
"required": ["code"]
}
},
{
"name": "list_directory",
"description": (
"List the files and directories at a given path. "
"Use this to understand project structure before diving into files."
),
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The directory path to list.",
"default": "."
}
},
"required": []
}
}
]


# ── Tool execution ────────────────────────────────────────────────────────────

def execute_tool(tool_name: str, tool_input: dict[str, Any]) -> str:
"""Execute a tool call and return the result as a string."""

if tool_name == "read_file":
path = tool_input["path"]
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
return f"Contents of {path}:\n\n{content}"
except FileNotFoundError:
return f"Error: File '{path}' not found."
except PermissionError:
return f"Error: Permission denied reading '{path}'."
except Exception as e:
return f"Error reading file: {e}"

elif tool_name == "run_python":
code = tool_input["code"]
try:
result = subprocess.run(
["python3", "-c", code],
capture_output=True,
text=True,
timeout=10
)
output_parts = []
if result.stdout:
output_parts.append(f"stdout:\n{result.stdout}")
if result.stderr:
output_parts.append(f"stderr:\n{result.stderr}")
if result.returncode != 0:
output_parts.append(f"Exit code: {result.returncode}")
return "\n".join(output_parts) if output_parts else "(no output)"
except subprocess.TimeoutExpired:
return "Error: Code execution timed out after 10 seconds."
except Exception as e:
return f"Error executing code: {e}"

elif tool_name == "list_directory":
path = tool_input.get("path", ".")
try:
entries = sorted(os.listdir(path))
if not entries:
return f"Directory '{path}' is empty."
# Show type: files vs directories
annotated = []
for entry in entries:
full_path = os.path.join(path, entry)
suffix = "/" if os.path.isdir(full_path) else ""
annotated.append(f"{entry}{suffix}")
return f"Contents of {path}/:\n" + "\n".join(annotated)
except FileNotFoundError:
return f"Error: Directory '{path}' not found."
except Exception as e:
return f"Error listing directory: {e}"

else:
return f"Error: Unknown tool '{tool_name}'. Available tools: {[t['name'] for t in TOOLS]}"


# ── The agent loop ────────────────────────────────────────────────────────────

def run_agent(task: str, max_iterations: int = 20, verbose: bool = True) -> str:
"""
Run the agent loop until the task is complete or max_iterations is reached.

The loop works like this:
1. Send current messages to Claude with tool definitions
2. Claude responds with either text (done) or tool_use blocks (wants to act)
3. If tool_use: execute the tools, add results to messages, loop
4. If end_turn: extract final text and return it

Args:
task: The task description for the agent
max_iterations: Safety limit to prevent infinite loops
verbose: Whether to print progress

Returns:
The agent's final response as a string
"""
if verbose:
print(f"\n{'='*60}")
print(f"Agent task: {task}")
print(f"{'='*60}\n")

# Messages accumulate across iterations.
# This list IS the agent's short-term memory.
messages = [{"role": "user", "content": task}]

system_prompt = """You are an autonomous AI agent with access to tools for \
exploring and understanding your environment.

When given a task:
1. Start by understanding the environment (list directories, read relevant files)
2. Plan your approach before taking actions
3. Execute your plan step by step
4. Verify the results
5. Report clearly what you did and what you found

Be systematic. Be thorough. Use tools efficiently - don't read the same file twice."""

for iteration in range(max_iterations):
if verbose:
print(f"[Iteration {iteration + 1}/{max_iterations}]")

# Call the LLM with all accumulated context
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
system=system_prompt,
tools=TOOLS,
messages=messages
)

if verbose:
print(f" Stop reason: {response.stop_reason}")
print(f" Input tokens: {response.usage.input_tokens}, Output tokens: {response.usage.output_tokens}")

# Add Claude's response to history (crucial - Claude must see its own outputs)
messages.append({
"role": "assistant",
"content": response.content
})

# Termination condition: Claude has no more tool calls
if response.stop_reason == "end_turn":
final_text = next(
(block.text for block in response.content if hasattr(block, "text")),
"Task completed."
)
if verbose:
print(f"\n{'='*60}")
print("Agent completed.")
print(f"{'='*60}")
return final_text

# Process tool calls
if response.stop_reason == "tool_use":
tool_results = []

for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_use_id = block.id

if verbose:
args_preview = json.dumps(tool_input)[:80]
print(f" Tool: {tool_name}({args_preview})")

# Execute the tool and capture the result
result = execute_tool(tool_name, tool_input)

if verbose:
print(f" Result: {result[:100]}...")

# Build the tool result message
# This is how Claude learns what happened
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": result
})

# Add all tool results as a user message
# The next LLM call will see these results
messages.append({
"role": "user",
"content": tool_results
})

# If we reach here, the agent exceeded max_iterations
return (
f"Agent stopped after {max_iterations} iterations without completing. "
f"The task may require more iterations or a different approach."
)


# ── Example usage ─────────────────────────────────────────────────────────────

if __name__ == "__main__":
result = run_agent(
"Explore the current directory, then write a Python script that "
"prints the first 15 Fibonacci numbers, run it, verify the output "
"is mathematically correct, and explain what you found."
)
print(f"\nFinal result:\n{result}")

Running this agent, you will see it:

  1. Call list_directory() to understand its environment
  2. Call run_python() with a Fibonacci implementation
  3. Examine the output, verify correctness
  4. Return a clear explanation of what it did

This is the core pattern. Every production agent - LangChain, LangGraph, AutoGen, Claude Code - is a more sophisticated version of this same loop.


The Capability Trajectory

Understanding where we are requires understanding how fast things have moved.

2023: Proof of concept. AutoGPT showed that LLMs could be run in loops with tools. It was unreliable and expensive, but proved the concept. GPT-4 plugins let ChatGPT browse the web. These were demonstrations, not deployments.

2024: Useful in constrained domains. Devin showed agents could do real software engineering (14% SWE-bench). Claude 3.5 Sonnet achieved ~49% on the same benchmark. GitHub Copilot Workspace shipped to developers. Claude Code began rolling out. Agents became tools that engineers actually used for real work.

2025: Production-grade in specific domains. Agents now handle significant portions of code review, bug fixing, documentation, and data analysis workflows at real companies. The best agents solve ~55-65% of SWE-bench issues.

2026 and beyond: Honest uncertainty. No one knows if this trajectory continues at the same rate. Scaling laws for agentic capabilities are less well-understood than for base model capabilities. The reliability wall - compound error rates across many steps - remains a genuine engineering challenge.


Production Engineering Notes

:::tip Use max_iterations religiously Every agent loop needs an explicit maximum iteration count. An agent without this limit can run indefinitely, burning API credits and failing silently. Set it based on your task complexity - simple tasks: 10 iterations, complex tasks: 50, never unlimited. :::

:::warning Context window pressure is real Every tool result you add to the message history consumes context window tokens. A 200,000-token context sounds large until you are reading 50 files that average 4,000 tokens each. Build context management (summarization, pruning) into your agent design from day one, not as an afterthought. :::

:::danger Never give agents irreversible write access by default An agent that can delete files, send emails, or execute arbitrary code with no confirmation can cause serious damage. Always start with read-only tools, add write tools only when needed, and require explicit confirmation (or human-in-the-loop) for destructive operations. The cost of getting this wrong is high. :::


Common Mistakes

:::danger Calling anything that uses an LLM an "agent" A chatbot with a system prompt is not an agent. A fixed pipeline with LLM steps is not an agent. An agent requires: (1) dynamic tool selection based on observations, (2) variable number of steps determined at runtime, (3) goal-directed autonomous execution. If you can predict the exact sequence of operations before the run starts, it is not an agent. :::

:::warning No stopping condition Every agent loop needs explicit stopping conditions: task completion detected by the LLM, max iterations, timeout, or error threshold. An agent without stopping conditions will run forever (or until your API budget is exhausted). This is one of the most common and costly mistakes in early agent implementations. :::

:::warning Treating agent reliability like deterministic code reliability A single LLM call is roughly 95-99% reliable for well-specified tasks. An agent making 20 calls compounds that error rate multiplicatively. At 99% per step across 20 steps: 0.99^20 = 82% success rate. At 95% per step: 0.95^20 = 36% success rate. Build reliability into your expectations and your architecture from day one. :::


Interview Questions

Q: What precisely distinguishes an AI agent from a language model with tool use?

An AI agent is a system that runs an autonomous loop: perceive, reason, act, perceive, repeat until a goal is achieved. A language model with tool use is a capability - the model can generate structured tool calls in its output. An agent is an architecture - you orchestrate the loop, manage state, handle errors, and decide termination conditions. You can build an agent using a language model with tool use, but the tool use capability alone is not the agent. The agent is the scaffolding that runs the loop. The key test: does the system autonomously decide what to do next based on what it observes, or does it always do what you told it to do?

Q: What are the 5 key properties of an AI agent and why does each matter?

Goal-directed: agents have objectives that persist across steps and guide every decision - without a goal, you have a reactive system, not an agent. Environment-aware: agents perceive the current state of the world through tools, not just what the user told them - this enables grounded action. Action-capable: agents change the world, not just describe it - this is what makes them useful for real tasks beyond conversation. Adaptive: agents respond to actual outcomes, not expected ones - when something fails, they adjust - this handles the inevitable surprises in real environments. Persistent: agents maintain state across steps - without memory, each step is blind to everything before it, making coherent multi-step plans impossible.

Q: The SWE-bench benchmark shows the best agents solving roughly 55-65% of GitHub issues. What does the remaining 35-45% tell us?

The hardest issues require understanding subtle semantic invariants in a codebase that are not captured in tests, coordinating changes across multiple systems with precise dependencies, historical context about why something was built a certain way, and judgment calls about tradeoffs that automated tests do not evaluate. These failures point to three core limitations of current agents: they struggle with tacit knowledge embedded in codebases, with very long-horizon dependencies across many files and systems, and with tasks where correctness cannot be verified through automated testing. The failure mode is not "the agent crashes" - it is "the agent produces plausible but subtly wrong code that passes all existing tests."

Q: An engineer argues that agents are just chatbots in a loop. How do you respond?

The description is technically accurate but misses what matters. Yes, an agent is architecturally an LLM called in a loop with tool results fed back into context. But this is like saying a program is just logic gates in sequence. The architecture enables qualitatively different capabilities: tasks that require iterative exploration, self-correction based on real observations, multi-step planning with backtracking, and actually acting on the world to achieve goals. The loop with environment interaction is what enables Claude Code to fix bugs it has never seen before. The architecture is precisely the point - and dismissing it misses why agents represent a fundamentally different category of AI application.

Q: How do you choose between different Claude model tiers for an agent task?

Cost-quality tradeoffs drive model selection per step type. For observation steps where the LLM is parsing structured tool output into a clear category - use Haiku. For reasoning steps where the LLM is making plans and selecting among multiple reasonable tool calls - use Sonnet. For complex reasoning where the LLM needs to handle highly ambiguous, multi-step problems with many failure modes - use Opus. A well-engineered agent often uses different models for different steps: cheap model for parsing, capable model for planning, most capable model only for the hardest decisions. This can reduce cost by 5-10x without meaningful quality loss. The key insight is that not every step in an agent trajectory is equally hard.

Q: What is the difference between a reactive agent and a deliberative agent, and when do you use each?

A reactive agent maps observations directly to actions without maintaining an internal model or planning ahead. It is fast, simple, and reliable for well-defined situations. A deliberative agent builds an internal model of the current state, reasons about it, plans multiple steps ahead, then acts. It is slower and more compute-intensive but handles complex, ambiguous situations much better. Use reactive agents when decisions are time-critical, the task is well-defined with a small action space, and incorrect actions have low cost. Use deliberative agents when the task requires multi-step planning, the environment is partially observable, and incorrect actions are expensive to reverse. Most production agents are hybrid - reactive for simple tool calls, deliberative for high-stakes decisions like refactoring core logic or sending external communications.

© 2026 EngineersOfAI. All rights reserved.