The Web overlay disables base's 32 agent-plane rows and mounts the preset roster instead, so each session composes its own tools and prompt rather than sharing one process-wide set. The TUI keeps base unchanged: it is single-session and composing its agent process-wide is correct there. `roots` is patched in by AppCLIEntry, like `distIndex`: the shipped presets sit beside the composition that names them and the user's live under the Harness home, neither of which a config author chooses. A session's preset is fixed at creation. Naming a different one for an existing identity is `agent-preset-conflict` rather than a switch, because that session's history was produced under the first preset's tools. The guard sits after `await creation`, beside the cwd check, so it covers every path that yields a live agent — freshly created, adopted live, resumed, or recovered by the concurrent-creation catch. A request naming no preset adopts the session as it is, keeping reconnect and retry ordinary. Two bugs the real-composition test caught, both invisible to unit tests: `PresetTree` now refuses to write. The Loader persists a tree whose plugin self-disposed, and tearing an agent down disposes its whole subtree — inherited, that rewrote the shipped composition, truncating a 241-line preset to `[]` the first time a session ended. `dsh-tool-skill` compared against a lookup of its own name in the global layer, so it threw inside any preset: `register()` files into the calling context's scope. It now compares against the definition it registered, which is what the identity check meant all along. The `standard` catalog is asserted exactly, not spot-checked: a row that registers into the wrong layer mounts cleanly and simply contributes nothing, so an omission is this design's quietest failure. It matches the shipped TUI catalog plus `glob`/`grep`, the pair that composition documents as ripgrep-dependent. Re-records `cordis-inspect-jsdoc`, whose rendered `SessionHeader` gains the `agentPreset` field. `fs-glob-sampling` fails identically on pristine master and is untouched here. The browser e2e scaffold gains the roster fact AppCLIEntry supplies. `roots` is resolved and patched in by the CLI entry, like `distIndex` on the webserver row, and this lane boots the shipped tree without that entry — so it has to supply the same fact or the roster resolves nothing and every session in the lane composes an agent with no tools, no persona, and no token meter. Only the shipped root: a developer's own `~/.dsh/.agent-presets` must not decide a golden. The `cordis:group` builtin comes with it, exactly as `boot()` registers it, because a preset resolving package names from its own directory cannot reach `@cordisjs/plugin-group` by name. The lane stays red through this layer and the next four for the reason stated above — the api-proxy injects `subagents`, `workspace`, and `tools`, so `api-gateway` cannot activate and the browser has no `/api` at all. It goes green again in the layer that returns those registries to the host plane; this change is what makes that layer's fix sufficient rather than partial.
@deepseek-ai/dsh
English | 中文
The dsh command is the product launcher for profiles: ordered stacks of plugin-bundle patch layers under the user's own overrides. src/args.ts owns the command grammar, and src/bin.ts loads only the selected runner. Invalid commands, options from another mode, configuration errors, and boot failures exit nonzero.
Entry modes
| Command | Purpose |
|---|---|
dsh --profile <name> |
Boot the named profile under $DSH_HOME/profiles/<name>. |
dsh --profile headless "task" |
Run one fresh persisted session, print the final answer, and exit. |
dsh web |
Alias of --profile web with the Web flag family (--host, --port, --dev, ...). |
dsh plugin --profile <name> <pnpm args> |
Manage a profile's plugins by forwarding to pnpm in the profile directory. |
The invoking directory is the default workspace root. The web and headless profiles auto-initialize on first use from shipped templates; any other profile must be created through dsh plugin.
Profiles
A profile directory holds a package.json (out-of-tree plugin dependencies plus the profile manifest dsh.profile with its ordered bundles list) and a cordis.patch.yml (the user's own patch layer, hot-reloaded on long-lived surfaces). The tree composes over an empty root: each bundle's patch in dsh.profile.bundles order, then the profile's cordis.patch.yml, then the home-level $DSH_HOME/cordis.patch.yml, then --patch overlays, then flag patches. Bundles named in dsh.profile.bundles resolve from the dsh installation first (@deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app, @deepseek-ai/dsh-headless), then from the profile's own node_modules, where pnpm installs out-of-tree plugins. Use --dump-default-config and --dump-config to inspect the composed tree without booting it.
The CLI behavior reference owns exact layer precedence, flags, shutdown behavior, deployment defaults, and the source launcher.
Development
Production runs require built package and frontend artifacts. From a checkout, pnpm run dsh runs the TypeScript entry and forwards arguments; the source-launcher reference describes the PATH symlink and module-resolution contract.