What dsh is, and what actually happens when you run it

The product in one paragraph

DeepSeek Harness (dsh) is an open-source agent harness: the scaffolding that turns an LLM into a working coding agent — tool execution, sandboxing, session persistence, a web GUI, subagents, skills, hooks. Its one architectural bet is that everything is a plugin on a vendored copy of the Cordis framework (README.md:7). It is in developer preview and says so loudly: README.md:11"THERE WILL BE COMPATIBILITY-BREAKING CHANGES." External pull requests are not accepted (CONTRIBUTING.md:9), so this is open-source-as-window, not open-source-as-community.

What actually ships

Three bundles exist in the box (packages/bundle/): base, web-app, and headless. That yields exactly two usable surfaces:

The CLI binary itself is a launcher, not a product surface — its own README says so (apps/cli/README.md:5: "The dsh command is the product launcher for profiles"). There is no interactive terminal UI in the box. The --help text and README advertise a tui profile (apps/cli/src/args.ts:68-71, apps/cli/README.md:24), but no tui bundle exists and PROFILE_TEMPLATES (packages/boot/app-boot/src/profile.ts:114-117) knows only web and headless. The TUI is a third-party GitHub install (apps/cli/reference/README.md:46: dsh plugin --profile tui add github:deepseek-harness/turtle-ui). A first-run user who types the help example gets a "profile does not exist" error.

npx @deepseek-ai/dsh web, demystified

Hop 1 — the bin lib/bin.js parses argv, imports the mode module Hop 2 — pure alias web ⇒ --profile web unknown flags pass through Hop 3 — materialize rewrites profile config, rebuilds symlink farm writes to disk on every boot Hop 4 — boot tree one root Cordis context = the whole product patch layers, applied in this order — a later layer overrides an earlier one: 1 · bundle patches (base / web-app / headless) 2 · profile cordis.patch.yml 3 · $DSH_HOME/cordis.patch.yml 4 · --patch overlays merged into one composition HTTP server, agent loop, LLM adapters, persistence — all rows in this tree
The four boot hops of npx @deepseek-ai/dsh web — all inside one Node process. Hop 3 has side effects; hop 4 composes the whole product from patch layers.

Four hops, all in one Node process.

Hop 1 — the bin. The published package's only entry is a bundled ESM file (apps/cli/package.json:14-19: "bin": { "dsh": "lib/bin.js" }). It parses argv first, then dynamically imports the mode module (apps/cli/src/bin.ts:27-38).

Hop 2 — web is a pure alias. There is no web-specific launcher code; web resolves to --profile web (apps/cli/src/args.ts:156-168), and unknown tokens pass through to the booted tree (args.ts:123-128).

Hop 3 — profile materialization, with side effects. Every boot writes to disk before doing anything else: it rewrites the profile's root config file (apps/cli/src/profile-boot.ts:98-103 — the rationale at :88-93 is that the loader's write-back would otherwise duplicate bundle rows on the next boot), and it rebuilds a symlink farm under $DSH_HOME/profiles/node_modules containing the entire dependency closure of the installed package (packages/boot/app-boot/src/profile.ts:223-226, :191-198) so out-of-tree plugins share one Cordis instance. Consequence: a read-only $DSH_HOME breaks boot outright.

Hop 4 — one Cordis tree from patch layers. A single root context boots with layers applied in order: bundle patches → profile cordis.patch.yml$DSH_HOME/cordis.patch.yml → any --patch overlays (apps/cli/src/profile-boot.ts:122-129, :248). The entire product — HTTP server, agent loop, LLM adapters, persistence — is rows in this composition.

Process model

One process. No daemon, no Electron, no separate server. Code Mode runs in a worker thread (packages/bundle/web-app/cordis.patch.yml:41-43); shell tools spawn subprocesses; the workflow worker is disabled on Web. Signals: SIGTERM exits 0, SIGINT exits 130 (apps/cli/src/profile-boot.ts:221-222).

Network posture

Browser SPA shell from dsh-web-frontend, window.__DSH_BOOT__ injected by server dsh server 127.0.0.1:3080 one Node process --host 0.0.0.0 rejected on purpose HTTP uplink under /api WebSocket — downlink only (client→server WS = protocol violation) per-plugin bundles at /plugins/<id>/client.js No authentication — loopback-only is the security model; the CSRF fence disclaims being one
Three channels on one loopback port. The uplink is HTTP; the WebSocket carries traffic in one direction only.

The server binds 127.0.0.1:3080 by default (packages/bundle/web-app/cordis.patch.yml:115-120). The --host flag exists but is effectively a no-op: the schema admits exactly two values (packages/host/webserver/src/index.ts:61z.union([z.const('127.0.0.1'), z.const('0.0.0.0')])), and the only non-default one is rejected at the flag layer with an honest error (packages/bundle/web-app/src/startup.ts:69-71: "--host 0.0.0.0 is intentionally not supported yet for safety: it would expose remote code execution to the network"). There is no authentication — only a DNS-rebinding/CSRF fence that explicitly disclaims being one (packages/client/connection/src/api-request-trust.ts:12-13: "this fence is not an auth layer"). Loopback-only is the security model.

The browser talks to the server over three channels on one port: HTTP uplink under /api (packages/client/connection/src/api-path.ts:8), a WebSocket downlink only — client-to-server WebSocket messages are a protocol violation (packages/client/connection/src/websocket-downlink.ts:47-50) — and per-plugin browser bundles served at /plugins/<id>/client.js (packages/bundle/web-app/cordis.patch.yml:145-148). The SPA shell is served from the separately published @deepseek-ai/dsh-web-frontend package, with a server-injected window.__DSH_BOOT__ manifest; running the frontend under bare Vite is hard-failed on purpose (apps/web/vite.config.ts:7-17).

Where state lives on disk

Everything is under ~/.dsh (override with $DSH_HOME; packages/util/home-paths/src/index.ts:12-18):

Path What it is Evidence
profiles/<name>/ profile package.json, cordis.yml, patch file packages/boot/app-boot/src/profile.ts:152-168
profiles/node_modules/ the boot-time symlink farm profile.ts:223-226
cordis.patch.yml machine-wide user config layer apps/cli/src/profile-boot.ts:49-51
sessions/ append-only JSONL session event logs packages/bundle/base/cordis.patch.yml:101
storages/ JSON key-value stores, mode 0700 packages/storage/storage-json/src/index.ts:64
attachments/v1/ image attachments packages/attachment/attachment-local/src/index.ts:53
.credentials.yaml (0600) + .env API keys packages/credentials/credentials-local/src/index.ts:7-9, :394
settings.yaml user settings packages/settings/settings-file/src/index.ts:56
.agent-presets/ user-authored agent presets packages/preset/agent-presets/src/discovery.ts:41
AGENTS.md user-global agent instructions packages/context/agent-instructions/src/render.ts:93-94

Two details worth knowing. Credentials get real permission enforcement on POSIX — boot refuses to start if the file is readable beyond its owner (credentials-local/src/index.ts:117-119) — but the check is skipped on Windows (:113: "its ACLs are not expressible here — so the check is skipped rather than faked"). And the session search index on Web is configured :memory: with openAt: never (packages/bundle/web-app/cordis.patch.yml:31-33), so sidebar search matches session titles and workspace names only, not content.

Platform support, honestly

Target dsh runs kernel sandbox Python SDK runtime
linux-x64 / arm64 yes yes (bwrap → Landlock) yes
macos-arm64 yes yes (Seatbelt) yes
macos-x64 yes yes (Seatbelt) no
win32-x64 yes partial (restricted-token ACL) no

Windows is a real engineering target, not an afterthought: the base bundle swaps the whole shell family per platform in config (packages/bundle/base/cordis.patch.yml:210-216: tool-bash disabled on win32, tool-pwsh disabled elsewhere), there is a CVE-2024-27980 spawn workaround (apps/cli/src/plugin.ts:127-132), junction-aware symlinking, and three Windows CI lanes. But the blocking Windows CI signal runs under Wine on Ubuntu, not real Windows (see how-they-build-it.html), and the Windows ACL sandbox self-reports as structurally partial (packages/sandbox/sandbox-local/src/index.ts:181-186: NTFS hard links can alias a granted file outside the workspace).

The Python SDK (python/sdk) drives the harness as a subprocess over stdio JSON-RPC, but it is unreleased — python/sdk/pyproject.toml:7 says version = "0.0.0.dev0" — and defines wheels for only three platforms (python/sdk-runtime/platforms.json:1-13): no Windows, no Intel Mac.

Traps for a new user