2026-07-03 01:13:52 +08:00
<!-- Generated by scripts/gen - doc - graphs.ts - do not edit by hand.
Run `pnpm run gen-doc-graphs` to regenerate. -->
# Tool Execution Pipeline
2026-07-23 02:53:43 +08:00
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, definition-owned `finalizeContent` , and `tools/result` are the owner-enforced boundaries around them.
2026-07-03 01:13:52 +08:00
```mermaid
flowchart TD
model["Assistant message contains tool-call block"]
2026-07-05 01:25:58 +08:00
toolCall["Session event: < code > tool/call< / code > < br / > logged before execution"]
2026-07-04 12:50:06 +08:00
presentCall["UI pending card< br / > presentCall(args)"]
2026-07-05 01:25:58 +08:00
pre["< code > tools/pre-execute< / code > waterfall< br / > hooks, permission, sandbox"]
2026-07-11 22:55:40 +08:00
guards["Registered monotonic guards< br / > deny or abstain; identity protected"]
2026-07-11 23:14:09 +08:00
denied["denied or approval refused< br / > tool body skipped"]
2026-07-09 15:25:18 +08:00
approval["< code > ctx.approval< / code > one-shot prompt< br / > absent or unanswerable: deny"]
2026-07-08 10:06:07 +08:00
around["< code > tools/execute< / code > waterfall< br / > timeout, retry, metrics (around dispatch)"]
2026-07-03 01:32:01 +08:00
toolBody["Registered tool execute() body"]
2026-07-05 01:25:58 +08:00
fsGate["< code > fs/write-intent< / code > or < code > fs/edit-intent< / code > < br / > tool-fs mutations only"]
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
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 > "]
2026-07-05 01:25:58 +08:00
post["< code > tools/post-execute< / code > waterfall< br / > accept, block, replace, add context"]
2026-07-23 03:15:15 +08:00
normalized["Registry outer normalization< br / > pipeline/result snapshot throws become isError"]
2026-07-23 02:53:43 +08:00
finalize["ToolDefinition.finalizeContent< br / > last content-only invariant"]
2026-07-13 11:58:55 +08:00
final["< code > tools/result< / code > synchronous notification< br / > frozen authoritative outcome"]
2026-07-23 19:15:45 +08:00
context["Active-batch additionalContexts FIFO< br / > injected user/message after recorded tool results"]
2026-07-05 01:25:58 +08:00
toolResult["Session event: < code > tool/result< / code > < br / > single model-facing outcome"]
2026-07-15 12:49:50 +08:00
allResults["Tool batch settled< br / > recorded tool/result events complete"]
2026-07-04 12:50:06 +08:00
presentResult["UI completed card< br / > presentResult(args, result)"]
model --> toolCall
toolCall --> presentCall
toolCall --> pre
2026-07-11 22:55:40 +08:00
pre -->|allow| guards
guards -->|allow| around
guards -->|deny| denied
2026-07-23 02:53:43 +08:00
guards -.->|throw| normalized
2026-07-08 10:06:07 +08:00
around --> toolBody
2026-07-09 15:25:18 +08:00
pre -->|deny| denied
pre -->|ask| approval
2026-07-11 23:14:09 +08:00
approval -->|allowed-once| guards
2026-07-09 15:25:18 +08:00
approval -->|rejected, cancelled, unavailable| denied
2026-07-23 02:53:43 +08:00
approval -.->|throw| normalized
2026-07-04 12:50:06 +08:00
denied --> post
2026-07-23 02:53:43 +08:00
pre -.->|throw| normalized
2026-07-04 12:50:06 +08:00
toolBody --> fsGate
fsGate --> toolBody
2026-07-03 01:32:01 +08:00
toolBody --> owned
2026-07-08 10:06:07 +08:00
toolBody --> around
around --> post
2026-07-23 02:53:43 +08:00
around -.->|wrapper throws| normalized
post -.->|throw| normalized
post --> finalize
normalized --> finalize
finalize --> final
2026-07-11 22:55:40 +08:00
final --> toolResult
2026-07-04 12:50:06 +08:00
toolResult --> presentResult
2026-07-11 22:55:40 +08:00
toolResult --> allResults
allResults --> context
2026-07-03 01:13:52 +08:00
```
2026-07-23 03:15:15 +08:00
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` , surface denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.
2026-07-05 02:54:01 +08:00
Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs.