All writing

Tool Calling at Scale: Why Agent Tool Design Breaks

Most agentic systems break not because the model reasoned wrong, but because the tool it called was designed in a way that made the right call impossible. I've watched teams debug mysterious agent loops for days, only to find the root cause was an ambiguous action parameter that meant three different things depending on context. Tool design is the unglamorous discipline that determines whether your agent actually works at scale — and almost nobody is treating it seriously.

Why Tool Schema Is the Real Agent Interface

When you call a function in code, you read the signature, the docstring, the types. The LLM does the same thing — except it has no ability to ask a clarifying question mid-execution and no ambient knowledge of what your team "meant" when they wrote process_request(data: dict). The schema you write is the entire contract between the model's reasoning and your infrastructure. If that schema is ambiguous, overcrowded, or violates the principle of single responsibility, you will get bad tool calls — not occasionally, but systematically.

Here's the uncomfortable truth: your tool schema is a prompt, and it has all the same failure modes as a bad prompt. Vague descriptions, overloaded parameters, inconsistent naming conventions, missing constraints — they all degrade call accuracy in ways that are hard to trace because the failure shows up three steps later when a downstream agent gets garbage input.

The Four Failure Patterns That Kill Tool Reliability

1. The God Tool

A single tool that handles five related but distinct operations, usually because it was easier to expose one endpoint. The model has to infer which operation to invoke from context, and when context is ambiguous — which it is constantly in multi-turn conversations — it picks wrong. Split your tools. A tool that books a flight and a tool that checks availability are not the same tool.

2. Unconstrained String Parameters

Imagine a team building a travel agent with a destination field typed as string. The model sends "Dubai", "DXB", "Dubai, UAE", and "dubai" across different sessions. Downstream validation fails intermittently. The fix is a 15-minute schema change — add an enum or a pattern constraint and document the expected format explicitly. The engineering hours wasted debugging were entirely avoidable.

json
// Fragile
{
  "name": "search_flights",
  "parameters": {
    "destination": { "type": "string" }
  }
}

// Robust
{
  "name": "search_flights",
  "parameters": {
    "destination_iata": {
      "type": "string",
      "pattern": "^[A-Z]{3}$",
      "description": "Three-letter IATA airport code, e.g. DXB, LHR, JFK"
    }
  }
}

The second version removes an entire class of model error. The description is doing active work — it gives the model an example, which consistently outperforms relying on the type alone.

3. Missing Required/Optional Distinction

When every parameter is listed without a required array, the model makes its own judgment about what to include. Sometimes it omits a field you needed. Sometimes it hallucinates a value for a field you didn't want touched. Be explicit. Mark required fields. Document optional fields with their defaults and the conditions under which they should be set.

4. Tool Namespace Collision

In multi-agent systems, different agents may expose tools with similar names — send_message, notify_user, alert — that do subtly different things. The orchestrator model picks between them based on description similarity, and it gets it wrong under load because the signal-to-noise ratio collapses when there are 15+ tools in context. This is a naming and description architecture problem, not a model capacity problem.

The Tool Design Framework: SPAR

Here's a framework I apply when auditing or building tool schemas — I call it SPAR. It's a practical decision heuristic, not a formal spec, but working through it systematically catches the failure modes that burn teams in production:

DimensionQuestionPass Condition
Single responsibilityDoes this tool do one thing?Yes — no branching on an action param
Precise typesAre all parameters constrained?Enums, patterns, or ranges — never raw object
Actionable descriptionsDoes the description tell the model when NOT to use the tool?Includes negative examples or boundary conditions
Result contractIs the return schema documented?The model needs to know what it will receive to plan next steps

The "R" is the most commonly skipped. Teams document inputs obsessively and leave the output schema as a surprise. But the agent needs to reason about what it will receive to decide what to call next. If search_flights can return either a list of results or an error object with no consistent structure, downstream tool chaining becomes a guessing game.

Designing for Graceful Degradation, Not Happy-Path Only

Every tool needs an explicit failure contract. This is different from error handling in traditional software — in agentic systems, the model has to decide what to do when a tool fails, and it can only make a good decision if the error is structured and informative.

python
# Weak: the agent gets an unstructured exception string
def book_hotel(hotel_id: str, check_in: str, check_out: str) -> dict:
    result = external_api.book(hotel_id, check_in, check_out)
    return result  # Could raise, could return None, could return {"error": "..."}

