Designing an Agent's Tools
Updated June 2026
The tools you give an agent shape its behavior more than the prompt does. A few principles that keep coming up.
Start with bash, promote deliberately
A bash tool gives an agent enormous breadth: it can do almost anything. But it gives you almost nothing, because every action arrives as an opaque command string your harness can’t distinguish from any other. The art is knowing when to promote an action to a dedicated tool with typed arguments. Promote when you need to:
- Gate it. Hard-to-reverse actions (sending messages, deleting data, external API calls) want a confirmation step. A
send_emailtool is easy to intercept and confirm;bash -c "curl -X POST ..."is not. - Enforce an invariant. A dedicated
edittool can refuse to write a file that changed since the agent last read it. Bash can’t enforce that. - Parallelize it. Read-only tools can be marked safe to run concurrently. When everything goes through bash, the harness can’t tell a harmless
grepfrom agit push, so it has to serialize everything. - Render it. Some actions deserve real UI. Promoting “ask the user a question” to a tool lets it render as a form instead of a paragraph.
Rule of thumb: bash for breadth, dedicated tools wherever you need to gate, audit, render, or parallelize.
The description is the most important field
The model decides whether to call a tool by reading its description, and the most common mistake is describing what the tool does without saying when to use it. “Searches the product database” tells the model what happens on invocation; “Call this whenever the user asks about price, stock, or availability; do not answer those from memory” tells it when to invoke. Trigger conditions in the description measurably improve call rates, especially on models that are conservative about reaching for tools.
The same applies in reverse: if a tool is being over-called, the fix is usually narrowing its description, not adding system prompt warnings.
Fewer tools, sharper edges
Every tool definition occupies context on every request, and overlapping tools create genuine ambiguity: given search_docs, query_knowledge_base, and find_articles, the model has to guess, and it will guess differently on different days. Merge tools that do the same thing. If the catalog is legitimately large, load definitions on demand (tool search patterns) rather than carrying all of them everywhere.
Errors are model-facing, too
A tool that fails with Error: code 500 teaches the model nothing. A tool that fails with “rate limited, retry after 30 seconds” or “file not found; did you mean one of these three?” lets the agent adapt instead of flailing or giving up. Write error messages for the model the way you’d write them for a junior teammate: state what went wrong and what to try instead.
Keep secrets out of the loop
The agent’s context (prompts, messages, tool results) gets persisted, logged, and replayed. Don’t put API keys in any of it. The pattern that works: the agent calls a tool, your code executes the authenticated request with credentials it already holds, and only the result enters the conversation. The model never needs to see a secret to use it, and what the model never sees, a prompt injection can never exfiltrate.