Tool design is API design under uncertainty
A tool schema is an API. Everything we know about API design — clear contracts, good errors, least surprise — still applies. What changes is the caller: it's probabilistic. A tool is the contract between a deterministic system and a non-deterministic agent, and that one shift rewrites the priorities. Ambiguity that a human developer would resolve by reading your source becomes, for an agent, a wrong call at runtime, sampled thousands of times a day.
Start with documentation, because the economics inverted. Human developers skim docs once and internalize them; an LLM re-reads your tool descriptions on every single call — they're injected into the context, at 100–300 tokens per tool, every time. The description isn't supplementary material; it's the only documentation the caller ever sees, and it's load-bearing. Anthropic's "intern test" is the right bar: could a competent newcomer, given only the description, use the tool correctly? The highest-leverage sentence in most descriptions is the one teams never write — the when-NOT-to-use clause that draws the selection boundary against a neighboring tool.
Ambiguity compiles to wrong calls
With a probabilistic caller, every underspecified corner of the schema becomes a failure distribution. The taxonomy is well documented: parameter key errors (hallucinated argument names), value errors (out-of-range, invalid), type mismatches. The mitigations are classic API discipline turned up a notch. Name parameters unambiguously — user_id, not user, because "user" invites a name, an email, or an object. Use enumerated values instead of free strings wherever the domain is closed. Apply poka-yoke thinking: design arguments so the wrong call is hard to express. Default fail-closed. Then validate before execution and enforce the schema strictly — treat the model's output as untrusted input, the same way you'd treat any external caller's.
Errors deserve the same inversion. A human reads a stack trace and reasons about your internals; an agent reads your error string and decides its next action from it alone. So the error message must teach recovery: what was wrong, what a valid call looks like, whether retrying can help. The anti-patterns are all failures of that duty — silent failures that return success-shaped nothing, opaque codes like ERR_4012, raw stack-trace dumps. What works is an error taxonomy the agent can branch on: validation, auth, not-found, transient, system. "Transient" tells the agent to retry; "validation" tells it to fix the call; "auth" tells it to stop and escalate. That's control flow you're shipping inside a string.
I now make that control flow explicit in a typed failure envelope. Retryability, effect status, failed precondition, safe correction, and escalation destination should be data, not implications buried in prose. This lets the harness enforce the next transition and keeps uncertain side effects out of blind retry loops. Tool errors must be typed for the agent because recovery is part of the tool contract, not an operator-only concern.
Least surprise, finally, becomes a budget problem. Tool selection accuracy degrades measurably as the catalog grows — there's a soft ceiling somewhere under twenty functions before confusion sets in — so fewer, broader tools beat many fine-grained ones, and consolidation beats 1-to-1 mapping of your API endpoints. (Beware the opposite ditch: the god-object tool that does everything through a mode parameter.) At real scale, retrieval takes over — deferred loading and server-side tool search can serve catalogs of up to 10,000 tools while cutting context by 85%. Context economy is the unifying constraint that classic API design never had: every tool you expose taxes every decision the agent makes, whether or not that tool is used. This is harness engineering, not prompt polish.
The honest caveat: strict schema enforcement has genuinely absorbed part of this problem. With strict mode, structurally invalid calls essentially disappear, and some poka-yoke effort is now redundant. But strict mode guarantees shape, not sense — the model can still pick the wrong tool, or pass a perfectly valid, perfectly wrong value. The syntax is solved; the semantics are yours. Design for the caller you actually have: brilliant, literal, and guessing.