Tool Design for Agents: The Interface Layer That Decides Whether an Agent Works
When an agent fails in production, the cause is far more often a badly designed tool surface than a weak model. It called the wrong tool because two descriptions were indistinguishable. It passed a malformed argument because a parameter took a free string where four values were legal. It looped because an error said "request failed" instead of what to do next. It ran out of context because a tool returned a whole API payload when the next decision needed three fields. None of those are reasoning failures, and none get fixed by upgrading the model.
That makes tool design the highest-leverage work available to a team that cannot change models, which is most teams. A model upgrade is a procurement decision with a release schedule. Rewriting a description is an afternoon, and the measured improvement is frequently larger.
The state of the practice as of the week of September 21, 2026 is that tool-calling capability is no longer the bottleneck. Frontier models call tools reliably when the tools are well specified, and protocol standardization has made the mechanics routine. What has not become routine is the design judgment: which operations to expose, at what granularity, described how, returning what. That is the subject of this guide.

Key Takeaways
- Design one tool per user intent, not one per API endpoint. Endpoint-shaped surfaces force the model to do your orchestration, the most expensive and least reliable place to put it.
- The description is not documentation, it is the instruction the model actually reads. Descriptions written for human developers are the most common defect in a production tool surface.
- Return-value design is the neglected half and often the larger win. Return what the next decision needs plus a status the model can branch on, never the raw upstream payload.
- Errors are retry instructions. A terse error produces a loop; one naming the problem and the corrective action produces a recovery.
- Selection accuracy and argument accuracy are different failures with different fixes, so measure them separately. A single pass rate hides which one is broken.
- Tool-count degradation starts well before any context limit. Past roughly twenty to thirty tools, move from registering everything to retrieving a relevant subset per task.

What a Tool Is to a Model
A tool, from the model's side, is a name, a description, and a parameter schema, rendered into the prompt. That is the whole interface. The implementation behind it is invisible, and when to call it and what to pass are decided entirely from those three strings and the schema shape.
Three consequences follow, and most tool-surface defects are a failure to internalize one of them.
The definition is prompt text, subject to everything true of prompt text. It competes for attention, it is read in full every turn, and ambiguity is resolved by the model guessing rather than asking.
The model cannot see your system, so it cannot infer intent from your architecture. A developer reading update_record knows which service owns records and what surrounds it. The model knows the string and whatever the description says.
The return value is the only feedback channel. The model learns whether the call worked, and what to do next, exclusively from what comes back. A tool that returns success on a partial failure has told it something false, and it proceeds confidently.

Granularity: One Tool Per Intent, Not Per Endpoint
The most consequential decision is the one made most carelessly, usually by generating tools from an API specification.
The endpoint-shaped surface fails because it pushes orchestration into the model. Consider booking a meeting. An endpoint-shaped surface exposes list_calendars, get_availability, create_event, add_attendee, and send_invite. The model must know the sequence, carry identifiers between calls, handle availability changing mid-flow, and recover when step four fails after the first three have run. Each of those is a place to go wrong, each round trip costs latency and context, and the orchestration is now model reasoning rather than code you can test.
The intent-shaped surface exposes schedule_meeting with participants, a duration, and a time window, and sequences internally where it is deterministic, testable, and transactional. The model makes the one decision only a model can make: that scheduling is what the user wants.
The rule generalizes. Expose the unit of work a user would name, and implement the steps below it in code. If a human calls it one action, it is one tool.
The counter-case deserves respect, because taken too far this produces one omnibus tool with thirty parameters, worse than either extreme. Composition genuinely matters when intermediate results change what happens next in ways only the model can judge: a research agent needs to see search results before deciding what to read, because relevance is a judgment call. Where the branch depends on semantic content the model must evaluate, split the tool. Where it depends on logic you could write down, do not.
The practical test: could you write the sequencing as a function without calling a model? If yes, write it and expose one tool. If no, the model belongs in the loop and the steps stay separate.
Naming and Description Are Prompt Surface
Names should be verb-first, specific, and distinguishable at a glance. search_internal_docs and search_public_web are. search and query are not, and a surface with both produces selection errors no prompting fixes.
The description is where most of the value and most of the defects live. The dominant failure is one written for a human developer, reading like API reference material and omitting what the model needs: when to use this rather than something else.
A description should answer four questions in order. What it does, in one sentence. When the model should reach for it. When it should not, naming the sibling it is most likely to be confused with. And what the caller needs to know about cost, latency, or side effects.
The third does the heavy lifting and is almost always missing. Selection errors are nearly always errors between two similar tools, and the fix is to write the boundary into both descriptions. A description for search_internal_docs ending with "for public information or anything after the last index refresh, use search_public_web instead" removes a class of failure in one sentence.
Describe behavior, not implementation. "Returns the customer's current subscription state, including trial status and renewal date" is useful. "Wraps the billing service v3 subscription endpoint" is not, because the model has no idea what that service is.
Parameter Design
Parameters are where malformed calls originate. Four rules prevent nearly all of them.
Flat beats nested. A deeply nested object makes the model construct a correct tree in one pass with no feedback. A flat parameter list does not. Where nesting is unavoidable, keep it to one level and small. The reasoning that governs structured model output applies here, as covered in the guide to getting reliable structured outputs from language models.
Enums beat free strings. Any parameter with a known legal set should be an enum. A free string for status eventually receives "Active", "active", "ACTIVE", and "currently active". An enum makes legal values visible in the schema and illegal ones unrepresentable.
Required fields should be few. Every one is something the model must have or invent, and a model under pressure to produce a valid call will invent. If a field can have a sensible default, give it one and make it optional.
Optional parameters need documented defaults, the subtlest of the four. An unstated default produces the quietest failures in the surface: the call succeeds, the schema validates, nothing errors, and the behavior is wrong because the model assumed a different default. An optional limit defaulting to ten silently truncates a result the agent believed complete, and the agent reasons confidently over the truncated set. State every default in the description.
One more that catches teams late: descriptive parameter names beat short ones. customer_id beats cid, and no token budget is worth the ambiguity.
Return Values: The Neglected Half
Most teams spend their design effort on the call and almost none on what comes back. That is backwards: the return value is the only feedback channel and the main consumer of context.
Returning the raw upstream payload is the default mistake. A response with sixty fields, most of them nulls and internal identifiers, costs a large share of the window and buries the two that matter. Return what the next decision needs.
Include a status the model can branch on. A result that might be empty, partial, or complete should say which: "no results" and "results truncated at the limit" call for different next actions and are otherwise indistinguishable.
| Return shape | What it tells the model | When to use it |
|---|---|---|
| Status plus compact payload | Whether the call succeeded, in the fields the next step needs | The default for nearly every tool |
| Status plus reference identifier | That a large result exists and how to fetch part of it | Results too large for context: documents, exports, log ranges |
| Status plus explicit empty marker | That the call worked and legitimately found nothing | Any search, lookup, or filter |
| Status plus truncation marker and cursor | That more data exists beyond what came back | Any list operation with a limit |
| Structured error with corrective action | What went wrong and what to do about it | Every failure path |
| Raw upstream payload | Nothing useful, at high context cost | Effectively never |
Error design decides whether an agent recovers or loops. A terse error is a loop generator: the model receives "request failed", has no basis for changing anything, and retries the identical call. A good error names what was wrong, which parameter caused it, and what a valid value looks like. "Invalid value 'last week' for parameter start_date. Expected ISO 8601 date, for example 2026-09-14." is a retry instruction the model follows next turn.
Distinguish retryable from terminal failures explicitly. A rate limit is retryable after a delay. A permission denial is not retryable at all and needs to reach a human. If the error does not say which, the agent treats both alike, and the wrong choice is expensive in both directions.
Side Effects, Idempotency, and Reversibility
A tool that changes state needs three things read-only tools do not.
An idempotency key, because agents retry: on timeouts, on ambiguous errors, and on their own uncertainty about whether the previous call landed. Without one, a retried payment tool makes two payments. Generate the key in the tool layer from the call arguments, not in the model, which cannot be relied on to produce a stable one.
An explicit reversibility marker in metadata: reversible, reversible with effort, or irreversible. Not decoration, it is the field a human-approval gate keys off. A gate that prompts on every state change trains humans to approve reflexively, worse than no gate. One that prompts only on irreversible actions preserves the signal.
A declared blast radius, meaning what the call can affect at most. A tool that deletes one record is a different risk from one that deletes a filtered set, though both are "delete". Where the radius is large, containment belongs at the execution boundary rather than the tool contract, the subject of the guide to sandboxing agents so they can run code safely.
The working pattern is a three-way classification applied at definition time: read-only, reversible write, irreversible write. Read-only runs freely. Reversible writes run with logging and an undo path. Irreversible writes require an approval gate. Teams that skip the classification end up gating everything or nothing.
Managing Tool Count
Tool-surface degradation begins well before any context limit. Selection accuracy falls once the surface grows past roughly twenty to thirty tools, and the mechanism is straightforward: more tools means more chances for two descriptions to overlap, on a longer list, every turn.
The fix is to stop registering everything. Retrieve the relevant subset per task: a lightweight step selects the ten or fifteen tools plausibly relevant to the request, and only those enter the context. That adds a step and removes a scaling problem, a good trade above the threshold and unnecessary below it.
Two techniques help before retrieval becomes necessary. Namespace by domain, so billing_*, crm_*, and docs_* give a coarse filter before the fine one. And audit for overlap: for every pair, ask whether a reasonable request could route to either. Every such pair is a latent selection error, fixed by a boundary sentence in both descriptions.
Protocol-based exposure makes this easier to hit, since connecting several servers adds dozens of tools at once, each designed without knowledge of the others. The mechanics are covered in the explainer on what the Model Context Protocol means for enterprises. The consequence is that a connected server's tools are part of your surface and inherit your overlap problem.
Versioning Without Breaking Running Agents
Tool surfaces change, and agents may be mid-run when they do.
Additive changes are safe: a new optional parameter with a documented default, a new return field, a new tool. Breaking changes are not: removing or renaming a parameter, changing a type, changing a return field's meaning, or removing a tool.
Handle breaking changes by running both versions concurrently under different names, marking the old one deprecated with a pointer to its replacement, and removing it only after traffic drains. Ordinary API versioning discipline, plus one addition: the description is part of the contract, so description changes need the same review and evaluation run as schema changes.
Evaluating a Tool Surface
A tool surface without an evaluation set is unmaintainable, because nothing tells you whether a description edit helped or hurt.
The essential move is to measure selection accuracy and argument accuracy separately: they fail for different reasons and are fixed by different edits.
Selection accuracy asks whether the model chose the right tool. Those failures are description and naming problems, fixed by sharpening boundaries between similar tools. Argument accuracy asks whether, having chosen correctly, it passed valid arguments. Those are schema problems, fixed with enums, defaults, and clearer parameter descriptions. A single end-to-end pass rate averages the two and tells you nothing about which to fix.
Build the eval set with deliberately planted ambiguity: cases sitting between two similar tools, cases where the right answer is to call nothing, cases with missing information where the correct behavior is to ask rather than invent, and cases where the first call fails and recovery is what is tested. A suite of clean happy-path cases passes at ninety-plus percent on a surface that performs badly in production.
Run the suite on every description change, schema change, and model change. Tool surfaces are model-sensitive: a description tuned for one model will not necessarily perform identically on another, which is a real cost of switching. In production, instrument selection and argument errors as distinct metrics, part of the broader problem covered in the guide to agent observability in production.
The Design Rules, and How to Test Each
| Rule | Failure it prevents | How to test it |
|---|---|---|
| One tool per user intent, not per endpoint | Model-side orchestration errors, wasted round trips, partial-failure states | Count round trips per request. More than two or three for one intent means the surface is endpoint-shaped |
| Verb-first, distinguishable names | Selection errors between similar tools | For every pair, construct a request that could route to either. Any success is a defect |
| Description states when NOT to use the tool | Confusion with a sibling | Eval cases sitting deliberately between two tools |
| Flat parameters, enums over free strings | Malformed arguments, case and format drift | Argument-accuracy metric on the suite |
| Every optional parameter documents its default | Silent wrong behavior, truncated results treated as complete | Read each optional description. A missing default is a bug |
| Return the next decision's inputs, not the raw payload | Context exhaustion, buried answers | Measure return tokens per call. Full upstream payloads are the signal |
| Status field the model can branch on | Empty and truncated results treated alike | Eval cases with empty results, and with results exactly at the limit |
| Errors name the problem and the corrective action | Retry loops on identical calls | Inject each error type and check the next call differs |
| Retryable and terminal errors distinguished | Wasted retries, or escalating what should have retried | Inject a rate limit and a permission denial. Behavior must differ |
| Idempotency key on every state-changing tool | Duplicate writes on retry | Call twice with identical arguments. One effect only |
| Reversibility marked on every write tool | Approval gates that fire on everything and get ignored | Confirm the gate fires only on irreversible actions |
| Tool count under the retrieval threshold | Selection degradation as the surface grows | Track selection accuracy against tool count. Expect decline past twenty to thirty |
Build Order for a Team Starting Now
- List user intents, not endpoints. Write down what someone would ask the agent to do, in their words. That list is your tool list.
- Write descriptions before implementations. If a description is hard to write, the tool boundary is wrong, and this is the cheapest moment to find that out.
- Write the boundary sentences. For every pair that could be confused, add the "use the other one when" sentence to both.
- Design return values before wiring the upstream call. Decide what the next decision needs, then map the response into it. The other order produces raw-payload returns by default.
- Classify every tool: read-only, reversible write, irreversible write. Idempotency keys on both write classes, an approval gate on the third.
- Build the eval suite with planted ambiguity, recording selection and argument accuracy separately from day one.
- Add retrieval when the count crosses the threshold, not before.
- Instrument production for the same two metrics, so the suite grows from real failures.
A team applying this to an existing surface can take most of the available improvement from steps two, three, and four alone, none of which touch the model or the agent loop. State persistence across runs is a separate concern with its own rules, covered in the guide to agent memory in production.
Frequently Asked Questions
How many tools can an agent handle?
Reliably, roughly twenty to thirty before selection accuracy degrades measurably. The constraint is not the context window, it is the probability that two descriptions overlap enough to confuse the model on some request. Above that range, switch from registering every tool to retrieving a relevant subset per task.
Should I generate tools automatically from my OpenAPI spec?
Not as a finished surface. Generated tools are endpoint-shaped, forcing the model to do orchestration that belongs in code, and generated descriptions are written for human developers rather than a model choosing between options. Generation is a reasonable starting inventory, but the surface needs redesigning around user intents before production.
What is the difference between a tool description and documentation?
Documentation explains how something works to a developer who can read the source, ask a colleague, and experiment. A tool description is the complete and only instruction a model receives, read fresh every turn, with no way to ask a follow-up. It must state when to use the tool, when not to, which sibling it is most confused with, and what every optional parameter defaults to.
How do I stop an agent from calling a tool repeatedly in a loop?
Fix the error messages first, the cause in the large majority of cases. An agent loops when the failure gives it no basis for doing anything different, so it repeats the identical call. An error naming the invalid parameter and showing a valid example changes the next call. If loops persist, add a call-count limit per tool per run as a backstop, not as the primary fix.
Do tools need to be versioned?
Yes, and the description is part of the versioned contract, not just the schema. Additive changes such as a new optional parameter with a documented default ship safely in place. Breaking changes need both versions running under different names, the old one deprecated, until traffic drains. Changing a description alters model behavior even with the schema untouched, so it needs the same review and evaluation run.