Profiles and Bundles in DeepSeek Harness, Explained
How DeepSeek Harness profiles and bundles fit together, where they live on disk, how dsh.profile.bundles stays in sync, and when to run more than one profile.
A profile is a runnable configuration you start with dsh --profile <name>. A bundle is an npm package that contributes a slice of that configuration. Every profile is just an ordered list of bundles plus your own overrides — understanding that split is the fastest way to make sense of how DeepSeek-Harness (dsh) config actually works.
Bundles: what a package contributes
A bundle is any npm package whose package.json declares a dsh.bundle field pointing at a patch file:
{
"name": "dsh-hello-plugin",
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
That cordis.patch.yml file is the actual payload — a YAML document that inserts or overrides rows in the Cordis plugin tree (the config layer covered in depth in DeepSeek Harness Configuration Guide). The bundle itself is just the packaging: a way to publish, version, and install that patch through the ordinary npm/pnpm supply chain.
Not every package you install into a profile is a bundle. If a package's package.json has no dsh.bundle field, dsh plugin still installs it as a normal dependency — useful when a real plugin depends on a plain library — but prints a warning and activates no configuration for it. The presence of dsh.bundle is the one authoritative signal that separates "a dsh plugin" from "a package a dsh plugin happens to need."
Profiles: what you actually run
A profile is a directory: $DSH_HOME/profiles/<name>/ (default $DSH_HOME is ~/.dsh). Inside, two files matter:
package.json— declaresdsh.profile.bundles, an ordered array of bundle package names, plus the profile's own npm dependencies (the bundles themselves, as installed packages).cordis.patch.yml— your personal override layer for that specific profile, on top of whatever the bundles already configured.
{
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "dsh-hello-plugin"]
}
}
}
Two profile names are reserved and get bootstrapped automatically the first time you use them: web (initialized from @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app) and headless (@deepseek-ai/dsh-base + the headless bundle). dsh web is documented as a hardcoded alias for dsh --profile web, and headless one-shot runs go through dsh --profile headless "task".
Any other profile name has to be initialized before you can start it — running dsh --profile <name> add <package> for the first time bootstraps that profile with just the base bundle, @deepseek-ai/dsh-base, and installs your requested package on top.
How dsh.profile.bundles stays in sync
You don't hand-edit the bundles list. Every dsh plugin --profile <name> <pnpm args> command — add, remove, update, why — forwards straight to pnpm, then re-scans the profile's actual installed dependencies afterward. Any installed package that declares dsh.bundle gets synced into the bundle list; anything removed drops out automatically. See How to Install DeepSeek-Harness Plugins for the full install-command walkthrough, including the allowBuilds prompt you'll hit on GitHub-sourced plugins that ship source instead of a prebuilt lib/.
# add a plugin to the web profile
dsh plugin --profile web add github:owner/repo
# see the resulting profile package.json
cat "$DSH_HOME/profiles/web/package.json"
Layering order: bundles, then you, then the machine
When dsh assembles the final config for a profile, layers apply in this order, later layers overriding earlier ones by row id:
| Order | Layer | Scope |
|---|---|---|
| 1 | Each bundle's patch, in dsh.profile.bundles list order (base bundle first) | Per-profile, from installed packages |
| 2 | The profile's own cordis.patch.yml | This profile only |
| 3 | $DSH_HOME/cordis.patch.yml | Every profile on this machine |
| 4 | Any --patch <path> flags on the command line, in argv order | This invocation only |
Notice that layer 3 — the machine-level patch — sits above the profile's own patch file. A setting you put in $DSH_HOME/cordis.patch.yml overrides what an individual profile configured for itself, which is exactly the point: it's the place for preferences you want applied everywhere, regardless of which profile you launch. This is covered from the "how config merges" angle in DeepSeek Harness Configuration Guide, including the fact that a patch replaces a target row's config wholesale rather than deep-merging it.
Running more than one profile
Because model provider configuration ($DSH_HOME/settings.yaml and .credentials.yaml) is shared across all profiles while bundles and their patches are per-profile, multiple profiles are cheap to maintain and genuinely useful for separating concerns:
web— your daily driver, with the plugins you actually rely on: a UI fix, a notifications plugin, maybe something from Development & Runtime.- A
minimalprofile — dsh ships a built-inminimalagent preset (fixed system prompt, onlybashandstr_replace_editormounted) for when you want the smallest possible surface area, no third-party plugins in the loop. - An experimental profile — a sandbox for trying a plugin you don't fully trust yet, kept isolated from your daily
webprofile so a bad install can't take down the setup you rely on.
You switch between them just by changing the --profile flag; nothing about the underlying dsh binary changes.
Bootstrapping a profile that isn't web or headless
The reserved-name shortcut only applies to two names. Try to start any other profile before it exists, and dsh won't silently invent a full starter configuration for you — it initializes the profile with just the base bundle, @deepseek-ai/dsh-base, and nothing else. You have to explicitly add whatever else you want:
# first use of "experiment" — bootstraps with @deepseek-ai/dsh-base only
dsh plugin --profile experiment add github:owner/some-plugin
# now it's a real profile you can launch
dsh --profile experiment
This is a deliberate asymmetry: web and headless are opinionated starting points that match how most people actually use dsh (a browser UI, or a scriptable one-shot task runner), while a custom-named profile starts as close to empty as the system allows, so it doesn't accumulate plugins you never asked for. If you're building a profile for a specific narrow purpose — a CI runner, a stripped-down review agent — this is the behavior that keeps it from silently inheriting the same defaults as your web profile.
FAQ
Can a bundle depend on another bundle?
The base bundle, @deepseek-ai/dsh-base, is always first in the layering order for the web and headless starter templates, and other bundles are expected to build on top of it via patch ordering. Beyond that, dsh's official docs (and this site's source material) don't spell out a formal bundle-to-bundle dependency graph beyond list order in dsh.profile.bundles — treat install order as the dependency mechanism.
What happens if I delete a profile directory by hand?
Deleting $DSH_HOME/profiles/<name>/ removes the profile entirely. web and headless will simply re-bootstrap from their default templates the next time you use them; a custom-named profile would need to be reinitialized with dsh plugin --profile <name> add <package> again.
Do profiles share plugins, or does each one install its own copy?
Each profile has its own node_modules and its own bundle list — installing a plugin into web does not make it available in a different profile. If you want the same plugin in two profiles, install it into each one separately.
Is dsh.profile.bundles something I should edit manually?
No — it's meant to be derived automatically from what's actually installed via dsh plugin add/remove. Hand-editing it risks drifting out of sync with node_modules; let the CLI's post-install re-scan keep it accurate.
Next steps
Now that you know how profiles and bundles compose, DeepSeek Harness Configuration Guide covers how patches actually merge (full-row replace, not deep merge) and how to debug the final combined config with --dump-config. If you haven't installed a plugin yet, start with How to Install DeepSeek-Harness Plugins, or browse what's available in Development & Runtime and the full plugin catalog — a good first bundle to look at is oh-dsh, a community distribution that packages TUI, desktop, and Web UI as one layered bundle.