ctx.approval (dsh-approval): request() dispatches the approval/request waterfall and always resolves a closed outcome — allowed-once / rejected / cancelled / unavailable — never rejects; zero listeners fall through to fail-closed unavailable; abort settles cancelled and discards late answers; throwing or rogue answerers are contained as unavailable; every ask lands the log-only approval/asked / approval/decided audit pair. dsh-tools routes a pre-execute ask through the seam opportunistically (ctx.get) with three distinct deny reasons, keeping the historical ask→deny degrade when the seam is absent. The per-session policy tier, the ACP bridge answerer, and the sandbox escalation asker are staged follow-ups of the approval-seam RFC.
2.5 KiB
Tool Execution Pipeline
This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, and UI rendering fit without changing the loop. The key extension points are the tools/pre-execute, tools/execute, and tools/post-execute waterfalls.
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"]
denied["denied<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"]
context["Buffered additionalContext<br/>context/message after all tool results"]
toolResult["Session event: <code>tool/result</code><br/>single model-facing outcome"]
presentResult["UI completed card<br/>presentResult(args, result)"]
model --> toolCall
toolCall --> presentCall
toolCall --> pre
pre -->|allow| around
around --> toolBody
pre -->|deny| denied
pre -->|ask| approval
approval -->|allowed-once| around
approval -->|rejected, cancelled, unavailable| denied
denied --> post
toolBody --> fsGate
fsGate --> toolBody
toolBody --> owned
toolBody --> around
around --> post
post --> context
post --> toolResult
toolResult --> presentResult
Filesystem read-before-edit checks live below tool-fs on the fs/* event gate; hook bridges and the approval seam's permission prompts live on the generic pre/post tool waterfalls; and around-dispatch concerns like the tool-call timeout policy (@deepseek-ai/dsh-timeout-policy) wrap core dispatch on tools/execute. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.
Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs.