cordis.patch.yml Explained: How DeepSeek-Harness Bundles Patch Config
How a DeepSeek-Harness plugin author writes cordis.patch.yml: what insert, id, name, and config declare, and how bundle vs profile package.json fields differ.
cordis.patch.yml is the YAML file a DeepSeek-Harness (dsh) bundle uses to declare what it contributes to the running Cordis plugin tree — a list of entries that either insert new rows or override an existing row's config by id. This post looks at it from the plugin author's side: what you write when you're the one packaging a bundle, not what an operator writes when overriding one.
The two operations
Every entry in a cordis.patch.yml is one of two shapes:
# insert one or more new plugin rows
- insert:
- id: my-tool
name: './index.js'
config:
greeting: hello
# override an existing row's config, by id
- id: my-tool
config:
greeting: goodbye
insert is how a bundle adds rows that weren't there before — this is what your own plugin's cordis.patch.yml does when someone installs it. The bare id + config form is how a later layer overrides a row that already exists, by the same id. As a bundle author writing your own patch file, you'll almost always be using insert; override-by-id is what operators (or your own profile's own patch) reach for afterward.
The three fields inside an insert row
id— the join key. It's how a profile's own patch, a machine-level patch, or a--patchflag can later target this exact row to override it. Pick something stable and specific to your plugin (dsh-hello-plugin, notplugin), since a collision with another bundle'sidmeans whichever layer loads later wins outright.name— what actually gets loaded: a module specifier (an npm package name once published) or a path to your entry file (absolute, for local development). This is the field that ultimately resolves to the module exporting yourapplyfunction.config(optional) — the initial value passed into your plugin'sConfigschema, if it has one. See Making Your DeepSeek-Harness Plugin Configurable with Schemastery for how that value flows intoapply(ctx, config).
Where this file gets referenced from
A cordis.patch.yml doesn't do anything on its own — it has to be pointed at by the dsh.bundle.patch field in your package's package.json:
{
"name": "dsh-hello-plugin",
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
That field is the entirety of what marks an npm package as an installable dsh bundle. A package without it still installs fine as a plain dependency, but dsh prints a warning and activates nothing from it — useful for shipping a helper library alongside a real plugin, without dsh mistaking the library for a plugin itself.
Bundle package.json vs. profile package.json: two different dsh fields
This is the distinction most likely to trip up a first-time plugin author, because both are package.json files with a dsh key, but they answer opposite questions:
dsh.bundle.patch | dsh.profile.bundles | |
|---|---|---|
| Lives in | The plugin package you author | $DSH_HOME/profiles/<name>/package.json |
| Answers | "What does this package contribute?" | "Which bundles compose this profile, in what order?" |
| Value | A path to one cordis.patch.yml | An ordered array of bundle package names, @deepseek-ai/dsh-base first |
| Who writes it | You, once, when packaging the plugin | dsh itself, automatically, as bundles are added/removed |
You write and publish the first one. The second one you never hand-edit — running dsh plugin --profile <name> add <package> installs your package via pnpm and, because it sees your dsh.bundle.patch field, appends your package's name to that profile's dsh.profile.bundles list automatically. If you're coming at this from the profile-operator side instead of the plugin-author side, Profiles and Bundles in DeepSeek-Harness, Explained covers that half in full.
How multiple bundles' patches actually combine
Once a profile has several bundles installed, dsh applies each bundle's cordis.patch.yml in the order that bundle appears in dsh.profile.bundles — @deepseek-ai/dsh-base first, then whatever was added afterward, in install order. On top of that stack sit the profile's own cordis.patch.yml, then $DSH_HOME/cordis.patch.yml (machine-wide, shared across every profile), then any --patch flags on the command line. The DeepSeek Harness Configuration Guide covers that full four-layer stack, the full-replace-not-deep-merge rule that governs every override in it, and how to inspect the composed result with --dump-config — this article focuses on the one layer you, as a bundle author, actually write.
A real example: the official MCP client bundle shape
The official @deepseek-ai/dsh-mcp-client plugin is configured with exactly this insert shape, which doubles as a good illustration of a non-trivial config block:
- id: mcp-github
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: github
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-github']
env:
GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
Note that name here is an npm package specifier, not a local path — this is what a published bundle's insert entry looks like once it's meant for other people to install, versus the absolute-path form used for local development.
Where the built-in bundles live
dsh ships three built-in bundles that follow this exact same dsh.bundle.patch + cordis.patch.yml shape, and they're real files in the official repository worth reading if you want to see the pattern at production scale rather than in a toy example: packages/bundle/base (the bundle every profile starts with), packages/bundle/web-app (what dsh web adds on top), and the headless bundle used by dsh --profile headless. Each has its own package.json declaring dsh.bundle.patch and a cordis.patch.yml alongside it, at packages/bundle/base/cordis.patch.yml and packages/bundle/web-app/cordis.patch.yml respectively in the deepseek-ai/deepseek-harness repository.
FAQ
Do I need both insert and override-by-id in my own bundle's patch file?
Usually just insert — you're adding rows that don't exist yet. Override-by-id is what a consumer of your bundle (a profile's own patch, or a machine-level patch) uses afterward to adjust a row you inserted.
Can name point to a TypeScript file directly?
The official examples show both a relative/absolute path form for local development and a package-name form for published bundles; whether a raw .ts file resolves without a build step, or needs compiling to .js first, depends on your prepare script and how the package is installed — see Installing DeepSeek-Harness Plugins from GitHub for the build-step implications of installing from source.
What happens if I forget the dsh.bundle.patch field?
Your package still installs as a normal npm dependency, but dsh treats it as a plain library, not a plugin — it prints a warning and doesn't load any cordis.patch.yml, even if one exists in the package.
Is config required on every insert row?
No — it's optional. A plugin with no Config schema, or one that's happy with every field's default, can omit config entirely from its insert entry.
Can one bundle's cordis.patch.yml insert more than one row?
Yes — insert takes a list, and a single bundle can register several plugin rows in one file (for example, a bundle that ships both a tool and a service it depends on). Each row in that list still needs its own unique id.
Does the id I choose need to match my package's npm name?
No — id and the npm package name are independent. id only has to be unique within the composed plugin tree for a given profile; using something recognizably tied to your package name just makes overrides easier for operators to find and target later.
Next steps
Start from Build a DeepSeek-Harness Plugin from Scratch if you're writing your first bundle, add configurable options with Making Your Plugin Configurable with Schemastery, and see how an MCP-bridging bundle like the example above fits into the bigger picture in How to Use MCP Servers with DeepSeek-Harness. For the operator-side view of layering and debugging, read the DeepSeek Harness Configuration Guide. Real MCP-client bundles like the one above are cataloged under MCP & Connectors on FindHarness, alongside the wider Development & Runtime category for bundles built by other plugin authors.