Tool Execution Pipeline
August 11, 2026 ยท View on GitHub
This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering run without changing the loop. The tools/pre-execute waterfall runs first, monotonic guards run next, and the tools/execute and tools/post-execute waterfalls follow; the three waterfalls may transform a call. Definition-owned finalizeContent and tools/result run afterward.
flowchart TD model["Assistant message contains tool-call block"] toolCall["Session event: <code>tool/call</code><br/>logged before execution"] presentCall["UI pending card<br/>presentCall(args)"] pre["<code>tools/pre-execute</code> waterfall<br/>hooks, permission, sandbox"] guards["Registered monotonic guards<br/>deny or abstain; identity protected"] denied["denied or approval refused<br/>tool body skipped"] approval["<code>ctx.approval</code> one-shot prompt<br/>absent or unanswerable: deny"] around["<code>tools/execute</code> waterfall<br/>timeout, retry, metrics (around dispatch)"] toolBody["Registered tool execute() body"] fsGate["<code>fs/write-intent</code> or <code>fs/edit-intent</code><br/>tool-fs mutations only"] owned["Tool-owned session events<br/><code>todo/write</code>, <code>fs/observed</code>, <code>hook/invoked</code>, <code>hook/result</code>, <code>tool/code-dispatch</code>"] post["<code>tools/post-execute</code> waterfall<br/>accept, block, replace, add context"] normalized["Registry outer normalization<br/>pipeline/result snapshot throws become isError"] finalize["ToolDefinition.finalizeContent<br/>last content-only invariant"] final["<code>tools/result</code> synchronous notification<br/>frozen authoritative outcome"] context["Active-batch additionalContexts FIFO<br/>injected user/message after recorded tool results"] toolResult["Session event: <code>tool/result</code><br/>single model-facing outcome"] allResults["Tool batch settled<br/>recorded tool/result events complete"] presentResult["UI completed card<br/>presentResult(args, result)"] model --> toolCall toolCall --> presentCall toolCall --> pre pre -->|allow| guards guards -->|allow| around guards -->|deny| denied guards -.->|throw| normalized around --> toolBody pre -->|deny| denied pre -->|ask| approval approval -->|allowed-once| guards approval -->|rejected, cancelled, unavailable| denied approval -.->|throw| normalized denied --> post pre -.->|throw| normalized toolBody --> fsGate fsGate --> toolBody toolBody --> owned toolBody --> around around --> post around -.->|wrapper throws| normalized post -.->|throw| normalized post --> finalize normalized --> finalize finalize --> final final --> toolResult toolResult --> presentResult toolResult --> allResults allResults --> context
Filesystem read-before-edit checks stay below tool-fs on fs/* events. Generic pre/post waterfalls host hooks and approval policy; ctx.approval resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap tools/execute. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition's snapshotted finalizeContent callback enforces its synchronous content-only invariant. tools/result then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. Code Mode sends both the reserved run_code transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log tool/code-dispatch, return denials as binding rejections, and omit additionalContexts to preserve call/result adjacency.
Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs.