User Interaction Model
Applications typically surface a tool call as it streams — a card naming the tool, arguments filling in — and, for side-effectful tools, ask the user before executing. The protocol does not mandate any particular interaction model, but see Security Considerations below.Events
Tool calls follow the streaming pattern, matched bytoolCallId.
TOOL_CALL_START
Opens a call.
toolCallNamenames the tool being called.parentMessageIdis OPTIONAL and attaches the call to the assistant message that carries it. When the parent message is attributed to a subagent, the call MUST agree with that attribution — a tool call belongs to the message that carries it (see Subagents).
TOOL_CALL_ARGS
Extends the open call. delta carries the next piece of the call’s arguments;
the concatenated deltas form the call’s argument text, conventionally a JSON
document — but the protocol carries it as text and does not validate it, which
is deliberate: providers emit malformed argument strings, and the application
deciding what to do with one beats the transport killing the run.
A consumer MUST NOT act on the arguments before TOOL_CALL_END: until the call
closes, the text is a prefix of whatever the producer is sending, not the
thing itself.
TOOL_CALL_END
Closes the call. The argument text is complete — closing establishes
completeness, not validity. Whoever executes the tool parses it, and what to
do with text that does not parse is the application’s decision, not a
protocol violation.
A closed call, like a closed message, MAY be reopened by a new
TOOL_CALL_START with the same toolCallId, and further arguments append. A
reopening start MUST agree with the call it reopens — the same
toolCallName, the same parentMessageId, the same owner. A consumer is not
required to detect a disagreement, and unlike a
reopened text message,
the protocol makes no promise about which of the two values a consumer that
missed the violation ends up holding.
TOOL_CALL_CHUNK
The compact spelling. A consumer MUST expand chunks as the
streaming pattern
specifies: the first chunk MUST carry toolCallId and toolCallName, and a
continuation repeating toolCallName or parentMessageId with a conflicting
value is fatal.
TOOL_CALL_RESULT
Carries the result of a call. It is a message in its own right — a tool message
with its own messageId — and does not reopen the call it answers.
Result content
content MUST be either a string or an ordered list of
ContentParts — the same parts a
user message carries: text, image,
audio, video and document, each media part with a source that is inline
data, a URL or a
provider file handle. The
tool message the event mints has the identical shape, so a result travels into
the next run’s messages unchanged.
- A tool returning structured data — a JSON object, say — serialises it into
the string form or into a
textpart. The protocol has no JSON part: every provider accepts a tool result as text, and a typed object would have to become text at the provider boundary anyway. - Anything a part does not model — a search hit’s source and title, a
document’s filename — rides in the part’s
metadata. The protocol does not define provider-specific result blocks (a citation-enabled search result, a browser state); a producer that needs one maps it onto these parts or carries it as passthrough. - A tool that uploaded its output to the model provider — a generated report
now sitting in the provider’s file store — returns the handle as a
filesource rather than re-sending the bytes; the same rules apply as on input, and a consumer that renders the result treats the handle as opaque. - A producer handed a part its model cannot take — an image to a text-only model, audio to a provider that accepts none in a tool result, a file handle another provider issued — MUST NOT fail the run because of it. It drops the part and continues, as the run input rules already say of user content, and it MUST still answer the call: a result whose every part was dropped is answered with the empty string, because a tool call left unanswered is one most models reject outright.
- A consumer that can only hold a string — a renderer, a store, a legacy
bridge — renders a list of parts as its
textparts concatenated in order and ignores the rest. Doing so is lossy and SHOULD be announced, the way any downgrade is (Versioning). - A peer from before content parts existed sends no
protocolVersion; a producer that knows it is talking to one MAY flatten a result the same way before emitting it, and MUST warn when the flattening dropped a part. It MUST NOT put a placeholder in the dropped part’s place: a downgrade reshapes, it does not invent.
Frontend tools
Thetools list in run input is the
application’s: the agent proposes a call, the application executes it. The
protocol has no mid-run channel from the consumer, so the answer can only
cross a run boundary — which gives the round-trip its shape:
- A producer that calls a frontend tool MUST NOT answer it: no
TOOL_CALL_RESULT, no fabricated tool message. The result is the application’s to produce. - The producer finishes the run with the call unanswered. It MUST use the success outcome, or none, and MUST NOT report the run as interrupted — and it SHOULD finish promptly once nothing remains that does not depend on the result. Several frontend calls MAY be left unanswered by one run; the application answers them all at once.
- The success outcome’s
pendingToolCallIdsnames the calls left unanswered, in the order they were made. A producer SHOULD send it when a run leaves calls pending; when it does, the list MUST contain exactly the tool calls the run started and did not answer withTOOL_CALL_RESULT.RUN_FINISHEDalone does not say whether a run left work for the application: when the list is absent or empty, a consumer that needs to know derives it from the stream — every tool call the run started that received no result — and MUST NOT read absence as “nothing pending”. On an interrupted run the pending calls are derived the same way; the interrupt outcome does not carry them. - After the run finishes, the application disposes of each unanswered
frontend call — executing it, or declining it, with whatever consent its
own rules require. A thread that continues MUST answer every one of them
first: the next
run’s
messagescarry a tool message per call, keyed bytoolCallId— a failure is still an answer, as a tool message witherrorset, and a call the user declined is answered by saying so. The tool message’scontentis a string or a list of parts, exactly as onTOOL_CALL_RESULT: a frontend tool that produced a screenshot or picked a file answers with a media part rather than a description of one. An unanswered call leaves the agent mid-thought, and a history with a dangling call is one many models reject outright. Abandoning the thread answers nothing and violates nothing — the rule binds continuation, the same way resume coverage does.
A run that stops on a frontend tool call is a completed run, not an
interrupted one, and it is not a third kind of ending either. The outcome
reports what the producer knows about why the run ended, and here the
producer does not know whether it is waiting: a frontend tool is often a
terminal effect — render a chart, navigate, highlight — and whether its
result starts another run is the application’s decision, made by the
application’s rules for that tool. So the producer says what it does know —
the run is complete, and these calls are unanswered — as detail on the
success outcome, where an older consumer that strips the field still reads a
successful run and derives the pending calls as it always has.
TOOL_CALL_RESULT.
Message Flow
A client-executed tool spans two runs:Data Types
The event shapes are defined by the schema reference:ToolCallStartEvent, ToolCallArgsEvent, ToolCallEndEvent,
ToolCallChunkEvent, ToolCallResultEvent. In conversation history a call
appears as a ToolCall on an AssistantMessage, and a result as a
ToolMessage whose content is a string or a list of
ContentParts. The advertised tools are Tool objects on RunAgentInput.
The parts are named by what they are — TextPart, ImagePart — rather than by
the direction they travel, because the same part goes into the model inside a
user message and comes out of the stream inside a tool result. The names
ReasoningPart, ToolCallPart and AssistantPart are reserved for a later
minor version, when assistant messages carry parts too; nothing defines them
yet, and a producer MUST NOT emit them.
Error Handling
The streaming pattern’s sequence rules apply unchanged: continuing or closing a call that is not open, or reopening one that is, is fatal. A call left open when the run finishes is a violation.Security Considerations
Tool calls are the protocol’s largest attack surface, because they turn model output into actions.- Arguments are model-generated and MUST be treated as untrusted input: validated against the tool’s declared parameter schema where one was advertised, scrutinised like any untrusted payload where none was, and never interpolated into shell commands, queries or markup unescaped.
- Applications SHOULD obtain user consent before executing a side-effectful tool call, and MUST NOT represent a call as user-approved when it was not.
- Tool results are data from wherever the tool got them. A consumer MUST NOT treat text inside a result as protocol material or as instructions carrying the user’s authority.
- A call naming a tool that was not advertised SHOULD NOT be executed without the same scrutiny a new tool would get.