Tool & Error Anti-Patterns
Minimal descriptions, too many tools, generic errors.
Tool & Error Anti-Patterns is a free Claude Architect lesson on CoddyKit — lesson 2 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 Claude Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Tools and Errors Fail Quietly
Most agent failures aren't dramatic crashes. They're quiet misroutes: the model picks the wrong tool, or a tool fails with a vague message the model can't recover from. The agent then improvises, fabricates, or silently abandons the task.
This lesson dissects three anti-patterns that ship constantly in production:
- Minimal tool descriptions that leave the model guessing
- Too many tools per agent, which degrades selection reliability
- Generic error statuses that block intelligent recovery
Each one is a frequent wrong answer on the exam. Learn to spot and fix them.
Descriptions Are the Selection Mechanism
A common misconception: the model routes by tool name. It does not. The tool description is the primary selection mechanism. Names are labels; descriptions are where the model decides whether a tool fits the situation.
A minimal description like "Looks up an order" tells the model almost nothing. When two tools have thin, overlapping descriptions, the model misroutes — it calls lookup_order when it needed get_customer, and the whole agentic loop drifts.
Anatomy of a Strong Description
A good tool description carries five things:
- Purpose — what it does and when to use it
- Return values — the shape of what comes back
- Input formats with examples — concrete, not abstract
- Edge cases — empty results, ambiguity, multiple matches
- Applicability boundaries — when NOT to use it
That last point is what disambiguates overlapping tools. Below is the weak version most teams ship.
lookup_order = {
"name": "lookup_order",
# Anti-pattern: minimal, ambiguous description
"description": "Looks up an order.",
"input_schema": {
"type": "object",
"properties": {"id": {"type": "string"}},
"required": ["id"],
},
}Rewriting for Disambiguation
Now the strong version. Notice how the description states the input format with an example, the return shape, the empty-result case, and an explicit boundary that prevents collision with get_customer.
This is the single highest-leverage fix for misrouting: you change the description, not the model.
lookup_order = {
"name": "lookup_order",
"description": (
"Retrieve a single order by its order ID. "
"Input: order_id as a string like 'ORD-48213' (NOT a customer ID). "
"Returns: {order_id, status, items[], total, placed_at}. "
"If no order matches, returns an empty result (not an error). "
"Use get_customer first if you only have a name or email; "
"do NOT use this to look up a customer's full order history."
),
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "e.g. 'ORD-48213'"}
},
"required": ["order_id"],
},
}Too Many Tools Degrades Selection
The second anti-pattern: handing one agent a giant toolbox. Selection reliability isn't constant — it degrades as the tool count grows.
- 4-5 tools per agent is the optimal range
- 18+ tools measurably degrades selection reliability
More tools mean more chances for overlapping descriptions, more surface area for ambiguity, and a longer list the model must reason over every turn. Breadth is not a feature here — it's a liability.
Scope Tools to the Role
The fix is architectural: scope tools to the role instead of giving every agent every capability. In a hub-and-spoke multi-agent system, the coordinator decomposes work and delegates to subagents — each subagent gets only the handful of tools its job needs, following least privilege.
A focused 4-tool support agent outperforms a 20-tool generalist because each tool is unambiguous within its small, role-scoped set.
support_agent = AgentDefinition(
name="order_support",
description="Handles order status and refund requests for a verified customer.",
system_prompt="You resolve order issues. Verify identity before any refund.",
# 4 tools, scoped to the role — not the whole company API
allowed_tools=[
"get_customer",
"lookup_order",
"process_refund",
"escalate_to_human",
],
)Split, Don't Stuff
When a workflow genuinely needs many capabilities, the answer is not one bloated agent — it's decomposition. Split responsibilities across role-scoped subagents and let the coordinator route between them.
Remember the multi-agent rule: subagents do not inherit the coordinator's conversation history. So when you delegate, pass all needed context explicitly in the subagent prompt. A clean split keeps each agent's tool set small AND keeps its context focused.
Generic Errors Block Recovery
The third anti-pattern lives in the error path. A tool that fails with "Operation failed" or a bare isError: true gives the model nothing to act on. It can't tell a transient network blip from a validation mistake from an empty result — so it either retries blindly, gives up, or fabricates an answer.
Generic error statuses block intelligent routing. Structured errors enable it.
# Anti-pattern: generic, unrecoverable error
return {
"isError": True,
"message": "Operation failed",
}Structured MCP Errors
A recoverable error carries structure. The MCP convention bundles the fields the model needs to decide what to do next:
isError: trueplus anerrorCategory: transient / validation / business / permissionisRetryable— should the model try again at all?message,attempted_query, andpartial_results
With these, the model routes intelligently: retry a transient fault, fix a validation error, escalate a permission denial, or surface partial results instead of nothing.
# Structured error: enables intelligent routing
return {
"isError": True,
"errorCategory": "transient", # transient|validation|business|permission
"isRetryable": True,
"message": "Order DB timed out after 5s",
"attempted_query": {"order_id": "ORD-48213"},
"partial_results": [],
}Failure Is Not the Same as Empty
One subtle distinction the exam loves: an access FAILURE (the tool couldn't run — timeout, permission) is not the same as a valid EMPTY result (the query ran fine and matched nothing).
Collapsing both into a generic error is a recovery-killer. A failure may be worth a retry; an empty result means "no matches" and should be reported as fact, not retried forever. Your lookup_order earlier got this right: no match returns an empty result, not an error.
Recover Locally, Escalate with Context
Put it together into an error-propagation strategy:
- Recover transient faults locally in the subagent — retry the timeout, don't bubble it up
- Escalate non-recoverable failures with structured context: failure type, attempted query, partial results, and alternatives
- Never silently suppress errors, and never abort the whole workflow because one subagent failed
The coordinator can then route around a single failed branch and still aggregate a useful answer — which is exactly what generic errors make impossible.
# Subagent error handling
if err["errorCategory"] == "transient" and err["isRetryable"]:
result = retry(call) # recover locally
else:
return { # escalate WITH context
"status": "failed",
"failure_type": err["errorCategory"],
"attempted_query": err["attempted_query"],
"partial_results": err["partial_results"],
"alternatives": ["try search_orders by date range"],
}Quick Check: Misrouting Fix
Apply the lesson to a concrete failure.
Recap: Three Anti-Patterns, Three Fixes
You can now spot and fix the tool-and-error anti-patterns that show up as wrong answers on the exam:
- Minimal descriptions cause misrouting. Fix: descriptions are the selection mechanism — state purpose, return values, input formats with examples, edge cases, and applicability boundaries.
- Too many tools degrade selection. Fix: 4-5 tools per agent is optimal, 18+ degrades reliability — scope tools to the role and split across subagents under least privilege.
- Generic errors block recovery. Fix: structured errors with errorCategory (transient/validation/business/permission), isRetryable, attempted_query, and partial_results — distinguish access failure from a valid empty result, recover transient faults locally, escalate the rest with context, and never silently suppress or abort the whole workflow.
Frequently asked questions
Is the “Tool & Error Anti-Patterns” lesson free?
Yes — the full text of “Tool & Error Anti-Patterns” is free to read here on the web, and the Claude Architect 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 Claude Architect course, upgrade to CoddyKit PRO.
What will I learn in “Tool & Error Anti-Patterns”?
Minimal descriptions, too many tools, generic errors. You practise Claude Architect 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 Claude Architect?
No prior experience is required. Claude Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Tool & Error Anti-Patterns” 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 Claude Architect lesson?
Yes. Every Claude Architect 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
- Loop & Orchestration Anti-Patterns
- Tool & Error Anti-Patterns
- Prompt & Review Anti-Patterns
- Escalation & Metrics Anti-Patterns