# dsh harness: DeepSeek Harness commands and config reference

DeepSeek Harness (dsh) on one page: install, --profile, model choice, tools, sandbox, plugins, config files, headless runs and common errors.

`dsh` is the command for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). Start it with `npx @deepseek-ai/dsh web` for the browser UI, or `dsh --profile headless "run the tests"` to run one task from a script. Everything else on this page builds on one idea: **a profile**, which is an ordered stack of plugin bundles plus your own overrides, booted by one launcher.



DeepSeek Harness is in developer preview, and the README warns that breaking changes will come. The flags below come from the repository's generated [CLI help](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/cli-help.md) as of 2026-11-06. Run `dsh --help` on your version to confirm.

## Install and launch

You need Node.js **24.9 or newer**. The [Node 24 upgrade guide](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/upgrade-guide/v0.2.1-rc.2/node-24/guide.md) dropped Node 22 support; older pages, including ours from launch week, say 22.

```bash
node --version            # must report 24.9 or newer
npx @deepseek-ai/dsh web  # starts the Web UI and opens it in your browser
```

Pass `--no-open` to serve without opening a browser. Over SSH, dsh prints the URL instead of opening it.

From source, per the README:

```bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
```

The directory you launch from is the default workspace root. A fresh Web UI still asks you to pick a workspace before the composer unlocks.

## The launcher and `--profile`

```text
dsh [--profile] <name> [options] [app-args...]
dsh plugin --profile <name> <pnpm-args...>
```

`dsh web` and `dsh --profile web` are the same thing. The launcher parses its own flags, and the first token it does not recognize starts the profile's own arguments, so `dsh --profile web --port 8080` hands `--port` to the web app.

| Launcher flag | What it does |
|---|---|
| `--profile <name>` | Boot the profile under `$DSH_HOME/profiles/<name>` |
| `--from-default-profile <template>` | Replace a custom profile with a fresh copy of a shipped template, then boot it |
| `--patch <path>` | Apply an extra patch overlay after the profile layer (repeatable) |
| `--dump-config` | Print the composed profile tree and exit |
| `--dump-default-config` | Print the tree without your user layer or `--patch` overlays |
| `--dump-config-schema` | Print JSON Schema for profile entries and patches without mounting |
| `-V`, `--version` | Print the version |

