Skip to main content

Designing Robust Tool Interfaces

Introduction

In L121 (Tools) you learned the tool-use lifecycle — the model requests a tool, your code runs it, the result is fed back. That's the mechanism. This lesson is about the craft: how to design a tool so the model uses it correctly, every time.

Here's the uncomfortable truth most people learn the hard way: when an agent misuses a tool, the tool is almost always at fault, not the model. A vague description, a free-string parameter that should be an enum, a required field the model can't fill, a UUID-soup return value — each is an interface defect that shows up as the model "being dumb." Tighten the interface and the same model becomes reliable.

Anthropic, building their SWE-bench agent, reported spending more time optimizing their tools than the overall prompt — and that "tool definitions and specifications should be given just as much prompt-engineering attention as your overall prompts."

This lesson is the six pillars of a robust tool interface:

  1. The description — the prompt living inside the tool
  2. Parameters & schema — names, types, enums, required vs optional
  3. Strict mode vs. tool-use examples — structural validity vs. usage
  4. Poka-yoke — make mistakes impossible
  5. The return value — design what comes back, not just the call
  6. Consolidate & namespace — fewer, higher-level, well-named tools

Scope: this is the interface design lesson. Running tools in parallel is L123 (Parallel & Sequential Tool Calls), the code-execution sandbox is L124 (Code Execution & Sandboxing), and the error-handling / retry deep dive is L125 (Handling Tool Errors & Retries) — we'll touch error messages here (they're part of the contract) but leave retry loops to L125 (Handling Tool Errors & Retries).

An infographic titled 'Designing Robust Tool Interfaces'. The big idea: a tool's interface is UX for the model — the agent is a new kind of end user (the Agent-Computer Interface idea from SWE-agent, NeurIPS 2024, which beat a raw shell by 10.7 points just by redesigning the interface). Tool definitions deserve as much prompt-engineering attention as the prompt itself; Anthropic reported spending more time optimizing tools than the overall prompt. THE SIX PILLARS of a robust tool interface. PILLAR 1 — THE DESCRIPTION: write it like onboarding a new teammate; make implicit context explicit (query formats, niche terminology, how resources relate) and be prescriptive about WHEN to call; even small refinements to descriptions yield dramatic improvements. PILLAR 2 — PARAMETERS & SCHEMA: name parameters unambiguously (user_id not user), use precise types and enums for fixed sets, and distinguish required from optional — force a required field that may be absent and the model hallucinates it; make it optional and the model omits it cleanly; keep nesting shallow. PILLAR 3 — STRICT MODE versus TOOL-USE EXAMPLES: strict true guarantees the call matches your schema structurally (additionalProperties false plus required), but JSON Schema cannot express usage patterns — when to include an optional field, which combinations make sense, or format and ID conventions; tool-use examples (concrete sample calls) teach exactly that and lifted accuracy from 72 to 90 percent on complex parameter handling. PILLAR 4 — POKA-YOKE: change the arguments so mistakes are impossible — Anthropic made a file tool require absolute paths after relative paths kept breaking, and the model then used it flawlessly; make invalid states unrepresentable. PILLAR 5 — DESIGN THE RETURN, NOT JUST THE CALL: return high-signal results — semantic, natural-language identifiers instead of opaque UUIDs (which significantly improves precision), the fields that drive the next action (name, not mime_type), an optional response_format of concise versus detailed for roughly 3-to-1 token savings, and pagination, filtering, or truncation with sensible defaults (Claude Code caps tool responses at 25,000 tokens). PILLAR 6 — CONSOLIDATE AND NAMESPACE: prefer fewer, higher-level tools that do the whole job (schedule_event over list_users plus list_events plus create_event; get_customer_context over three calls) and namespace related tools with prefixes (asana_search) so the model picks the right one. Also: ACTIONABLE ERRORS are part of the interface — not 'Invalid input' but 'Query returned 500+ results; add a filter or paginate with limit=50' — steer the model toward correct usage. And EVALUATE AND ITERATE: build realistic multi-step eval tasks, measure accuracy, tool-call count, tokens, and errors, then have the model itself analyze the transcripts and refine the tools. Takeaway: the model is your user and the tool spec is your UX — clear descriptions, tight typed schemas, strict mode plus an example, poka-yoke formats, high-signal returns, few high-level namespaced tools, and helpful errors; spend real effort on the interface, not just the prompt.

