Classifying Agent Failure Modes
Build a taxonomy of agent failures: tool errors, malformed outputs, reasoning loops, context exhaustion, and external service unavailability, and design recovery strategies for each.
Classifying Agent Failure Modes is a free AI Engineering Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Agents Fail in Distinctive Ways
Agents fail differently from simple LLM calls. A single-turn call either returns an answer or throws an error. An agent executing a multi-step task can fail at any point — and the failure might not be obvious from the final output. Understanding the taxonomy of agent failure modes is the first step toward building agents that detect, diagnose, and recover from their own failures.
Failure Mode 1: Tool Errors
Tool errors occur when the agent calls a tool with invalid arguments, the tool raises an exception, or the tool returns an empty or malformed result. Examples include: calling a search API with a malformed query, querying a database with invalid SQL, or invoking a code executor that times out. Tool errors are the easiest to detect because they produce explicit exception signals that can be caught and handled.
class ToolError(Exception):
def __init__(self, tool_name: str, args: dict, error: Exception):
self.tool_name = tool_name
self.args = args
self.original_error = error
super().__init__(f'Tool {tool_name} failed: {error}')
def safe_tool_call(tool_func, args: dict) -> str:
try:
result = tool_func(**args)
if not result:
return 'Tool returned empty result. Try a different approach.'
return str(result)
except Exception as e:
raise ToolError(tool_func.__name__, args, e)Failure Mode 2: Malformed Outputs
Malformed outputs happen when the agent generates text that does not match the expected format — for example, returning natural language when the next step expects JSON, or calling a tool with arguments in the wrong structure. This often happens when the agent confuses its current step with a previous one. Validate the format of every agent output before using it and re-prompt when the format is wrong.
import json
def validate_agent_output(raw_output: str, expected_format: str) -> dict:
if expected_format == 'json':
try:
return json.loads(raw_output)
except json.JSONDecodeError as e:
return {
'valid': False,
'error': f'Expected JSON but got invalid JSON: {e}',
'raw': raw_output[:200]
}
return {'valid': True, 'data': raw_output}Failure Mode 3: Reasoning Loops
Reasoning loops occur when an agent repeats the same action or thought indefinitely without making progress. The agent might call the same search query 10 times in a row, getting the same empty result and not knowing what to try next. Detect loops by tracking recent actions and checking for repeats. When a loop is detected, inject a meta-prompt telling the agent to try a different approach.
from collections import Counter
class LoopDetector:
def __init__(self, window: int = 5, threshold: int = 3):
self.recent_actions = []
self.window = window
self.threshold = threshold
def record(self, action: str) -> bool:
self.recent_actions.append(action)
if len(self.recent_actions) > self.window:
self.recent_actions.pop(0)
counts = Counter(self.recent_actions)
most_common_count = counts.most_common(1)[0][1] if counts else 0
return most_common_count >= self.threshold # True = loop detectedFailure Mode 4: Context Exhaustion
Context exhaustion happens when the agent's accumulated history (tool calls, observations, thoughts) exceeds the model's context window. The model either truncates the history silently (losing critical information) or raises a token limit error. Prevent this by tracking token usage across steps and compressing the history (summarizing old steps) before it reaches the limit.
import tiktoken
CONTEXT_LIMIT = 100_000 # tokens
COMPRESS_AT = 80_000 # trigger compression with headroom
enc = tiktoken.encoding_for_model('gpt-4o')
def total_tokens(messages: list) -> int:
return sum(len(enc.encode(str(m))) for m in messages)
def check_context(messages: list) -> str:
tokens = total_tokens(messages)
if tokens > COMPRESS_AT:
return 'compress'
if tokens > CONTEXT_LIMIT:
return 'critical'
return 'ok'Failure Mode 5: External Service Unavailability
External service failures occur when a tool's underlying service is down, rate-limited, or returns unexpected errors. An agent that cannot reach the database it needs to query is stuck. Unlike reasoning loops (agent's fault), external failures are environment failures. Handle them with retry + exponential backoff, and have fallback tools that can approximate the result using different data sources.
import asyncio
async def resilient_tool_call(tool_func, args: dict, max_retries: int = 3) -> str:
for attempt in range(max_retries):
try:
return await tool_func(**args)
except (ConnectionError, TimeoutError) as e:
if attempt == max_retries - 1:
return f'Service unavailable after {max_retries} attempts. Error: {e}'
wait = 2 ** attempt # 1s, 2s, 4s
await asyncio.sleep(wait)
return 'Unexpected error in resilient_tool_call'Failure Mode 6: Goal Misunderstanding
Goal misunderstanding is when the agent misinterprets the task and pursues a subtly different objective. This is the hardest failure to detect because the agent may complete successfully — just not the task the user intended. Mitigate it by asking the agent to restate the goal in its own words at the start, and by implementing a final verification step that checks whether the result actually answers the original question.
async def confirm_goal_understanding(original_task: str) -> str:
resp = await client.chat.completions.create(
model='gpt-4o',
messages=[
{'role': 'system', 'content': 'Restate the task in your own words. Be specific about what the final deliverable should be.'},
{'role': 'user', 'content': f'Task: {original_task}'}
]
)
return resp.choices[0].message.content
# Use the restatement as the first step of the agent
# to catch misunderstandings before any tools are calledBuilding a Failure Classification System
Create a structured failure classifier that labels every agent exception with its type. This enables automatic routing to the appropriate recovery strategy. Store failure logs with type labels so you can analyze which failure modes are most common and prioritize which to address first. Tool errors and loops are typically most frequent and most fixable.
from enum import Enum
from dataclasses import dataclass
class FailureType(Enum):
TOOL_ERROR = 'tool_error'
MALFORMED_OUTPUT = 'malformed_output'
REASONING_LOOP = 'reasoning_loop'
CONTEXT_EXHAUSTION = 'context_exhaustion'
EXTERNAL_SERVICE = 'external_service'
GOAL_MISUNDERSTANDING = 'goal_misunderstanding'
MAX_ITERATIONS = 'max_iterations'
UNKNOWN = 'unknown'
@dataclass
class AgentFailure:
failure_type: FailureType
step: int
tool_name: str | None
error_message: str
recoverable: boolMapping Failures to Recovery Actions
Each failure type has an appropriate recovery action. Tool errors warrant a retry with adjusted arguments. Loops warrant a diversity prompt telling the agent to try something new. Context exhaustion warrants compression. External service failures warrant fallback tools. Goal misunderstanding warrants a clarification request. Map these explicitly in a recovery router that the agent runtime invokes when failures occur.
RECOVERY_ACTIONS = {
FailureType.TOOL_ERROR: 'retry_with_corrected_args',
FailureType.MALFORMED_OUTPUT: 'reformat_output',
FailureType.REASONING_LOOP: 'inject_diversity_prompt',
FailureType.CONTEXT_EXHAUSTION: 'compress_history',
FailureType.EXTERNAL_SERVICE: 'use_fallback_tool',
FailureType.GOAL_MISUNDERSTANDING:'request_clarification',
FailureType.MAX_ITERATIONS: 'escalate_to_human',
FailureType.UNKNOWN: 'escalate_to_human'
}Setting Maximum Iteration Limits
Every agent must have a maximum iteration limit as a hard safety boundary. Without it, a looping agent runs indefinitely, consuming tokens and money. Set the limit based on expected task complexity: a simple question-answering agent might cap at 5 steps, while a complex research agent might allow 20. When the limit is reached, log the failure, save the partial results, and escalate to a human or return a partial answer.
MAX_ITERATIONS = 15
async def run_agent(task: str) -> str:
messages = [{'role': 'user', 'content': task}]
loop_detector = LoopDetector()
for iteration in range(MAX_ITERATIONS):
response = await get_agent_action(messages)
if response.is_final:
return response.answer
action_key = f'{response.tool}:{response.args}'
if loop_detector.record(action_key):
messages.append({'role': 'system', 'content': 'You are repeating yourself. Try a completely different approach.'})
continue
result = await execute_tool(response.tool, response.args)
messages.append({'role': 'tool', 'content': result})
return 'Task exceeded maximum iterations. Partial results: ' + get_partial_result(messages)Logging Failures for Post-Mortem Analysis
Log every agent failure with enough context to diagnose it after the fact: the full task description, the complete action history up to the failure point, the failure type and error message, the iteration count, and the token usage. Store this in a failures table with an index on failure_type and task_id. Regularly review failure logs to identify which task types are most prone to specific failure modes and prioritize fixes accordingly.
import json
from dataclasses import asdict
async def log_agent_failure(task_id: str, failure: AgentFailure, history: list, pool):
async with pool.acquire() as conn:
await conn.execute('''
INSERT INTO agent_failures
(task_id, failure_type, step, tool_name, error_message,
recoverable, action_history, failed_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
''',
task_id,
failure.failure_type.value,
failure.step,
failure.tool_name,
failure.error_message,
failure.recoverable,
json.dumps(history)
)Quick Check
Test your understanding of agent failure mode classification.
Lesson Recap
In this lesson you learned: six major agent failure modes include tool errors, malformed outputs, reasoning loops, context exhaustion, external service failures, and goal misunderstanding, loop detection via action history catches repetitive patterns before they exhaust the iteration budget, and mapping failure types to recovery actions enables automated self-healing. Next up we implement self-correction and reflective prompting.
Frequently asked questions
Is the “Classifying Agent Failure Modes” lesson free?
Yes — the full text of “Classifying Agent Failure Modes” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.
What will I learn in “Classifying Agent Failure Modes”?
Build a taxonomy of agent failures: tool errors, malformed outputs, reasoning loops, context exhaustion, and external service unavailability, and design recovery strategies for each. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Engineering Academy?
No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Classifying Agent Failure Modes” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Engineering Academy lesson?
Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Classifying Agent Failure Modes
- Self-Correction and Reflective Prompting
- Checkpointing and Task Resumption
- Human-in-the-Loop Escalation