The Tool Interface — Why Agents Need Structured I/O
A language model produces tokens. A program takes actions. The tool interface sits between them: a contract where the model requests an action and the host carries it out. Function calling on OpenAI, Anthropic, and Gemini, MCP's `tools/call`, and A2A's task parts are different encodings of the same four-step loop. This lesson names the loop and builds the minimum machinery to run it.
Learning Objectives
- Explain why an LLM that can only generate text cannot, on its own, take actions against the real world.
- Draw the four-step tool-call loop (describe → decide → execute → observe) and name who owns each step.
- Write a tool description as three parts: name, JSON Schema input, and a deterministic executor function.
- Distinguish pure and side-effecting tools and state why the split matters for safety.
The Problem
An LLM emits a probability distribution over the next token. That is the entire output surface. Ask a chat model "what is the weather in Bengaluru right now" and it can write a plausible sentence, but it cannot dial into a weather API. The sentence might be right by coincidence or three days stale.
The tool interface closes that gap. The host program (your agent runtime, Claude Desktop, ChatGPT, Cursor, or a custom script) advertises a list of callable tools to the model. When the model decides an action is needed, it emits a structured payload naming a tool and its arguments. The host parses that payload, runs the tool for real, and feeds the result back. The loop continues until the model decides no more calls are needed.
The first version of this contract shipped in June 2023 as OpenAI's "functions" parameter. Anthropic followed with tool_use blocks in Claude 2.1, and Gemini added functionDeclarations a few months later. All three now expose the same shape: a JSON-Schema-typed tool list in, a JSON-payload tool call out. The Model Context Protocol (November 2024) generalized the contract so one tool registry can serve many models. A2A (April 2026, v1.0) applies the same primitive to agent-to-agent delegation.
The four-step loop is the invariant underneath all of these. The rest of Phase 13 elaborates on it.
The Concept
Step one: describe
The host declares each tool with three fields.
- Name. A stable, machine-readable identifier.
get_weather, not "weather thing". - Description. A one-paragraph natural-language brief. "Use when the user asks about current conditions for a specific city. Do not use for historical data."
- Input schema. A JSON Schema object (draft 2020-12) describing the tool's arguments.
The model receives the list. Modern providers serialize these declarations into the system prompt using a provider-specific template, so as the caller you only deal with the structured form.
Step two: decide
Given the user's message and the available tools, the model picks one of three behaviors.
- Answer directly in text. No tool call.
- Call one or more tools. Emit structured call objects. Under
parallel_tool_calls: true(default on OpenAI and Gemini, opt-in on Anthropic) the model can emit multiple calls in one turn. - Refuse. Strict-mode structured outputs can produce a typed
refusalblock instead of a call.
A tool call payload has three stable fields: a call id, a tool name, and a JSON arguments object. The id lets the host match a later result to the specific call, which matters when parallel calls come back out of order.
Step three: execute
The host receives the call, validates arguments against the declared schema, and runs the executor. Invalid arguments mean the model hallucinated a field or used the wrong type, a common failure on weaker models. Production hosts do one of three things: fail fast and surface the error to the model, repair the JSON with a constrained parser, or retry the model with the validation error included in the prompt.
The executor itself is ordinary code. Python, TypeScript, a shell command, a database query. It produces a result, which is usually a string but can be any JSON value or a structured content block (text, image, or resource reference in MCP). The result has to be serializable.
Step four: observe
The host appends the tool result to the conversation (as a tool role message with the matching id) and re-invokes the model. With the tool output in context, the model can produce a final answer or request more calls. This continues until the model stops emitting calls or the host hits a safety limit on iteration count.
The trust split
Tools come in two flavors that matter for safety.
- Pure. Read-only, deterministic, no side effects.
get_weather,search_docs,get_current_time. Safe to call speculatively. - Consequential. Mutates state, spends money, touches user data.
send_email,delete_file,execute_trade. Must be gated.
Meta's 2026 "Rule of Two" for agent security says a single turn may combine at most two of: untrusted input, sensitive data, consequential action. The tool interface is where you enforce that rule, by rejecting calls, requiring user confirmation, or escalating scopes. See Phase 13 · 15 for the full security chapter and Phase 14 · 09 for agent-level permission policies.
Where the loop lives
| Context | Who describes | Who decides | Who executes |
|---|---|---|---|
| Single-turn function calling (OpenAI/Anthropic/Gemini) | App developer | LLM | App developer |
| MCP | MCP server | LLM via MCP client | MCP server |
| A2A | Agent Card publisher | Calling agent | Called agent |
| Web browser (function-calling agent) | Browser extension / WebMCP | LLM | Browser runtime |
The column names change from row to row. The four steps stay put.
Why not just prompt the model to emit JSON?
"Ask the model to reply in JSON" was the pre-function-calling pattern. It fails ~5 to 15 percent of the time on frontier models and far more on smaller models. Failure modes include missing braces, trailing commas, hallucinated fields, and wrong types. You then need a JSON repair pass, a retry, or a constrained decoder.
Native function calling is better for three reasons. First, the provider trains the model end-to-end on the exact call shape, so valid-JSON rate climbs to 98 to 99 percent on strict mode. Second, the call payload sits in its own protocol slot instead of inside free text, so a tool call does not leak into the user-visible reply. Third, providers enforce schema compliance with constrained decoding (OpenAI's strict mode, Anthropic's tool_use, Gemini's responseSchema), so the output is guaranteed to validate.
Phase 13 · 02 walks the three provider APIs side by side. Phase 13 · 04 goes deep on structured outputs.
Circuit breakers
The loop terminates when the model stops emitting calls or the host hits a maximum turn count. Production hosts set this between 5 and 20 turns. Past that, you are probably in a loop the model cannot exit. Claude Code defaults to 20, OpenAI Assistants to 10, and Cursor's agent mode to 25.
Unbounded loops are how you get "agent spent $400 in API calls overnight" post-mortems. Put a bound on yours before shipping.
Phase 14 · 12 covers error recovery and self-healing in depth. Phase 17 covers production rate limits.
Where Phase 13 goes from here
- Lessons 02 through 05 polish the provider-level tool-call surface.
- Lessons 06 through 14 generalize the loop into MCP.
- Lessons 15 through 18 defend the loop against hostile servers, adversarial users, and unauthenticated remote auth surfaces.
- Lessons 19 through 22 extend the pattern to agent-to-agent collaboration, observability, routing, and packaging.
- Lesson 23 ships a complete ecosystem using each primitive.
Hold the four-step loop in mind. Each remaining lesson elaborates on it.
Use It
code/main.py runs the four-step loop without an LLM. A fake "decider" function stands in for the model by pattern-matching on the user message, while the executor, schema validator, and observe-step harness are real. Run it to see the full request/response choreography with printable intermediate state, then swap the fake decider for any real provider in a later lesson.
What to look at:
- The tool registry holds five fields per tool: name, description, schema, an executor reference, and a
consequentialflag. - The validator is a minimal JSON Schema subset (types, required, enum) written in stdlib only. Phase 13 · 04 ships a fuller one.
- The loop bounds iteration count at five. Production agents need this kind of circuit breaker.
Ship It
This lesson produces outputs/skill-tool-interface-reviewer.md. Given a draft tool definition (name, description, schema, and executor outline), the skill audits it for loop fitness. Is the name machine-stable? Is the description a complete usage brief? Does the schema use JSON Schema 2020-12 correctly? Is the pure-vs-consequential classification explicit?
I'd run main.py before rereading the prose. It sends four queries through the loop and prints each step as DECIDE, EXECUTE, and OBSERVE lines, with the call id and arguments, so you can watch the choreography instead of imagining it. The last query, a haiku about tea, matches no tool and gets a plain text answer, which is the "answer directly" branch of the decide step.
My bet is that validate and run_loop are the parts worth reading slowly. Notice how little the validator does. It checks types, required fields, and enums, and it does not check for unknown fields, so an extra argument passes straight through. On a validation error the harness just prints and returns, where a real host would hand the error back to the model. Exercise 2 asks you to decide what the right behavior is.
I'd also keep the toy in proportion. fake_decide is a keyword router and get_weather returns canned data, so nothing here tells you how a real model behaves. The consequential flag exists on the Tool dataclass and prints a gate line, but all three tools are pure, so that branch never runs until you add a tool that needs it.
Exercises
-
Add a fourth tool to
code/main.pycalledget_stock_price(ticker). Write its description as "Use when the user asks for a current stock price by ticker. Do not use for historical prices or market summaries." Run the harness and confirm the fake decider routes queries mentioning tickers to the new tool. -
Break the schema validator. Pass a call whose
argumentsobject is missing a required field, and confirm the host rejects it before execution. Then pass a call with an extra unknown field. Decide: should the host reject or ignore? Justify your choice with a safety argument. -
Classify each tool in the harness as pure or consequential. The
Tooldataclass already has aconsequentialflag and the loop already prints a gate line when it is set. Add a fourth tool,send_email, withconsequential=True, and confirm the gate line appears before it executes. This is the shape of the confirmation gate every production host needs. -
Draw the four-step loop on paper with the provider-column table above filled in for your favorite client (Claude Desktop, Cursor, ChatGPT, or a custom stack). Cross-reference with the MCP-specific variant in Phase 13 · 06.
-
Read OpenAI's function-calling guide top to bottom. Identify the one field that sits in the request but not in the four-step loop as presented here. Explain what it adds and why it is convenient rather than essential.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Tool | "A thing the model can call" | A triple of name + JSON-Schema-typed input + executor function |
| Function calling | "Native tool use" | Provider-level API support for emitting structured tool calls instead of prose |
| Tool call | "The model's request to act" | A JSON payload with id, name, arguments emitted by the model |
| Tool result | "What the tool returned" | The executor's output, wrapped in a tool role message with matching id |
| Parallel tool calls | "Many calls at once" | Multiple call objects in one model turn, independent and orderable by id |
| Strict mode | "Guaranteed JSON" | Constrained decoding that forces the model's output to validate against the declared schema |
| Pure tool | "Read-only tool" | No side effects; safe to re-run |
| Consequential tool | "Action tool" | Mutates external state; requires gate, audit, or user confirmation |
| Four-step loop | "The tool-call cycle" | describe → decide → execute → observe |
| Host | "Agent runtime" | The program that holds the tool registry, calls the model, and runs the executor |
Further Reading
- OpenAI: Function calling guide: canonical reference for OpenAI-style tool declarations and call shapes
- Anthropic: Tool use overview: Claude's
tool_use/tool_resultblock format - Google: Gemini function calling:
functionDeclarationsand parallel-call semantics in Gemini - Model Context Protocol: Specification 2025-11-25: the provider-agnostic generalization of the tool interface
- JSON Schema: 2020-12 release notes: the schema dialect every modern tool API speaks