DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Tool System·Chapter 16

Tool Definition Primitives and the Execution Pipeline

Schemas, scope, execution modes, presentation, and waterfalls

VerifiedUpstream 47f943859bScope: Dissect ToolDefinition, registry snapshots, pre/guard/around/post/finalize/result, and Code Mode subcalls.

Conclusion: ToolRuntime Is Not a Function Table; It Is the Transaction Boundary That Commits Untrusted Model Calls into Replayable Results

DeepSeek Harness's tool core has five simultaneous responsibilities: resolve what each Agent scope can see; project definitions into model schemas; mint an unforgeable execution identity for every call; order pre-policy, approval, monotonic guards, around wrappers, the body, post-policy, and a tool-owned finalizer; and publish exactly one lossless, frozen authoritative result. The Agent loop owns durable top-level call/result events, while UI presenters reconstruct cards from the same durable outcome.

The important property is not merely “it has hooks.” Each layer has a different mutation budget: pre-policy cannot rewrite already-logged arguments; guards may only deny; around wrappers may temporarily replace the signal and wrap the body; post-policy may replace the canonical value or model content; the finalizer may change only content; result observers may change nothing.

Generated control flow

The generated execution graph and production source agree on pre → guards → around/body → post → normalization → finalization → frozen result, with Session logging and UI projection at owning boundaries outside the registry pipeline. TOOL-PIPELINE

1. One Tool Definition Contains Four Contracts

ContractFieldsConsumerSent to the model?
Invocationname, description, parametersPrompt and Provider adapterYes
Canonical outputoutput.schema, output.render, optional presentationMetaToolRuntime, Code Mode, UI replayThe output schema is not a native tool schema; rendered content enters history
Executionexecute, timeoutMs, isConcurrencySafeRegistry and external policy/schedulerNo
PresentationpresentCall, presentResult, finalizeContentHost/UI and final commit stagePresenters are hidden; finalized content enters model history
Definition shape

A successful body must return a canonical lossless JSON value. The registry validates it with the output schema, then calls pure render(args,value) to produce provider-neutral ContentBlocks; a direct top-level call may also project durable JSON metadata. TOOL-DEFINITION

2. Model Schema, Execution Value, Durable Content, and UI Metadata Are Different Data

model args string
→ parsed + lossless snapshot + frozen arguments
→ execute() returns canonical JSON value
→ output.schema validates value
→ output.render(args, value) produces model-facing ContentBlocks
→ presentationMeta(args, value) optionally produces replayable UI JSON

execution-local result: { value, content, meta? }
durable tool/result:     { content, isError, errorInfo?, meta? }
future model Surface:    content only
Data ownership

A successful in-process ToolExecutionResult carries a frozen value, but the top-level Session event deliberately persists only content, failure state, and optional metadata. The canonical value is not guaranteed reconstructable from the log. UI structure that cannot be losslessly recovered from prose must be projected explicitly through presentationMeta. TOOL-RESULT-TYPESTOOL-CANONICAL-OUTPUTTOOL-LOOP-COMMIT

This lets Code Mode pass a structured value back to a program for further computation while the main model sees only controlled rendered text. It also prevents a UI from reverse-parsing natural-language results to recover diffs, line numbers, or web sources.

3. The Schema DSL Is an Enforced JSON Schema Subset, Not the Complete Standard

SupportedRuleExplicitly not done
Scalarsstring/number/integer/boolean/null and type-correct enum/constKeywords such as pattern, minimum, and format are rejected
Containersarray/items; object/properties/required/boolean additionalPropertiesAn explicit object must declare openness to avoid an implicit default
UnionExact-one oneOf with at least two branchesConflicting sibling constraints beside oneOf are rejected
Any JSONThe author DSL's type: "json" compiles to an unconstrained nodeThe value must still be lossless JSON
Annotationsdescription/title/default/examplesDefaults are annotations and are never applied
Fail loud

