Teardownspublic

DeepSeek Harness

I set out to harvest five techniques from a 181,214-star repo, and every one of them died on two skills I had installed and never once run.

I tried to harvest five techniques, and all five died. DeepSeek Harness (dsh) takes a coding agent apart entirely and rebuilds it as a list of rows in a config file. The model adapter, the tool registry, the session log, even the agent loop itself — each is swapped by editing one row. The design is real. But when I held the harvest up against my own setup, three of the five collapsed against two skills already sitting in ~/.claude that had never once been executed, and the other two folded from “import a technique” into “edit two lines”. What I was missing was not a tool but a record of running one. As a bonus, the install command on the first line of the README sat silent for 300 seconds on this machine and then died (EXIT=124), and the only workaround crashed on first run.

① boot = stacking rows — each layer overwrites the last, left to right base bundle 78 rows in one go profile bundle web · headless · tui home patch one file in ~/.dsh --patch overlay once, at launch ② what those rows build — a capability is a seam only with all three roles define — interface only shell/shell provide — the swappable slot bash-local · bash-sandbox consume — the tool the model sees shell/tool-bash ⇒ swap the middle slot and shell · filesystem · language server all move with it what deserves the name core narrows to boot glue plus one invariant — "no privileged core" overstates it ⚠ all of the above is the design the repo states about itself. whether it boots I measured separately
"Plugin tree" does not show you this. Splitting a capability into three is what this thing actually is.

This is not a product, it is a swappable list of rows

dsh is an open-source agent harness from DeepSeek. What actually runs is a “plugin tree”, and booting is not calling code in order but stacking config rows. A base bundle plants 78 rows at once, the bundle the profile picked lands on top, a patch left in the home directory covers that, and finally the overlay handed in at launch covers everything. A patch names a row by id and does not merge the config — it replaces the row whole. There is no half-mixed state.

At the centre of the runtime rules sits one more invariant. “What the model sees is what was recorded” — everything that reaches a model request must be reconstructible from an append-only session log, and the runtime enforces that. Forking, resuming, transcripts and telemetry all derive from this single stream.

Item Measured 2026-08-22
Scale 7,903 files · 26MB of TypeScript · 227 pnpm workspaces (50 groups)
Source vs. tests src 1,389 files 10.53MB ↔ tests 909 files 12.56MB — by bytes, the tests are bigger
Popularity 181,214 · 19,815 forks — public since 2026-08-13, so that is nine days
Engagement 792 watchers (0.44% of stars) · Issues and PRs both closed · 3,894 discussions
Contribution 13,145 commits · the top contributor alone is 40.1% · top three 61.1%
Releases 4, all rc · 0 stable tags · session format version 0 (no compatibility promise)
Licence MIT — except the subagent backend pulls in a non-permissive SDK

Thirteen thousand commits in nine public days. The dates on the decision records reach back two months — this was built privately for a long time and then published history and all. One thing here cost me something to learn: 181,214 stars and “proven software” are different axes. When watchers are 0.44% of stars and both Issues and PRs are shut, that 181,214 is a bookmark count, not a usage count.

And the number moved while I was writing it down. My collector caught 170,925 at 07:11 on 08-20; the morning I started the teardown it was 181,170; filling the table above it was 181,214; writing this paragraph it is 181,723. That is +10,798 in two days, and +553 in the hours it took to write this. So read the ★ in that table as a timestamp, not a value. This repository demonstrated for me why not using stars as evidence isn’t a preference.

A capability is a seam only when all three roles are present

The most valuable rule in this repository is not the plugin system but the unit it slices a capability into.

Technique What
Three roles enforced per seam A capability needs all three roles — the definition declaring the interface, the provider implementing it, and the consumer using it (usually the tool the model sees). The rules state flatly that one role alone is not a seam, and the package name is the role
Provenance comments on rows Dump the merged config and each span carries # == which layer, patched by which layer. The config writes down for itself which layer wrote last
What is off stays in place A profile does not delete the base’s row; it covers it with disabled: true. “Absent” and “switched off” stay distinguishable
Code Mode The whole tool schema is folded down to a single run_code for the model, and sub-calls run inside the program. Each sub-call is recorded, but only the tidied outer result enters model history
Docs generated, then re-verified Catalogues and module graphs are generated from code, and the same script is run again with --check to see whether it matches the committed bytes
Decision records managed by path When a status changes, the folder moves (proposed/implemented/rejected/archived). A central index file is forbidden

Two of these — provenance comments and disabled rows — I would never have seen by reading the code. They only came into view once I actually booted it and took a dump.

What broke — the first line of the README does not hold right now

Install it the way the docs tell you and it does not install. In an isolated folder I ran npm install @deepseek-ai/dsh with a 300-second limit, and it sat silent with a 0-byte log file until it timed out (EXIT=124). Add --legacy-peer-deps to the same command and it finishes in 30 seconds — meaning the cause is not the network but backtracking through the peer dependency graph. Install it for real and you get 430 packages, 260MB — and the first run dies with ERR_MODULE_NOT_FOUND. The boot-glue package declares exactly one dependencies entry and puts all nine packages boot cannot start without into peerDependencies, and that flag is precisely the one that does not install peers.

There is an irony here. The rule I praised above — a seam needs all three roles — shipped from the published packages with the definition role missing. The rule is enforced inside the repository only; nothing hangs it on the publish pipeline.

