DeepSeek Harness / 03
运行中的 DSH:插件树、组合层与能力接缝
知道“一切皆插件”还不够。真正运行时,系统必须回答三件事:这次启动究竟装了什么,多个配置发生冲突时谁获胜,替换一个文件系统为什么能让 Bash、终端和 LSP 一起迁移到沙箱。
DSH 的答案是:产品不是一份写死的启动代码,而是一棵在启动时由有序配置层组合出来的插件树。
从产品到插件树
官方 Web 和 headless 不是两个独立内核,而是两份不同的产品配方。它们共享基础能力,再装入各自的宿主插件。
flowchart TD
A["Bundle patches"] --> B["Profile patch"]
B --> C["Harness Home patch"]
C --> D["CLI patch overlays"]
D --> E["启动器附加配置"]
E --> F["最终插件树"]
F --> G["Cordis Loader 激活 Fiber"]
这棵树让“本次运行由什么组成”变成可检查的事实。--dump-config 的价值不只是方便排错,它给构建、审计和复现提供了一份组装结果。
Profile、Bundle 与 Patch 各管什么
| 层 | 设计责任 | 不应该承担的责任 |
|---|---|---|
| Bundle | 分发一组可以共同安装的插件行 | 决定每台机器的最终偏好 |
| Profile | 定义 Web、headless 或定制产品的有序配方 | 偷偷修改全局环境 |
| Patch | 在明确层级替换或插入条目 | 依赖不透明的隐式合并 |
Bundle 解决复用和分发,Profile 解决产品形态,Patch 解决环境差异与临时实验。把三者混在一个配置文件里同样能启动,但很难判断一项变化应该跟着产品发布、跟着机器生效,还是只属于一次运行。
覆盖顺序就是架构事实
当前 rc.8 文档给出的组合顺序是:Bundle 层按 Profile 声明顺序叠加,然后是 Profile Patch、Harness Home Patch、命令行 Patch,最后还有启动器附加的 Agent preset 和遥测开关等配置。后层拥有更高优先级。
这里有一个容易误判的限制:按 id 覆盖配置时是整段替换,不做深合并。 只写一个新字段可能使旧字段全部消失。这降低了合并语义的魔法程度,却把复述配置和版本迁移的责任交给使用者。
!!js 则是配置中的窄逃生口,可以在 config 或 disabled 等受限位置引用运行时值。它提高了表达力,也意味着 Patch 不是天然安全的数据文件:来源、评审和执行权限必须像代码一样管理。
Seam:替换能力,不只是替换包名
插件树解释“装了什么”,能力接缝解释“为什么替换能传播”。一个完整 Seam 有三个角色:
| 角色 | 责任 | 文件系统例子 |
|---|---|---|
| Service Definition | 定义稳定契约 | ctx.fs 能做哪些文件操作 |
| Service Provider | 实现契约 | 本地文件系统或远程沙箱 |
| Consumer | 只依赖契约使用能力 | 文件工具、终端、LSP |
真正的可替换性不来自“用了接口”这句口号,而来自消费者没有绕过接缝直接访问本地实现。只要 Bash 工具仍直接调用宿主机进程,替换 ctx.fs 就不能把完整执行环境迁移到沙箱。
因此评审一个 Seam 时,要沿依赖链追问:定义是否稳定,提供方是否完整,消费者是否都经过它,错误与取消语义是否一致。三者缺一,替换就只存在于配置表面。
官方预置:四种 Agent Preset
Profile 组合层最直观的产物是四种预置模式,每种是一份 apps/cli/config/agent-presets/ 下的 YAML 配方:
| 模式 | 预置名 | 能力范围 | 适合场景 |
|---|---|---|---|
| Standard | standard |
文件编辑、Shell、检索、Skills、规划 | 日常 Coding Agent |
| PTC | code |
TypeScript 程序组合,多步操作 | 需要精确多步流程编排 |
| Minimal | minimal |
仅 bash + str_replace_editor | 基线测试、能力隔离实验 |
| Creative | cordis |
运行时检查、插件实验 | 开发者探索 Cordis 机制 |
四种模式说明一个关键事实:产品差异不在代码分支,而在 Profile 组合。Standard 装了搜索、规划和完整工具集;Minimal 只保留两个工具,其余能力通过不装对应插件来”关闭”。这比在代码里 if (mode === 'minimal') 更符合可替换设计:需要新模式时,写一份新 YAML 即可。
热更新改变的是依赖分支
Patch 变化后,理想行为不是重启全部系统,而是找到受影响条目,卸载旧 Fiber,再让依赖解析器重新激活相关分支。Cordis 的可逆 Effect 使这件事可行;Profile 的确定顺序则使重组结果可预测。
但 HMR 不会自动迁移所有业务状态。一个插件若持有无法重建的内存状态,仍需定义迁移或降级策略。配置解析失败、插件激活失败和 disposer 失败,也必须有清晰诊断;“最后一棵好树”只能保护框架状态,不能撤销已经提交到外部系统的副作用。
设计检查
- 能否从 Profile 和 Patch 重建一次运行,而不是依赖某台机器上的隐式状态?
- 覆盖规则是否明确,整段替换造成的字段丢失能否在评审或测试中发现?
- 每项可替换能力是否都有 Definition、Provider、Consumer 三个角色?
- 切换提供方后,是否仍有消费者绕过 Seam 访问旧实现?
- HMR 失败时,运行时状态和外部副作用分别怎样收场?
事实基线见官方 Architecture 与 Capability Seams。
小结
- 运行中的 DSH 是一棵由有序配置层启动出来的插件树。
- Bundle、Profile、Patch 分别承担分发、产品组合和覆盖责任。
- Patch 的顺序、整段替换和
!!js都是需要审计的运行时语义。 - Seam 由定义、提供方和消费者共同形成,替换能力必须沿完整依赖链成立。
下一篇建议继续看: