---
url: 'https://adk.nht.io/the-loop/primitives/toolcall.md'
description: >-
  One resolved tool invocation: tool name, validated args, results (artifact,
  tokenizable, or media), and a stable checksum.
---

# ToolCall

[Primitives](../primitives) covers the eight-primitive overview.

A [`ToolCall`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall) is one resolved tool invocation. If a [`Thought`](./thought) is the model reasoning, a `ToolCall` is the model *acting*: it pairs the tool name and the validated arguments with whatever the handler produced, plus enough provenance for the rest of the loop to reason about what just happened. By the time the record exists the call has settled — success or error, results in hand. (The in-progress streaming shape is [`TurnToolCallContent`](https://adk.nht.io/api/@nhtio/adk/turn_runner/interfaces/TurnToolCallContent), which the executor emits incrementally via helpers; the persisted record is `ToolCall`.)

[`ToolCall.args`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-args) does real work the moment the record is constructed: it is normalised — a JSON string is accepted as a convenience and the result is always a plain object — so downstream code never has to wonder which shape it has. [`ToolCall.checksum`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-checksum) is a stable hash of the tool name and the canonicalised arguments, so an identical call on a later iteration produces an identical checksum. That is the hook [`ctx.toolCallCount`](../llm-dispatch#ctx-iteration-ctx-toolcallcount-ctx-onack) (see [`DispatchContext.toolCallCount`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#toolcallcount)) uses to detect the model looping on itself, and the hook an executor following [nonce-keyed rendering](../trust-tiers/envelopes) uses to bind tool output to the call that produced it. The checksum is a **required constructor input**: the producer — the LLM battery, or your executor when you assemble a `ToolCall` by hand — computes it as `sha256(canonicalStringify({ tool, args }))` and passes it in; the constructor validates it but does not compute it. In normal use you rarely write the hash yourself (the bundled batteries and `tool.executor()` compute the matching `callId` for you), but if you construct a `ToolCall` directly — reconstructing one from storage, say — you must supply `checksum` or the constructor throws `E_INVALID_INITIAL_TOOL_CALL_VALUE`.

[`ToolCall.results`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-results) is the other interesting field, and it has three shapes. For a normal tool, the result is a [`SpooledArtifact`](../artifacts) — or `SpooledArtifact[]` when one call legitimately produces several bounded artifacts — because the artifact is what gives the model and the executor a uniform handle to work against regardless of payload size or shape. For an [`ArtifactTool`](../artifacts#artifacttool) call (the forged `artifact_*` tools that operate *on* an artifact), the result is a [`Tokenizable`](./tokenizable) instead, because wrapping the answer in another `SpooledArtifact` would just invite the model to query the artifact it built from querying the artifact — a recursion the loop has no business entertaining. The third shape is [`Media`](./media) — or `Media[]` — the explicit-modality silo for tools that return image, audio, video, or document bytes the provider can render natively. `Media` does **not** flow through [`Tool.artifactConstructor`](https://adk.nht.io/api/@nhtio/adk/forge/classes/Tool#property-artifactconstructor); it bypasses the artifact wrap the same way [`ArtifactTool`](../artifacts#artifacttool) does, because the handler has already declared the final result shape. The [`ToolCall.inline`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-inline) flag is the rendering hint that travels with the call: `true` (the default) tells the executor to render the artifact's content inline in the prompt; `false` tells it to surface the artifact as a handle and let the model fetch through the forged `artifact_*` tools. The producing tool, or middleware that knows better, decides which — there is no size threshold the ADK applies. See [Budgets → The handle pattern](../budgets#the-handle-pattern) for what an executor does with that hint.

[`ToolCall.fromArtifactTool`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-fromartifacttool) is the marker that keeps the recursion-breaker honest: when a `ToolCall` came from one of the ephemeral `artifact_*` tools that [`SpooledArtifact.forgeTools`](https://adk.nht.io/api/@nhtio/adk/spooled_artifact/classes/SpooledArtifact#forgetools) forges around a handle, the flag is set, and the next round of [`SpooledArtifact.forgeTools`](https://adk.nht.io/api/@nhtio/adk/spooled_artifact/classes/SpooledArtifact#forgetools) filters those calls out of the `callId` enum it offers the model. The model cannot, for example, call `artifact_grep` on the result of another `artifact_grep` and stack handles indefinitely.

::: warning Three names, three meanings — read this once
The ADK uses three closely-related identifiers around a `ToolCall`, and they do not mean the same thing. The distinction matters because they appear together in error messages, observability events, and forged-tool schemas.

| Name | What it is | Where it comes from | What it is used for |
| --- | --- | --- | --- |
| [`ToolCall.id`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-id) | The caller-supplied correlation key for *this* invocation | Set by the provider/model (e.g. OpenAI's `call_xyz` IDs) when the request is emitted; stored verbatim on the record | Correlating the model's request with its result; the value the forged `artifact_*` tools' `callId` enum is built from |
| [`ToolCall.checksum`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-checksum) | A content-derived hash of `{tool, args}` | `sha256(canonicalStringify({tool, args}))`, computed by the producer (LLM battery / executor) and passed in as a **required** constructor input — the constructor validates it, it does not compute it | Detecting model loops ([`DispatchContext.toolCallCount`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#toolcallcount)); binding tool output to the call that produced it in nonce-keyed envelopes; tamper-evidence on the record |
| `callId` (local variable) | The same checksum value, in flight inside [`Tool.executor`](https://adk.nht.io/api/@nhtio/adk/forge/classes/Tool#executor) before the `ToolCall` record exists | Computed by [`Tool.executor`](https://adk.nht.io/api/@nhtio/adk/forge/classes/Tool#executor) as `sha256(canonicalStringify({tool, args}))` *prior to validating the args*, so two semantically-identical invocations share a value | Stable correlation across the `toolExecutionStart` / `toolExecutionEnd` event pair, before there is a [`ToolCall.id`](https://adk.nht.io/api/@nhtio/adk/forge/classes/ToolCall#property-id) to refer to |

The collision with `forgeTools`' "`callId` enum" is unfortunate but deliberate: the forged `artifact_*` tools accept a parameter literally called `callId` because that is the name the *model* sees in the tool schema, and `callId` reads more naturally to a language model than `tool_call_id` or `correlation_key`. The value the model passes is a `ToolCall.id` (the caller-supplied correlation key), **not** a checksum — the enum is built from `tc.id` for the calls visible on `ctx.turnToolCalls`. If you find yourself writing executor code that uses both senses of `callId` in the same function, rename one to `correlationId` or `requestChecksum` and let the prose stay short.
:::

::: info `ToolCall.id` is contractually unique, not necessarily unguessable
The contract on `ToolCall.id` is **uniqueness within a turn** — the value comes from the provider and the ADK does not regenerate it. Provider ids are opaque enough for nonce binding: OpenAI's `call_*` IDs are 24+ random characters. If your executor mints `ToolCall.id` itself (an in-process battery with no upstream provider), use `crypto.randomUUID()` or an equivalent unguessable id. Sequential or timestamp-based ids satisfy uniqueness and still break the trust-envelope assumption that [trust-tier envelopes](../trust-tiers/envelopes) rely on.
:::