Shipped profiles, per the [CLI README](https://github.com/deepseek-ai/deepseek-harness/blob/main/apps/cli/README.md):

| Profile | Purpose |
|---|---|
| `web` | Browser UI |
| `headless` | One task, answer to stdout, then exit |
| `sdk`, `sdk-minimal` | SDK clients over JSON-RPC stdio |
| `acp` | Editor clients over Agent Client Protocol stdio |

These five initialize on first use. The name `desktop` is reserved for the desktop app, and the CLI refuses to boot it. To make your own profile from a template:

```bash
dsh rescue --from-default-profile web   # "rescue" is the new profile's name, not a subcommand
```

`dsh acp` here is Zed's Agent Client Protocol for editors. It has nothing to do with the Agentic Control Plane. [Which ACP is which](/acp-vs-acp) sorts that out.

## Where config lives

`$DSH_HOME` defaults to `~/.dsh`.

| Path | Holds |
|---|---|
| `$DSH_HOME/profiles/<name>/package.json` | Out-of-tree plugin dependencies and the `dsh.profile` manifest with its ordered `bundles` list |
| `$DSH_HOME/profiles/<name>/cordis.patch.yml` | Your patch layer for that profile; the Web UI's settings write here |
| `$DSH_HOME/cordis.patch.yml` | A home-level patch applied to every profile |
| `$DSH_HOME/.credentials.yaml` | API keys saved through the Models page |

Layers compose in this order, last wins: each bundle's patch in `bundles` order, then the profile's `cordis.patch.yml`, then the home `cordis.patch.yml`, then any `--patch` overlays. With HMR on, which the shipped profiles use, edits apply live. Without it, restart.

Before you trust a change, check what actually mounted:

```bash
dsh --profile web --dump-config
```

The [config catalog](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/config-catalog.md) lists every plugin's fields and defaults.

## Choosing the model

The launcher has no model flag. Models are configured per profile, per the [model configuration guide](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/guide/providers.md):

1. Open **Settings → Models** in the Web UI.
2. Paste a DeepSeek API key, or choose **Add model provider** for a shipped provider such as `anthropic`, `openai`, `moonshotai` or `zai`, or a **Custom model API** for a gateway that speaks OpenAI Chat Completions, OpenAI Responses or Anthropic Messages.
3. Pick a model in the model picker. That also makes it the default for new sessions.

A session that has already sent a request keeps the model recorded in its own log. The API-key DeepSeek adapter's credential reference defaults to `DEEPSEEK_API_KEY`.

For hand edits, the providers live in `$DSH_HOME/profiles/<profile>/cordis.patch.yml`, the same file the page writes. For `dsh web` that is `$DSH_HOME/profiles/web/cordis.patch.yml`. The default model is a plugin row with three fields, `provider`, `model` and an optional `reasoningEffort`.

## The tool set

The [tool catalog](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/tool-catalog.md) is generated by booting each tool plugin. The core names the model sees:

| Area | Tools |
|---|---|
| Shell | `bash` (Linux, macOS), `pwsh` (Windows) |
| Files | `read`, `write`, `edit`, `apply_patch`, `read_image`, `str_replace_editor` |
| Search | `glob`, `grep` (bundled ripgrep, no host `rg` needed) |
| Web | `web_fetch`, `web_search` |
| Delegation | `subagent`, `list_subagent_models`, `send_message`, `wait_agent`, `list_agents`, `interrupt_agent` |
| Background jobs | `job_list`, `job_output`, `job_kill` |
| Planning | `todo_write`, `exit_plan_mode`, `ask_user_question` |
| Code Mode | `run_code`, which runs a program whose sub-calls go through the same tool pipeline |
| Plugins | `plugin_manager`, `plugin_market` |

Which tools a session gets depends on the profile's bundles. `apply_patch`, for example, registers only when its plugin is configured with `applyPatch: true`. MCP tools appear as `mcp__<server>__<name>` and run through the same pipeline. How that pipeline can be intercepted is in the [dsh hooks reference](/blog/deepseek-harness-hooks-reference).

## The sandbox

dsh confines shell commands with a file-write sandbox. The [sandbox reference](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/sandbox.md) defines three modes:

- **`read-only`**: writes denied. The documented default and fail-safe.
- **`workspace-write`**: writes allowed under the workspace root, any extra directories you name, and the backend's temp area.
- **`danger-full-access`**: no confinement.

Backends: bwrap or Landlock on Linux, Seatbelt on macOS, a restricted-token ACL runner on Windows. Each reports its enforcement as `full` or `partial`; older Landlock versions and the Windows runner are documented partial cases. The modes cover **file effects only**. Network and process visibility are outside them.

In the UI you pick a **permission preset**, which pairs a sandbox mode with an approval policy. The [presets reference](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/permission-presets.md) ships two:

| Preset | Sandbox | Approval |
|---|---|---|
| `workspace-write` | `workspace-write` | `ask` |
| `danger-full-access` | `danger-full-access` | `never` |

The deployment default lives in the `@deepseek-ai/dsh-sandbox-policy` row, whose `mode` defaults to `read-only`. How approvals, presets and the session log fit together is covered in [the dsh control model](/controls/dsh).

## Installing plugins

```bash
dsh plugin --profile web add <package>
dsh plugin --profile web remove <package>
dsh plugin --profile web why <package>
```

`dsh plugin` forwards everything after the flags to pnpm, run inside the profile directory, and initializes the profile on first use. Two rules from the CLI docs:

- **Version checks.** Installation and startup enforce the plugin's `engines.dsh` range against `dsh --version`. A mismatch is refused unless you grant an explicit exact-version exemption, which the docs warn can crash or lose data.
- **No installing over an installed package with HMR.** It now fails with `already-installed`. To upgrade, remove first, then add, per the [upgrade note](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/upgrade-guide/v0.2.1-alpha.2/plugin-install-over-installed/guide.md).

The agent can also manage plugins itself through the `plugin_manager` tool. Every action requires `danger-full-access` or an approval for that call, and changes apply to every session in the profile. Community plugins are tagged with the `dsh-plugin` topic on GitHub.

## Headless use

```bash
dsh --profile headless "run the tests"
echo "run the tests" | dsh --profile headless
dsh --profile headless --json "run the tests"
dsh --profile headless --session-id <id> "continue"
dsh --profile headless --permission-mode workspace-write --add-dir /repo/.git "commit the fix"
```

The answer goes to stdout and diagnostics to stderr. Options:

| Flag | Effect |
|---|---|
| `--json` | Newline-delimited run events on stdout instead of the final message |
| `--session-id <id>` | Resume a persisted session; an unknown id is an error |
| `--permission-mode <preset>` | Pick a configured permission preset for this task |
| `--approval-policy ask\|never` | Override approval behavior after the preset |
| `--add-dir <path>` | Allow writes to another absolute directory under `workspace-write` (repeatable) |

A task argument of `-` reads stdin. With no approval service mounted, an `ask` resolves to deny rather than waiting forever; [the controls page](/controls/dsh) explains why that matters for unattended runs.

## Common errors

| Symptom | Cause and fix |
|---|---|
| Boot fails with `Promise.withResolvers is not a function` or similar | Node too old. Upgrade to 24.9 or newer. |
| `MISSING_CREDENTIAL` | No key for the selected provider. Save it under Settings → Models or set the referenced environment variable. |
| `UNKNOWN_MODEL` | The session names a model the provider does not list. Pick a configured model or add it to the custom provider. |
| Composer shows **Select model** and blocks input | The saved default names a deleted provider. Pick another model. |
| Every request to a custom gateway is refused | Request shape mismatch. Try `compat.supportsDeveloperRole: false` and `compat.maxTokensField: max_tokens` on the route. |
| `already-installed` on plugin add | Remove the package first, then add the new version. |
| A plugin you added does not load | Check `--dump-config`. On rc.6 we saw bare `npx` launches fail to resolve profile-installed plugins; a normal install or a `--patch` overlay by file path worked. |
| Shell writes fail with a permission error | The session is running under `read-only`, the sandbox policy's default mode. Switch the session to the `workspace-write` preset. |

## Limits

- **A stable interface.** This is a developer preview. Flags, profile names and file layouts have changed since launch, including the Node requirement. Check `dsh --help` and `--dump-config` after every upgrade.
- **A full security boundary.** The sandbox limits file writes. It does not restrict network access, and some backends report only partial enforcement.
- **Per-command rules.** Core ships modes and presets, not allow and deny rules per command. Rules come from plugins on the tool pipeline; see the [hooks reference](/blog/deepseek-harness-hooks-reference).
- **The desktop app.** It bundles its own runtime and owns the reserved `desktop` profile.

ACP has a dsh plugin that checks every dsh tool call, including custom plugins and Code Mode sub-calls, and logs it with your other coding agents ([install guide](/integrations/dsh)). One gap as of 2026-11-08: when a rule says deny, the plugin reports it to the agent but does not stop the call. The fix is tracked. An ask is blocked and waits for your approval on the console or in the email, then dsh retries.

## Frequently asked questions



## Where to read more

- [DeepSeek Harness repository and README](https://github.com/deepseek-ai/deepseek-harness)
- [dsh CLI help, generated](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/cli-help.md)
- [CLI README: profiles and layering](https://github.com/deepseek-ai/deepseek-harness/blob/main/apps/cli/README.md)
- [Configure models](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/guide/providers.md)
- [Process sandbox](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/sandbox.md) and [permission presets](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/permission-presets.md)
- [Tool schema catalog](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/tool-catalog.md) and [config catalog](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/config-catalog.md)
