This document summarizes the tool-call pipeline used by the Pi agent runtime. It is based on the implementation and documentation in the local Pi project, with details flattened into a public-facing technical note. The key invariant is simple: the model never executes tools directly. It emits structured intent; the runtime validates, executes, records, and feeds results back into the next model turn.

Model intent -> Runtime validation -> Tool result
A compact view of the tool-call boundary: intent, validation, execution, and feedback.

Scope and invariants

  • The model receives tool declarations and emits structured tool-call blocks.
  • The local runtime owns lookup, argument preparation, schema validation, policy hooks, execution, and result emission.
  • The assistant message is finalized before tool preflight begins.
  • Execution is driven by assistant message content, not only by a provider stop reason.
  • Tool results are persisted as transcript messages and become input to the next model request.
  • Live execution order and persisted transcript order can differ in parallel mode.

Runtime data model

Pi separates the provider-facing tool shape from the local runtime tool shape. The provider-facing layer describes what the model is allowed to request. The runtime layer adds local execution behavior, progress reporting, execution-mode hints, and hooks.

Concept Role in the pipeline
Tool declaration Name, description, and schema exposed to the model.
Assistant tool call Structured assistant content containing call id, tool name, and parsed arguments.
Runtime tool Local executable definition with label, schema, optional argument preparation, execution mode, and execute handler.
Tool result Model-visible content plus structured runtime details and an optional termination hint.
Tool-result message Persisted transcript message paired back to the assistant tool-call id.

Provider normalization

Provider APIs serialize tool calls differently. Pi normalizes those formats into one internal stream shape so the agent loop does not need provider-specific execution logic. Streaming adapters can accumulate partial JSON arguments while the model is still producing them, expose partial updates to the UI, and keep only parsed arguments in the finalized assistant message.

Provider family Typical wire shape Internal representation
Chat-completion style APIs Function-call deltas Streaming tool-call start, delta, and end events.
Response-item APIs Function-call and function-output items Assistant tool-call blocks and tool-result messages.
Block/part-based APIs Tool-use and tool-result content blocks or parts The same assistant/tool-result transcript pair used by the runtime.

Request construction

Before a model can request tools, the active tool set is serialized into the provider request. The schema remains the source of truth for arguments. Provider-level options can influence whether the model may emit tool calls, but they do not decide how local execution is scheduled. Local scheduling is controlled by the agent configuration and per-tool execution mode.

Execution stages

  1. Scan the finalized assistant message for tool-call blocks.
  2. Choose batch mode: sequential if globally configured, or if any requested tool requires sequential execution; otherwise parallel.
  3. Emit a tool-execution start event for each requested call as it enters preflight.
  4. Find the runtime tool by name. Unknown names become immediate error results.
  5. Optionally prepare raw arguments for compatibility before validation.
  6. Validate arguments against the tool schema.
  7. Run the pre-execution hook. A blocking result becomes an immediate error result.
  8. Execute the tool with the validated arguments, abort signal, and optional progress callback.
  9. Convert thrown errors into error tool results.
  10. Run the post-execution hook, which may replace content, details, error state, or termination hint.
  11. Emit tool-execution end, create the persisted tool-result message, and emit message lifecycle events.

Sequential execution

Sequential mode processes the assistant's tool calls in source order. Each call is prepared, executed, finalized, emitted, and persisted before the next call begins. This mode is selected when the agent is configured for sequential execution, or when any tool in the batch declares that it must run sequentially. The latter rule is batch-wide: one sequential-only tool makes the whole batch sequential.

1 Call A requested Validate, execute, record result
2 Call B requested Starts after A is finalized
3 Next model turn Receives A then B
Sequential mode keeps execution order, transcript order, and next-turn input order aligned.
tool_execution_start A
tool_execution_update A, optional
tool_execution_end A
message_start toolResult A
message_end toolResult A
tool_execution_start B
tool_execution_update B, optional
tool_execution_end B
message_start toolResult B
message_end toolResult B
turn_end toolResults [A, B]

Parallel execution

