Files
deepseek-harness/docs/tool-catalog.md
_Kerman 97e7d68340 Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts:
#	examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl
2026-08-05 10:39:18 +08:00

67 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 Agent Note.

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.

Tool Package Map

This table connects model-visible tool names to the plugin package and service seams behind them. Exact JSON Schemas follow in the package sections below.

Tool package Model-visible names Requires Writes / affects Shipped aliases Deployment note
@deepseek-ai/dsh-tool-ask-user ask_user_question ctx.tools, ctx.userInteraction tool/call, tool/result after a UI/provider answers the question - ask_user_question pauses the tool call until the active UI provider returns a human answer.
@deepseek-ai/dsh-tools run_code ctx.tools, ctx.codeRuntime (execution time), ctx.systemPrompt tool/call, one tool/code-dispatch-start + tool/code-dispatch pair per bridged sub-call, tool/result - Owned by the tool registry as a reserved transport outside filterable capability layers under mode: code / mode: both (see the Code Mode Agent Note). Under code it is the registry's only wire contribution; the other visible capabilities are declared in a generated TypeScript SDK section, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to maxParallelSubCalls) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.
@deepseek-ai/dsh-plan-mode exit_plan_mode ctx.tools, ctx.systemPrompt, ctx.userInteraction (execution time, opportunistic) tool/call, plan/mode inactive on an approved review, tool/result - exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.
@deepseek-ai/dsh-tool-bash bash ctx.tools, ctx.bash, ctx.systemPrompt, ctx.bashEnv, ctx.tasks at call time for run_in_background tool/call, tool/result - The bash tool is the model-facing consumer of the bash executor seam. A run_in_background run registers with the generic ctx.tasks runtime and is collected/stopped through the task_* tools from @deepseek-ai/dsh-tool-tasks; the enableRunInBackground config (default true) removes the parameter entirely when disabled.
@deepseek-ai/dsh-tool-pwsh pwsh ctx.tools, ctx.bash, ctx.systemPrompt, ctx.bashEnv, ctx.tasks at call time for run_in_background tool/call, tool/result - The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as @deepseek-ai/dsh-pwsh-local backs ctx.bash); it mirrors the bash tool call-for-call minus the sandbox surface — run_in_background runs register with the generic ctx.tasks runtime and are collected/stopped through the task_* tools, and the managed DSH_* environment comes from @deepseek-ai/dsh-bash-env. Each call runs in a fresh process (no persistent PTY session; ConPTY is roadmap work), with native C:\... paths and $env:NAME variables.
@deepseek-ai/dsh-tool-cordis cordis_inspect, cordis_mount, cordis_unmount ctx.tools tool/call, tool/result, process-local temporary Plugin lifecycle - Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.
@deepseek-ai/dsh-tool-bash-persistent bash ctx.tools, ctx.pty, an owning Agent at execution time tool/call, PTY shell state, tool/result - One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description.
@deepseek-ai/dsh-tool-str-replace-editor str_replace_editor ctx.tools, ctx.fs tool/call, fs/observed after successful file operations, tool/result - Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface.
@deepseek-ai/dsh-tool-fs edit, read, write ctx.tools, ctx.fs, ctx.systemPrompt tool/call, fs/write-intent or fs/edit-intent for mutations, fs/observed after successful file operations, tool/result - 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-fs-search glob, grep ctx.tools, ctx.subprocess, ctx.systemPrompt tool/call, tool/result - glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (@vscode/ripgrep) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host rg install and no shell layer. The catalog uses sampleOverCapGlobResults: true; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.
@deepseek-ai/dsh-tool-pty terminal_close, terminal_list, terminal_open, terminal_read, terminal_send, terminal_signal ctx.tools, ctx.pty, ctx.systemPrompt, ctx.tasks at call time for run_in_background tool/call, tool/result - The six terminal tools are opt-in and complement one-shot bash/filesystem tools. terminal_send(run_in_background: true) registers with ctx.tasks; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.
@deepseek-ai/dsh-tool-goal create_goal, get_goal, update_goal ctx.tools, ctx.agents, ctx.goals, ctx.systemPrompt, a calling Agent in an authorized open turn tool/call, goal/change for mutations, tool/result - create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.
@deepseek-ai/dsh-tool-lsp lsp ctx.tools, ctx.lsp, ctx.systemPrompt tool/call, tool/result - The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. @deepseek-ai/dsh-lsp-local) at runtime; without one, a query returns the structured LSP_UNAVAILABLE error rather than changing the schema.
@deepseek-ai/dsh-tool-ralph ralph ctx.tools, ctx.workflows, ctx.subagents, ctx.systemPrompt, a calling Agent (exec.agent parents every fresh round) tool/call, tool/result, workflow and child session events during execution - A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap.
@deepseek-ai/dsh-tool-skill skill ctx.tools, ctx.agents, ctx.skills tool/call, tool/result, user/message replacement catalogs via agent.inject() - -
@deepseek-ai/dsh-tool-session-query session_event_read, session_event_search, session_event_trace, session_search, session_trace ctx.tools, ctx.systemPrompt, ctx.sessionQuery, a calling Agent for workspace authority tool/call, tool/result - The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies.
@deepseek-ai/dsh-tool-subagent subagent ctx.tools, ctx.subagents tool/call, tool/result, child session events through the chosen provider subagent, subagent_fork 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 apps/cli/config/base.cordis.yml and examples/acp-agent/cordis.yml.
@deepseek-ai/dsh-tool-subagent-control list_agents, send_message ctx.tools, ctx.subagents, ctx.sessionQuery (list_agents only) tool/call, tool/result, child session events through ctx.subagents - The globally named control tools over continuable background subagents: provider-bound tool-subagent instances register distinct delegation tools, while this package registers send_message once, plus list_agents from its separately loaded /list-agents plugin (which additionally requires session query).
@deepseek-ai/dsh-tool-subagent-report report ctx.subagents, a live continuable in-process child Agent tool/call, tool/result, a user-role message in the direct parent session - Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global toolFilter. The parent-facing send_message tool is installed independently.
@deepseek-ai/dsh-tool-tasks task_kill, task_list, task_output ctx.tools, ctx.tasks, ctx.systemPrompt tool/call, tool/result, user/message via agent.inject() for background completion notices - The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' ctx.tasks.start().
@deepseek-ai/dsh-tool-todo todo_write ctx.tools, owning Agent session tool/call, todo/write, tool/result - todo_write is session-owned state; UIs render the latest todo/write event as a checklist.
@deepseek-ai/dsh-tool-workflow workflow ctx.tools, ctx.workflows, ctx.systemPrompt, a calling Agent (exec.agent parents the script children) tool/call, tool/result - -
@deepseek-ai/dsh-tool-web web_fetch, web_search ctx.tools, ctx.web, ctx.systemPrompt tool/call, tool/result - web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps.

@deepseek-ai/dsh-tool-ask-user

ask_user_question

Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.

{
  "type": "object",
  "properties": {
    "questions": {
      "type": "array",
      "description": "Questions to ask the user before continuing.",
      "items": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable id for this question; echoed in the answer."
          },
          "question": {
            "type": "string",
            "description": "The specific question to ask the user."
          },
          "header": {
            "type": "string",
            "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
          },
          "options": {
            "type": "array",
            "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "label": {
                  "type": "string",
                  "description": "Short user-facing option label."
                },
                "description": {
                  "type": "string",
                  "description": "One sentence explaining the tradeoff or impact."
                }
              },
              "required": [
                "label"
              ]
            }
          },
          "multi_select": {
            "type": "boolean",
            "description": "Whether the user may select more than one option. Defaults to false."
          }
        },
        "required": [
          "id",
          "question"
        ]
      }
    }
  },
  "required": [
    "questions"
  ]
}

Source: packages/ui/tool-ask-user/src/index.ts

ask_user_question pauses the tool call until the active UI provider returns a human answer.

@deepseek-ai/dsh-tools

run_code

Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level await and return work) and call tools as await tools.name(args) per the declarations in the system prompt. Only what you print or return comes back — curate it.

{
  "type": "object",
  "properties": {
    "code": {
      "type": "string",
      "description": "The program: the body of an async TypeScript function."
    },
    "description": {
      "type": "string",
      "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."
    }
  },
  "required": [
    "code",
    "description"
  ]
}

Source: packages/core/tools/src/code-mode.ts

Owned by the tool registry as a reserved transport outside filterable capability layers under mode: code / mode: both (see the Code Mode Agent Note). Under code it is the registry's only wire contribution; the other visible capabilities are declared in a generated TypeScript SDK section, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to maxParallelSubCalls) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.

