Rotifer 现在可以装进 DeepSeek Harness。DSH 从我们这里加载的,是两行配置和四个技能文件。两行配置挂的都是宿主自己已经带着的插件——它自己的技能读取器、它自己的 MCP 桥。没有任何属于我们的东西在它里面运行。
这不是为了简洁而简洁。正因为如此,我们才敢往一个明说自己会破坏 API 的宿主里发东西。
宿主不是竞品
DeepSeek Harness 在八月开源:MIT 协议、TypeScript、基于 Cordis 元框架,纲领是"一切皆插件"。模型层、工具层、agent 循环、沙箱、界面——每一样都是配置树里可替换的一行。
大厂发布 agent 运行时,第一反应总是问"这跟我们冲不冲突"。对 Rotifer 来说,答案是结构性的而非战术性的:harness 决定 agent 怎么跑;Rotifer 关心的是 agent 该跑哪个能力、以及有没有更好的那一个。这两者是叠放关系,不是碰撞关系。一个没有能力层的 harness,恰恰就是能力层该去的地方。
所以问题从来不是要不要去,而是怎么去才不会被冲掉。
最顺手的那个做法是错的
DSH 处在 developer preview,并且明说破坏性变更还在后面。就在我们调研它的这几天里,它连续走了好几个候选版本,还中途重编过一次版本号。针对这样一个界面写原生插件,维护周期是以天计的。
我们最初从这个事实里得出的结论——值得说出来,因为它是错的——是"那就先别发"。前提正确,推论错误。我们把选项枚举成了"现在写原生插件"和"等"两个,从没问过有没有第三种。
有。而且生态里早就在用了。
一个 bundle 到底是什么
在 DSH 里,可安装单元叫 bundle:一个 npm 包或 git 目录,清单里声明一个字段 dsh.bundle.patch,指向一份 cordis.patch.yml。那份 patch 就是一串要插进配置树的行。
关键性质在于一行可以引用什么。行里写的是模块路径——而没有任何规则要求那个模块必须是你自己的。如果你写的是宿主本来就自带的模块,那你的包贡献的就只有配置,别的什么都没有。
我们写了两行:
- insert:
- id: rotifer-skills
name: '@deepseek-ai/dsh-skill-filesystem'
config:
providerName: rotifer
includeDefaultRoots: false
customSkillDirs: [ <本包的 skills/ 目录> ]
- id: rotifer-mcp
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: rotifer
transport: stdio
command: npx
args: ['-y', '@rotifer/mcp-server@0.16.1', '--tools=evolve']第一行是宿主自带的文件系统技能读取器的第二个实例,作用域限定在我们的目录里。includeDefaultRoots: false 比看上去重要:不加它,这个读取器还会去扫用户自己项目和个人目录下的技能,可能把用户亲手写的技能遮蔽掉。限定之后,它能贡献的永远只有我们包里那四个。
第二行是宿主自带的 MCP 桥,指向 Rotifer 的 MCP 服务。
两行都不是我们写的代码。它们将来怎么升级是 DeepSeek 的事,不是我们的事——在 preview 期,这就是全部要点。
零代码是一条安全性质,不只是工程好处
工程上的好处一句话说得完:我们不调用的 API,坏不了我们。
另一个后果我们过了更久才体会到。装这个插件,不等于把我们的代码放进你的 agent 运行时。 这个包里没有行为可供审计,因为包里根本没有行为——只有一份"挂宿主的哪些组件、怎么挂"的声明。
对一个职责里包含"往你机器上装第三方能力"的插件来说,这个区别比便利性值钱。你让渡出去的信任,被收窄到我们通过 stdio 拉起的一个进程,而且版本是钉死的、跑之前你可以自己查:
npm view @rotifer/mcp-server@0.16.1 dist.integrity启动行不只钉了版本,还收窄了工具面。不声明的话,这个服务会把全部能力都端出来,包括发布和登录。声明 --tools=evolve 之后,它只提供十个工具——搜索、比较、查看、安装、回滚,以及运行本地 Agent——其余一律拒绝。启动行也没有 --allow,所以沙箱的逃生口是关着的。经由这个插件能触达的一切,都无法替你发布,也无法让你登录。
这十个工具的 schema,在该行挂载期间每次请求都要付 6,736 字节的 JSON——实测值,不是估算。把这行去掉,四个技能照常工作,它们只占各自的目录条目。我们宁愿现在把这个数字告诉你,也不想让你在账单里发现它。
路上从生态学到的东西
去读别的能力供应方是怎么接入的,比自己关起门来推理有用得多。上面那个模式不是我们发明的巧办法,而是这一类集成在那边本来就在用的做法。有一个几千星的 agent 框架,整套系统的接入就是一行 MCP,没有自己的 TypeScript。
而我们找到的最有价值的东西,是一个似乎没人在盯的失效模式。DSH 取技能名,用的是 frontmatter 里的 name,不是目录名。一个技能文件如果缺 name 或 description,或者名字不是 kebab-case,就会被丢弃——只留一行日志,别的什么都没有。它不出现,而且不报错。
我们那四个技能恰好都合规、也恰好已经加了前缀。但"恰好"不是保证,所以现在有一条测试在盯着它。如果你也往那个生态发技能,去检查一下 frontmatter——没有报错并不等于成功了。
这一版没做到什么
Rotifer 要做的事,是让 agent 能发现"某个能力有更好的实现"并且换上去,而"更好"由性能数据决定,不由观点决定。一个满是能力、却没有办法互相比较的 harness 生态,恰恰就是这个问题存在的意义。
我们还没走到那一步,这一版也没让我们更接近那一步。那个生态里的插件是 TypeScript 模块;Gene 是带声明式 schema、在沙箱里运行、可被测量的 WASM 工件。Arena 今天评不了前者,我们也不会去暗示它可以——一个无法复现的排名,比没有排名更糟。补上这道鸿沟是另一件独立的工作,不是这一件。
这一版做到的,是把能力层放到了宿主所在的地方。循环里"排名"那半边已经在 Gene 上跑着:适应度由运行时性能算出,自动进行,不靠投票。但装任何东西都要等你点头——每一次替换都是先提议、后批准、可撤销,被替换掉的那份会留着,让你能回滚。
安装
这个插件是从仓库装的,不是从 npm 装的,这是有意为之:npm 上那个不带 scope 的 rotifer 名字,是一个专门用来防止工具链被冒名的指路包,而这个插件里没有可执行文件去顶替它的位置。
dsh plugin --profile web add "github:rotifer-protocol/rotifer-playground#path:/plugins/rotifer"同一个目录还同时服务 Cursor、CodeBuddy、OpenClaw 和 Claude Code,共用一份源码和一个版本号——因为"在旁边再维护一份一样的技能",正是两份副本不知不觉漂开的方式。
装好之后,在 设置 → 插件 里找 rotifer-skills 和 rotifer-mcp。没有一个叫 "Rotifer" 的单独条目:一个挂宿主自带组件的 bundle,贡献的是配置行,不是一个同名的包。这就是"不发代码"看得见的代价,我们认为这笔交易划算。
接下来
上面点名的那道鸿沟——让 harness 生态里的能力变得可测量——才是有意思的问题,而今天诚实的答案是:它没解。它需要一个真的答案,而不是把元数据启发式包装成一个分数。
在那之前,能力层比上周多了一个可触达的地方,而通往它的启动行,跟其他每一个宿主拿到的是同一条:同样收窄、同样钉死版本。这部分我们现在就能做,所以就先做了。
