# Tool Execution Pipeline
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.
```mermaid
flowchart TD
model["Assistant message contains tool-call block"]
toolCall["Session event: tool/call
logged before execution"]
presentCall["UI pending card
presentCall(args)"]
pre["tools/pre-execute waterfall
hooks, permission, sandbox"]
guards["Registered monotonic guards
deny or abstain; identity protected"]
denied["denied or approval refused
tool body skipped"]
approval["ctx.approval one-shot prompt
absent or unanswerable: deny"]
around["tools/execute waterfall
timeout, retry, metrics (around dispatch)"]
toolBody["Registered tool execute() body"]
fsGate["fs/write-intent or fs/edit-intent
tool-fs mutations only"]
owned["Tool-owned session events
todo/write, fs/observed, hook/invoked, hook/result, tool/code-dispatch"]
post["tools/post-execute waterfall
accept, block, replace, add context"]
normalized["Registry outer normalization
pipeline/result snapshot throws become isError"]
finalize["ToolDefinition.finalizeContent
last content-only invariant"]
final["tools/result synchronous notification
frozen authoritative outcome"]
context["Active-batch additionalContexts FIFO
injected user/message after recorded tool results"]
toolResult["Session event: tool/result
single model-facing outcome"]
allResults["Tool batch settled
recorded tool/result events complete"]
presentResult["UI completed card
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.