The raw-schema walker rejects unknown or misplaced keywords, cycles, sparse or decorated arrays, invalid required lists, and non-lossless annotations. Compilation and validation use explicit task stacks instead of recursive JavaScript calls. TOOL-SCHEMA-SUBSET

The parameter root compiles as an implicit open object; every nested explicit object must state additionalProperties: true|false. “Tool parameters reject extra keys by default” is therefore false unless the author closes the relevant explicit object.

4. Only defineTool() Automatically Validates Arguments

Helper contract

defineTool() compiles parameter and output schemas, wraps execute, runs validation before the body, and turns path-qualified violations into ToolArgsError(code: INVALID_ARGS). It also softly validates presenters and the concurrency classifier: mismatched historical arguments return undefined or false instead of crashing replay or scheduling. TOOL-DEFINE-HELPER

Direct-registration boundary

ToolRuntime.register() validates the output schema and timeout metadata but does not apply the parameters schema to each invocation. Execution creation only takes a lossless snapshot and freezes arguments. A third-party raw ToolDefinition must validate its own input. A model-visible schema is a prompting contract, not automatically a runtime input guard. TOOL-REGISTRATIONTOOL-PIPELINE-PREPARE

This is not a gap in first-party tools, which use the helper, but it is an important extension-author trust boundary. Treating raw registration as “the registry validates for me” sends arbitrary JSON into the body.

5. Scope Resolution Builds Inherited Capability, Then Applies Restrictions and Local Shadowing

global tools
→ ancestor scope registrations (nearer shadows farther)
→ intersect every restriction along the scope chain
→ exact scope's own registrations (exempt from inherited filter)
→ reserved run_code transport when mode requires it
Visibility resolver

A duplicate name in one layer fails; a scoped registration may shadow an inherited or global definition. Restrictions require a scoped context and filter inherited capability only, preserving the exact scope's reporting and structured-output machinery. Multiple allow/deny filters intersect, and unknown names or the reserved transport fail loudly. TOOL-SCOPE-VIEW

get(), model schemas, the generated SDK, and dispatch share this view, reducing “the prompt showed A but execution selected B” drift. Definitions and restrictions are live effects, however: a queued call can observe a registry change before it actually starts. Chapter 18 follows that reclassification behavior.

6. Native, Code, and Both Change the Model Entry Point, Not the Tool Implementations

ModeProvider toolsPrompt additionDirect-execution boundary
nativeAll visible schemasOrdinary per-tool guidanceThe model directly calls a visible tool
codeOnly reserved run_codeCode-only rule plus typed SDKA direct native name becomes UNKNOWN_TOOL before policy; an SDK subcall may invoke it
bothVisible native schemas plus run_codeTyped SDK without the code-only ruleBoth entry forms are callable
One source view

Every prompt assembly reprojects name, description, and parameters for the calling scope and allowlists only those three fields. The Code SDK additionally consumes output schemas for typed returns, while run_code stays outside capability filtering. TOOL-PRESENTATION-MODESTOOL-SCHEMA-PROJECTION

In code-only mode, native names remain visible to the SDK catalog, but a model-direct execution is deterministically rejected before pre hooks, approval, and guards. Policy cannot accidentally approve an entry point that can never run, and the error tells the model to call it inside run_code.

7. Every Call Receives an Unforgeable Identity Before Policy

FieldSourcePurpose
callIdProvider tool-call block or composite dispatcherSession call/result correlation
rootCallIdDefaults to callId for a root and propagates down a nested treeCorrelate one composite execution tree
tokenA same-process Symbol minted by the registryProve nested parentage and that a canonical result belongs to this execution
agentExplicitly supplied by the Agent loop; optionalScope routing, approval, and Session ownership
signalOwned by the callerCancellation that an around wrapper cannot detach
Materialization

Before policy, the registry losslessly snapshots, detaches, and deep-freezes parsed arguments. Public execution fields are readonly, and the complete execution object is frozen before final observers receive it. Code Mode gives a nested dispatcher only the opaque parent token rather than exposing the mutable outer execution. TOOL-EXECUTION-IDENTITY

