# DeepSeek Harness 能自己改自己，也能被别人改

> 实测安装 DeepSeek Harness：343MB、255 个包、490 行插件树。cordis 创造模式让模型自己写插件热装进正在跑的主进程；但装插件就是 pnpm install 一份进程内全权限代码——最漂亮的部分和最没锁的部分，是同一处。

- 原文链接: https://laojin.blog/blog/20260817_dsh_plugin_supply_chain
- 作者: 老金
- 发布日期: 2026-08-17
- 标签: DeepSeek, Agent Harness, 插件安全, 供应链, Claude Code

---

![DeepSeek Harness 能自己改自己，也能被别人改](https://pic-1258874139.cos.ap-hongkong.myqcloud.com/laojinblog/posts/20260817/00_cover.png)

8 月 13 日，DeepSeek 建了 `deepseek-ai/deepseek-harness` 这个仓库。四天后 14 W 个 Star，1.4 W 个 fork，但 0 个 open issue——issue 那一栏是关着的。

当前已经有不少写得很好的架构拆解了，机制层面我不重复。我想干的是把它装到自己机器上，一步步走完，看这套"一切皆插件"在真实的安装-启动-装插件流程里长什么样。

走完之后我发现，这套架构最漂亮的部分和最没锁的部分，是同一处。

---

## 2 分钟，343M，255 个包

我这台是 Node v22.21.1（官方要求 `^22.19.0 || >=24.0.0`），pnpm 10.32.1。

```bash
mkdir dsh-test && cd dsh-test
npm install @deepseek-ai/dsh
```

实测下来耗时 1 分 42 秒，`node_modules` 343 MB，顶层 255 个包，其中 `@deepseek-ai/` 作用域下 195 个。

195 个官方包按前缀数一下，能看出这个产品的重心在哪：

| 前缀 | 包数 |
|---|---|
| `client-*`（前端） | 40 |
| `tool-*`（工具） | 19 |
| `session-*`（会话） | 16 |
| `host-*` | 8 |
| `cordis-*`（vendor 进来的框架） | 8 |
| `agent-*` | 6 |
| `sandbox-*` / `llm-*` / `fs-*` / `subagent-*` | 各 4 |

40 个前端包，6 个 agent 包。界面被切得比 agent 循环还碎。

当前版本 `0.1.0-rc.6`。

## 启动之后只有一行输出

```bash
./node_modules/.bin/dsh web
```

等了大概 40 秒，终端吐出一行：

```
dsh web: http://127.0.0.1:3080
```

就这一行。没有 banner，没有 "listening on"，没有提示你下一步该干什么，没告诉你要去哪儿填 API key。这个克制和它那份 1700 字符、不带截图的 README 是同一个风格：它假设你已经知道自己在干什么。

`curl` 一下首页，HTTP 200，12,076 字节。有意思的是 HTML 的第一个 `<script>`：

```html
<script>window.__DSH_BOOT__ = {"rev":"dcccb8324e44","entries":[
  {"id":"@deepseek-ai/dsh-typert-registry","url":"/plugins/@deepseek-ai/dsh-typert-registry/client.js?rev=f41d56e0b747","inject":[],"immediately":true},
  {"id":"@deepseek-ai/dsh-api-gateway","url":"...","inject":["@deepseek-ai/dsh-typert-registry","@deepseek-ai/dsh-client-connection"]},
  ...
```

我把这段 JSON 解出来数了数：38 个前端插件条目，9 个标了 `immediately: true` 立即加载，另外 29 个按 `inject` 声明的依赖关系加载。每一个都带自己的 `rev` 哈希。

![前端插件依赖树：9 个立即加载，其余按 inject 声明等待](https://pic-1258874139.cos.ap-hongkong.myqcloud.com/laojinblog/posts/20260817/01_plugin_tree.png)

所以前端自己就是一棵插件树，浏览器按 inject 顺序把它拼起来，每个插件一个独立 URL、一个独立版本号。文档里说"webui 也是插件、可以在服务不中断的情况下换掉"，不是修辞——`rev` 变了，浏览器就去拉新的那一份。

后端这棵树可以直接打出来：

```bash
dsh --profile web --dump-config
```

`web` profile 490 行，`headless` profile 333 行。每一行是一个插件条目，长这样：

```yaml
- id: agent-default-model
  name: '@deepseek-ai/dsh-agent-default-model'
  config:
    provider: deepseek-official
    model: deepseek-v4-flash
```

注释还会告诉你这一行是谁贴的、被谁改过：

```yaml
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: hmr
  name: '@deepseek-ai/cordis-plugin-hmr'
  disabled: true
```

`dsh-base` 铺了 hmr 这一行，`dsh-web-app` 把它 patch 成 `disabled: true`。配置的每一层来源都是可追溯的，这一点做得确实干净——你想改任何一行，往自己的 `cordis.patch.yml` 里写一条同 id 的覆盖就行。

顺带记两个数：主进程稳定后 RSS 137 MB；`~/.dsh` 目录本身只有 32 KB（依赖全在 pnpm store 里，profile 目录下是符号链接，不占额外空间——这点比我预期的克制）。

## 四种模式，第四种是让它改自己

装完之后 `config/agent-presets/` 下躺着四个预设，中文名是官方自己写的：

| preset | 名字 | 配置行数 | 干什么 |
|---|---|---|---|
| `minimal` | 极简模式 | 62 | 只有持久 bash + str_replace_editor 两个工具 |
| `standard` | 标准模式 | 251 | 完整编码 agent：文件、Shell、检索、Skills、计划、子代理、工作流 |
| `code` | PTC 模式 | 262 | 标准模式 + Code Mode SDK，让模型写一个 TypeScript 程序编排多步操作 |
| `cordis` | 创造模式 | 262 | 标准模式 + 运行时检查 + 插件实验 + preset 创作指导 |

前三个是常规操作。第四个我想多说两句。

`cordis` 这个 preset 目录里带了两份 SKILL.md，一份 154 行，一份 420 行。420 行那份叫 `cordis-plugin-development`，它教 agent 的事情是这个：

```
1. cordis_inspect_list   —— 看当前运行时注册了哪些服务、方法、schema
2. cordis_inspect_query  —— 精确查它要用到的 Service / Event / Slot / 主题 token
3. cordis_inspect_self   —— 读某个插件现在的源码和运行诊断
4. 在 code.host / code.client 里写 JavaScript，调 cordis_define
5. cordis_run            —— 把这个 package 激活
6. cordis_stop           —— 临时停掉
7. cordis_undefine       —— 永久删除
```

也就是模型自己写一份插件代码，塞进正在运行的这个进程里，装上、跑起来、坏了再滚回去。不重启，不发版本，不退出会话。

skill 里有一句写得很直白：

> Both `code.host` and `code.client` are plain JavaScript function bodies that return a Cordis Plugin. They are not compiled by TypeScript, JSX, or a bundler.

不过 TS、不过 bundler，模型写的 JavaScript 函数体直接是插件本体。

它的版本模型也设计过：每次 `cordis_define` 追加一个不可变的 package，不改旧的；`run` 指向哪个 `packageId`，当前跑的就是哪一版；回滚就是 `run` 回旧的那个 id。这套 append-only 加版本指针和它的会话日志是同一个思路。

![append-only 版本链：指针指向当前版本，回滚就是把指针指回去](https://pic-1258874139.cos.ap-hongkong.myqcloud.com/laojinblog/posts/20260817/02_version_chain.png)

授权这一层有个细节值得单独拎出来：

> A single check mark authorizes only the current Package; double check marks authorize future versions of the same Plugin.

单勾只授权眼下这一个 package。**双勾授权这个插件以后的所有版本。**

你点第二个勾的那一刻，是把"这个插件将来自己改自己的每一版都不用再问我"这件事永久批掉了。一个只需要点一下、语义极重的开关。

还有两处诚实的注脚，都在官方 skill 里：

> A failed update does not automatically restore the old physical Run; explicitly run current when recovery is required.

更新失败不会自动恢复旧的运行，得你（或者模型）显式地 run 回去。这给"可逆"打了个折扣：框架保证效果被撤销，不保证服务自动回到可用状态。

另一份 154 行的 skill 里还有一句更有画面感的：

> Never edit, delete, or overwrite a preset that ships with the deployment... corrupting `cordis` disables preset authoring itself.

别改自带的预设——把 `cordis` 这个预设搞坏了，你就再也没法创作预设了。自进化系统怎么自救，官方用一条禁令绕过去了。

到这儿我对这套架构的评价还是正面的。

## 装一个插件，然后我看到了那条警告

`dsh` 自己的插件管理是这么设计的：

```bash
dsh plugin --profile web add <package>
```

我先试了 `dsh plugin --profile web --help`，想看看有哪些子命令。打出来的是这个：

```
Version 10.32.1
Usage: pnpm [command] [flags]
...
Manage your dependencies:
      add                  Installs a package and any packages that it depends on...
```

pnpm 自己的帮助。`dsh plugin` 是一层薄薄的转发器，`--help` 也一起转过去了。你问 dsh 插件怎么用，它给你 pnpm 的手册。

然后我装了个官方的工具包试流程：

```bash
dsh plugin --profile web add @deepseek-ai/dsh-tool-web
```

1.7 秒，7 个包。最后一行是这个警告：

```
dsh: warning: @deepseek-ai/dsh-tool-web declares no dsh.bundle — installed as
a plain dependency, not a profile layer (a later update that gains one
activates it automatically)
```

前半句没问题：这个包没声明 `dsh.bundle`，所以只当普通依赖装着，不进插件树。

关键在括号里那半句：**a later update that gains one activates it automatically.**

一个包今天装进来是惰性的，什么都不做。它的作者明天发一个新版本，在 `package.json` 里加上 `dsh.bundle` 字段，它就自动变成一个挂载在你 harness 进程里的插件层。不需要你重新 add，不需要你确认，你的 `package.json` 里那行依赖不用改一个字符。

![惰性依赖在一次版本更新后自动跨进边界，成为挂载中的插件](https://pic-1258874139.cos.ap-hongkong.myqcloud.com/laojinblog/posts/20260817/03_silent_activation.png)

这条路径不是我推测出来的，是官方的警告文案自己讲的。

于是我去数了一下这条路上有什么。装完之后 `~/.dsh/profiles/` 下：

| | |
|---|---|
| 包总数 | 547 |
| 带安装期生命周期脚本的包 | 68 |

68 个包在 `install` / `postinstall` / `preinstall` / `prepare` 里挂着命令。比如 `node-pty`：

```json
"install": "node scripts/prebuild.js || node-gyp rebuild",
"postinstall": "node scripts/post-install.js"
```

这些脚本在 `pnpm install` 的时候就在你机器上跑了，跟 dsh 有没有启动无关。

把这几件事放在一起看。装插件就是 `pnpm install`，没有权限清单，manifest 里没有能力声明，没有沙箱。插件代码在 harness 主进程里跑，跟 `dsh-tool-bash`、跟你的凭据、跟你的会话日志同一个进程。一个惰性依赖可以在某次版本更新之后自动升级成挂载中的插件。依赖树里还有 68 个包带安装期脚本。

架构把可替换性做到了头，代价是信任边界也一起被拆掉。Cordis 那套可逆副作用保证的是卸载时不留垃圾，它从来没打算保证装进来的东西不干坏事。这两个是不同的问题，很容易被"可逆"这个词混过去。

## 社区已经在替官方补这一层了

我去 npm 上按关键字数了一下：`dsh-plugin` 这个 keyword 下 1098 个包。GitHub 上 `dsh-plugin` 这个 topic 下 5964 个仓库。

翻这个列表的时候，我看到一个包的描述停住了：

> **dsh-plugin-vetting**：装插件前，先体检：第三方插件 = 进程内全权限代码，这个工具让"盲装"变成"知情安装"。

我把它的 tarball 解出来读了 README（只解包，没装、没跑）。它的"威胁模型"章节写得比官方文档清楚：

> DSH 插件在 harness 进程内执行，拥有完整权限。因此本插件**不是安全边界**——它是**启发式绊线**（类似杀毒软件）：静态扫描插件源码，命中可疑模式就报告，**从不执行插件代码**，**不拦截**（拦截会误伤正常插件）。

它做的事：15 条恶意规则（网络外传、凭据访问、eval、持久化、读会话日志、生命周期脚本）、3 条误伤规则、传递依赖统计、官方包内容哈希基线（防的正是"信任名字"被投毒）、以及一个默认关闭的工具门禁。

然后是这句：

> 真正的根治方案应由 harness 提供"挂载前扫描钩子"——本插件是该思路的独立原型。

一个社区开发者，在官方开源四天之内，写了一个插件来扫别的插件，并且在 README 里说：这事本来该 harness 干。

这里有一个绕不过去的圈。dsh-plugin-vetting 自己也是一个插件，你要用它来审查第三方插件，得先 `pnpm install` 它——一份进程内全权限代码。它自己的 README 里还专门留了个 `allowlist` 配置项，理由是"安全插件规则里引用密钥路径，启发式必然高分"，也就是说它扫自己都会报警。

你需要信任一个插件，来帮你判断该不该信任插件。这不是这位作者的设计缺陷，当挂载层没有锁的时候，任何补救方案都只能长成这个形状。

## 和 Claude Code 摆在一起看

我上一篇正好写了 Claude Code 的 auto mode，那边的整个工程投入是往相反方向去的：

| | Claude Code | dsh |
|---|---|---|
| 每个工具调用 | 过一遍分类器（hard_deny / soft_deny / allow / 用户意图四级） | 由插件自己实现的 `tools/pre-execute` 决定 |
| 扩展代码的位置 | 大部分能力在核心里，扩展受权限模型约束 | 插件在主进程内，全权限 |
| 装第三方扩展 | 有明确的信任决策界面 | `pnpm install` |
| 不可逆动作 | `permissions.deny` 在分类器之前硬拦 | 靠插件自觉 / 社区插件启发式扫描 |
| 出厂默认 | 8 月 14 日起 auto mode 默认开 | 无 |

Claude Code 花大力气量化"人类审核只拦住 13.6%"，然后用分类器把每个工具调用重新审一遍。dsh 把架构做到连 agent loop 都能换掉，然后让你用 pnpm 装进程内代码。

不矛盾，它们在解不同的题。Claude Code 那道题是 agent 在你机器上干活怎么不出事，dsh 那道题是 agent 的运行时怎么在不重启的前提下改自己。

## 门槛清单（照实说）

想上手，先过这几道：

- Node `^22.19.0 || >=24.0.0`。我的 22.21.1 刚好够。
- pnpm。插件管理就是 pnpm，你得会读它的报错。我这台 10.32.1，仓库自己钉的是 `pnpm@11.7.0`。
- 磁盘：一份 343 MB 起。每个 profile 一套 workspace（好在走 pnpm store 链接，增量不大）。
- 内存：主进程稳定 137 MB。
- 版本：`0.1.0-rc.6`，README 原话 "THERE WILL BE COMPATIBILITY-BREAKING CHANGES"。三天一个 rc。
- 反馈渠道：issue 关着。有问题你只能去 fork、去社区仓库、去 npm 包作者那儿。

## 我的判据

如果你的目的是学"一个运行时怎么改自己而不重启"，dsh 是今天最值得读的一份实现。去读 `cordis` preset 那 420 行 SKILL.md，比读论文快。

要让它替你干活就是另一回事了。在挂载前扫描钩子进官方之前，把它当成一个你愿意给 root 的进程：独立环境、假凭据、只装官方包。

对我自己，结论是**现在不迁**。我需要的那个能力（让助手自己长出新技能、不重启就生效）dsh 确实给了，而且给得比任何现成产品都彻底。但我这台机器上有 App Store 的证书、有几个 SaaS 的生产凭据。让一个"插件即进程内全权限代码"的运行时跑在这台机器上，换来的自进化能力配不上这个风险。

我在等的信号只有一个：dsh 什么时候提供挂载前的权限声明和扫描钩子。
