Skip to content
Skip to main content
A tidy row of precision hand tools on a slate-blue surface, a metaphor for well-designed Claude agent tools
8 min readBy Carlos Aragon

Claude Tool Use Best Practices for Reliable Agents

The biggest lever on whether a Claude agent calls the right tool isn't the model — it's how you write the tools. In production, four boring habits fix most “it picked the wrong tool” bugs: write each description like it's the only documentation the model gets, make the input schema strict enough that a bad call is hard to construct, return errors the model can recover from, and keep the tool count small. Here's how I apply each one, and the failures that taught me why.

The Model Reads Your Tools, Not Your Code

Here's the thing most people miss when an agent misbehaves: Claude never sees your implementation. It sees three things per tool — the name, the description, and the input schema. That little block of JSON is the entire contract. If it's ambiguous, the model guesses, and a guess at 2am is how you end up debugging “why did it call send_invoice when the user asked a question.”

The first agent I shipped had two tools — search_orders and get_order — with descriptions that were basically the same sentence. The model flipped between them at random, sometimes searching when it had the exact ID, sometimes fetching a single order when it needed a list. I spent an evening editing the promptbefore I realized the prompt was fine. The tools were the problem. I rewrote the two descriptions to say plainly when to use each one — and the “wrong tool” bug just stopped.

The mental model:

Your tool definitions are a prompt. They're just a prompt you forgot you were writing. Spend the same care on them you spend on your system message.

Write the Description Like It's the Only Docs

A good tool description does two jobs: it says what the tool does, and it says when to reach for it and when not to. That second half is what people skip, and it's the half that prevents overlap. If two tools could plausibly answer the same request, at least one description needs a line that pushes the model toward the other.

Compare these two definitions for the same tool:

# vague — the model will misfire
{
  "name": "get_order",
  "description": "Get an order."
}

# specific — the model knows exactly when to call it
{
  "name": "get_order",
  "description": "Fetch ONE order by its exact order_id and
    return its full details (items, status, total). Use this
    only when you already know the order_id. To find an order
    from a customer email or date range, use search_orders
    instead."
}

Same tool, same schema, wildly different behavior. Anthropic makes the same point in their tool use documentation: the description is the highest-leverage part of the whole definition. Give it three or four real sentences, not four words. This is the same “be explicit with the model” discipline I lean on when building dynamic Claude Code workflows.

Make the Schema So Strict a Bad Call Is Hard

The input schema isn't just validation — it's guidance. Every constraint you add is one fewer way the model can call the tool wrong. Loose schemas (“status is a string”) invite invented values. Tight schemas (“ status is one of these four”) make the invalid call impossible to express.

  • Use enums for anything with a fixed set of values — statuses, categories, modes. The model can't pass a value that isn't on the list.
  • Mark required fields required, and describe each parameter in one line. "email: the customer's email, lowercased" beats a bare string type.
  • Prefer a few well-typed parameters over one free-text blob the model has to structure itself.
  • Give examples in the description when a format is fiddly (dates, IDs) — the model copies the shape.

Then validate anyway. The schema shapes what the model tends to send; your handler still has to check what actually arrived and reject bad input with a clear message. Trusting the JSON blindly is the same mistake as trusting user input blindly — it works until the one time it doesn't.

Precision tools laid out in a row, illustrating a small, deliberate set of well-defined agent tools
A few sharp, well-labeled tools beat a drawer full of vague ones.

Return Errors the Model Can Recover From

When a tool fails, you have three options, and only one of them is any good. You can throw and kill the turn (brittle), return a raw stack trace or an empty result (the model retries blindly or hallucinates), or return a short, plain message that tells the model what happened and what to do next. Do the third.

# bad — the model can't recover from this
{ "type": "tool_result", "content": "" }

# bad — noise the model will just retry against
{ "type": "tool_result", "content": "Traceback ...
  KeyError: 'customer_id'" }

# good — recoverable, actionable
{
  "type": "tool_result",
  "is_error": true,
  "content": "No customer found for that email.
    Ask the user to confirm the address, or use
    search_customers to look them up by name."
}

Set is_errorso the model knows the call failed, then tell it the next move. This is exactly the “fail loud, fail recoverable” habit I wrote about when my n8n agents started failing silently. A silent empty result is the worst outcome: the loop keeps going, spends tokens, and produces confident nonsense.

Use tool_choice and Parallel Calls on Purpose

Two levers people reach for too fast. tool_choice lets you force a specific tool or force “some tool.” It's great for a router step that must return a classification, or a final step that must record a result — anywhere the correct action is singular. For normal agent turns, leave it on auto. Forcing a tool on every turn makes the agent call tools it doesn't need, which costs tokens and adds failure surface.

Parallel tool calls are the other one. Claude can request several tools in a single turn, and for independent reads — fetch three records at once, hit two APIs that don't depend on each other — that's a real latency win. But only parallelize calls that are genuinely independent. If tool B needs tool A's output, let them run in sequence; a parallel call there just races and returns garbage. I go deeper on the cost side of this — and why transport choice matters as much as tool count — in MCP vs CLI for AI agents.

How Many Tools Is Too Many?

Fewer than you think. Every tool you expose is text injected into every single turn, and it's one more thing the model can pick wrong. I start with three or four and only add one when the logs show the current set can't do a real task. Twenty narrow tools is not a feature — it's twenty chances to misfire and a fat token bill on every call.

When you truly need broad capability, don't list fifty endpoints. Group them behind a few higher-level tools, or let the model write code that orchestrates the calls itself. That “give it a runtime, not a menu” move is the same reason a subagent that owns a whole subtask beats micromanaging it — which I broke down in the real token cost of Claude subagents. Anthropic's own guide to writing tools for agents lands in the same place: a few consolidated, well-described tools beat a sprawl of thin ones.

The Short Version

  • The model reads your tool name, description, and schema — not your code. That block is the contract.
  • Write each description to say what the tool does AND when not to use it. Overlap is the #1 cause of wrong-tool calls.
  • Make schemas strict: enums, required fields, per-parameter descriptions. Then validate in your handler anyway.
  • Return errors as tool results with is_error and a plain next step — never a stack trace or an empty string.
  • Force tool_choice only when the action is singular; parallelize only genuinely independent calls.
  • Keep the tool count small — three or four to start. Consolidate or hand the model code instead of listing fifty tools.

Want a Claude Agent That Calls the Right Tool Every Time?

I build production AI agents on the Claude API, the Anthropic SDK, n8n, and Supabase — with tool definitions, schemas, and error handling designed for reliability from day one. If your agent keeps picking the wrong tool or failing silently, let's fix the tools, not just the prompt.

Related Posts

AI Agents

MCP vs CLI for AI Agents: The 4–32× Token Tax Nobody Warns You About

For most agent tasks a CLI is 4–32× cheaper than an MCP server — 1,365–8,750 tokens per task instead of 32,000–82,000 — because every connected MCP server injects all of its tool definitions into every turn, used or not. One Microsoft Intune test came out ~35× cheaper on the CLI (~4,150 vs ~145,000 tokens), and at 10k ops/month that's roughly $3.20 vs $55.20. The CLI was also more reliable in one 75-run benchmark (100% vs 72%, MCP's failures were mostly TCP timeouts on its persistent connection). Use a CLI when a mature one exists and you own the box; keep MCP for OAuth SaaS, multi-tenant per-user auth, governance/audit needs, and tools with no CLI. For high-volume fan-out, let the model write code that orchestrates the calls (programmatic tool calling / Code Mode) to cut tokens 98–99%. The best agents mix all three; measure tokens per completed task, not per call.

AI Agents

Building AI Agents with the Claude API: A Production Guide

A complete production guide to building reliable AI agents with the Claude API — tool use, retries, observability, and orchestration.

AI Agents

Claude's Memory Tool + Context Editing: Agent Memory That Survives the Context Window

The Claude memory tool (memory_20250818) lets an agent create, read, update, and delete files in a persistent /memories directory across conversations — client-side, all Claude 4+ models, GA on the Messages API with no beta header. It's just-in-time retrieval: the agent reads back only the memory files relevant to the current step, so knowledge lives on disk, not in the window. Context editing pairs with it, auto-clearing stale tool results near the token limit — 84% fewer tokens on Anthropic's 100-turn eval, a 29% quality lift alone and 39% combined with memory. The must-not-ship bug is path traversal: validate every path resolves inside /memories before touching disk.