Designing Tools Agents Can Actually Use
2 August 2026
Most agent failures I debug are not reasoning failures. They are interface failures: a tool returned something unusable, and the model did something reasonable with bad information. The fix is almost never a better prompt. It is a better tool.
Error messages are prompts
When a tool fails, its error message goes straight into the model’s context and becomes an instruction about what to do next. This makes error text part of your prompt engineering whether you designed it that way or not.
Compare:
Error: invalid argument
against:
Error: 'timeout' must be an integer number of milliseconds (got "30s").
Example: {"timeout": 30000}
The first produces a retry loop — the model has no information, so it guesses, fails, and guesses again. The second produces a correct call on the next attempt. Same failure, different outcome, and the difference is entirely in your string.
The rule: every error should say what was wrong, what was expected, and ideally show a correct example. Write them for a competent colleague who cannot see your source code.
Don’t summarize what the caller must verify
There is a class of tool that helpfully condenses its output — a wrapper that turns a diff into “files are similar”, or truncates a log to the first few lines. This is actively dangerous, because the caller cannot tell the difference between “no differences” and “differences the wrapper decided not to show”.
I have been burned by exactly this: a token-saving wrapper that reported two differing files as identical. Every downstream conclusion was wrong, and nothing in the transcript revealed it. If a tool’s output will be used to verify something, it must not editorialize. Summarize for reading; never summarize for verification.
Granularity: fewer, fatter tools
The instinct is to expose many small tools, one per operation. In practice this inflates the schema, burns context on definitions that are never called, and forces the model to chain calls where one would do.
Prefer tools that match a task, not an API endpoint. search_issues(query, status, assignee) beats six separate filter tools. The model should express
intent once, not assemble it from primitives. When you find yourself writing
prompt instructions explaining which combination of tools to call in which
order, that ordering wants to be a single tool.
Make repeat calls safe
Agents retry. They retry on timeouts, on ambiguous errors, after compaction loses track of what already happened. Any tool with side effects should be safe to call twice — take an idempotency key, or be explicit and queryable about what already exists.
The corollary: destructive operations should require something the model cannot supply by accident. A confirmation flag that the model can set itself is not a safeguard; it is a speed bump the model will drive over while being helpful.
Say what happened, not what you attempted
Return the observed result, not the intent. “Deployed to production” when the deploy was queued is a lie that surfaces three steps later as an inexplicable failure. “Queued deploy job 4471; status: pending” is honest and lets the model poll. Tools that overstate success are the single most reliable way to make an agent confidently wrong.
The test
Read your tool’s output as if you were the model: no source access, no memory of the last call, a limited budget of attention. Is it obvious what happened? Is it obvious what to do next? If not, that is where your agent’s next failure comes from — not from the model.