DeepSeek Harness DSH plugin tool execute contract: values

Plugin DevelopmentPublished 2026-10-07Author: DeepSeek Plugin Market
DSH pluginDeepSeek Harnesstool authoringdefineToolexecute
DSH plugin tool execute contract: the eight defineTool rules. Covers validation, read-only definitions, immutable token and args, canonical JSON, and isError.

A DSH plugin tool is not "fine as long as it runs": the official tool authoring reference lays down eight execute rules, from parameter validation and read-only definitions to return values and error semantics, and breaking any one of them shows up on the real path (source). This article covers the contract clauses themselves — the minimal defineTool form and its four elements are already in tool plugin development, and how tools run inside the pipeline is in the tool pipeline explained, so neither is repeated here.

Parameter validation in a DSH plugin tool: what defineTool checks and what you still must

Parameters are already validated for you: defineTool checks the model-generated arguments against the unified ParameterSchemaSpec before execute runs, so args inside execute matches InferArgs (source). Know the boundaries:

  1. What it covers — types, required keys, literal constraints, unions that match exactly one branch, and nested values.
  2. additionalProperties must be explicit — explicit object nodes must declare additionalProperties: true | false; an implicit parameter root object stays open. Skipping this lets illegal fields slip through silently.
  3. Constraints the DSL cannot express are yours — non-empty strings, positive numbers, and cross-field rules are not expressible in the schema DSL, so check them inside execute.
  4. Raw JSON Schema tools self-validate — a raw JSON Schema ToolDefinition registered directly (which is how MCP-sourced tools arrive) does not go through defineTool validation and owns its own input checks.

Remember one line: defineTool blocks type-and-structure errors; semantic errors are on you.

Read-only definitions and execution identity in a DSH plugin tool

Registration borrows your read-only definition, and execution identity is protected during dispatch (source). Two disciplines:

  1. Do not touch the definition after registering — a typed in-process contribution is not a serialization boundary, so do not modify the schema or replace callbacks afterward; schemas() only materializes the explicit model-visible projection. To hot-swap a tool, dispose its owning effect and register a replacement; mutable state inside the callback closure is still ordinary plugin state and is fine to use.
  2. Execution identity is frozen throughout — in one recursive pass the registry materializes arguments into detached lossless JSON, freezes it before policy starts, and assigns an opaque exec.token. callId, name, arguments, agent, token, the required caller-held signal, and the optional outer-transport parent token stay immutable for the whole dispatch; parent is identity only and never exposes an active outer execution. Treat args as read-only input.

The one exception is exec.signal: only an around-dispatch wrapper receives a mutable view, and it may replace and restore the required exec.signal to impose a deadline, but it can never remove it. To wrap dispatch with a timeout, this is the only route.

What a DSH plugin tool should return: canonical JSON and isError semantics

Declaring and returning a canonical JSON value is the hard interface between the tool and its callers (source).

  1. Return only the derived value — output.schema uses ValueSchemaSpec, whose root may be an object, array, scalar, or null. execute returns only the derived value; the registry snapshots it as lossless JSON, validates and freezes it, then passes it to output.render(args, value).
  2. Do not return content blocks — tool bodies should not return content blocks or force callers to parse ids and fields out of natural language. Doing so breaks both rendering and programmatic use (Code Mode's ToolOutputMap derives from the same schemas).
  3. An exception or invalid value means isError — the registry catches exceptions and converges schema, renderer, metadata projector, and lossless JSON failures before observers run. Throw for infrastructure failures; a successful domain outcome — even an undesirable state like a process exiting non-zero — should be written as a canonical value that its Native renderer explains.

Signal, replay metadata, and async notifications in a DSH plugin tool

The last three rules are about runtime cooperation (source).

  1. Honor exec.signal — cancel in-progress work when it fires. Pass signal down through long I/O (for example readFile(path, { signal })) instead of ignoring it.
  2. Project persisted card data with presentationMeta (optional) — output.presentationMeta(args, value) derives replayable JSON from the same canonical value; the core persists it on tool/result and passes it to presentResult, so result-time facts such as applied hunks from write / edit reappear during replay without persisting the canonical value. Nested Code dispatch has no card and skips this projector.
  3. Send async notifications with exec.agent — agent.inject({ content, source: { kind: 'plugin', plugin: '<name>' } }) appends persisted context that the next model request will see; this is not a wake-up, and an idle agent stays idle. Guard against a disposed agent (wrap in try/catch).

Four self-checks when done: did explicit object nodes declare additionalProperties; does anything still mutate the schema or swap callbacks after registration; does execute return only canonical values instead of assembled text; and is exec.signal passed down through long operations. To compare production-grade implementations, find tool plugins on the DSH Plugin Hub; for how tool results render see the tool card render contract, and for where gates, timeouts, and observation belong see the feature-to-mechanism map.

FAQ

Does a DSH plugin defineTool validate parameters for me, and what is left to do?

A DSH plugin defineTool validates the model-generated arguments before execute runs, using the unified ParameterSchemaSpec — types, required keys, literal constraints, unions that match exactly one branch, and nested values — so args inside execute matches InferArgs. But explicit object nodes must declare additionalProperties: true | false, constraints the schema DSL cannot express (non-empty strings, positive numbers, cross-field rules) still need manual checks, and raw JSON Schema tools you register directly are responsible for their own input validation.

Can a DSH plugin change its schema or swap the execute callback after registration?

No — once a DSH plugin tool is registered it borrows your read-only definition, so you cannot change the schema or swap the execute callback afterward. A typed in-process contribution is not a serialization boundary, and schemas() only materializes the explicit model-visible projection. To hot-swap a tool, dispose its owning effect and register a replacement; mutable state inside the callback closure is still ordinary plugin state.

What should execute return in a DSH plugin tool?

execute in a DSH plugin should return only the derived canonical JSON value: output.schema uses ValueSchemaSpec, whose root may be an object, array, scalar, or null, and the registry snapshots the return value as lossless JSON, validates and freezes it, then passes it to output.render(args, value). Tool bodies should not return content blocks or force callers to parse ids and fields out of natural language.

Should a DSH plugin tool throw on failure or return an error value?

In a DSH plugin tool, throwing an exception or returning an invalid value both mean isError: the registry catches exceptions and converges schema, renderer, metadata projector, and lossless JSON failures before observers run. Throw for infrastructure failures; a successful domain outcome — even an undesirable state like a process exiting non-zero — should be written as a canonical value that its Native renderer can explain.

What are exec.token and exec.signal for in a DSH plugin tool?

In a DSH plugin, exec.token is an opaque, fully immutable execution identity: callId, name, arguments, agent, token, signal, and the optional parent stay immutable throughout dispatch, and args must be treated as read-only input. exec.signal is the only replaceable operational field — only an around-dispatch wrapper may replace and restore it to impose a deadline, and it can never remove the signal; execute must honor it and cancel in-progress work when it fires.

Related Terms

ParameterSchemaSpec
ParameterSchemaSpec is DeepSeek Harness's unified parameter schema description: defineTool validates the model-generated arguments against it before execute runs, covering types, required keys, literal constraints, unions that match exactly one branch, and nested values, after which args matches InferArgs.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
exec.token
exec.token is the opaque execution identity token the registry assigns before dispatch; together with callId, name, arguments, agent, signal, and the optional parent it stays immutable for the whole dispatch, used only for identity and never exposing an active outer execution.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
ValueSchemaSpec
ValueSchemaSpec is the return-value schema specification used by output.schema, whose root may be an object, array, scalar, or null; execute returns only the derived canonical JSON value, which the registry snapshots, validates, and freezes before handing it to output.render.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool
presentationMeta
presentationMeta is the optional output.presentationMeta(args, value) projector that derives replayable JSON from the same canonical value; the core persists it on tool/result and passes it to presentResult, so result-time facts can be reproduced during replay without persisting the canonical value.— https://deepseek-harness.github.io/deepseek-harness/en/reference/cookbook/adding-a-tool

Sources