Agentic AI
5 Agent Mistakes I Made So You Don't Have To
Nine posts into this series, here's the honest tally: the concepts were never the hard part. Every mistake below is something I actually shipped, at least once, before learning why it was wrong. Save yourself the debugging session.
1. No limit on the agent loop
Covered in depth earlier in this series, but it's mistake #1 for a reason — it's the one that turns every other bug into an expensive, runaway one instead of a quick fix.
# Wrong: nothing stops this if Claude gets stuck in a loop
while True:
response = client.messages.create(...)
if response.stop_reason != "tool_use":
break
# ... run tools ...
# Right: a hard ceiling, always
for turn in range(max_turns):
response = client.messages.create(...)
if response.stop_reason != "tool_use":
breakOne line. It's the cheapest insurance in this entire list.
2. No error handling around tool calls
A tool call fails for boring reasons constantly — a timeout, a malformed argument, an API that's down. Without handling, one failed call crashes the whole agent instead of letting Claude adapt.
# Wrong: one bad API call kills the entire run
result = tool_functions[block.name](**block.input)
# Right: catch it, tell Claude what happened, let it decide what to do next
try:
result = tool_functions[block.name](**block.input)
is_error = False
except Exception as e:
result = f"Tool failed: {str(e)}"
is_error = True
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
"is_error": is_error,
})The is_error flag matters — Claude reads it and responds differently than it would to a normal result. Often it'll retry with different arguments, or fall back to a different tool entirely, exactly like a person would.
3. Trusting tool output without checking it
Not every tool failure looks like an exception. A search tool that returns an empty list, an API that returns a 200 with an error message inside the body, a file read that silently returns blank content — none of these throw, so naive error handling misses them entirely.
# Wrong: assumes any non-crashing result is a good result
def search_docs(query: str) -> str:
results = vector_db.search(query)
return "\n".join(r.text for r in results) # empty string if results is []
# Right: make emptiness or low quality explicit, so Claude can react to it
def search_docs(query: str) -> str:
results = vector_db.search(query)
if not results:
return "No results found. Try a different or broader query."
if results[0].score < 0.4:
return f"Only weak matches found (top score: {results[0].score:.2f}). Consider rephrasing."
return "\n".join(r.text for r in results)This is the exact mechanism that makes agentic RAG work — Claude can only decide to search again if the tool is honest about a weak result instead of quietly handing back something unhelpful dressed up as success.
4. No cap on cost per run
Covered in the previous post in depth: a turn limit protects you from infinite loops. It does not protect you from a loop that's technically bounded but expensive — ten turns on your most capable model, on a task that should have taken two.
# Add a running cost check alongside the turn limit, not instead of it
if estimated_cost > max_cost_usd:
return "Stopped: exceeded budget for this task."Cheap insurance again, and it catches a failure mode the turn limit alone doesn't.
5. Ambiguous system prompts and tool descriptions
This one doesn't crash anything — it just quietly produces wrong behavior, which is worse, because nothing tells you it happened. A vague tool description is the single most common reason Claude picks the wrong tool, or the right tool with wrong arguments.
# Wrong: technically accurate, gives Claude nothing to reason with
{
"name": "update_record",
"description": "Updates a record.",
...
}
# Right: says what it does, when to use it, and when not to
{
"name": "update_record",
"description": (
"Updates an existing customer record's contact fields (email, phone, address). "
"Use only after confirming the customer's identity. "
"Do not use this to create new records — use create_record instead."
),
...
}The model relies entirely on that description to decide when and how to call the tool — it's not a comment for humans, it's the actual interface Claude reasons against. Treat it with the same care you'd give a public API's documentation, because functionally, that's exactly what it is.
The pattern across all five
None of these are exotic. They're the same lesson five times: an agent is only as reliable as the boundaries and information you give it — turn limits, error handling, honest tool output, cost caps, and clear descriptions. Every one of them is a small amount of extra code that turns "usually works" into "reliably works," which is the entire gap between a weekend demo and something you'd trust running unattended.
Reference links
- Claude tool use — error handling: platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls
- Define tools (writing good descriptions): platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools
- Claude Agent SDK (built-in versions of several of these guardrails): platform.claude.com/docs/en/agent-sdk/overview
That closes out this 10-part series on building agentic AI with Claude — from a single tool call to cost-aware, multi-agent systems with real guardrails.
Related articles
Agentic AI
I Connected Claude to My Terminal and Broke My Laptop (Lessons on Agent Guardrails)
A weekend project, a bash tool with no restrictions, and the exact three mistakes that let it happen — plus the guardrails that would have stopped every one of them.
- #claude
- #agentic-ai
- #safety
- #guardrails
- #python
Agentic AI
How Much Does an Agentic Claude Workflow Actually Cost? (Real Numbers)
Almost nobody publishes real token math for multi-step agents. Here's a worked example — a 6-turn agent loop, priced out — plus a small calculator function you can reuse.
- #claude
- #agentic-ai
- #pricing
- #python
- #cost-optimization
Agentic AI
I Gave Claude Hands: My First Tool-Use Agent in 20 Lines
Agents sound complicated until you see the actual request-response loop underneath. Here's the whole thing, stripped down to one tool and 20 lines of Python.
- #claude
- #agentic-ai
- #tool-use
- #python
- #anthropic