Skip to content

The Tool Surface

The loop is bounded. Now: what can it reach, and in what shape?

This topic contains the most useful principle in the module, and it is one that rarely appears in discussions of agent design. It answers the question everyone asks — how granular should my tools be? — with something better than taste.

A model does not execute anything. It emits a request to call a tool, with arguments; your own code receives that request and decides what to do. Your code is where authorisation, auditing, rendering and scheduling live, and it is the only place they can live.

Which gives the principle:

The shape of the tool call determines what your code can do about it.

A single broad tool — run this command, call this endpoint, execute this query — gives the model enormous reach and gives your code an opaque string. Every action arrives in the same shape, so your code cannot tell a harmless read from a consequential write without parsing intent out of a payload. You cannot gate what you cannot distinguish.

Promoting an action to its own tool, with typed arguments, gives your code a specific hook for that action: somewhere to check a limit, require a confirmation, render a dialogue, write an audit record, or decide whether it is safe to run in parallel.

One broad tool — maximum reach, nothing your code can distinguish:

graph LR
  M["Model"] -->|"opaque<br/>command string"| H["Your code<br/><i>cannot tell a read<br/>from a refund</i>"]
  H --> X["Everything,<br/>ungated"]

  classDef m fill:#5B8FB9,stroke:#4A7A9E,stroke-width:2px,color:#ffffff
  classDef bad fill:#D9695B,stroke:#B4483A,stroke-width:2px,color:#ffffff
  class M m
  class H,X bad

Promoted tools — narrower reach, and a hook per action:

graph LR
  M2["Model"] -->|"typed call"| R["read_job<br/><i>parallel-safe</i>"]
  M2 -->|"typed call"| S["reschedule_job<br/><i>audit + notify</i>"]
  M2 -->|"typed call"| C["issue_credit<br/><i>limit + confirm</i>"]

  classDef m fill:#5B8FB9,stroke:#4A7A9E,stroke-width:2px,color:#ffffff
  classDef ok fill:#7D9A4E,stroke:#5F7A38,stroke-width:2px,color:#ffffff
  classDef gated fill:#E8A33D,stroke:#C4832A,stroke-width:2px,color:#ffffff
  class M2 m
  class R ok
  class S,C gated

Same capability, two shapes. In the first, every action looks identical to the code that must police it. In the second, each one has somewhere for a control to attach.

Four reasons to promote an action to its own tool

Section titled “Four reasons to promote an action to its own tool”

It needs gating. Reversibility is the useful criterion: anything hard to undo — money leaving, a message sent, data deleted — should be distinguishable so it can be held for confirmation. A dedicated issue_credit call is trivial to gate; the same action expressed as a generic API call buried in a command string is not.

It needs an invariant enforced. A dedicated tool can refuse. A reschedule tool can reject a change if the job has been modified since the loop last read it — a staleness check that a generic executor cannot perform, because it does not know what the call means.

It needs rendering. Some actions should appear to a human: a confirmation dialogue, a diff, a choice. Promoting “ask the person” to a tool in its own right is a useful trick — it makes the request appear in the interface and blocks the loop until answered, turning human oversight into an ordinary part of the flow rather than a special case.

It needs scheduling. Read-only tools can be marked safe to run in parallel; consequential ones must be serialised. With one generic tool, your code cannot tell which is which, so everything must be serialised — slower, for no benefit.

The rule of thumb that follows: start broad for reach, promote when you need to gate, enforce, render or parallelise. Promotion is not refactoring for tidiness; each promotion buys a specific capability in your own code.

A tool’s description is not documentation. It is how the model decides whether to call it, which makes it part of the system’s behaviour and a legitimate subject of review.

The practical guidance: be prescriptive about when to call it, not merely what it does. “Returns job details” tells the model what exists; “Call this before any reschedule, to confirm the job exists and is not already in progress” tells it when, and that trigger condition measurably improves whether the right call gets made. Current models tend to reach for tools conservatively, which makes explicit trigger conditions more valuable, not less.

Two consequences. Descriptions belong under the same review as the code — a change to one changes behaviour. And every description is part of the prompt, which means it costs context on every request.

A large tool set degrades selection: the model has more options to distinguish between, and the schemas consume context that retrieved material needed.

Two mitigations, both forms of progressive disclosure. Deferred loading keeps tools declared but out of context until something surfaces them — and because the schemas are appended rather than swapped, the cached prefix survives, which matters for the reasons Module 4 established. Packaged instructions work the same way at a different level: a short description sits in context and the full detail is read only when the task calls for it.

The trade-off is indirection. A tool the model cannot see is a tool it cannot use, so a discovery mechanism that fails silently looks exactly like a model that has become less capable.

One more design rule. A tool that fails should return a structured, informative error the loop can act on — not throw. A model that receives “no job matches that identifier for this customer” can correct itself; one that receives a stack trace, or nothing, cannot.

The cost: a loop that can see errors can also loop on them, retrying a failing call until a bound fires. Which is why the previous topic came first.

This is the tool and action boundary artifact, with the next topic. For each tool, record the promotion reason — gate, invariant, render, or parallelise — because that reason is what justifies the shape of your surface to someone reviewing it.


→ The surface says what shape a request takes. It does not yet say who may actually pull the trigger. — The action boundary