Parallel mode is the default. It still performs preflight sequentially in assistant source order: lookup, argument preparation, schema validation, and the pre-execution hook run deterministically. Calls that fail preflight are finalized immediately as error results. Calls that pass preflight are started concurrently.

Source order A then B The model requested this order.
Execution A and B run together B may finish before A.
Transcript A then B Results are saved in stable source order.
Parallel mode can report completion as it happens while still preserving deterministic replay.

Completion events follow actual completion order. Persisted tool-result messages follow assistant source order. That split lets UI update quickly while keeping transcript replay and the next model request deterministic.

assistant source order: A, B
completion order:       B, A

tool_execution_start A
tool_execution_start B
tool_execution_end B
tool_execution_end A
message_start toolResult A
message_end toolResult A
message_start toolResult B
message_end toolResult B
turn_end toolResults [A, B]

Preflight failures in parallel mode

A failed preflight does not necessarily cancel the entire batch. An unknown tool, invalid arguments, blocked action, or observed abort can become an immediate error result. Other prepared calls can still execute if the batch has not been aborted. The final transcript remains ordered by the assistant's original call order.

assistant source order: A invalid, B valid

tool_execution_start A
tool_execution_end A error
tool_execution_start B
tool_execution_end B success
message_start toolResult A
message_end toolResult A
message_start toolResult B
message_end toolResult B

Progress updates

Tools can emit partial results while running. These partial results produce tool-execution update events for the interface, but they are not persisted as transcript messages. The runtime stops accepting late updates after the tool promise settles, then waits for already queued update emissions before finalizing the call.

Hooks and mutation boundaries

The pre-execution hook receives validated arguments and may block execution. In the coding-agent integration, extension-level tool-call handlers can mutate the input object before execution, and later handlers observe earlier mutations. Those mutations are not revalidated automatically, so hook authors must preserve the schema contract themselves.

The post-execution hook runs after the tool finishes but before final execution-end and tool-result message events. It can replace the visible content, structured details, error flag, or termination hint. Missing fields keep the original values; no deep merge is performed.

Termination semantics

A tool result may include a termination hint. The agent stops after the current tool batch only when every finalized tool result in that batch sets the hint. If any finalized result does not terminate, the agent sends all tool results to the next model request and continues normally.

terminate = finalizedCalls.length > 0
  and every finalized result has terminate === true

Steering, follow-up, and next-turn refresh

Steering messages are not injected in the middle of a tool batch. The loop waits for the assistant response and its tool executions to complete, emits the turn-end event, refreshes next-turn state, checks stop conditions, and then polls steering messages. Follow-up messages are polled only after the agent would otherwise stop because there are no more tool calls or steering messages.

In the coding-agent integration, active tools, system prompt, model, and thinking level are refreshed at the next safe turn boundary. Changing the active tool set during a run affects the next provider request, not the in-flight request or already executing tools.

Persistence and replay

Tool calls and tool results are stored as transcript messages. The assistant message contains the tool-call blocks; later tool-result messages point back to the original call id. Replay and export use that id pairing. Provider adapters may normalize ids when replaying history across providers and must keep matching tool-result ids in sync. If a provider requires every historical tool call to have a matching result, missing results can be synthesized as explicit error results before serialization.

UI event contract

UI layers should treat assistant streaming events and local tool execution events separately. Assistant message updates show the model constructing text and tool-call arguments. Tool execution events show local execution lifecycle.

Event family Use it for
message_start / message_update / message_end Rendering user, assistant, and final tool-result transcript messages.
tool_execution_start Creating a live tool card and showing that preflight has begun.
tool_execution_update Streaming progress output from a running tool.
tool_execution_end Marking local execution complete. In parallel mode this follows completion order.

Failure modes

Failure is represented as data whenever possible. This keeps the model, transcript, and UI in sync.

Failure Runtime behavior
Tool name is unknown Immediate error tool result.
Arguments fail validation Immediate error tool result.
Pre-execution hook blocks Immediate error tool result with the block reason when provided.
Tool throws during execution Thrown error is converted to an error tool result.
Post-execution hook throws Final result becomes an error tool result.
Abort signal is observed Preparation or execution returns an aborted/error result depending on where it is observed.