# Strong: the agent always gets a typed, parseable result
def book_hotel(hotel_id: str, check_in: str, check_out: str) -> dict:
    try:
        result = external_api.book(hotel_id, check_in, check_out)
        return {"status": "success", "booking_id": result["id"], "confirmation": result["ref"]}
    except RateLimitError:
        return {"status": "error", "code": "RATE_LIMITED", "retry_after_seconds": 30}
    except UnavailableError:
        return {"status": "error", "code": "UNAVAILABLE", "message": "Hotel not available for selected dates"}

When the model receives RATE_LIMITED with a retry hint, it can reason about whether to wait and retry, escalate to a human, or offer an alternative. When it receives an exception stack trace, it usually just apologizes to the user and breaks the task.

This connects directly to the broader agent reliability problems I covered in Agent Failure Modes: 5 Production Breaks Nobody Demos — if that framing is new to you, the tool layer is where most of those breaks originate.

Scaling Tool Sets Without Degrading Accuracy

Here's a production reality nobody talks about: model tool-call accuracy degrades as the number of available tools grows. The relationship isn't linear — most frontier models handle 10–15 tools reasonably well, but accuracy starts dropping meaningfully above 20–25, especially when tool descriptions are similar or overlapping. This pattern is consistent with published evals from the major labs and with what practitioners consistently report in production systems.

The mitigation strategies that actually work in production:

Dynamic tool loading. Don't inject all tools into every agent call. Classify the user intent first, then load only the relevant tool subset. An intent classifier that routes to one of three tool groups costs you one small model call and buys you meaningfully higher call accuracy on the specialist tools.

Tool grouping by agent specialization. In a multi-agent architecture, each sub-agent should own a narrow, coherent tool set. The orchestrator doesn't call booking tools directly — it delegates to a booking agent that has exactly the tools it needs and nothing else. This is the composability principle, and it works.

Semantic deduplication. Before adding a new tool, check if an existing tool's description would cause the model to confuse them. If yes, either rename and differentiate aggressively, or consolidate. The question to ask: "If I gave a new engineer only the tool names and descriptions, would they pick the right one?" If not, the model won't either.

The Versioning Problem Nobody Plans For

Tool schemas change. APIs change. Parameters get renamed, deprecated, or type-shifted. In a traditional codebase, you handle this with versioned APIs and migration paths. In an agentic system, you have an additional challenge: cached or logged tool calls from old schema versions can corrupt agent memory and replay pipelines.

The discipline required here:

  • Version your tool schemas explicitly (book_hotel_v2 or a version field in the schema metadata)
  • Keep old tool versions active with deprecation notices in the description until you've confirmed no agents reference them
  • If you're storing agent trajectories for fine-tuning or evals, tag them with schema version so you don't train on stale call patterns

This is unglamorous infrastructure work. It's also the difference between a system you can iterate on safely and one where every schema change is a production incident waiting to happen.

What to Actually Do

  1. Audit every tool in your current system against SPAR. Run through Single responsibility, Precise types, Actionable descriptions, Result contract. Flag anything that fails. Prioritize fixing tools called most frequently or involved in the most agent failures.

  2. Add examples to every description. Add at least one positive and one negative example to each tool description this week. This is the highest-ROI single change you can make to tool call accuracy — the model responds strongly to concrete examples embedded in the schema.

  3. Standardize your error contract. Pick a response envelope — {status, code, data, message} or similar — and apply it to every tool. Update the schema documentation to reflect it. This alone will eliminate a category of downstream agent confusion.

  4. Instrument tool call accuracy, not just task completion. Add logging that captures which tool was called, what arguments were passed, and whether the call was valid against your schema. A task that completes successfully despite a bad intermediate tool call is a latent failure — you need visibility on it before it becomes a production break.

  5. Cap per-agent tool counts at 15. If a sub-agent needs more than 15 tools, decompose it. This is a hard architectural heuristic, not a soft preference.

Tool design is the discipline that separates an agent demo from an agent product. Get the schema right and the model does its job. Get it wrong and you're debugging phantom failures in a system that looks correct on every layer except the one you haven't looked at yet.

Working on something like this? I take on a few fractional-CTO and AI engagements at a time.

The AI CTO playbook

Get my AI playbooks — straight to your inbox

Practical notes on shipping production AI, scaling teams, and the calls a CTO actually has to make. A few times a month. No spam, no fluff.