Special Topic · Inside DeepSeek Harness

Profile / Bundle / Patch: How Far Users Can Reshape the Product

How the three-layer config lets users swap deep capabilities without editing source

THE QUESTION THIS PAGE ANSWERS

ANSWER FIRST

What is the key idea behind “Profile / Bundle / Patch: How Far Users Can Reshape the Product”?

How the three-layer config lets users swap deep capabilities without editing source

DECISION RULE

Make the claim earn its place. Use this page as a decision aid, not a definition to memorize. Connect the idea to one real task, one observable result, and one failure that would change your mind.

TRY NEXT

Write one question you could answer with evidence after trying this idea.

WATCH FOR

A conclusion that sounds complete but leaves the key assumption untested.

Course goalAfter this lesson you can explain three things: why DSH splits config into Bundle (release defaults), user patches (Profile layer + Home layer), and --patch (one-shot experiments), and how those layers prioritize; why a patch that hits the same entry does whole-entry replace, and which counterintuitive result that choice causes; and how dsh --dump-config lays out the final effective config for you.
Interactive demo · Patch layer sandbox

The sandbox below turns the four config layers into toggleable cards — bottom to top is application order. Hit Play and watch each layer brush its changes onto the final entry on the right. Watch layer 3 closely: it writes only one field, and the fields the two layers below carefully set get wiped. When you’re done, switch to deep-merge contrast mode and see another ending for the same change under merge semantics.

Semantics
Composed final entry · id: conversation-model
Empty entry list. DSH’s root config is just an empty array — the plugin tree is brushed on from scratch, layer by layer of patches.
Hit Play to start stacking, or flip each layer’s toggle first to change who participates. Scrolling here auto-plays once.
Teaching simulation: entry ids, fields, and filenames are simplified; stack order and replace semantics match apps/cli/src/profile-boot.ts lines 121–129 and vendor/include/src/index.ts lines 58–128. deep-merge mode is a teaching hypothesis — DSH does not implement that semantics.
First, nail three terms

Up front: DSH has no one big config file. The product shape is stacks of patch lists, and each stack has a clear owner.

Bundle is the release default. It’s an npm package; the substance is the patch list shipped inside. The three built-ins are base (shared core), web-app (browser surface), and headless (one-shot task mode).

Profile is a user’s assembly. It’s a directory under $DSH_HOME/profiles/<name>: the manifest lists which bundles to use and in what order, with the user’s own patch file beside it. First use of the names web or headless auto-initializes a template.

Patch is the smallest unit of change. One patch finds a target entry by id, then changes config, disables it, or inserts a new entry. Out-of-tree plugins enter here too: dsh plugin add installs into a profile, and afterward it’s just another patch layer.

Source: the three built-in bundles and how package.json declares bundles — see packages/bundle/README.zh.md; profile template auto-init at apps/cli/src/profile.ts lines 114–117.

Design idea 1 · Every config layer has an owner

What problem it solves

Imagine the opposite: one big config file where release defaults, this assembly’s customizations, personal prefs, and one-off experiments all live together. Three months later you upgrade the release — new defaults and your old edits tangle in the same file. You can’t tell which line who wrote, which line is safe to touch; upgrades become a manual diff.

There’s a more everyday failure: you temporarily try an experimental model, tweak config and forget to revert — next day the whole environment runs on experiment settings. The root cause is no ownership of change: who wrote it, what it rides with, when it should disappear — one file can’t answer those three.

What the idea is

DSH splits config into four layers, each with an owner: Bundle rides with the release, Profile with this assembly, Home with this machine, --patch with this one command. At boot it starts from an empty array and brushes the four patch layers on in fixed order — later wins.

The starting point really is an empty array: the root config file is just [], and the template comment says don’t edit this file — edit patch files. All real content arrives via patch insert, so every line in the final plugin tree can answer which layer it came from.

Four patch layers apply 1 to 4 in order — later wins 1 · Bundle layer Release defaults; rides with the dsh install 2 · Profile user layer Rides with this assembly 3 · Home user layer Machine-wide prefs; applies to every profile 4 · --patch overlay One-shot experiment; rides with the command Apply in order onto an empty array The only patch algorithm in the whole repo Final plugin tree boot() mounts and runs dsh --dump-config Same algorithm, offline compose
Same patch stack, same algorithm — mount and dump cannot drift apart.

The two user-patch layers also divide carefully: Profile rides with this assembly; Home is machine-local preference, applies to every profile, so it sits after Profile and overrides it. When both change the same id, Home wins. --patch comes last for one-shot experiments you don’t want in files — you can pass multiple, applied in CLI order. In long sessions both user-layer files are watched; save a change and the running tree recomposes in the same layer order.

