Tools & Protocols

Tools are how an agent touches the world, and the seam between model and tool is where a surprising share of agent bugs live. These plates cover how a tool should be shaped for a model rather than a programmer, how MCP connects any tool to any host, how large catalogues stay out of the window, and what happens to a call on the way out and its result on the way back.

T-01Designing Tools for Agents

/tools/tool-design

A tool is an interface for a model, not a wrapper around an endpoint: its name, description, schema and return shape are everything the model knows about it.

Step-by-step walkthrough2 variants · 12 steps

API wrappers

  1. The task is one sentence: book thirty minutes with Ana next week.
  2. But the tools mirror the API, one endpoint each. The agent has to orchestrate them itself.
  3. Call 1 lists every user so it can find Ana — four thousand rows of ids, all of it now in context.
  4. Call 2 dumps the calendar, so the model can find a free slot by reading it.
  5. Call 3 books with an id copied out of row 2,317. One wrong character and it is someone else’s meeting.
  6. Three round trips, and most of the window went on plumbing the tool should have done.

Agent-shaped

  1. The same one-sentence task.
  2. This time there is one tool shaped like the task, not like the API.
  3. Its description says when to use it and what comes back; the schema takes a name and a length, not a uuid.
  4. The lookup, the free slot and the booking happen inside the tool — deterministic code, not tokens.
  5. It returns only what the next step needs: a name, a time, a status.
  6. One call, one short result — and the agent never handled an identifier it could mistype.
Key insight

Shape tools around the tasks an agent actually performs. One schedule_event that does the lookup, the availability check and the booking in code beats three endpoints the model has to stitch together in tokens.

Failure mode

Wrapping every endpoint one-to-one and returning raw JSON. The agent spends its window on plumbing, copies opaque ids between calls, and picks the wrong one of three near-identical tools.

Further reading

T-02MCP: Hosts, Clients, Servers

/tools/mcp

One protocol between the app that runs the model and the services that offer tools: the host starts a client per server, asks what each offers, and routes calls through it.

Step-by-step walkthrough8 steps
  1. The host is the app the user runs. The model lives inside it and never talks to a server directly.
  2. For every server, the host starts one client — one connection each, isolated from the others.
  3. A local server runs as a child process and talks over stdin and stdout.
  4. A remote one is reached over HTTP. Only the pipe differs; the messages are the same.
  5. On connect, the client asks what the server offers — its tools, resources and prompts — instead of assuming.
  6. Tool definitions travel up into the model’s context; a call travels back down the same client to the same server.
  7. Every connected server also writes text into that context. Connect one like a dependency, not a bookmark.
  8. Add or swap a server and nothing else changes — the protocol is the contract, not a bespoke integration per app.
Key insight

MCP turns every-app-times-every-integration into one implementation on each side. The transport is a detail — stdio for a local process, HTTP for a remote service — and the model sees tool definitions and results, never the server itself.

Failure mode

Treating a connected server as a trusted plugin. Every server writes text straight into the model’s context, and every tool it lists costs tokens on each request whether it is used or not.

Further reading

T-04Structured Output & Validation

/tools/structured-output

Getting data a program can trust out of a model: validate and repair after generation, or constrain generation so an invalid shape cannot be produced.

Step-by-step walkthrough2 variants · 12 steps

Validate + repair

  1. Downstream code needs typed data, so the schema goes into the prompt.
  2. The model generates freely — and usually gets it right.
  3. Every output is parsed and checked against the schema before anything uses it.
  4. Invalid: a string where a number belongs.
  5. The exact error goes back with the bad output — fix this field, not “try again”.
  6. The retry validates, and only then reaches your code — at the cost of a loop, latency, and a failure path you own.

Constrained decoding

  1. Same schema — but this time it is compiled into a grammar before generation starts.
  2. At every token, anything that would break the schema is masked out.
  3. So the output cannot be malformed: the right keys, the right types, every required field.
  4. Not a proof, though. A response cut off at max_tokens, or a refusal, still won’t match the schema.
  5. And some rules — ranges, lengths — can’t be expressed in the grammar, so your code still checks them.
  6. Check the stop reason, keep the range checks, and shape errors are gone by construction.
Key insight

A repair loop treats the schema as a test; constrained decoding treats it as a grammar. The grammar removes shape errors entirely — but not truncation, refusals, or rules it cannot express, so validation moves rather than disappears.

Failure mode

Trusting whatever parses. The string “1,200” in a number field sails through a lenient parser and fails three services later, in the billing code.

Further reading

T-05Parallel Tool Calls

/tools/parallel-tool-calls

Several independent tool calls in one model turn, run concurrently, with every result returned together in the next message.

Step-by-step walkthrough6 steps
  1. The model needs three things, and none of them depends on the others.
  2. So it asks for all three in one response: three tool calls, one turn.
  3. The harness runs them concurrently.
  4. One of them fails. It still gets a result — flagged as an error — rather than silently going missing.
  5. Every result goes back in one message, each matched to its call by id.
  6. One round trip instead of three, and the model sees the whole picture at once.
Key insight

Latency drops from the sum of the calls to the slowest one, and the model reasons over all the results at once. The rule is independence: reads fan out freely; anything with side effects or an ordering dependency waits for the next turn.

Failure mode

Feeding the results back one turn at a time. The history now shows one call per turn, and the model learns from its own transcript to stop calling in parallel.

Further reading

T-06Tool Errors as Observations

/tools/tool-errors

A failed tool call becomes a result the model can read — what went wrong and what to try instead — rather than an exception that ends the run.

Step-by-step walkthrough2 variants · 11 steps

Error as exception

  1. Twenty steps in, the model calls a tool with a value the API rejects.
  2. The tool raises. Nothing between it and the loop was written to catch that.
  3. The exception unwinds the harness — twenty steps of work gone, over a value the model could have fixed in one.
  4. The usual patch: catch it and hand back a status code. Better, but opaque.
  5. The model can’t tell what to change, so it sends the identical call again, and again.

Error as observation

  1. Twenty steps in, the model calls a tool with a value the API rejects.
  2. The tool raises — inside a wrapper that never lets an exception reach the loop.
  3. The failure becomes a tool result, flagged as an error, that says what was wrong and what is allowed.
  4. The model reads it like any other observation.
  5. Transient failures are different: the harness retries those itself, with an idempotency key so a retried write lands once.
  6. The model fixes the argument on its next call. One step lost, not the run.
Key insight

The model is the best error handler in the loop, but only for errors it can see. Bad arguments and refused actions go back as instructive results; transient failures are retried by the harness, with idempotency keys so a retried write lands once.

Failure mode

Opaque errors. “Error 400” says that something failed but not what to change, so the model resends the identical arguments and learns nothing from the attempt.

Further reading

Elsewhere in the atlas