@deepseek-ai/dsh-plan-mode

exit_plan_mode

Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.

{
  "type": "object",
  "properties": {
    "plan": {
      "type": "string",
      "description": "The complete plan, as markdown, starting with a # heading that names it."
    }
  },
  "required": [
    "plan"
  ]
}

Source: packages/plan/plan-mode/src/index.ts

exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.

@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]. Current harness environment facts are exposed through managed $DSH_* variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as [sandbox: file access denied under <mode> mode] — a policy denial, not a bug in the command; do not retry another way. 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; read its output with task_output and stop it with task_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 (collect with task_output, stop with task_kill). No timeout applies."
    }
  },
  "required": [
    "command",
    "description"
  ]
}

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

The bash tool is the model-facing consumer of the bash executor seam. A run_in_background run registers with the generic ctx.tasks runtime and is collected/stopped through the task_* tools from @deepseek-ai/dsh-tool-tasks; the enableRunInBackground config (default true) removes the parameter entirely when disabled.

@deepseek-ai/dsh-tool-pwsh

pwsh

Execute a PowerShell command (pwsh -Command) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass workdir instead of using cd. Paths use native Windows form (C:\...); read environment variables with $env:NAME. Non-zero exits are reported as [exit code: N]. Current harness environment facts are exposed through managed $env:DSH_* variables; inspect them when needed. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as [exit code: 1] without a signal marker — treat it as an interruption, not a command failure. Set run_in_background: true for long-running commands: the call returns a task id immediately; read its output with task_output and stop it with task_kill.

{
  "type": "object",
  "properties": {
    "command": {
      "type": "string",
      "description": "The PowerShell 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\"; \"Get-Process\" → \"List running processes\"."
    },
    "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 (collect with task_output, stop with task_kill). No timeout applies."
    }
  },
  "required": [
    "command",
    "description"
  ]
}

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

The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as @deepseek-ai/dsh-pwsh-local backs ctx.bash); it mirrors the bash tool call-for-call minus the sandbox surface — run_in_background runs register with the generic ctx.tasks runtime and are collected/stopped through the task_* tools, and the managed DSH_* environment comes from @deepseek-ai/dsh-bash-env. Each call runs in a fresh process (no persistent PTY session; ConPTY is roadmap work), with native C:\... paths and $env:NAME variables.

@deepseek-ai/dsh-tool-cordis

cordis_inspect

Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: services (every provided ctx service and the plugin fiber that owns it), plugins (all live plugin fibers with their lifecycle states), tools (the model-facing tools currently registered, i.e. what you can call), temporary (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), api (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), events (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The temporary section is a subset of plugins. Omit what to get all six sections. With what:"api" or what:"events", pass an exact name to narrow to one service/event and include its original source JSDoc.

{
  "type": "object",
  "properties": {
    "what": {
      "type": "string",
      "description": "Limit the report to one section. Omit for all sections.",
      "enum": [
        "services",
        "plugins",
        "tools",
        "temporary",
        "api",
        "events"
      ]
    },
    "name": {
      "type": "string",
      "description": "Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."
    }
  }
}

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

cordis_mount

Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. code runs now as the body of an async JavaScript function in an isolated sandbox and MUST return a plugin. Two forms: FUNCTION form return (ctx) => { … } — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } } — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside apply, use the standard cordis API: ctx.on(event, listener) to observe events (see cordis_inspect what:"events"), or call harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } })) to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's execute MUST return the lossless JSON value declared by output.schema; output.render(args, value) separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may ctx.provide('name', value) a service and another may declare inject: ['name'] to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside apply is cleaned up automatically by cordis_unmount. Sandbox globals: console (tagged [cordis:<id>], writes through to the harness terminal), harness.defineTool, harness.registerTool, btoa, atob, TextEncoder, TextDecoder. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: require, setTimeout/setInterval, and fetch throw redirect errors; process and Buffer are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:"api" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no as, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing next callback which MUST be called — returning without next() VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your ctx is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.

{
  "type": "object",
  "properties": {
    "code": {
      "type": "string",
      "description": "JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."
    }
  },
  "required": [
    "code"
  ]
}

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

cordis_unmount

Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."
    }
  },
  "required": [
    "id"
  ]
}

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

Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.

@deepseek-ai/dsh-tool-bash-persistent

bash

Run commands in a persistent bash shell. State, including the current directory and exported environment variables, persists across calls for this agent.

{
  "type": "object",
  "properties": {
    "command": {
      "type": "string",
      "description": "The bash command to run. Relative path is preferred in the command."
    }
  },
  "required": [
    "command"
  ]
}

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

One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description.

@deepseek-ai/dsh-tool-str-replace-editor

str_replace_editor

Custom editing tool for viewing, creating and editing files

  • State is persistent across command calls and discussions with the user
  • If path is a file, view displays the result of applying cat -n. If path is a directory, view lists non-hidden files and directories up to 2 levels deep
  • The create command cannot be used if the specified path already exists as a file
  • If a command generates a long output, it will be truncated and marked with <response clipped>

Notes for using the str_replace command:

  • The old_str parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!
  • If the old_str parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in old_str to make it unique
  • The new_str parameter should contain the edited lines that should replace the old_str
{
  "type": "object",
  "properties": {
    "command": {
      "type": "string",
      "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
      "enum": [
        "view",
        "create",
        "str_replace",
        "insert"
      ]
    },
    "path": {
      "type": "string",
      "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`."
    },
    "file_text": {
      "type": "string",
      "description": "Required parameter of `create` command, with the content of the file to be created."
    },
    "insert_line": {
      "type": "integer",
      "description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`."
    },
    "new_str": {
      "type": "string",
      "description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert."
    },
    "old_str": {
      "type": "string",
      "description": "Required parameter of `str_replace` command containing the string in `path` to replace."
    },
    "view_range": {
      "type": "array",
      "description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
      "items": {
        "type": "integer"
      }
    }
  },
  "required": [
    "command",
    "path"
  ]
}

Source: packages/fs/tool-str-replace-editor/src/index.ts

Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface.

@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.

glob

Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result instead returns 100 paths sampled across top-level entries, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.

{
  "type": "object",
  "properties": {
    "pattern": {
      "type": "string",
      "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth."
    },
    "path": {
      "type": "string",
      "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it."
    }
  },
  "required": [
    "pattern"
  ]
}

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

grep

Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.

{
  "type": "object",
  "properties": {
    "pattern": {
      "type": "string",
      "description": "Regular expression to search for (ripgrep syntax)."
    },
    "path": {
      "type": "string",
      "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it."
    },
    "include": {
      "type": "string",
      "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported."
    }
  },
  "required": [
    "pattern"
  ]
}

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

glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (@vscode/ripgrep) through ctx.subprocess as ordinary foreground calls (never background tasks) — no host rg install and no shell layer. The catalog uses sampleOverCapGlobResults: true; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.

@deepseek-ai/dsh-tool-pty

terminal_close

Close one persistent terminal and wait until its captured owned process tree is gone.

{
  "type": "object",
  "properties": {
    "sessionId": {
      "type": "string",
      "description": "Terminal session id."
    }
  },
  "required": [
    "sessionId"
  ]
}

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

terminal_list

List persistent terminal sessions owned by the current agent.

{
  "type": "object",
  "properties": {}
}

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

terminal_open

Create a persistent, owner-isolated terminal session from a registered backend type. Use this for shell or REPL state that must survive across tool calls.

{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "description": "Registered terminal backend type, usually \"shell\"."
    },
    "name": {
      "type": "string",
      "description": "Optional owner-local display name such as \"main\" or \"gdb\"."
    },
    "cwd": {
      "type": "string",
      "description": "Initial working directory. Defaults to the deployment workspace root."
    }
  },
  "required": [
    "type"
  ]
}

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

terminal_read

Read a bounded page of retained output from a persistent terminal without sending input.

{
  "type": "object",
  "properties": {
    "sessionId": {
      "type": "string",
      "description": "Terminal session id."
    },
    "offset": {
      "type": "number",
      "description": "Newest-relative line offset (default 0)."
    },
    "count": {
      "type": "number",
      "description": "Requested line count (default 500; backend caps apply)."
    }
  },
  "required": [
    "sessionId"
  ]
}

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

terminal_send

Send text to a persistent terminal. By default Enter is submitted and the call waits for a prompt, stdin wait, output silence, timeout, or session exit. Background mode returns a task id for task_output/task_kill.