8. The Complete Pipeline Gives Each Extension Point a Different Authority

StagePermitted actionAfter failure or denial
tools/pre-executeallow / deny / ask through an async waterfallA denial becomes an error result that can still reach post
Approval seamResolve ask into allowed-once or a specific denialNo service, no Agent, or unavailable channel fails closed
Monotonic guardsSynchronously return the first denial reasonNo allow return exists, so another guard cannot reverse denial
tools/executeWrap the next stage and replace the signal for this wrapper's lifetimeA wrapper throw becomes a final error
Tool bodyReturn a canonical value, defer context, conclude a TurnA body throw is normalized and still reaches post
tools/post-executeAccept, replace value or content, block, add contextA throw becomes a final error
finalizeContentSynchronously replace content only, for successes and failuresA throw becomes a final error; the finalizer is not called recursively
tools/resultObserve the frozen authoritative snapshotObserver failures are logged and contained
Production implementation

The scheduler-facing prepare/dispatch/finalize/finish view merely splits this pipeline into ordered policy, overlap-eligible body work, and ordered post/commit. Ordinary ToolRuntime.execute() still composes them into one result. TOOL-PIPELINE-PREPARETOOL-PIPELINE-DISPATCHTOOL-PIPELINE-FINALIZE

9. Pre-Policy and Guards Deliberately Cannot Rewrite Arguments

The model's raw argument string has already been appended as tool/call, and the UI uses it for the pending card. If a pre hook could replace arguments, the log would claim A ran while the body received B, breaking replay and audit. PreToolDecision therefore has no rewrite arm.

Monotonic authorization

The waterfall may request approval, but a final allow must still pass every synchronous guard from global through the scope chain. A guard can return a denial reason or abstain; listener order cannot elevate an existing denial back into permission. TOOL-DECISIONSTOOL-SCOPE-VIEW

Chapter 19 covers approval in detail. The core property here is that extensible policy may propose an allow, while an owner guard retains an irreversible final veto.

10. An Around Wrapper May Replace the Signal but Cannot Sever Caller Cancellation

tools/execute hosts around concerns such as timeouts, retries, and metrics. A wrapper may replace mutable exec.signal while delegating. Before entering the body, ToolRuntime fuses that signal with the original caller signal; after settlement it removes listeners and restores the wrapper view.

Quiescence

Cancellation never abandons a started Promise. The registry waits for the body to settle, then replaces an otherwise successful result with ABORTED; if the body never began, it uses ABORTED_BEFORE_DISPATCH. Same-process code cannot be hard-killed, so tool authors must forward the signal. TOOL-PIPELINE-DISPATCHTOOL-CANCELLATION

11. A Successful Value Crosses Three Boundaries Before Becoming Canonical

1

Lossless snapshot

Reject undefined, BigInt, cycles, sparse arrays, non-finite numbers, negative zero, and exotic graphs.

2

Output schema

Produce all path-qualified violations; failure becomes INVALID_TOOL_OUTPUT.

3

Pure projections

Snapshot render and optional presentationMeta again; a projection throw is also an output error.

4

Per-execution mark

A WeakMap ties the result to the registry token, preventing an around wrapper from impersonating a validated result.

Renormalization

If an around wrapper returns a success not marked for this execution, the registry trusts only its value and reruns the current tool's output validation and rendering. Wrapper-authored content cannot bypass the canonical projection. Error results are normalized separately. TOOL-CANONICAL-OUTPUT

12. Post-Policy May Replace Value or Content, but Never Both at Once

DecisionOutcomeRevalidation
accept without replacementKeep body or denial result and append contextsThe existing canonical result remains
accept {value}Replace a successful canonical valueRerun output schema, render, and metadata; a failure cannot become a value
accept {content}Replace model-facing blocks onlyDo not reinterpret the canonical value
block {feedback}Produce isError; feedback supplies content and the messageKeep only contexts explicitly supplied by the blocker
Authority boundary

Body-deferred contexts precede post-policy contexts on accept. Block discards body contexts so a policy-rejected result cannot still inject a hidden instruction into the next request. TOOL-POST-POLICY

Content replacement is a deliberate presentation and policy seam. It supports redaction, spill previews, or corrective feedback without fabricating a new canonical value. The cost is that durable text no longer proves the body's original rendering; earlier evidence needs telemetry or tool-owned events.

13. finalizeContent Is the Tool-Owned Last Invariant

The finalizer is captured from the then-visible definition when the call starts, preventing an argument getter or hot reload from replacing it during snapshotting. It is attempted exactly once for every normalized outcome, including pre, around, and post failures. It may return replacement content only; it cannot alter isError, value, metadata, contexts, or concludesTurn.

Final commit stage

The registry materializes the candidate, runs the captured finalizer, materializes again, and deep-freezes the outcome. A finalizer throw becomes an ordinary error result. tools/result observers then receive frozen execution and result objects; synchronous or asynchronous observer failure cannot alter the returned outcome. TOOL-PIPELINE-FINALIZE

Why it is last

A tool can guarantee that every content value leaving the registry satisfies its own final format or redaction rule, beyond generic post-policy. Conversely, a faulty finalizer replaces the original error text with its own error, so audit may require earlier telemetry or events.

14. Additional Context and Conclude-Turn Are Control Signals Carried by the Result

A body may call exec.deferContext(userMessage), and post-policy may attach contexts. Neither appends directly from inside the body. They return with the final result to the Agent loop. The loop writes the corresponding tool/result first, then places contexts into the active-batch FIFO; the next Step commits them as user messages retaining their own source and metadata.

Turn control

exec.concludeTurn() creates concludesTurn:true only on a final successful result. If the body later fails or post-policy blocks it, the Turn is not concluded. The scheduler aggregates the marker only after committing that result in model order. TOOL-EXECUTION-IDENTITYTOOL-LOOP-COMMIT

This keeps side-channel context out of call/result adjacency and forces a composite transport to ferry nested context or terminal intent through its parent result rather than mutating the parent Agent's inbox directly.

15. The Agent Loop Owns Top-Level Durability; the Registry Itself Does Not Write the Session

assistant/tool_call block
→ append tool/call (raw argument string)
→ ToolRuntime staged pipeline
→ append tool/result (final content + isError + error info + meta)
→ accept result.additionalContexts
→ next Step boundary
Commit ownership

The loop appends tool/call before any pre-policy, preserving raw Provider arguments. Invalid JSON, denial, or a body that never starts can still receive one paired error result. The result's sourceEventSeqs cites the exact call event. TOOL-LOOP-COMMIT

An in-process component that calls ctx.tools.execute() directly receives a frozen result but does not automatically create top-level Session events. An Agent-less call also cannot route approval and fails closed on ask. Code Mode is the exception: the loop logs the outer run_code, while its bridge appends separate log-only nested dispatch events.

16. UI Presenters Emit Provider-Neutral Render Intent and Must Survive Replay

PhaseOptional intentFallback
Pending callgeneric / terminal / diffA generic card with tool name and raw arguments
Completed resultgeneric / terminal / diff / search / read / webKeep the pending title and render raw result content
Historical arguments no longer validatedefineTool soft validation returns undefinedGeneric replay
Presenter throws or call is missing across a pageThe Host contains it or cannot pair itThe event still ships without a view
Replay contract

A presenter may depend only on arguments and the durable result because both live UI and historical replay invoke it. Shapes such as read line numbers or web sources that cannot be reconstructed from model text must first enter durable metadata and then be interpreted by presentResult. TOOL-PRESENTATION-INTENTSTOOL-HOST-PRESENTER

17. Failure Normalization Depends on Whether the Body Started and Which Layer Failed

Failure pointRuns post?Typical final code / content
Code-only direct collapseNoUNKNOWN_TOOL with a route through run_code
Arguments are not lossless JSONNoOrdinary error result; body never starts
Pre denial / approval denial / guard reasonYesError: reason; post may block or replace content
Pre or guard throwsNoThe exception is normalized as a final error
Unknown tool / body throw / invalid outputYesStructured HarnessError info when present plus Error text
Around wrapper throwsNoFinal error
Post throwsIt failed inside postThe finalizer still runs, then publishes an error
Finalizer throwsDoes not re-enter postThe finalizer error becomes authoritative
Caller abortsDepends on reached stageABORTED_BEFORE_DISPATCH or ABORTED

errorMessage() also has a total fallback for hostile thrown values, preventing error normalization from throwing again. Success and failure both end as lossless frozen snapshots, so ordinary tool failures do not require exception control flow at the caller. TOOL-DECISIONSTOOL-CANCELLATION

18. The Runtime Invariant Proves Ordering and Freezing, Not Business-Policy Correctness

Companion checks

The invariant observes internal dispatch, requires legal pre → execute → post ordering for one execution, and checks that execution, result, and content are frozen with nonempty identity at tools/result. For nested Code Mode events it also checks root/parent lineage and open-Turn enclosure. TOOL-RUNTIME-INVARIANT

It does not prove that a raw tool validates against its parameter schema, that the timeout wrapper is mounted, that an isConcurrencySafe claim is operationally true, that a presenter is pure, or that policy's allow/deny decision matches an organization's rules. The invariant protects protocol shape; it does not replace capability-owner policy tests.

19. Confirmed Boundaries, Dynamic Windows, and Design Tradeoffs

FindingClassificationConsequence
Raw ToolDefinition inputs are not automatically schema-validated by the registryExtension trust boundaryThird-party authors must validate or use defineTool
timeoutMs requires a separate policyComposition boundaryA missing wrapper means no deadline
Pre-policy cannot rewrite argumentsDeliberate audit tradeoff“Repairing” input requires denial and model retry
Canonical values do not enter the durable logData-minimization and Code Mode tradeoffReplay recovers content and metadata, not structured intermediates
The Code runtime is read separately during prompt assembly and executionDocumented dynamic windowA cross-language hot swap could run code written for the old SDK in the new runtime
Definitions and registry entries are live effectsDynamic-composition tradeoffA capability can disappear or be reclassified after queueing and before start
Same-process bodies support cooperative cancellation onlyExecution-model limitationA body that ignores the signal delays quiescence
Code-runtime window

Source comments explicitly state that assembly and run_code execution read the current runtime separately. This is harmless with one stable backend, but a cross-language hot reload can create a schema/runtime mismatch; binding the runtime to the request is deferred until a second backend makes the case testable. TOOL-CODE-RUNTIME-WINDOW

20. Design Assessment and Verification Status

ChoiceBenefitCost
Separate canonical value from model contentClear structured orchestration, controlled context, and UI metadataReplay cannot recover the execution-time value
Separate capability view from the policy pipelineOne implementation composes and restricts per Agent scopeA live registry makes temporal boundaries more complex
Give every stage a distinct mutation budgetExtensibility does not become arbitrary rewritingAuthors must know which hook solves which concern
Lossless snapshots and a frozen final resultLogs, observers, and replay resist later mutationLarge JSON graphs incur material copying cost
Caller-owned durable loggingThe registry works for Agent-less and composite scenariosDirect callers that need audit must provide their own carrier
Verification scope

This chapter cross-checked ToolDefinition, the schema DSL and raw validator, scope layers, presentation modes, execution identity, every pipeline stage, Agent-loop top-level commit, Host presenter fallback, and the tools invariant, plus the generated execution graph and core README. Upstream dependencies are not installed, so no upstream test execution is claimed. Later chapters cover the parallel pool, approval policy, and the Code Mode worker in depth.

My Learning Notes

Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.