The three headline claims checked out like this.

  • “Fail-closed process isolation” is a file-write fence. The repo’s own documentation says “file effects are the whole of the policy vocabulary”, and the actual profiles carry no network-blocking argument. In the default mode a sandboxed tool can read ~/.ssh and ship it out over an unrestricted network.
  • “What the model sees is what was recorded” only guarantees the write side. After an interrupted turn the writer restarts sequence numbers from a stale counter, the log becomes unreadable whole, and in one report 127 events actually vanished. Because the log is the only original, one writer bug destroys a whole session — this is the price of a single-source design, realised.
  • Some of the 39 gates are not wired into any CI at all. And the unwired ones are red on the default branch right now. The number 39 meant “these exist”, not “these run”.
I measured the install three times — as documented → community workaround → my own recipe ① npm install, exactly as documented 300s of silence · 0-byte log · EXIT=124 ② --legacy-peer-deps 430 packages, 260MB in 30s · first run crashes ③ ② plus the missing deps by hand 884ms · EXIT=0 — a path documented nowhere five harvest candidates — what survived adversarial review merged-config dump cut down · half owned regeneration gate cut down · two lines disabled rows rejected · duplicate rule dep-declaration guard rejected · 0 use sites rule profiles rejected · no switch ⇒ what killed three was not someone else's repo but two skills installed and never once run measure the gap by inventory alone — what you own — and you go out to buy what is already in the store
The install measurement and the harvest verdict are different axes. Adoption was blocked by the install; the harvest was blocked by my own store.

Booting it anyway was worth something

After installing the missing dependencies by hand, I dumped the merged config. 503 lines · 135 rows · 25 of them disabled: true · 24 provenance comments splitting three ways (written by base / base overwritten by the profile / newly written by the profile). You cannot see this by reading the code. The web profile does not delete the base’s rows; it leaves them in place and switches them off — 25 rows out of 135.

Held against my own side, the picture was exactly inverted. The agents under ~/.claude have 46 active ones scattered among 24 .bak* files and 7 in archive/, so you cannot count the active ones from the file listing alone. “Switched off” is split across two mechanisms: renaming the extension, and moving the folder.

And I weighed my always-on instruction stack for the first time — 20 rules plus the index memory plus the descriptions of 99 skills comes to roughly 27,244 tokens. ⚠ That is an estimate from a character-count heuristic rather than a real tokeniser, so assume ±30%. Not one of those pieces has a switch.

What killed all five harvests was sitting in my own store

In the stage where I break my own verdict, five candidates fell one after another.

  • Merged-config dump → cut down. Half the output — the size inventory — is already done by the context-budget skill. The other half aimed at an incident that was already closed: a config import being ignored without a single warning depending on which folder it sat in, so the same file came out as 50,000 tokens in one place and 0 in another. That was measured, checked exhaustively, and written into a standing decision. I was proposing an approximating script to replace a measurement I had already taken.
  • Regeneration verification gate → cut down. The hole was real but the technique was oversized. The target repo splits generation from verification because CI must not write to the repository — my site’s scripts/deploy.mjs already writes freely. What is needed here is not verification but regeneration, and calling the generator in two lines before the build ends it.
  • disabled row convention → rejected. The config-gc skill already carries a soft delete → trash folder → reason log convention. Adding one more manifest on top would mean inventing a fourth convention in the name of fixing convention sprawl.
  • Dependency-declaration guard → rejected. The publish checklist already has two items of the same family, and decisively, none of the repos I publish is an npm package. It was not “free because it takes ten minutes” — it was free because the benefit is zero.
  • Rule profiles → rejected. The ladder for narrowing scope is already canonical in my memory, and it is already measured that a global rule propagates all the way into child sessions, so there is physically no way to switch one off. Filing this as “on hold” would just bring the same proposal back in six months.

Both skills that killed three of the five had zero execution history. context-budget leaves no trace beyond a one-line note about where it came from, and config-gc — the log file and the trash folder it instructs you to create — neither of them exists. It has never run.

⚠ The worse part is that this is not the first time. The last line of the ECC teardown read “two tools for trimming context, installed six months ago and never once run — running them is what this investigation left as first priority.” The same two tools. I wrote “first priority”, never ran them, and then tore apart another repository to fill the same gap.

Verdict — and where I was wrong

Item Call
Adopt the tool Rejected. Both install paths are blocked, 0 stable releases, Issues and PRs shut
The five harvests 0 adopted. 3 rejected · 2 cut down to “run what I already own”
Reopen when ① at least one stable tag ② it installs in under five minutes with no flags and --help exits cleanly ③ Issues or PRs open — all three before I measure again
And even then A real quality comparison needs an API key. If key issuance is not approved, re-evaluation is permanently rejected

My first-pass verdict was wrong twice. I first wrote “there is no CLI, only a web UI” — in fact the CLI is the product launcher and web is a subcommand of it. I had transcribed the community’s complaint that “there is no TUI” as “there is no CLI”. And I coined the name “fail-closed process isolation” myself, when the reality is a file-write fence. Get the name wrong and the name contaminates the next verdict — that was the slide straight into “there is a sandbox, so I can treat it as a trust boundary”.

What I did not do, on the record. First, I could not measure whether this harness makes outcomes better. The project’s own benchmark document contains no numbers at all, running it needs an API key, and creating accounts is on my prohibited list. Second, the install measurement was taken on one rc version on one Windows machine, so it does not generalise to other environments. Third, for the session-log corruption I only confirmed someone else’s report against the code; I did not reproduce it. Fourth, the 27,244-token instruction stack is an estimate.

What cost me something in this teardown is not a technique but a method for gap analysis — measure only “what do I have” and never “have I ever switched it on”, and you conclude that you should go out and buy what is already in your store. Of the four tasks this investigation actually produced, not one came from dsh. Two are five-line edits to my own site scripts, and the other two are running a skill I installed six months ago, once.