Tool use, mechanically
By the end: Trace one tool call from declaration through the model’s request to the result you send back.
Tool use looks like magic from the outside and is completely ordinary from the inside. There are four moves, and every provider implements the same four with slightly different names.
You declare the tools: a name, a description of when to use it, and a JSON Schema for its input. You send that list with every request — the model has no memory of tools you declared last turn. The model replies with either text, or a structured request to call one or more tools with specific arguments. You run whichever calls you are willing to run, and send the results back as a new message. Then the model sees those results and continues.
→ request
tools: [{ name: "get_order", description: "...", input_schema: {...} }]
messages: [{ role: "user", content: "Where is order 4417?" }]
← response (stop_reason: tool_use)
content: [{ type: "tool_use", id: "t_01", name: "get_order",
input: { order_id: "4417" } }]
→ request (the same conversation, two messages longer)
messages: [ ...previous...,
{ role: "assistant", content: [the tool_use block] },
{ role: "user", content: [{ type: "tool_result",
tool_use_id: "t_01", content: "{\"status\":\"shipped\"}" }] } ]
← response (stop_reason: end_turn)
content: [{ type: "text", text: "Order 4417 shipped on..." }]Two practical consequences. A model may request several tools in one turn, and you should return all of their results together in the next message rather than one at a time. And because you are the one executing, "the agent did X" is always shorthand for "my code did X because the model asked" — which is where responsibility sits when it goes wrong.