The Mental Shift: The Model Is Your User

Before any tactic, internalize the frame that makes all of them obvious: a tool interface is UX — and the user is the model.

This is the Agent-Computer Interface (ACI) idea from SWE-agent (Princeton, Yao et al., NeurIPS 2024). Their thesis, verbatim in spirit: "language-model agents represent a new category of end users with their own needs and abilities, and would benefit from specially-built interfaces to the software they use." They didn't build a smarter model — they redesigned the interface the model uses to read, edit, and navigate code, and it solved 10.7 percentage points more SWE-bench issues than the same model driving a raw Linux shell.

That reframes everything:

  • A confusing parameter name isn't a model failure — it's bad labeling on your UI.
  • A tool that returns 5,000 lines of logs isn't thorough — it buries the signal your user needs.
  • An error that says "Invalid input" is a dead-end dialog box with no recovery path.

You wouldn't ship a human-facing form with a field labeled x and no validation. Don't ship one to the model either. Design the tool the way you'd design a great UI — for a brilliant, literal, non-deterministic user who reads everything you wrote and nothing you didn't.

Pillar 1 — The Description (the Prompt Inside the Tool)

The description is the highest-leverage field on a tool — it's a prompt, and the only thing the model knows about what your function does and when to reach for it. Anthropic's rule: write it "as you would explain it to a new team member," and "make implicit context explicit" — the query formats you assume, the niche terminology, how your resources relate to each other.

# ❌ WEAK — the model has to guess what this does and when to use it
{"name": "search", "description": "search the database", "input_schema": {...}}

# ✅ STRONG — a description written like onboarding a new teammate
{"name": "search_customer_orders",
 "description": (
   "Search a customer's orders by date range, status, or amount. "
   "Call this when the user asks about a customer's order history or a "
   "specific past order. Returns order id, items, shipping, and payment "
   "status. Dates are ISO-8601 (YYYY-MM-DD). Does NOT create or modify orders."
 ),
 "input_schema": {...}}
# Make implicit context EXPLICIT: formats, niche terms, how resources relate,
# and — critically — WHEN to call it. Small description refinements yield big gains.

