Design tools for the model, not for the API.
Wrapping every endpoint as a tool hands an agent an interface built for programmers. We design tools around intent, honest errors, and visible side effects instead.
The fastest way to give an agent tools is to wrap an existing API. Every endpoint becomes a function, every parameter becomes an argument, and the agent can, in principle, do anything the API can do.
In practice, it does a lot of things badly. The API was designed for a programmer who reads the docs once, writes code against them, and tests that code before it runs. A model reads the tool definitions every time, decides in the moment, and acts on the first plausible reading. An interface that is fine for code becomes a trap for an agent. We treat the tool surface as its own layer, designed for the caller that actually uses it.
One tool, one intent
An API is organized around resources. An agent thinks in tasks. When the two are mapped one to one, the model has to compose several low-level calls to do one thing it understands, and every extra step is a chance to pick the wrong one or stop halfway.
Our rule is that a tool should match an intent a person would name. "Find the open invoices for this account" is a tool. Four calls to list, filter, page, and join are an implementation detail the tool hides. Fewer, clearly distinct tools also shrink the space of wrong choices.
The description is the prompt
A tool's name, description, and argument docs are not metadata. They are instructions the model reads before every decision, and they shape behavior as much as anything in the system prompt.
So we write them the way we write instructions. Say when to use the tool and when not to. Name the tool it is most likely to be confused with and say how they differ. Describe arguments in terms of what the caller knows, not what the backend stores, so the model passes a customer name or an exact phrase instead of guessing at an internal identifier. When an agent misuses a tool, the first fix we reach for is the description, not more rules elsewhere.
Errors should say what to do next
A stack trace or a bare status code tells a model that something went wrong and nothing about how to recover. The usual result is a blind retry or a confident guess.
We design errors as guidance. A good error names what failed, why, and what a valid next step looks like: the argument that was out of range and the accepted range, the record that was not found and the search tool that can find it, the permission that was missing and the fact that the agent should stop and report rather than try another route. That last case matters. An error that reads like a puzzle invites the model to solve it. An error that says the action is not allowed, and that the answer is final, gets reported.
Side effects belong in the signature
The most important property of a tool is whether it changes anything outside the system. Reading, searching, and drafting are reversible. Sending, posting, purchasing, and deleting are not.
We keep that distinction structural instead of leaving it to the description. Read tools and write tools are separate tools, never one tool with a mode flag. Tools that act on the outside world are marked as such, which lets the harness attach approval gates to the action itself rather than hoping the model remembers to ask. Where we can, a write tool has a draft or dry-run counterpart, so the agent can prepare the exact change and show it before anything irreversible happens.
Make retries safe
Agents retry. A call times out, the connection drops, or a long-running task resumes after an interruption and cannot tell whether its last step landed. If repeating a call can create a second order or a second message, the system will eventually do exactly that.
We design write tools to be idempotent where the underlying system allows it, with a caller-supplied key so that a repeated request is recognized rather than replayed. Where that is not possible, the tool exposes a cheap way to check whether the action already happened, and its description tells the agent to check before trying again.
Return less, and say where it came from
Tool output goes straight into the model's context, where it competes with everything else for attention. A tool that returns an entire record dump, or a full web page, buries the part that matters and spends context the agent will need later.
We shape output for the next decision: the fields the task needs, a clear signal when results were truncated, and a reference the agent can follow for the rest. Output is also labeled by origin. Content that came from outside, such as a fetched page, an inbound email, or a shared file, is marked as untrusted data, so the model reasons about it instead of taking direction from it.
Test tools with the model that uses them
A tool can pass every unit test and still fail in use, because the failure is in how a model reads it. So we evaluate tools the way they are actually exercised: realistic tasks, run end to end by an agent, checked for whether it picked the right tool, passed sensible arguments, and recovered from errors. Those runs become regression tests. When we change a description, add a tool, or route the work to a different model, we run them again before we trust the change.
The tool surface is where an agent's reasoning becomes action. Models change, and the next one will read the same definitions differently. Tools that are narrow, honest about failure, and explicit about side effects hold up across that change. That is why we build them as part of the operating layer, not as a thin wrapper around someone else's API.