Layer order in source is literally an array literal: the launcher flattens the composed profile into a patch array, and array order is application order. The function is short enough to quote as a slogan — it proves the four-layer sequence is hard-coded in one array, with no conditionals:

apps/cli/src/profile-boot.tslines 121–129
/** The full patch stack of one composed profile, in application order. */
function allPatches(composed: ComposedProfile): PatchOptions[] {
  return [
    ...composed.bundlePatches,
    ...composed.profile.patches,
    ...composed.homePatches,
    ...composed.overlays,
  ]
}
Source snapshot note: Based on the local deepseek-harness-master repo; verified against apps/cli/src/profile-boot.ts, verified on 2026-08-13. Code blocks keep the original source text.

Source: empty-array root config and template comment at apps/cli/src/profile-boot.ts lines 60–64; why Home overrides Profile at packages/boot/app-boot/README.zh.md line 43; repeatable --patch at apps/cli/src/args.ts line 132; hot reload via same-package watchUserPatches.

Why it lasts

Layered override is a config-system universal: CSS cascade, systemd drop-in dirs, editor user settings over defaults — same structure. Whenever one product is edited by release, team, individual, and single-command roles at once, layers must separate, or upgrades and rollbacks have nowhere to start. Rewrite the whole harness in another language and these four layers are still these four layers.

Design idea 2 · Hit means whole-entry replace

What problem it solves

Stacking still needs one key decision: what when two patch layers hit the same entry? The intuitive answer is deep-merge — field-level merge, upper layer writes only what it wants to change, other fields keep automatically. Easy to write.

Easy has two costs. First, merge can’t express delete: if a lower layer set a field and the upper wants it gone, merge has no such action — you can only overwrite with another value. Second, the final result matches no file’s literal content; debugging config means running every layer’s merge algorithm in your head before you know what’s actually in effect.

What the idea is

DSH chose whole-entry replace. After a patch hits a target by id, every top-level key in the patch except id is assigned over. config is one top-level key, so the old config object is wholly replaced — none of its fields survive. Want to change one field? You still restate the fields to keep. Official docs call this a known limitation, in so many words: “profile overrides must restate bundle fields that should be preserved.”

That yields the easiest counterintuitive trap — the scene layer 3 demos: Home writes only model, and provider and temperature set by the two layers below get wiped, with no warning, just a wrong result. Three behavior edges to remember: a patch pointing at a missing id warns but doesn’t error — one stderr line, then skip; an empty or comment-only patch file throws, because the parse result isn’t a list; to make a layer do nothing, write [].

Replace semantics pays off in predictability. There’s one copy of this algorithm in the whole repo: mount uses it, offline compose for dsh --dump-config uses it — dump and real boot cannot drift. Inputs are never mutated; results are always deep copies, so when hot-reload drops a patch it truly restores — earlier values aren’t baked into a cache. There’s also a what-you-see-is-what-you-edit map: docs/config-catalog.zh.md, a 3152-line generated doc listing every loadable package’s config types as-is. Want to know which keys a patch can write? Check that catalog.

What dump shows is what editing config gets you.

Source: replace semantics (top-level keys assigned one by one) at vendor/include/src/index.ts lines 110–124; the single-algorithm and immutable-input promise in the same file’s JSDoc lines 43–52; official whole-entry replace note at packages/boot/app-boot/README.zh.md line 60.

Why it lasts

It’s an old declarative-config choice: merge’s convenience vs replace’s readability. Replace gives each entry a unique last author — when something breaks, find the last patch and its literal content is what’s in effect; the cost is restating when you write. DSH scored the ledger toward predictability. Any config system that allows multi-party stacking and expects users to self-debug faces this question — independent of implementation language.

Side-by-side · How three products layer config

DeepSeek Harness

Plugin-entry level · whole-entry replace

The unit of a layer is a plugin entry: one patch replaces a whole config. Four layers from Bundle to --patch in fixed order; root config is an empty array; every line traces to a layer.

Swapping a deep capability means swapping one entry: replace the compaction plugin’s config wholesale, or even insert a third-party implementation.

Claude Code

Settings-field level · five sources

SETTING_SOURCES lists five layers: userSettings, projectSettings, localSettings, flagSettings, policySettings — later is larger (settings/constants.ts lines 7–22). Admin policy always sits on top, and some fields only take effect from managed sources.

What gets layered is settings fields — behavior parameters. The plugin tree itself isn’t on the config table.

Grok Build

TOML field level · deep merge

xai-grok-config recursively merges with deep_merge_toml (loader.rs lines 415–426): tables merge, arrays replace, upper fields win. Layer order is system_managed, managed, user; requirements and MDM managed layers sit on top last (loader.rs lines 234–248).

It picked the deep-merge contrast from the DSH demo: change one field without restating, but the upper layer can’t delete a lower-layer field.

