# Tool Execution Pipeline This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering fit without changing the loop. The transformable extension points are the `tools/pre-execute`, `tools/execute`, and `tools/post-execute` waterfalls; monotonic guards and `tools/result` are the owner-enforced boundaries around them. ```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"] final["tools/result parallel notification
frozen authoritative outcome"] context["Buffered additionalContext
context/message after all tool results"] toolResult["Session event: tool/result
single model-facing outcome"] allResults["All calls in the step settled
and tool/result events recorded"] presentResult["UI completed card
presentResult(args, result)"] model --> toolCall toolCall --> presentCall toolCall --> pre pre -->|allow| guards guards -->|allow| around guards -->|deny| denied around --> toolBody pre -->|deny| denied pre -->|ask| approval approval -->|allowed-once| guards approval -->|rejected, cancelled, unavailable| denied denied --> post toolBody --> fsGate fsGate --> toolBody toolBody --> owned toolBody --> around around --> post post --> final final --> toolResult toolResult --> presentResult toolResult --> allResults allResults --> context ``` Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate; hook bridges and approval-triggering permission policy enter through the generic pre/post tool waterfalls, while `ctx.approval` resolves an `ask` before the monotonic guards; owner policy that must not be reordered uses registered guards; and around-dispatch concerns like the tool-call timeout policy (`@deepseek-ai/dsh-timeout-policy`) wrap core dispatch on `tools/execute`. The awaited `tools/result` notification observes the immutable final outcome after every transform, lossless-JSON validation, and outer error normalization. That split lets the same hooks observe bash, fs, web, todo, skill, and subagent calls without coupling those tools to one policy service. Code Mode rides the whole pipeline twice over: `run_code` is the reserved registry-owned transport whose body enters the pipeline, and each tool call its program makes re-enters `ctx.tools.execute()` — serialized one at a time, carrying the outer execution's opaque token for correlation, and logged as a `tool/code-dispatch` session event, with a deny surfacing to the program as a binding rejection (a sub-call's `additionalContext` is deliberately dropped — no safe outlet mid-run preserves call/result adjacency). Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs.