Two tools named search_users and find_user, both plausible for the same request, and the agent picks the wrong one. A limit parameter with no stated bound, and the agent passes 10000 because nothing said it maxes out at 100. A destructive tool with a name that sounds read-only. None of these are model failures in the sense people usually mean it — they're description failures. The model chose correctly given what it was told; what it was told was ambiguous, incomplete, or misleading. Fixing the wording and schema fixes the behavior far more reliably than switching models or adding a bigger system prompt disclaiming the ambiguity.
When two tools could plausibly handle a request and nothing distinguishes them, the agent doesn't average or hedge — it picks one, consistently, based on whatever signal is available (name similarity, order in the list, a keyword overlap with the request). That means an ambiguous pair of tools doesn't cause occasional confusion; it causes the same wrong choice on every session that hits the ambiguous case, until someone notices the pattern in logs. Treat any two tools that could both plausibly apply to a real request as a description bug to fix immediately, not a rare edge case to tolerate.
| Weak pattern | Stronger equivalent |
|---|---|
| two tools with overlapping purpose and no stated distinction ("search_users" vs. "find_user") | descriptions that state the distinguishing condition explicitly: "use this when you have an exact ID; use the other when searching by partial name" |
| a numeric parameter described only in prose ("a reasonable page size") | an actual schema bound (min/max, default) the caller can't misread as a suggestion |
| a destructive or irreversible action with a neutral-sounding name | a name and description that state the side effect up front: "permanently deletes — cannot be undone" |
| documenting what a tool does but not when it should not be called | an explicit negative case: preconditions, states where the call will fail or misbehave |
| assuming the parameter names are self-explanatory to a model with no domain context | a short description per parameter, especially for anything domain-specific (status codes, enum values, unit of a number) |
A tool description that says what a tool does but not what it assumes going in, or what it changes on the way out, forces the agent to guess at both. If a tool requires another tool to have run first (an id from a prior lookup, a session established earlier), say so — otherwise the agent will call it standalone and get a confusing error, then have to reconstruct the dependency by trial and error. If a tool has a side effect beyond its obvious purpose (writes a log entry, sends a notification, invalidates a cache), say that too — an agent that doesn't know a call is irreversible will use it as casually as a read, and an agent that doesn't know a call has a side effect beyond its return value may call it more than once believing it's idempotent when it isn't.
A constraint stated only in a description ("must be a valid ISO date," "should be under 100 items") is a request the model can misread or ignore under pressure from the rest of the prompt. The same constraint enforced by the schema — an enum instead of free text, a maximum in the type definition, a required field instead of an optional one described as "usually needed" — can't be misread, because the call fails validation before it ever reaches the tool. Prose is for explaining why a constraint exists or how to work within it; the schema should be the thing that actually prevents violating it.
The test to apply to any tool an agent keeps misusing: read only the name, description, and parameter schema — nothing else — and ask what a reasonable caller with no other context would conclude about when to use it, what it assumes, and what it changes. If that reading doesn't rule out the wrong call, the fix is the description, not a retry, a bigger model, or a scolding line in the system prompt.
A correctly-described tool still returns output the caller has to trust — see validating and repairing agent structured output for what happens after the right tool gets called with the right arguments. And a well-described tool can still fail at runtime — see designing tool error messages agents can actually recover from for what the response should contain when it does. A tool description can also stay accurate on its own terms while the API it wraps quietly changes shape underneath it — see tool contract drift for what happens when nobody edited the tool, but the ground moved anyway. And the description itself is the first place a planned removal should show up — see deprecating a tool without breaking sessions already mid-task for the staged process that starts with a deprecation notice right here, before anything stops working. A well-described tool can also just return too much or too little on a given call — see sizing tool output for agents for the separate decision of how much a response should contain in the first place.