Two comparison foci. First, merge semantics: Grok and Claude Code both do field-level merge — easy to write; DSH does entry-level replace — verbose to write, in exchange for dump matching file literals. Second, what gets layered: those two layer settings fields; DSH layers the plugin tree itself, so how deep users can reach differs — one insert patch can wire a third-party compaction into the main loop, with no counterpart in the other two config systems. Claude Code also has what DSH lacks: an admin policy layer and locked fields that only apply from managed sources — DSH has no equivalent today, based on public materials.

Classroom Exercise
01

Hand-trace a two-layer conflict

Profile layer writes id: conversation-model, config: { provider: deepseek, model: v3.2, temperature: 0.2 }; Home writes id: conversation-model, config: { temperature: 0 }.

Q1: Under whole-entry replace, write the final entry’s config — which fields vanish, and will any warning tip you off?

Q2: Swap which layer each patch sits in — what does the result become?

Q3: Using the dsh --dump-config idea, explain how to verify your answer without starting the product.

Takeaway: DSH config is four patch layers brushed onto an empty array — Bundle, Profile, Home, --patch, later wins. When a patch hits the same id it replaces the whole config, no field merge; fields you want to keep must be restated. The same algorithm drives mount and dump, so what --dump-config shows is what editing config gets you.

The handoffs inside “Interactive demo · Patch layer sandbox”

“The sandbox below turns the four config layers into toggleable cards — bottom to top is application order.” shows that an Agent is not defined by the model alone. Each handoff between model, context, tools, state, permissions, and people affects both progress and recovery.

Write the state before adding capability

Starting from “Up front: DSH has no one big config file.”, split the workflow into starting state, next action, tool result, state update, and stop condition. Debugging then means finding the first lost piece of information or authority instead of saying vaguely that the model “got worse”.

A happy path is not reliability

Use “Q3: Using the dsh --dump-config idea, explain how to verify your answer without starting the product” to replay one successful and one failed run. Record the context, tool result, and owner at each turn; the workflow is maintainable when a second person can follow it without the original builder.

From “Interactive demo · Patch layer sandbox” to “First, nail three terms”

“Interactive demo · Patch layer sandbox” grounds the problem in “The sandbox below turns the four config layers into toggleable cards — bottom to top is application order. Hit Play and watch each layer brush its changes onto the final entry on the right. Watch layer 3 closel…”. “First, nail three terms” then moves it toward “Up front: DSH has no one big config file. The product shape is stacks of patch lists, and each stack has a clear owner”. Together, they show that the lesson is not just a conclusion to remember, but a claim with conditions.

Carry the judgment into the next situation

When analyzing an Agent, trace state, action, tool result, and next step in order. Each handoff should explain where information came from, who confirmed it, and where failure stops.

  • “Interactive demo · Patch layer sandbox”: The sandbox below turns the four config layers into toggleable cards — bottom to top is application order. Hit Play and watch each layer brush its changes onto the final entry on the right. Watch layer 3 closel…
  • “First, nail three terms”: Up front: DSH has no one big config file. The product shape is stacks of patch lists, and each stack has a clear owner
  • “The closing point”: DSH splits config into four layers, each with an owner: Bundle rides with the release, Profile with this assembly, Home with this machine, --patch with this one command. At boot it starts from an empty array an…

The final “The closing point” brings the discussion to “DSH splits config into four layers, each with an owner: Bundle rides with the release, Profile with this assembly, Home with this machine, --patch with this one command. At boot it starts from an empty array an…”. The useful thing to carry forward is knowing which judgments must be revisited when input, scale, or risk changes.

Mark as learned Your reading progress updates automatically
← PreviousNext →

Keep reading

The next useful article in the thread.

ARTICLE DISCUSSION

Leave one useful thought here.

Keep the idea that clicked, the question that stayed open, or a small note for the next learner.

Discussing Profile / Bundle / Patch: How Far Users Can Reshape the Product Inside DeepSeek Harness
3discussionsArticle discussion · synced with the Circle
View in the learning circle
AM
Asha MorganContent editor
INSIGHTField note

I turned one judgment from this article into a small experiment I could run today. Knowing what to observe next is more useful than simply remembering the conclusion.

ARTICLE DISCUSSION7 helpful
LH
Lin HarperIndie developer
INSIGHTInsight

After reading this, I first looked for the conditions behind the idea instead of copying the method into a project. That order made the later trade-offs much clearer.

ARTICLE DISCUSSION5 helpful
KM
Kiki MooreProduct operations
QUESTIONQuestion

When this judgment reaches real work, which constraint should be added first? I am curious which step matters most between reading and the first practical attempt.

ARTICLE DISCUSSION4 helpful