A tool call is text in, text out: definitions render first in the prompt, the model emits a tool_use block, your code runs it and appends a tool_result — every guarantee lives in the code you write after that edge.
The loop
- Tool: a function you expose; the model asks by name with arguments, your code runs it, you hand back the answer. The model never runs anything.
- Harness: your loop — send request, read reply, execute the tool asked for, append result, send again.
- Three JSON pieces carry it all:
- Tool definition (schema):
name,description(English prose for the model),input_schema(JSON Schema args). Sent in thetoolsarray. tool_useblock (comes back):name, choseninput, andid;stop_reason: "tool_use"signals it waits on you.tool_resultblock (you send next): carries the sametool_use_id, sent with"role": "user"— tool output is input from the world, same standing as a user typing (this is the security basis).
- Tool definition (schema):
- Denials and thrown exceptions do not break the loop — both return a
tool_resultwithis_error: true, so the model sees the failure and can react. The error string is the next prompt: make it actionable.
What it is mechanically
- No function-calling engine — text in, text out the whole way. Definitions serialize into tokens (~4 chars each) placed ahead of the system prompt, billed every turn.
- Constrained decoding is off by default.
strict: truecompiles the schema into a grammar and applies logit masking — setting illegal next-tokens to probability zero. - Three consequences of definitions rendering first:
- They sit in the cacheable prefix (position 0). Prompt caching: cached read bills 0.1×, the write 1.25×. Prefix match — one changed byte invalidates everything after, so a per-user tool list kills the cache for everyone.
- Tool count costs tokens every turn (~150 tok/tool as an order-of-magnitude; count your own).
- The description is prompt text competing for attention, not registry metadata.
The description decides when
- Highest-leverage field:
description(not schema, not executor) decides whether the tool ever fires. Modern models under-trigger — they reason more, reach for tools less; expect tool-call rate to drop on newer models, recover it with explicit trigger conditions. - A strong description has four parts: (1) what, (2) when to call — positive trigger as a condition, with the reason attached, (3) when not to call — the boundary (most often missing), (4) what comes back.
- Sibling overlap: when two tools could answer the same turn, each description must name the other by exact
nameand rule itself out. No mutual exclusion = coin flip = test flakiness. - Debug “right tool, never used” in order: trigger condition → sibling overlap → system prompt implying the model already knows. Prompt nudging is last.
Tool surface design
- Any one yes means a dedicated tool; three noes route to
bash:- Needs a permission gate?
- Needs custom UI, audit trail, or staleness check?
- Must the harness know it is parallel-safe?
bashhands over an opaque string — the harness can’t gate, render, audit, parallelize, or enforce invariants. Start with bash for breadth; promote an action the moment you need any of the five.- Practical rules: 5–15 tools comfortable, accuracy degrades past ~20; one tool one job;
enumfor closed sets; return structure not prose; truncate loudly; make errors actionable.
Strict — shape, never meaning
- Default: schema is prose the model usually follows, but the API promises nothing — wrong types, missing required fields, undeclared fields all possible.
strict: trueenforces declared types, allrequiredpresent, no undeclared field, valid toolname. RequiresadditionalProperties: falseand a completerequiredlist. Flag sits at top level, next toname, not insideinput_schema.- Fixes shape, never semantics — a well-shaped call can still refund $5,000 for a nonexistent order. Business validation stays in your harness, always.
Selection vs extraction failures
| Symptom | Diagnosis | Fix |
|---|---|---|
| Wrong tool chosen | Selection — overlapping descriptions | Sharpen triggers, state each boundary |
Right tool, date="next Tuesday" | Extraction — under-specified schema | format, example in description, strict |
| Invents a tool name | Hallucinated capability | is_error with real names; audit system prompt |
null for a required field | Optionality ambiguity, no strict | Real default, or say what to do when unknown |
- Extraction fix ladder (cheapest first): (1) property description with literal example, (2)
enum/format, (3)strict: true, (4) tool-use example in the definition, (5) prompt engineering last. Only 2 and 3 are structural (reach constrained decoding); 2 declares, 3 arms.
Scale (50+ tools)
| Branch | Who narrows | Cost | Cached prefix |
|---|---|---|---|
| Router | Cheap classifier, before the call | Hand-maintained table that rots silently | Clean |
| Tool search | The model, mid-turn | One extra round trip | Preserved — schemas appended, not swapped |
| Namespacing | You, at design time | Args move out of the schema | Clean, but smaller surface |
- Tool search: a built-in tool that finds other tools;
defer_loading: Truekeeps schemas out of context until queried. The search tool never defers and at least one ordinary tool stays loaded. Datedtype(e.g...._20251119) is a frozen-behavior name, not a rank — pick by feature, not by larger number. server_tool_use= provider ran it on its own infra; notool_resultowed (unlike ordinarytool_use).- Namespacing collapses N tools into one
actionenum — the opposite of “one tool one job.” Trade favors it only when schema volume dominates; if you namespace, re-add the gate by hand inside the handler.
MCP
- MCP (Model Context Protocol): a published convention for how a provider describes and exposes tools, so one integration works with any client. A wire format, not a broker you deploy — connections stay one per pair. Turns N×M integrations into N+M.
- Parts: server (separate process wrapping an API), client (speaks MCP to exactly one server), host (your app, one client per server). Client sends
initialize, callstools/list(returns the same three fields you’d write), host splices them into the sametoolsarray; atool_useroutes to the client viatools/call. - Transport is a trust boundary: stdio (child process, runs as you, no auth) vs HTTP/SSE (remote endpoint, real auth/multi-tenancy/network). Pin server versions — an update ships new descriptions straight into your prefix.
- Three primitives by who holds the trigger: Tools (model acts unprompted), Resources (application pulls, gated by your code, don’t self-enter context), Prompts (user invokes deliberately).
- Security: a third-party server is untrusted input at prefix position 0. Its descriptions are a first-class field, never through your content pipeline, so content-wrapping doesn’t apply — this is prompt injection territory. Pin versions, diff descriptions on update, limit granted authority. MCP doesn’t solve selection-at-scale, authorization, or cost.
Sandboxes, PTC, computer use
- Sandbox: OS/hypervisor-level isolation (never
exec()orRestrictedPython— language-subset jails are escapable via introspection chains). Constraints: no network (allowlist only), ephemeral FS, CPU/mem/wall-clock caps, non-root with dropped caps, return only stdout/stderr/exit code. Fresh container per session, not per call. - Programmatic tool calling (PTC): the model writes a script calling your tools as functions inside the sandbox; intermediate payloads stay there, only
printoutput crosses back. Context cost isO(1)in payload size, notO(D). Mark tools callable withallowed_callersnaming the exact containertype. - Computer use: action space is a screen (
screenshot,click,type,key,scroll); every turn ships an incompressible image.- Image tokens =
ceil(width/28) × ceil(height/28)(28×28 px patches; ceilings because a partial edge patch costs a whole one). - The token cap bites before the long-edge cap. Current tier (Opus 4.7+): 2,576 px / 4,784 tok, 1080p = 2,691 tok. Standard tier: 1,568 px / 1,568 tok, 1080p downscaled to 1,560 tok — ~1.7× difference; always state the tier.
- Shrink resolution before trimming old screenshots (trimming breaks the cache). If an API exists, use it — computer use is ~10–50× the cost and less reliable.
- Image tokens =
Skills and OKF
- Skill: a folder with a
SKILL.md(instructions plus optional scripts/refs). Only the short description sits in context; the body is read on demand via a file-read tool — progressive disclosure, same cost profile asdefer_loading. Use for “how we do X here” runbooks (need all of it, once, in order); an API reference is a retrieval problem instead. - OKF (Open Knowledge Format): emerging spec for portable, model-agnostic domain knowledge; MCP standardizes actions, OKF aims at knowledge. Early — know the one-liner, don’t over-claim.