Three things separate a great description from a weak one:

  • What it does and the shape of what it returns (so the model knows what it'll get back).
  • When to call it — prescriptive triggers ("Call this when the user asks about a customer's order history"). Recent models reach for tools conservatively; an explicit trigger measurably raises the should-call rate.
  • What it does not do — boundaries prevent misuse ("Does NOT create or modify orders").

How much does this matter? Anthropic credits precise description refinements with helping Claude reach state-of-the-art on SWE-bench"even small refinements to tool descriptions can yield dramatic improvements." This is prompt engineering; treat it as such.

Pillar 2 — Parameters & Schema Design

The input schema is where most tool calls go wrong. Three rules carry almost all the weight:

1. Name parameters unambiguously. Anthropic's own example: "Instead of a parameter named user, try a parameter named user_id." The name tells the model what kind of value to produce — start_iso8601 invites a different answer than when.

2. Constrain with types and enums. A free-string priority invites "urgent", "P0", "ASAP" — pick one with enum: ["low","normal","high"] and the model can only choose a valid value. Match the enum to words the model would naturally use, so you're not fighting it.

3. Required vs optional is a real decision, not a default. This one is subtle and bites hard:

Make a field required only if it must exist. Make it optional if the data may be absent — because forcing a required field when there's no source value is an invitation to hallucinate one.

# ❌ WEAK schema — everything is a free string, nothing is required
"input_schema": {
  "type": "object",
  "properties": {
    "attendee": {"type": "string"},     # which Dana? a name isn't addressable
    "when":     {"type": "string"},     # "next tuesday"? no timezone? unparseable
    "priority": {"type": "string"},     # the model will invent "urgent", "P0", ...
    "room_id":  {"type": "string"}}}    # optional by omission -> silently dropped

# ✅ ROBUST schema — unambiguous names, precise types, enums, required vs optional
"input_schema": {
  "type": "object",
  "properties": {
    "attendee_email": {"type": "string", "format": "email",
                       "description": "The invitee's email address."},
    "start":          {"type": "string",
                       "description": "Start time as an ISO-8601 instant, e.g. 2026-07-07T15:00:00Z."},
    "priority":       {"type": "string", "enum": ["low", "normal", "high"]},
    "room_id":        {"type": "string", "description": "Room id, e.g. RM-204."},
    "agenda":         {"type": "string", "description": "Optional one-line agenda."}},
  "required": ["attendee_email", "start", "room_id"],   # absent != hallucinated
  "additionalProperties": false}
# Rule: make a field REQUIRED only if it must exist; OPTIONAL if data may be absent.
# A required field with no source value is an invitation to hallucinate.

Two more: keep nesting shallow (2–3 levels — deep objects raise error rates and slow schema compilation), and prefer flat, primitive params over elaborate structures when you can. The schema is a contract the model fills in token by token; the simpler and more constrained it is, the less room to go wrong.

Pillar 3 — Strict Mode vs. Tool-Use Examples

Now the distinction that separates people who know tool design from people who just write schemas. There are two different kinds of "correct," and they need two different tools.

strict: true — structural validity. Add it to a tool definition and Claude's call is guaranteed to match your schema exactly: every required field present, no extra fields, every enum honored, every type right (it needs additionalProperties: false + a required list). No more parsing a call only to find a missing key.

But here's the catch Anthropic states plainly:

"JSON Schema excels at defining structure — types, required fields, allowed enums — but it can't express usage patterns: when to include optional parameters, which combinations make sense, or what conventions your API expects."

A call can be 100% schema-valid and still wrong: the right date in the wrong format, an optional field included when it shouldn't be, an ID as 12345 when your API wants USR-12345.

Tool-use examples — usage patterns. You close that gap by showing concrete example calls in the tool definition. From a few examples (minimal, then with optionals), the model learns the conventions a schema can't state:

# strict:true guarantees the call matches the schema EXACTLY (structure, types, enums).
tools = [{
  "name": "schedule_meeting",
  "description": "...",
  "strict": True,                       # validated: additionalProperties:false + required
  "input_schema": {...},
  # JSON Schema says what's STRUCTURALLY valid. It can't say what's CORRECT usage:
  # when to fill `agenda`, which date format, that priority tracks urgency. Show it:
  "input_examples": [
    {"attendee_email": "dana@acme.co", "start": "2026-07-07T15:00:00Z",
     "priority": "high", "room_id": "RM-204"},                    # minimal valid call
    {"attendee_email": "sam@acme.co",  "start": "2026-07-08T09:30:00Z",
     "priority": "normal", "room_id": "RM-101", "agenda": "Q3 planning"},  # with optional
  ]}]
# Tool-use examples teach the patterns the schema can't — Anthropic measured
# 72% -> 90% accuracy on complex parameter handling. (~20-50 tokens each; they
# work on your tools, NOT on server tools like web_search.)

The payoff is measured, not hand-wavy: Anthropic found tool-use examples "improved accuracy from 72% to 90% on complex parameter handling." They cost ~20–50 tokens for simple cases (~100–200 for complex nested ones), pay off most for many optional parameters and domain-specific conventions, and add nothing for trivial single-param tools. Note: examples work on your tools (and Anthropic-defined client tools), not on server tools like web_search.

strict guarantees the call is valid. Examples guarantee it's right. Robust tools usually want both.

Pillar 4 — Poka-Yoke: Make Mistakes Impossible

Poka-yoke (Japanese for "mistake-proofing") is the manufacturing idea of designing a part so it physically can't be assembled wrong. Apply it to tools: change the arguments so the wrong thing can't happen — don't just document the right way, make the wrong way unrepresentable.

Anthropic's canonical example, from Building Effective Agents: while building their SWE-bench agent, the model kept making mistakes with relative file paths once it had moved out of the root directory. The fix wasn't a sterner prompt:

"We changed the tool to always require absolute filepaths — and we found that the model used this method flawlessly."

The relative-vs-absolute ambiguity was designed out of existence. Generalize the move:

  • Require absolute paths / fully-qualified ids, never relative/ambiguous ones.
  • Require an ISO-8601 instant (with timezone), not a free-form date string.
  • Make illegal combinations unrepresentable — e.g. a single target enum instead of three booleans where two-true is invalid.
  • Split a dangerous overloaded tool so the destructive path needs an explicit, separate argument.

When you catch yourself writing "the model should pass X" in a description, ask: can I make it so the model can't pass anything but X? That's poka-yoke — and it's why Anthropic spent more time on the tools than the prompt.

See It — The Tool Spec Clinic

Time to feel pillars 1–4 at once. Below is a single tool — schedule_meeting — with a weak spec, and the model's resulting call is broken in five ways. Toggle the five fixes and watch each defect flip from red to green and the robustness meter climb:

The Tool Spec Clinic. One tool — schedule_meeting — starts with a WEAK spec, and the model's tool_use call is riddled with five defects: a display name where an address is needed, the invented value 'urgent', the unparseable date 'next tuesday', a silently-dropped required room, and a guessed optional field. Toggle five interface FIXES — clear param names, an enum, a poka-yoke ISO-8601 format, required-vs-optional, and a tool-use example — and watch the spec tighten, each red defect flip to green, and the robustness meter climb from ~48% to ~98%. The point lands physically: the model isn't broken, the interface is — and strict:true buys structural validity but only the example + enums + clear names + poka-yoke format buy correct usage.

Play with the order and notice:

  • No single fix is enough — clear names fix the attendee, but the date is still garbage until you poka-yoke the format; the enum kills "urgent"; required stops the room from vanishing; the example is what finally teaches when to include the optional agenda.
  • It's the same model the whole time. Nothing about its intelligence changed — only the interface. The meter is really measuring your spec, not the model.
  • strict: true (appears when you mark required) guarantees the call is valid — but it's the example + enum + names + format that make it right. Validity ≠ correctness.

Pillar 5 — Design the Return, Not Just the Call

Half of tool design is the return value — and it's the half beginners skip. Whatever the tool sends back lands in the model's context and becomes the basis for its next action. Two principles:

1. Return high-signal, semantic data — not raw system internals. Anthropic: "merely resolving arbitrary alphanumeric UUIDs to more semantically meaningful and interpretable language significantly improves Claude's precision." Return name, email, file_type — the fields that drive the next decision — not uuid, mime_type, 256px_image_url.

# ❌ The model has to decode this and can't act on it
{"id": "u_8a3f21e0-...-9c", "mime": "image/png", "img": "https://.../256px/...",
 "ts": 1751900400}

# ✅ Return high-signal, action-ready fields — semantic names, not opaque UUIDs
{"customer": "Dana Lee", "email": "dana@acme.co",        # natural-language id
 "plan": "Enterprise", "open_tickets": 2, "last_order": "2026-06-30"}
# + give the model knobs to control volume:
#   response_format: "concise" | "detailed"   (~3:1 token savings)
#   page/limit/filter with sensible DEFAULTS   (Claude Code caps results at 25k tokens)
# Resolving UUIDs to meaningful language "significantly improves Claude's precision."

2. Give the model volume control — with sensible defaults. A tool that dumps 5,000 rows blows the context window and buries the answer. Build in:

  • Pagination / filtering / truncation with default parameter values, so the default call is already bounded. (Claude Code caps tool responses at 25,000 tokens by default.)
  • An optional response_format enum — "concise" vs "detailed" — letting the model trade completeness for tokens (Anthropic measured ~3:1 savings).

3. Consolidate computation into the tool. Instead of making the model call three tools and stitch the results, return an enriched object: get_customer_context that bundles profile + recent orders + open tickets in one call beats get_customer_by_id + list_orders + list_tickets. The tool does the join; the model does the reasoning.

Pillar 6 — Consolidate & Namespace

This pillar is about the tool set, not a single tool — and it follows directly from L121 (Tools)'s finding that too many (or too-similar) tools degrade selection.

Consolidate: fewer, higher-level tools. Anthropic: "more tools don't always lead to better outcomes." Build tools around tasks, not raw API endpoints:

  • schedule_event instead of list_users + list_events + create_event
  • search_logs (returns only relevant lines + context) instead of read_logs (dumps everything)
  • get_customer_context instead of three separate lookups

Each consolidation removes a multi-step dance the model would otherwise have to orchestrate (and possibly botch).

Namespace related tools. Group with consistent prefixes — asana_search, asana_projects_search, jira_search — so the model can disambiguate. Anthropic notes that even the choice between prefix- and suffix-based namespacing has "non-trivial effects on tool-use evaluations," and that the most common failure is wrong tool selection between similar names like notification-send-user vs notification-send-channel. Clear, namespaced, distinct names are how you prevent it.

Rule of thumb: if two tools are easy for you to confuse by name, they're easy for the model to confuse too. Rename, namespace, or merge.

Actionable Errors Are Part of the Interface

When a tool call fails, the error message is the model's only feedback — and a good one is a recovery instruction, not a tombstone. Compare:

# ❌ Opaque — the model has no idea what to do next
return {"error": "Invalid input: too many results", "is_error": True}

# ✅ Actionable — the error itself steers the model to the fix
return {"error": ("Query returned 500+ results. Narrow it with a filter "
                  "(date_range, status) or paginate (limit=50, offset=0)."),
        "is_error": True}
# The error message is part of the interface. (Transient failures, backoff, and
# retry LOOPS are their own topic — that's L125.)

The unhelpful version dead-ends the agent; the helpful one steers it to the fixwhich filters exist, what the pagination params are. Anthropic's guidance: errors should "clearly communicate specific and actionable improvements." Return errors as a normal tool_result with is_error: true so the model sees them and adapts.

This is the interface facet of errors — the message contract. The mechanics of transient failures (timeouts, rate limits), backoff, and retry loops are a topic of their own: that's L125 (Handling Tool Errors & Retries). Here, the point is simply that a well-worded error is part of designing the tool.

Eval-Driven Iteration (How Experts Actually Get There)

You don't intuit a great tool interface — you measure your way to it. Anthropic's loop for building agent tools, and the right one to copy:

  1. Build a prototype of the tools against real docs/data (not toy stubs).
  2. Generate realistic evaluation tasks — grounded in actual workflows, multi-step ("strong evaluation tasks might require multiple tool calls — potentially dozens"). A one-call sandbox task proves nothing.
  3. Run a simple agentic loop over the tasks and collect hard metrics: accuracy, runtime, tool-call count, token consumption, and error rate.
  4. Analyze the transcripts — with the model. Paste the runs into Claude and let it spot where tools confused it. Anthropic: "Claude is an expert at analyzing transcripts and refactoring lots of tools all at once."
  5. Refine and re-run, validating on a held-out set so you don't overfit the tools to one eval.

The meta-point: tool design is empirical. The pillars tell you where to look; evals tell you what's actually broken for your model, your tasks, your data — and let the model help you fix its own interface.

🧪 Try It Yourself

Reason through these, then use the Clinic to confirm:

  1. Predict: in the Clinic, you enable only strict: true-style required-marking. Is the call now valid? Is it correct? What's still wrong, and which fix addresses it?
  2. A tool param is date: string and the model keeps sending "next Friday". Give the poka-yoke fix (not a prompt fix).
  3. Your extraction tool marks middle_name as required, and the model keeps inventing middle names for people who have none. One-word diagnosis; one-word fix.
  4. Your tool returns rows like {"id":"u_9f2c…","mime":"image/png"} and the agent keeps mis-referencing records. Name two changes to the return value.
  5. You have notification_send_user and notification_send_channel and the model keeps picking the wrong one. What's the failure called, and what are two fixes?

(1) It's now valid (schema-guaranteed) but not correct — the date is still "next tuesday" (unparseable) and the optional agenda is still guessed. strict buys structure, not usage; you need the poka-yoke format and the tool-use example. (2) Change the argument: require an ISO-8601 instant (2026-07-07T15:00:00Z) so a free-form phrase is structurally impossible — and add an example showing the format. (3) Diagnosis: a required field that may be absent → hallucination. Fix: make it optional. (4) Return semantic identifiers ("customer":"Dana Lee", "email":…) instead of UUIDs, and return the action-relevant fields (name/email) instead of system internals (mime); resolving UUIDs to language "significantly improves precision." (5) Wrong tool selection between similar names. Fixes: namespace / rename so they're clearly distinct, add tool-use examples that disambiguate, and/or consolidate into one notification_send with a target enum.

Mental-Model Corrections

  • "The model misused my tool — it's not smart enough." Almost always the interface is at fault: a vague name, a free string that should be an enum, a required field it can't fill. Fix the spec, not the model.
  • "strict: true means the model will call the tool correctly." It guarantees the call is structurally valid — not that it's right. Format conventions, optional-inclusion, and param correlations need tool-use examples.
  • "A schema is enough documentation." JSON Schema can't express usage patterns. The description (when/why) and examples (how) carry what the schema can't.
  • "Mark every field required so the model always fills them." A required field with no real value gets hallucinated. Required = must exist; optional = may be absent.
  • "More tools = a more capable agent." Past a point, more (and more-similar) tools degrade selection. Prefer fewer, higher-level, namespaced tools.
  • "My job ends at the function call." The return value is half the interface — semantic IDs, action-relevant fields, bounded size, volume controls.
  • "Errors just need to report failure." A good error is a recovery instruction that steers the model to the fix.
  • "I'll design the perfect tool up front." You measure your way there — realistic multi-step evals, then iterate (with the model's help).

Key Takeaways

  • The model is your user; the tool spec is your UX (the Agent-Computer Interface — SWE-agent gained +10.7pp by redesigning the interface alone). Give tools as much prompt-engineering attention as the prompt.
  • Description: write it like onboarding a teammate — make implicit context explicit, say when to call and what it doesn't do. Small refinements → big gains.
  • Schema: unambiguous names (user_id), precise types + enums, required only if it must exist (else optional, or it gets hallucinated), shallow nesting.
  • Strict vs. examples: strict: true guarantees structural validity; tool-use examples teach the usage a schema can't (72%→90%). Robust tools use both.
  • Poka-yoke: make mistakes impossible — require absolute/ISO/fully-qualified values; make invalid states unrepresentable (relative→absolute paths fixed it flawlessly).
  • Design the return: semantic IDs not UUIDs, action-relevant fields, bounded size (Claude Code's 25k-token cap), a concise|detailed knob (~3:1).
  • Consolidate & namespace: fewer, higher-level, task-shaped tools; namespace similar ones to prevent wrong-tool selection. Actionable errors are part of the contract.
  • Iterate empirically: realistic multi-step evals → measure → let the model analyze transcripts and refactor → validate on held-out tasks.
  • Next — L123: Parallel & Sequential Tool Calls (now that each tool is well-designed, how the model orchestrates many of them).