Skip to content
Everything Agents

Lesson 5 of 14 · 5 min

The tool registry and what makes it trustworthy

Tools are typed, validated, and gated. The registry is where hallucinated calls become impossible, or where they slip through.

The tool registry is the harness component most people think of as "just the tools." It's actually the layer that decides whether a tool call is real: whether it has the right arguments, comes from an allowed source, can run in the current state, and gets logged after it runs.

Get it right and a whole class of failures goes away. Get it wrong and the model can call functions that don't exist, with arguments that don't match the schema, and you find out in production.

What a tool definition actually contains

A typed tool definition has three pieces:

name:        Read
schema:      { file_path: string (required), offset?: int, limit?: int }
implementation: read_file()

The model sees the name and a description. The harness sees the schema and the implementation. The schema is what the registry checks before dispatch.

That separation is load-bearing. The model's job is to emit a call that looks like a valid tool invocation. The registry's job is to verify that it actually is one. If the model writes Read({ path: "..." }) instead of Read({ file_path: "..." }), the registry rejects it before any code runs.

What the registry checks

A trustworthy registry runs at minimum these checks before dispatch:

  1. Tool exists. The model named a tool actually in the registry.
  2. Schema match. Every required argument is present and of the right type. No extras unless the schema allows them.
  3. Permission. Lifecycle hooks (lesson 6) run pre-call. They can refuse the call.
  4. Availability. The tool may be conditionally available, gated on agent state, user permission, or environment. (See the available when pattern from the guided-determinism topic.)

A call that fails any of these never reaches the implementation. It's returned to the model as a tool error, the model gets to try again, and the world is unchanged.

Why this is where hallucinations get caught

A "hallucinated tool call" is a call the model invented: wrong name, wrong args, or wrong shape. The honest claim is: a typed registry makes most hallucinations impossible to execute.

It does not make them impossible to emit. The model will still sometimes write read_file("foo.py") when the tool is named Read({ file_path: "foo.py" }). The registry catches it. The model sees the error and corrects. The world is unaffected.

This is most of the value of tool typing. Not "the model never makes mistakes" but "the mistakes never reach the implementation."

Tool descriptions are part of the surface

A subtle point: the description you give a tool steers the model's choice of when to call it. Identical implementations with different descriptions produce different agent behavior.

The Pi Research result from lesson 1 (6.7% to 68.3% on the same model and benchmark) was a tool format change. (source: 2604.agent-harnesses-survey.pdf, citing Bölük 2026) Same model. Same task. Different shape on the tool-call surface.

This is one of the easiest harness wins available to you:

  • Write the description for the agent reading it, not the human reviewing it. "Use this when you need X" beats "Reads a file."
  • Be explicit about when not to call the tool. The model will often reach for the wrong tool because nothing told it not to.
  • If the tool returns a complex object, name the fields the agent should care about most. The description is the agent's only docs.

MCP and the multi-tool world

Modern harnesses don't ship every tool inline. The Model Context Protocol (MCP) lets a harness load tools from external servers: a GitHub MCP, a database MCP, a browser MCP. From the registry's point of view they're identical: typed schemas, validated calls, hooked dispatch. The fact that the implementation lives in another process is a runtime detail.

This is why T's job is mediation, not implementation. The registry is a switchboard. It owns the schema and the dispatch contract. Anything that wants to be a tool plugs into it.

Tools as the code-as-harness action interface

The Ning et al. survey gives this mediation a sharper name: a tool call is one form of code-as-harness interface, the boundary where model output becomes a permitted state transition. The model proposes intent; the registry, plus pre-call hooks, plus the implementation together convert intent into a typed, validated, governed action. (source: 2605.code-as-harness.pdf, §2.2)

One way to see how much work the registry is doing: imagine a tool call as an evidence-carrying action, not a black box. The call arrives with a schema; the registry validates it; a permission gate decides whether it may run; the implementation runs it; the result comes back with structured success or failure data. AutoHarness is the extreme case where the entire harness collapses to a single legality predicate, but the same pattern shows up wherever you convert "the model said do X" into "X is provably safe to run right now." (source: 2605.code-as-harness.pdf, §2.2 and §3.3)

A small worked example

The same harness can hold all of these:

Read              local file read,         in-process
Bash              shell, gated by hook,    in-process
Edit              local file edit,         in-process
github.pr         GitHub PR ops,           via MCP
db.query          read-only SQL,           via MCP, allowlisted
playwright.click  browser action,          via MCP

Each one has a typed schema. Each one runs through the same registry. The only difference between Read and db.query is the dispatch target.

Quick check

A model emits a tool call with a typo in the argument name. What should the registry do?