← Back to the journal

Design Tool Contracts an Agent Can Use Correctly

Make tools discoverable, narrow, and recoverable with explicit schemas, authorization boundaries, useful results, and honest failure semantics.

A tool named update_record accepts an arbitrary object and returns “OK.” It looks flexible, but every caller must infer what can change, which account owns the record, whether the operation completed, and how to recover if the request times out.

An agent needs a contract that answers those questions before execution. Good tool design makes the correct action easier to describe and the wrong action harder to submit. It also gives application code something precise to validate.

Anthropic’s tool-design guidance emphasizes selecting useful tools, clear descriptions, and results that help an agent perform its task. The design below applies those concerns to a concrete publishing workflow. Writing effective tools for agents

Organize around decisions the user understands

Start with the tasks, not a one-to-one wrapper around every database table. An editorial assistant might need to search articles, read an immutable revision, prepare an update, and submit that update for review.

Those operations correspond to meaningful steps. A generic SQL tool exposes implementation details and a much broader capability. Conversely, a single manage_publication tool hides several distinct consequences behind one name.

Use names that convey the effect. read_article_revision is clearer than article_helper. prepare_article_update should actually prepare a proposal; it should not publish because a loosely documented boolean happened to default to true.

Separate scope from model arguments

Derive tenant, user identity, and permissions from authenticated application context. The model may supply an article identifier, but the executor must resolve that article inside the allowed account.

Avoid accepting an is_admin flag or a model-chosen credential. A valid schema cannot make those fields trustworthy. For cross-account administrative work, expose an explicitly authorized route with its own scope rather than silently widening the everyday tool.

Distinguish parameter validation from resource authorization. A well-formed article identifier can still name an inaccessible article. Check both before returning content or making a change.

Define a small input vocabulary

For an update proposal, require the article identifier, expected revision, replacement title, and replacement body. Reject unknown properties so a misspelled field cannot appear accepted while being ignored.

JSON Schema provides object-property definitions, required-property lists, and controls for additional properties. Those features describe input shape; the executor still needs business and permission checks. JSON Schema object reference

The following complete TypeScript function validates a search tool’s input without dependencies. It illustrates a narrow contract, not a search implementation. It was statically typechecked, not executed.

export type SearchInput = Readonly<{ query: string; limit: number }>;

export function parseSearchInput(value: unknown): SearchInput {
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error('Search input must be an object');
  }
  const input = value as Record<string, unknown>;
  const keys = Object.keys(input);
  if (keys.length !== 2 || !Object.hasOwn(input, 'query') ||
      !Object.hasOwn(input, 'limit')) {
    throw new Error('Expected exactly query and limit');
  }
  if (typeof input.query !== 'string' || !input.query.trim() ||
      input.query.length > 500) {
    throw new Error('Query must contain 1–500 UTF-16 code units');
  }
  if (typeof input.limit !== 'number' || !Number.isInteger(input.limit) ||
      input.limit < 1 || input.limit > 20) {
    throw new Error('Limit must be an integer from 1 through 20');
  }
  return Object.freeze({ query: input.query, limit: input.limit });
}

These limits are example product choices, not measured optimal values. Validate request size before parsing too. TypeScript’s static types do not inspect incoming JSON at runtime; the checks above do.

Return evidence the next step can use

A search response should include stable article and revision identifiers, a title, a relevant excerpt, and a reference for fetching the complete permitted revision. If results are truncated, say so and provide an explicit continuation mechanism.

Do not return a huge document dump when the next decision only needs to choose a source. Equally, avoid summaries that erase the exact identifiers required for a later tool call. The useful result size depends on the next task.

For writes, return a durable operation identifier and an honest state such as accepted, completed, or awaiting review. “Success” is ambiguous when the operation only entered a queue. Include a resource version or receipt when the caller will need to reconcile or continue.

Make failures actionable without granting new power

Separate invalid input, unavailable resource, denied access, stale version, temporary capacity failure, and unknown remote outcome. The agent should know whether correcting an argument, asking the user, waiting, or stopping is appropriate.

Return enough detail to explain the failure without exposing inaccessible records or credentials. Preserve the original diagnostic in controlled logs when the public message must be narrower. Do not convert every exception into an empty list; “no matches” and “search service unavailable” demand different behavior.

A stale version should cause a reread and a new proposal. It should not trigger an automatic overwrite that defeats the review boundary. An uncertain write should follow the retry and reconciliation contract.

Document effects and recovery beside the schema

For each tool, maintain a short contract covering whether it reads or writes, its authorization scope, idempotency behavior, expected result states, cancellation semantics, and error categories.

Keep the description and implementation together in review. A tool that recently gained an external side effect needs a different evaluation and often different permissions, even if its arguments did not change.

Use versioning when semantics change incompatibly. A stable name with silently changed defaults can invalidate prompts and recorded workflows. Historical traces should identify which contract the agent used.

Review the complete interaction

Walk through a real task: search, select a source, read it, propose an update, review, and execute. Check whether each result contains what the next step requires and whether a caller can bypass review through another tool.

The best contract is not necessarily the shortest schema. It is the smallest complete interface that makes a task understandable, constrains its effects, and preserves enough evidence to recover when execution does not go as expected.

← Explore the journal