{
  "type": "object",
  "properties": {
    "sessionId": {
      "type": "string",
      "description": "Terminal session id returned by terminal_open or terminal_list."
    },
    "text": {
      "type": "string",
      "description": "UTF-8 text to write to the terminal."
    },
    "submit": {
      "type": "boolean",
      "description": "Submit Enter after text (default true). Set false for control characters or incomplete REPL input."
    },
    "run_in_background": {
      "type": "boolean",
      "description": "Return a task id immediately; collect with task_output or stop with task_kill."
    }
  },
  "required": [
    "sessionId",
    "text"
  ]
}

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

terminal_signal

Send an allowed signal to the current foreground process group of a persistent terminal.

{
  "type": "object",
  "properties": {
    "sessionId": {
      "type": "string",
      "description": "Terminal session id."
    },
    "signal": {
      "type": "string",
      "description": "Signal to deliver. Shell-targeted SIGKILL is rejected; use terminal_close.",
      "enum": [
        "SIGINT",
        "SIGTERM",
        "SIGKILL",
        "SIGTSTP",
        "SIGHUP"
      ]
    }
  },
  "required": [
    "sessionId",
    "signal"
  ]
}

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

The six terminal tools are opt-in and complement one-shot bash/filesystem tools. terminal_send(run_in_background: true) registers with ctx.tasks; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.

@deepseek-ai/dsh-tool-goal

create_goal

Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say "create a goal". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.

{
  "type": "object",
  "properties": {
    "objective": {
      "type": "string",
      "description": "The concrete completion objective inferred from the direct human request."
    },
    "max_goal_rounds": {
      "type": "number",
      "description": "Optional positive safe-integer limit on automatic continuation rounds."
    }
  },
  "required": [
    "objective"
  ]
}

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

get_goal

Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.

{
  "type": "object",
  "properties": {}
}

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

update_goal

Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.

{
  "type": "object",
  "properties": {
    "goal_id": {
      "type": "string",
      "description": "Exact id returned by get_goal."
    },
    "revision": {
      "type": "number",
      "description": "Exact positive revision returned by get_goal."
    },
    "action": {
      "type": "string",
      "description": "edit | pause | resume | complete | blocked",
      "enum": [
        "edit",
        "pause",
        "resume",
        "complete",
        "blocked"
      ]
    },
    "objective": {
      "type": "string",
      "description": "Replacement objective; valid only with action edit."
    },
    "max_goal_rounds": {
      "type": "number",
      "description": "Replacement cap; valid only with action edit."
    },
    "blocked_reason": {
      "type": "string",
      "description": "Concrete blocking condition; required only with action blocked."
    }
  },
  "required": [
    "goal_id",
    "revision",
    "action"
  ]
}

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

create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.

@deepseek-ai/dsh-tool-lsp

lsp

Query a language server for precise code navigation. operation is one of goToDefinition, findReferences, goToImplementation, hover. line and character are one-based UTF-16 cursor coordinates. findReferences includes the declaration.

{
  "type": "object",
  "properties": {
    "operation": {
      "type": "string",
      "description": "goToDefinition, findReferences, goToImplementation, or hover.",
      "enum": [
        "goToDefinition",
        "findReferences",
        "goToImplementation",
        "hover"
      ]
    },
    "file_path": {
      "type": "string",
      "description": "The source file to query, relative to the workspace or absolute."
    },
    "line": {
      "type": "number",
      "description": "One-based line of the cursor."
    },
    "character": {
      "type": "number",
      "description": "One-based UTF-16 column of the cursor."
    }
  },
  "required": [
    "operation",
    "file_path",
    "line",
    "character"
  ]
}

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

The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. @deepseek-ai/dsh-lsp-local) at runtime; without one, a query returns the structured LSP_UNAVAILABLE error rather than changing the schema.

@deepseek-ai/dsh-tool-ralph

ralph

Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.

{
  "type": "object",
  "properties": {
    "objective": {
      "type": "string",
      "description": "The immutable completion objective for every fresh Ralph round."
    },
    "maxRounds": {
      "type": "number",
      "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling."
    }
  },
  "required": [
    "objective"
  ]
}

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

A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap.

@deepseek-ai/dsh-tool-skill

skill

Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The exact skill name from the available skills list."
    }
  },
  "required": [
    "name"
  ]
}

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

@deepseek-ai/dsh-tool-session-query

session_event_read

Read one full unabridged event and optional neighboring raw-event summaries from an authorized session.

{
  "type": "object",
  "properties": {
    "session_id": {
      "type": "string",
      "description": "Target session id. Omit for the current session."
    },
    "seq": {
      "type": "integer",
      "description": "Target event sequence number."
    },
    "before": {
      "type": "integer",
      "description": "Number of preceding raw events to summarize. Omit for none."
    },
    "after": {
      "type": "integer",
      "description": "Number of following raw events to summarize. Omit for none."
    }
  },
  "required": [
    "seq"
  ]
}

Source: packages/session-query/tool-session-query/src/index.ts

Search prior events in one authorized session; the current session excludes the step performing this call.

{
  "type": "object",
  "properties": {
    "session_id": {
      "type": "string",
      "description": "Target session id. Omit for the current session."
    },
    "query": {
      "type": "string",
      "description": "Literal full-text query over the target session."
    },
    "seq_from": {
      "type": "integer",
      "description": "Inclusive event sequence lower bound."
    },
    "seq_to": {
      "type": "integer",
      "description": "Inclusive event sequence upper bound."
    },
    "time_from": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 event-time lower bound."
    },
    "time_to": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 event-time upper bound."
    },
    "event_types": {
      "type": "array",
      "description": "Event types to include.",
      "items": {
        "type": "string"
      }
    },
    "surfaces": {
      "type": "array",
      "description": "Event surfaces to include.",
      "items": {
        "type": "string",
        "enum": [
          "current",
          "shadowed",
          "log-only"
        ]
      }
    }
  },
  "required": [
    "query"
  ]
}

Source: packages/session-query/tool-session-query/src/index.ts

session_event_trace

Read every direct replacement and provenance relationship for one event in an authorized session.

{
  "type": "object",
  "properties": {
    "session_id": {
      "type": "string",
      "description": "Target session id. Omit for the current session."
    },
    "seq": {
      "type": "integer",
      "description": "Target event sequence number."
    }
  },
  "required": [
    "seq"
  ]
}

Source: packages/session-query/tool-session-query/src/index.ts

Search prior sessions in the caller workspace and return the strongest matching event from each session.

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Literal full-text query over prior session history."
    },
    "session_ids": {
      "type": "array",
      "description": "Optional session ids to include.",
      "items": {
        "type": "string"
      }
    },
    "created_at_from": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 creation-time lower bound."
    },
    "created_at_to": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 creation-time upper bound."
    },
    "parent_session_ids": {
      "type": "array",
      "description": "Optional direct parent session ids.",
      "items": {
        "type": "string"
      }
    },
    "include_root_sessions": {
      "type": "boolean",
      "description": "Include sessions with no parent in the parent filter."
    },
    "availability": {
      "type": "array",
      "description": "Require at least one selected source availability.",
      "items": {
        "type": "string",
        "enum": [
          "live",
          "persisted"
        ]
      }
    },
    "event_seq_from": {
      "type": "integer",
      "description": "Inclusive event sequence lower bound."
    },
    "event_seq_to": {
      "type": "integer",
      "description": "Inclusive event sequence upper bound."
    },
    "event_time_from": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 event-time lower bound."
    },
    "event_time_to": {
      "type": "string",
      "description": "Inclusive timezone-qualified ISO 8601 event-time upper bound."
    },
    "event_types": {
      "type": "array",
      "description": "Event types to include.",
      "items": {
        "type": "string"
      }
    },
    "event_surfaces": {
      "type": "array",
      "description": "Event surfaces to include.",
      "items": {
        "type": "string",
        "enum": [
          "current",
          "shadowed",
          "log-only"
        ]
      }
    }
  },
  "required": [
    "query"
  ]
}

Source: packages/session-query/tool-session-query/src/index.ts

session_trace

Read the authorized session lineage around one session, including complete visible ancestor and descendant relationships.

{
  "type": "object",
  "properties": {
    "session_id": {
      "type": "string",
      "description": "Target session id. Omit for the current session."
    }
  }
}

Source: packages/session-query/tool-session-query/src/index.ts

The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies.

@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. Set run_in_background: true to return a task id; collect with task_output and stop with task_kill.

{
  "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."
    },
    "run_in_background": {
      "type": "boolean",
      "description": "Run as a background task and return its id; collect with task_output or stop with task_kill."
    }
  },
  "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 apps/cli/config/base.cordis.yml and examples/acp-agent/cordis.yml.

@deepseek-ai/dsh-tool-subagent-control

list_agents

List your continuable background subagents by durable id and label. Status is a snapshot of the stored record: running means the subagent session is currently live in this process, complete means it exists only in storage and a send_message starts a new turn on the same conversation. The snapshot is not a delivery promise — send_message performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped.

{
  "type": "object",
  "properties": {}
}

Source: packages/subagent/tool-subagent-control/src/list-agents.ts

send_message

Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.

{
  "type": "object",
  "properties": {
    "subagent_id": {
      "type": "string",
      "description": "The subagent id returned when the background subagent was started."
    },
    "message": {
      "type": "string",
      "description": "The message to deliver to the subagent."
    }
  },
  "required": [
    "subagent_id",
    "message"
  ]
}

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

The globally named control tools over continuable background subagents: provider-bound tool-subagent instances register distinct delegation tools, while this package registers send_message once, plus list_agents from its separately loaded /list-agents plugin (which additionally requires session query).

@deepseek-ai/dsh-tool-subagent-report

report

Report selected content to the agent that started you. Call this zero or more times for progress, findings, or a final answer. Reporting does not end your turn or finish your work, and only your direct parent receives it. A failed call may still have arrived, so do not blindly repeat it.

{
  "type": "object",
  "properties": {
    "output": {
      "type": "string",
      "description": "Self-contained content for your parent; it does not see your private work."
    }
  },
  "required": [
    "output"
  ]
}

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

Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global toolFilter. The parent-facing send_message tool is installed independently.

@deepseek-ai/dsh-tool-tasks

task_kill

Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.

{
  "type": "object",
  "properties": {
    "task_id": {
      "type": "string",
      "description": "Task id returned by the tool that started the background work."
    },
    "reason": {
      "type": "string",
      "description": "Optional short reason, recorded in the log and forwarded to the task."
    }
  },
  "required": [
    "task_id"
  ]
}

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

task_list

List your background tasks (running and finished) with their ids, kinds, and statuses.

{
  "type": "object",
  "properties": {}
}

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

task_output

Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with [status: ...]. Reads are non-blocking unless wait: true, which waits up to the configured cap.

{
  "type": "object",
  "properties": {
    "task_id": {
      "type": "string",
      "description": "Task id returned by the tool that started the background work."
    },
    "wait": {
      "type": "boolean",
      "description": "Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."
    },
    "timeout_ms": {
      "type": "number",
      "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."
    }
  },
  "required": [
    "task_id"
  ]
}

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

The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' ctx.tasks.start().

@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",
        "additionalProperties": false,
        "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

todo_write is session-owned state; UIs render the latest todo/write event as a checklist.

@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 workflow's identity rides the meta parameter as JSON: required name (short kebab-case) and description strings, optional whenToUse string and phases array ({title, detail?, provider?, model?}). The script parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO export const meta statement — meta is a parameter, not code), 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/oneOf — no 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), and independent provider/model LLM target overrides (either may be provided alone). 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; no filesystem, network, timers, or Node.js APIs are provided — 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 plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`)."
    },
    "meta": {
      "type": "object",
      "description": "The workflow identity block (plain JSON — never code).",
      "additionalProperties": true,
      "properties": {
        "name": {
          "type": "string",
          "description": "Short kebab-case workflow name."
        },
        "description": {
          "type": "string",
          "description": "One-line description of what the workflow does."
        },
        "whenToUse": {
          "type": "string",
          "description": "Optional guidance on when this workflow applies."
        },
        "phases": {
          "type": "array",
          "description": "Optional phase declarations matched by phase() calls.",
          "items": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "title": {
                "type": "string",
                "description": "The phase title phase() calls match by exact string."
              },
              "detail": {
                "type": "string",
                "description": "Optional one-line description of the phase."
              },
              "provider": {
                "type": "string",
                "description": "Optional provider override this phase is expected to use."
              },
              "model": {
                "type": "string",
                "description": "Optional model override this phase is expected to use."
              }
            },
            "required": [
              "title"
            ]
          }
        }
      },
      "required": [
        "name",
        "description"
      ]
    },
    "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\": [...]}).",
      "additionalProperties": true
    }
  },
  "required": [
    "script",
    "meta"
  ]
}

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."
    }
  },
  "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

web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps.