---
name: "tool-design-for-agents"
description: "Design tools, functions, CLIs, and API endpoints that AI agents call reliably. Use when defining a new tool schema, reviewing one that agents misuse, or adding safety (dry-run, idempotency, undo) to an existing tool."
---

# Tool Design for Agents

## Purpose

A model calling your tool has no intuition about it. Everything it can rely on is in the schema, the description, the error messages, and the output shape. This skill turns those four surfaces into a tool an agent can actually drive: narrow enough to route correctly, descriptive enough to call correctly, and forgiving enough to recover from mistakes.

## Workflow

### 1. Inventory operations, one tool per action

List every operation the toolset must support. Then split until each tool does one thing:

- One verb per tool: `search_invoices`, not `invoice_manager`. Agents route by name, and a kitchen-sink name forces them to guess which mode they are in.
- Do not expose every backend endpoint. If two operations are always called back to back with no decision between them, merge them. If an operation exists only for humans, leave it out.
- Aim for 4-5 tools per domain. More tools means more routing mistakes, not more power.

### 2. Write the schema like a micro-prompt

The description carries as much weight as the parameter list. Treat it like a system prompt for this one tool:

- Name it `verb_noun`. Say what it returns, not just what it does: "returns the matching rows, or an empty list" beats "queries the database".
- Give every parameter a description with an example value and the failure mode: `"status": filter by status. One of: open, paid, overdue. Anything else returns an error listing the valid values.`
- Prefer enums over free strings, explicit required lists over guessing, and small bounded inputs: `limit` with a default and a max, not unbounded lists.
- Keep outputs token-lean: paginate lists (`limit`/`cursor`), summarize instead of dumping, return references (ids, paths) the agent can fetch later.

### 3. Design the error contract before the happy path

Agents recover from errors constantly; your error message is their debugger. Every failure returns a structured error with these fields:

- `code`: machine-readable, names the category (`invalid_enum`, `not_found`, `conflict`, `rate_limited`, `needs_approval`).
- `message`: plain-language, states what was wrong and what to do next.
- `retryable`: true or false. The agent decides whether to retry based on this, so get it right: `rate_limited` is retryable, `invalid_enum` is not.
- `details`: the missing piece. Invalid enum? List the valid values. Not found? Say what was searched and where. The fix belongs inside the error.

Rules: never return a bare `"Error: something went wrong"`. Never make the agent guess from HTTP codes alone. For duplicates, return the semantically equivalent success (see step 5), not a conflict error the agent cannot resolve.

A ready-to-use error catalog template is in `references/error-catalog-template.md`.

### 4. Classify side effects, then gate them

Sort every mutating tool into one of three tiers and treat each tier differently:

- **Read-only**: no gating. It should be obviously safe from the name and description.
- **Reversible writes** (drafts, config changes, non-published edits): run them, but return an `undo` handle in the result: the exact inverse call or an id that reverts it.
- **Irreversible writes** (sending, charging, deleting, publishing): require an explicit confirmation signal. The pattern that works: a `dry_run` flag that returns the full plan (what would change, conflicts found, cost/impact estimate, a `preview_id`), then an `apply` step that commits the exact preview. Previews expire and log the actor and inputs. Never apply a plan the agent has not seen.

### 5. Make retries safe: idempotency

Agents retry. Networks drop. The tool must make "call it again" mean "finish it once":

- Every mutating tool accepts an `idempotency_key` (client-generated, one per logical operation). Same key + same inputs returns the stored result. Same key + different inputs is rejected.
- Key insight from the AWS builders library: a duplicate should return the semantically equivalent success, e.g. `{"status": "already_done", "id": "inv_123"}`, not an error the agent has to interpret.
- Keep a durable key-to-result map with a TTL (24 hours is a common default). The worked example in `references/worked-example.py` shows a minimal in-memory version with tests.

### 6. Test like the agent will use it

Before shipping, run this gauntlet against the real implementation:

1. **Schema test**: assert the generated schema has the right name, parameters, and required fields.
2. **Misroute test**: give the tool a deliberately ambiguous prompt and check the description alone is enough to route correctly.
3. **Error recovery test**: feed an invalid enum, a missing id, and a duplicate call; confirm each error contains a usable next step.
4. **Retry test**: call a mutating tool twice with the same idempotency key; confirm exactly one effect and two equivalent responses.
5. **Dry-run test**: run `dry_run` and confirm nothing changed, then apply and confirm the change matches the preview exactly.

## Output Contract

For each tool you design, deliver:

- **Tool spec**: name, one-line description, full JSON schema for inputs, and the error catalog (which codes it can return and what each means).
- **Safety spec**: side-effect tier (read-only / reversible / irreversible), dry-run or preview behavior, idempotency key policy, undo handle if reversible.
- **Worked examples**: at least one successful call and one error-recovery call, written exactly as the agent would make them.
- **Checklist sign-off**: every item in `references/checklist.md` checked, with the failing items fixed or the tool redesigned.

## Operating Rules

1. Never ship a schema the agent has not called in a dry run. A schema you have not tested is a guess.
2. Every factual claim about a tool's behavior (limits, TTLs, defaults) must be true of the implementation. If the implementation changes, the description changes with it.
3. One tool, one job. When a tool grows a second verb, split it.
4. Descriptions are load-bearing code. Review them with the same care as logic.
5. When an agent misuses a tool twice the same way, the tool is wrong, not the agent. Fix the schema or the error message.
6. No destructive test runs against production state. The test gauntlet runs against mocks or throwaway fixtures.
