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

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。
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。
启动之后只有一行输出
./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>:
<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 哈希。

所以前端自己就是一棵插件树,浏览器按 inject 顺序把它拼起来,每个插件一个独立 URL、一个独立版本号。文档里说"webui 也是插件、可以在服务不中断的情况下换掉",不是修辞——rev 变了,浏览器就去拉新的那一份。
后端这棵树可以直接打出来:
dsh --profile web --dump-config
web profile 490 行,headless profile 333 行。每一行是一个插件条目,长这样:
- id: agent-default-model
name: '@deepseek-ai/dsh-agent-default-model'
config:
provider: deepseek-official
model: deepseek-v4-flash
注释还会告诉你这一行是谁贴的、被谁改过:
# == @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.hostandcode.clientare 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 加版本指针和它的会话日志是同一个思路。

授权这一层有个细节值得单独拎出来:
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
cordisdisables preset authoring itself.
别改自带的预设——把 cordis 这个预设搞坏了,你就再也没法创作预设了。自进化系统怎么自救,官方用一条禁令绕过去了。
到这儿我对这套架构的评价还是正面的。
装一个插件,然后我看到了那条警告
dsh 自己的插件管理是这么设计的:
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 的手册。
然后我装了个官方的工具包试流程:
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 里那行依赖不用改一个字符。

这条路径不是我推测出来的,是官方的警告文案自己讲的。
于是我去数了一下这条路上有什么。装完之后 ~/.dsh/profiles/ 下:
| 包总数 | 547 |
| 带安装期生命周期脚本的包 | 68 |
68 个包在 install / postinstall / preinstall / prepare 里挂着命令。比如 node-pty:
"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,仓库自己钉的是
[email protected]。 - 磁盘:一份 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 什么时候提供挂载前的权限声明和扫描钩子。