The round trip: four messages and one correlation id

Lesson 1 of 5 in Function Calling Deep Dive: The Wire Under Tool Use.

You already know the tool contract: name, description, schema. This module goes one layer down, to the wire. Because underneath every framework — LangGraph, the Claude Agent SDK, Strands, Pydantic AI, whatever you are using next year — the same four-message dance is happening, and when your agent breaks at 2 a.m. you will be reading that, not the framework’s abstractions.

Here is the universal contract, and it really is universal:

  1. You declare the tools available for this turn, each as a name, a description, and a parameters JSON Schema.
  2. The model may return a structured tool call instead of prose: a tool name, its arguments as JSON, and a call id.
  3. You execute it — the model cannot. Your runtime parses the arguments, validates them, decides whether this call is even allowed, and runs the code.
  4. You return a result message keyed to that call id, append it to the conversation, and call the model again. It continues with the result in context.

Nothing in that list is a vendor feature. It is the shape of the problem: a text-generating model needs a way to request effects it cannot perform, and you need a way to tell it what happened.

One tool-call round trip, message by message

  1. Request 1: messages + tool declarations + tool_choice

    Every request carries the whole conversation so far plus the tool list. The API is stateless: nothing is remembered for you between calls.

  2. Model: prose, or a tool call?

    The stop/finish reason on the response tells you which happened. This branch is the model-directed control flow from foundations, expressed as a field on a JSON payload.

  3. Assistant message: name + arguments (JSON) + call id

    The id exists for one reason: to correlate this request with the result you will send back. With parallel calls, it is the only thing that tells them apart.

  4. Your runtime: parse → validate → authorize

    Three separate jobs. Parsing can fail, validation can fail, and authorization can refuse a perfectly valid call. None of them are the model’s responsibility.

  5. Execute the tool

    Your code, your credentials, your timeouts, your rate limits. The model has no idea what any of that is.

  6. Result message tagged with the same call id

    Errors go here too — as a result the model can read, not as an exception that kills the loop. That is what lets it retry.

  7. Request 2: same messages + call + result

    You resend the entire thread, now two messages longer. The model reads the result and picks the next move.

  8. Model answers, or calls again

Two properties of that diagram surprise people the first time they see the raw payloads.

The API is stateless; the transcript is the state. There is no server-side memory of your agent’s turn. Every request re-sends the full message list — system prompt, user message, the assistant’s tool call, your tool result, the next tool call — which is why long loops get expensive: you are paying for a transcript that grows on every iteration. The conversation array is the agent’s working memory.

The call id is load-bearing. It looks like plumbing, and then you send back a result with a mismatched or missing id and the request fails outright, or the model reasons over a result it thinks belongs to a different call. When the model emits three calls at once, the ids are the only way anyone — API or model — can tell which answer belongs to which question.

Key terms: function calling, tool call, tool-use id, tool result, JSON Schema, context window

Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.