Files
deepseek-harness/docs/tool-catalog/tools.md
Tianyi Cui 1d43ea3cd5 workflow: dynamic workflows — script-driven multi-agent orchestration
A new capability family at packages/workflow/ in the bash seam shape,
modeled on Claude Code's dynamic workflows: the model writes a JavaScript
orchestration script (export const meta = {...} + plain-JS body), a runtime
executes it, and the script — not the conversation — holds the loop, the
branching, and the intermediate results.

- dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary
  (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events
  carrying data snapshots (id + meta, never the live run), per-listener
  contained like subagent/*.
- dsh-workflow-vm: in-process node:vm engine. Meta extraction via a
  string/comment-aware scanner (template interpolation rejected; literal
  evaluated alone in an empty timed context; statement blanked line-
  preservingly so stacks keep script line numbers). Hooks: agent(prompt,
  {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline()
  (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline:
  hook misuse (unknown/deferred options, bad arguments, unsupported
  schemas, tripped caps, seam start failures, cancellation) throws fatal
  WorkflowErrors the combinators RE-THROW — never dissolved into the
  per-item null reserved for child failures. Realm boundary: inbound values
  materialized by descriptor walks that never invoke accessors (defineProperty
  copies, __proto__-safe); outbound values rebuilt in-realm via the
  context's own JSON.parse. Determinism bans (Date.now/Math.random/argless
  new Date) kept so future resume support cannot break scripts. Caps and
  timeouts are validated Config. Every hook promise carries a no-op
  rejection consumer (app-boot exits on unhandled rejections).
- dsh-tool-workflow: the model-facing workflow tool, synchronous like
  dsh-tool-subagent (start → await → try/finally dispose; abort bridged;
  non-completed → isError). Generic render card titled by a textual
  meta.name sniff. The tool description carries the authoring contract.

Wired into examples/{coding-agent,acp-agent} with explicit-ask-only
guidance. Coverage at every tier: unit (meta scanner, materializer incl.
counting-getter and __proto__ regressions, combinator semantics,
concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon),
integration over the real spawn stack, with-key e2e (real two-phase run +
the tool through the registry pipeline), and a recorded ACP snapshot
scenario (workflow-run, 1 child session). RFC:
docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred
work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's
layout line.
2026-07-05 13:29:35 +08:00

14 KiB

Tool Schema Catalog

Every model-facing tool a shipped plugin contributes to ctx.tools: the name, description, and JSON-Schema parameters the model receives via the system-prompt assembly. It complements the cordis events & services catalogs (the wiring a plugin listens to and calls) and core-data-structures/ (the types those signatures move) — this page is the tools the agent is offered.

This file is GENERATED and verified fresh by pnpm run verify-tool-catalog (part of doc-sync) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads ctx.tools.schemas(), because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs packages/*/tool-* and fails if any package is missing from the generator's boot manifest, so a new tool cannot be silently undocumented. See the tool-schema-catalog RFC.

Scope: shipped product tools under packages/*/tool-*, each booted with its DEFAULT config. The registered tool NAME can be a load-time config (e.g. tool-subagent's toolName), so a deployment may surface a package under a different or additional name — a per-package note records those shipped aliases where they exist. The examples/ demo tools (e.g. echo) are excluded, matching the cordis catalog's packages-only scope.

@deepseek-ai/dsh-tool-bash

bash

Execute a bash command (bash -c) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass workdir instead of using cd. Non-zero exits are reported as [exit code: N]. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set run_in_background: true for long-running commands: the call returns a task id immediately; poll it with bash_output and stop it with bash_kill.

{
  "type": "object",
  "properties": {
    "command": {
      "type": "string",
      "description": "The bash command to execute."
    },
    "description": {
      "type": "string",
      "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."
    },
    "timeoutMs": {
      "type": "number",
      "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."
    },
    "workdir": {
      "type": "string",
      "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."
    },
    "run_in_background": {
      "type": "boolean",
      "description": "Run in the background and return a task id immediately. No timeout applies."
    }
  },
  "required": [
    "command",
    "description"
  ]
}

Source: packages/bash/tool-bash/src/index.ts

bash_kill

Ask the executor to kill a running background bash task by task id.

{
  "type": "object",
  "properties": {
    "task_id": {
      "type": "string",
      "description": "Task id returned by the bash tool."
    }
  },
  "required": [
    "task_id"
  ]
}

Source: packages/bash/tool-bash/src/index.ts

bash_output

Read new output from a background bash task started with bash + run_in_background. Returns only output produced since the previous bash_output call, plus the task status. Tasks keep running while you do other work; poll again later for more output.

{
  "type": "object",
  "properties": {
    "task_id": {
      "type": "string",
      "description": "Task id returned by the bash tool."
    }
  },
  "required": [
    "task_id"
  ]
}

Source: packages/bash/tool-bash/src/index.ts

@deepseek-ai/dsh-tool-fs

edit

Edit an existing UTF-8 text file by replacing literal text.

{
  "type": "object",
  "properties": {
    "file_path": {
      "type": "string",
      "description": "Path to edit, resolved by the filesystem backend."
    },
    "old_string": {
      "type": "string",
      "description": "Literal text to replace. Must match exactly."
    },
    "new_string": {
      "type": "string",
      "description": "Literal replacement text. Use an empty string to delete the match."
    },
    "replace_all": {
      "type": "boolean",
      "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
    }
  },
  "required": [
    "file_path",
    "old_string",
    "new_string"
  ]
}

Source: packages/fs/tool-fs/src/index.ts

read

Read a UTF-8 text file and return line-numbered content.

{
  "type": "object",
  "properties": {
    "file_path": {
      "type": "string",
      "description": "Path to read, resolved by the filesystem backend."
    },
    "offset": {
      "type": "number",
      "description": "1-based first line to return. Defaults to 1."
    },
    "limit": {
      "type": "number",
      "description": "Maximum number of lines to return. Defaults to 2000."
    }
  },
  "required": [
    "file_path"
  ]
}

Source: packages/fs/tool-fs/src/index.ts

write

Create or fully replace a UTF-8 text file.

{
  "type": "object",
  "properties": {
    "file_path": {
      "type": "string",
      "description": "Path to write, resolved by the filesystem backend."
    },
    "content": {
      "type": "string",
      "description": "Full UTF-8 text content to write."
    }
  },
  "required": [
    "file_path",
    "content"
  ]
}

Source: packages/fs/tool-fs/src/index.ts

The read-before-write/edit policy is added by @deepseek-ai/dsh-fs-policy (an fs/* event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin.

@deepseek-ai/dsh-tool-subagent

subagent

Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation.

{
  "type": "object",
  "properties": {
    "description": {
      "type": "string",
      "description": "A short (3-5 word) description of the delegated task, for display."
    },
    "prompt": {
      "type": "string",
      "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
    }
  },
  "required": [
    "description",
    "prompt"
  ]
}

Source: packages/subagent/tool-subagent/src/index.ts

The registered tool name is the load-time toolName config (default subagent); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees subagent_fork (bound to the fork backend) with an identical schema — see examples/coding-agent/cordis.yml and examples/acp-agent/cordis.yml.

@deepseek-ai/dsh-tool-todo

todo_write

Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Keep AT MOST ONE todo in_progress at a time; while work remains, exactly one active task should be in_progress. Mark a todo completed the moment it is done (do not batch completions), and allow no in_progress item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: pending (not started), in_progress (being worked on now), completed (finished).

{
  "type": "object",
  "properties": {
    "todos": {
      "type": "array",
      "description": "The COMPLETE task list, replacing any previous list.",
      "items": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "description": "What the task is — a short imperative line."
          },
          "status": {
            "type": "string",
            "description": "pending (not started) | in_progress (now) | completed (done).",
            "enum": [
              "pending",
              "in_progress",
              "completed"
            ]
          }
        },
        "required": [
          "content",
          "status"
        ]
      }
    }
  },
  "required": [
    "todos"
  ]
}

Source: packages/todo/tool-todo/src/index.ts

@deepseek-ai/dsh-tool-workflow

workflow

Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.

The script MUST begin with export const meta = {...} — a PURE object literal (no variables, calls, or template interpolation) with required name (short kebab-case) and description strings, optional whenToUse string and phases array ({title, detail?, model?}). The body after it is plain JavaScript (NOT TypeScript) running with top-level await; end with return <value> — the value must be JSON-serializable and is this tool's result.

Script-body hooks:

  • agent(prompt, opts?): Promise<any> — run one subagent to completion. Without opts.schema it resolves to the child's final text; with opts.schema (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const — no oneOf/pattern/format/numeric bounds) it resolves to the validated object. Resolves null when the child fails (filter with .filter(Boolean)). Other opts: label (display), phase (progress group), model (override). Anything else (effort/isolation/agentType) is rejected loudly.
  • pipeline(items, ...stages): Promise<any[]> — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives (prev, item, index). An ordinary stage throw drops that ITEM to null and skips its remaining stages.
  • parallel(thunks): Promise<any[]> — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to null.
  • phase(title) — start a progress phase; log(message) — narrate progress; args — the tool call's args input, verbatim.

Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item null.

Constraints: concurrency and total-agent caps apply; Date.now(), Math.random(), and argless new Date() throw (pass timestamps via args); no filesystem, network, timers, or Node.js APIs — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.

{
  "type": "object",
  "properties": {
    "script": {
      "type": "string",
      "description": "The complete workflow script: `export const meta = {...}` followed by the plain-JS body (top-level await allowed; end with `return <json-value>`)."
    },
    "args": {
      "type": "object",
      "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]})."
    }
  },
  "required": [
    "script"
  ]
}

Source: packages/workflow/tool-workflow/src/index.ts

@deepseek-ai/dsh-tool-web

web_fetch

Fetch the content of a specific HTTP(S) URL and return it decoded to text.

{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "The HTTP(S) URL to fetch."
    },
    "timeout_ms": {
      "type": "number",
      "description": "Optional fetch timeout in milliseconds (capped by the provider)."
    }
  },
  "required": [
    "url"
  ]
}

Source: packages/web/tool-web/src/index.ts

Search the web for current information. Returns an optional summary answer and a list of source URLs.

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "The search query."
    }
  },
  "required": [
    "query"
  ]
}

Source: packages/web/tool-web/src/index.ts