Special Topic · Inside DeepSeek Harness

Tool Execution Pipeline: Three-Stage Cascade & Monotone Guard

A three-stage line from pre-execute to post-execute — Guard can only tighten, never allow

THE QUESTION THIS PAGE ANSWERS

ANSWER FIRST

What is the key idea behind “Tool Execution Pipeline: Three-Stage Cascade & Monotone Guard”?

A three-stage line from pre-execute to post-execute — Guard can only tighten, never allow

DECISION RULE

Make the claim earn its place. Use this page as a decision aid, not a definition to memorize. Connect the idea to one real task, one observable result, and one failure that would change your mind.

TRY NEXT

Write one question you could answer with evidence after trying this idea.

WATCH FOR

A conclusion that sounds complete but leaves the key assumption untested.

Course goalAfter this lesson you can explain three things: which three cascade stages a tool call passes in DSH and what each owns; why Guard has no allow in the type system so plugin order cannot overturn a denial; and that a denied call does not vanish — it materializes as a model-visible error result and still finishes the pipeline.
Interactive demo · pipeline run
bash: rm -rf build/
Stage 1 · pre-execute cascade
Vote before entry: allow / deny / ask; listeners reorderable
Guard layer · monotone guard
Only a deny reason or abstain — no allow in the type
Stage 2 · execute cascade
Wrap execution: timeout, retry, metrics
Stage 3 · post-execute cascade
Rewrite before exit: accept / block; swap content or value
Pick a scenario and hit Play, or scroll here to auto-play Scenario A.
Teaching simulation: listener and guard names are course examples; stage order and Guard's monotone semantics map to packages/core/tools/src/index.ts and docs/tool-execution-pipeline.zh.md. Watch one thing while playing: once any step gives a deny reason, nobody later can overturn it.
Mechanism · what each of the three stages owns

State the problem first. Permission checks, human approval, timeouts, result rewriting, UI rendering — all want to hang on the single act of tool execution. If every tool handles it alone, 40 tools mean 40 copies of permission code. DSH makes tool execution a pipeline: policy lives at fixed stations; the tool body does one thing — execute and return a value.

Pipeline order is at line 8 of docs/tool-execution-pipeline.zh.md: tools/pre-execute first, then monotone guards, then tools/execute and tools/post-execute. Waterfall is DSH's listener queue: each listener gets (exec, next) — call next() to pass the decision, or return a decision and settle on the spot.

The three-way split is clear. Stage 1 pre-execute votes before the tool runs — only allow, deny, or ask (human approval). ask continues only with allowed-once from the approval service; without an approval channel it is deny. Stage 2 execute is wrapping: timeout, retry, metrics wrap the real body — it can replace the cancel signal but not the call identity. Stage 3 post-execute checks after the result: accept as-is, swap content, swap value, or block into a corrective error.

Denial is not silence

A denied call materializes as an isError result Error: reason and still walks post-execute and tools/result. The model sees why it was denied — the loop does not stall on one denial.

Parameters cannot change

pre-execute can veto but cannot rewrite parameters. The tool/call event is logged before execution, and the UI pending card already rendered original params — changing them would desync history, UI, and execution (index.ts lines 583–586 type comments spell out this exclusion).

Guard is synchronous final review

Guard runs after every pre-execute vote and before the tool body — a sync function: a string is the deny reason; undefined abstains. Global Guards first, then along the agent's scope chain far to near (index.ts lines 1118–1127).

Core visual · full path of one call
tool/call logged UI renders pending card in sync pre-execute cascade allow / deny / ask ctx.approval approval continue only on allowed-once Monotone Guard deny or abstain — no allow execute cascade timeout / retry / tool body post-execute accept / block deny materializes as Error result skip tool body; still run post-execute after finalizeContent tools/result freezes the final draft
Teaching diagram: path maps to the official flowchart in docs/tool-execution-pipeline.zh.md; node copy is course-adapted.
Guard monotonicity · why the type has no allow

Start with the edge case: two pre-execute listeners — one wants allow, one ask — who wins? Whoever is earlier. Waterfall short-circuits; the first listener that skips next() and returns a decision settles it. So pre-execute is order-sensitive by nature — change plugin load order and the security conclusion may change.

DSH's fix is an order-insensitive final review after pre-execute. Guard's return type has only two shapes: a string (deny reason) or undefined (abstain). No return value can express consent. Register ten Guards or a hundred — however you order them, the conclusion can only get stricter, never looser. The type definition is the evidence:

packages/core/tools/src/index.tslines 703–711
/**
 * A monotonic execution guard evaluated after every `tools/pre-execute`
 * listener and before the tool body. Returning a reason denies the call;
 * returning `undefined` leaves it unchanged. Because guards have no allow
 * result, listener ordering cannot turn a denial back into permission.
 * @param execution - the identity-protected call after extensible pre-execute policy completed.
 * @returns a final denial reason, or `undefined` to leave the call allowed.
 */
export type ToolGuard = (execution: Readonly<ToolExecution>) => string | undefined
Source snapshot note:Based on the local deepseek-harness-master repo; verified against packages/core/tools/src/index.ts, verified on 2026-08-13. Code blocks keep the original source text.

That comment line is worth copying: “guards have no allow result, listener ordering cannot turn a denial back into permission.” A malicious plugin wanting to allow a denied call needs no runtime defense — it cannot write that action in the type system. Cleaner than checking allow rights at runtime; the problem class is erased.

Then what happens after denial. The scheduler settles in two steps: only if pre-execute decided allow (including ask that got approval) do Guards vote one by one; pre-execute deny reasons and Guard deny reasons merge into one variable — either side giving a reason materializes the call as Error: reason. The tool body is never touched, but the result still carries the post-result mark into post-execute and final observers.

So audit plugins and context-injection plugins still work under denial — denial is just another ordinary result for the rest of the pipeline. Tool exceptions and UNKNOWN_TOOL take the same normalization path.

Source: settlement and materialization at packages/core/tools/src/index.ts lines 1486–1499; exception and UNKNOWN_TOOL normalization at 1546–1555, verified on 2026-08-13.

Side-by-side · same slot, three answers from three systems

Claude Code spreads permission checks across Tool interface methods: each tool brings checkPermissions, validateInput, isReadOnly; BashTool also chains allowlists and an ML classifier (study/chapters/02-tool-system.md lines 350–376). External extensions use PreToolUse / PostToolUse hooks. Interestingly DSH ships its own CC hooks bridge packages/hooks/hooks-claude-code, hanging CC hooks on DSH's waterfall — the bridge docs casually expose two protocol gaps:

packages/hooks/hooks-claude-code/README.zh.md · line 92 (known limits)
PreToolUse only supports a subset: deny and ask work; allow does not pre-approve, defer is unsupported, additionalContext is ignored, updatedInput is logged + warned but not applied”

Those two limits have another reason: pipeline invariants are in the way. Native CC hooks can allow-preapprove and rewrite tool params with updatedInput; DSH's bridge downgrades the former and only logs the latter — allow power is not lent out in DSH, and params are immutable after tool/call is logged. The same CC hook config can do less under another host — that measures the two protocols' expressiveness. Multiple CC hooks also fold to the strictest (deny over ask over allow), order-independent (README line 49) — the same spirit as monotone Guard.

Grok Build's hooks system (crates/codegen/xai-grok-hooks) has only pre_tool_use as a blocking point; decisions are Allow or Deny (src/result.rs lines 5–10), and the module comment states failure semantics outright:

grok-build-main/crates/codegen/xai-grok-hooks/src/lib.rs · lines 16–17
“- pre_tool_use hooks can deny/allow (blocking); all others are non-blocking
- Fail-open by default: hook failures do not block normal operation”

Fail-open means if the hook itself crashes or times out, the call still proceeds. DSH flips it: a pre-execute listener exception normalizes the call into an error result — better to over-block. Both stances make sense: Grok treats hooks as optional add-ons that must not take down the main flow; DSH treats policy as a formal station — if the station collapses, the call should not pass. Grok's tool registry and read-only semantics are fully covered on-site in ToolKind provides default read-only semantics.

Classroom Exercise
01

Hand-trace a full rm -rf path

The deploy registers two pre-execute listeners (first the CC hooks bridge with an ask rule; then a allowlist plugin that returns allow for rm) and one sandbox Guard (returns a reason for commands writing outside the workspace). The model fires bash: rm -rf /tmp/x. Q1: does the approval dialog appear? Q2: swap the two pre-execute registration order — does the answer change? Q3: does the sandbox Guard's conclusion depend on that order? Why? (Hint: waterfall short-circuit + line 1486's denialReason only asks Guards after allow.)

Takeaway:Three cascade stages each own a slice: vote before entry, wrap execution, rewrite results before exit. Order-sensitive extensions live in the waterfall; order-insensitive veto goes to Guard — Guard's type has no allow, so once denial stands nobody overturns it. Denial is a first-class result: materialize as Error text for the model, and the pipeline still finishes.

The handoffs inside “Interactive demo · pipeline run”

“State the problem first.” shows that an Agent is not defined by the model alone. Each handoff between model, context, tools, state, permissions, and people affects both progress and recovery.

Write the state before adding capability

Starting from “Pipeline order is at line 8 of docs/tool-execution-pipeline.zh.md : tools/pre-execute first, then monotone guards, then tools/execute and tools/post-execute .”, split the workflow into starting state, next action, tool result, state update, and stop condition. Debugging then means finding the first lost piece of information or authority instead of saying vaguely that the model “got worse”.

A happy path is not reliability

Use “The deploy registers two pre-execute listeners (first the CC hooks bridge with an ask rule;” to replay one successful and one failed run. Record the context, tool result, and owner at each turn; the workflow is maintainable when a second person can follow it without the original builder.

From “Interactive demo · pipeline run” to “Mechanism · what each of the three stages owns”

“Interactive demo · pipeline run” grounds the problem in “Scenario A · All clear Scenario B · Guard blocks Scenario C · Malicious allow plugin Play Step Reset bash: rm -rf build/ Stage 1 · pre-execute cascade Vote before entry: allow / deny / ask; listeners reorderabl…”. “Mechanism · what each of the three stages owns” then moves it toward “State the problem first. Permission checks, human approval, timeouts, result rewriting, UI rendering — all want to hang on the single act of tool execution. If every tool handles it alone, 40 tools mean 40 copi…”. Together, they show that the lesson is not just a conclusion to remember, but a claim with conditions.

Carry the judgment into the next situation

When analyzing an Agent, trace state, action, tool result, and next step in order. Each handoff should explain where information came from, who confirmed it, and where failure stops.

  • “Interactive demo · pipeline run”: Scenario A · All clear Scenario B · Guard blocks Scenario C · Malicious allow plugin Play Step Reset bash: rm -rf build/ Stage 1 · pre-execute cascade Vote before entry: allow / deny / ask; listeners reorderabl…
  • “Mechanism · what each of the three stages owns”: State the problem first. Permission checks, human approval, timeouts, result rewriting, UI rendering — all want to hang on the single act of tool execution. If every tool handles it alone, 40 tools mean 40 copi…
  • “The closing point”: DSH's fix is an order-insensitive final review after pre-execute. Guard's return type has only two shapes: a string (deny reason) or undefined (abstain). No return value can express consent. Register ten Guards…

The final “The closing point” brings the discussion to “DSH's fix is an order-insensitive final review after pre-execute. Guard's return type has only two shapes: a string (deny reason) or undefined (abstain). No return value can express consent. Register ten Guards…”. The useful thing to carry forward is knowing which judgments must be revisited when input, scale, or risk changes.

Mark as learned Your reading progress updates automatically
← PreviousNext →

Keep reading

The next useful article in the thread.

ARTICLE DISCUSSION

Leave one useful thought here.

Keep the idea that clicked, the question that stayed open, or a small note for the next learner.

Discussing Tool Execution Pipeline: Three-Stage Cascade & Monotone Guard Inside DeepSeek Harness
3discussionsArticle discussion · synced with the Circle
View in the learning circle
AM
Asha MorganContent editor
INSIGHTField note

I turned one judgment from this article into a small experiment I could run today. Knowing what to observe next is more useful than simply remembering the conclusion.

ARTICLE DISCUSSION7 helpful
LH
Lin HarperIndie developer
INSIGHTInsight

After reading this, I first looked for the conditions behind the idea instead of copying the method into a project. That order made the later trade-offs much clearer.

ARTICLE DISCUSSION5 helpful
KM
Kiki MooreProduct operations
QUESTIONQuestion

When this judgment reaches real work, which constraint should be added first? I am curious which step matters most between reading and the first practical attempt.

ARTICLE DISCUSSION4 helpful