LESSON 05 · 完成装配

Profile、Bundle 与 Patch 是什么关系?

三种配置名词分别解决什么问题,又是怎样层层合并的?

Profile、Bundle、Patch 经常一起出现,很容易让人觉得它们只是三种配置文件。其实它们分别回答三个不同的问题:我要启动什么形态?这套形态需要哪些成套能力?我想对默认方案改哪里?

1

回到“公司组团队”的比喻

假设公司要同时组建三个团队:

  • Web 产品团队,需要前端界面和长期运行的服务器;
  • 自动化团队,只接收一次任务,执行完就退出;
  • 内部定制团队,还要接入公司的私有工具。

三个团队共享很多岗位,例如模型、Session、Agent Loop 和安全策略,但入口与附加能力不同。

如果复制三份完整名单,任何公共岗位调整都要改三遍。DeepSeek Harness 因此把配置拆成可复用层。

2

Profile:选择整套运行形态

Profile 是一套有名字的宿主组合,例如 web 或 headless。

它的主要职责不是亲自列出每个插件,而是声明要按什么顺序叠加哪些 Bundle,并保存属于这个 Profile 的 cordis.patch.yml。

可以把 Profile 理解成“团队组建方案”:

  • web 方案选择公共底座,再加 Web 应用;
  • headless 方案选择公共底座,再加一次性命令入口;
  • 你也可以建立 company-internal,增加自己的内部 Bundle。

因此 dsh --profile web 的重点不是“启动一个叫 web 的插件”,而是“采用 web 这套完整组装方案”。

3

Profile 在磁盘上到底长什么样

第一次运行某个内置 Profile 时,启动器会在 Harness Home 下准备对应目录。以 web 为例,最值得先看的不是 cordis.yml,而是下面两份文件:

<DSH_HOME>/profiles/web/
  package.json
  cordis.patch.yml

package.json 中的核心信息可以简化成:

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app"
      ]
    }
  }
}

它回答的是“按什么顺序取哪些 Bundle”。旁边的 cordis.patch.yml 则属于当前 Profile 自己,用来修改这些 Bundle 展开后的结果。

目录里还会有一份根 cordis.yml,但它只是空的起点。真正的插件树不是人工完整写在这里,而是启动时把多层 Patch 依次应用到这个空列表上得到的。

4

Bundle:分发一组可以复用的插件配置

Bundle 是插件配置行及其代码的分发单元。

dsh-base Bundle 带来大多数运行形态都要用到的公共能力;dsh-web-app Bundle 带来浏览器应用;dsh-headless Bundle 带来一次性任务入口。

Bundle 像一套已经配好的部门套餐。你安装的是一包,但展开以后会得到许多具体岗位。

它和普通插件的区别是:

  • 插件是一项真正运行的功能;
  • Bundle 自己更像装箱清单,负责把一组插件配置交给上层。
先记住这一点

Bundle 不是一个更大的插件。它是用于分发和复用 Cordis 配置行的包装方式。

在源码仓库中,可以直接查看下面三组材料:

  • packages/bundle/base/cordis.patch.yml:公共底座究竟加入了哪些配置行;
  • packages/bundle/web-app/cordis.patch.yml:Web 入口在底座上增加了什么;
  • packages/bundle/headless/cordis.patch.yml:一次性命令运行形态增加了什么。

每个 Bundle 的 package.json 还会通过 dsh.bundle.patch 指向自己的补丁文件。也就是说,Bundle 的身份不是靠目录名猜出来的,而是通过包清单明确声明。

5

Patch:不复制整套方案,只改需要改的地方

默认 Bundle 不可能适合所有人。你可能想换模型 Provider、关闭某个工具、修改端口,或者加入自己的插件。

Patch 允许你针对已有配置行进行替换,也可以插入新行。它像团队组建完以后的一份调整通知:

  • 把默认数据库换成公司的数据库;
  • 给模型配置增加自定义地址;
  • 新增一个内部审批插件;
  • 暂时停用浏览器自动打开。

这样你不需要复制并维护整个 Bundle,只维护自己与默认方案不同的部分。

6

配置层按什么顺序生效

DSH 从空配置开始,依次应用:

  1. Profile 列出的各个 Bundle;
  2. Profile 自己的 cordis.patch.yml;
  3. Harness Home 级别的 Patch;
  4. 本次命令传入的 --patch。

越靠后的层越接近用户,也越有机会覆盖前面的默认值。

下面的流程图可以从上往下读。它表达的不是六个程序依次启动,而是六层配置依次叠加:后来的 Patch 可以调整前面 Bundle 给出的默认项,最后只产出一棵插件树。

流程图 · 手机端可横向滑动
正在绘制流程图…

Profile 选择 Bundle,Patch 调整配置,Cordis 最终启动插件树

图 5-1:Profile 不是插件清单本身,它先选择 Bundle,再由所有配置层合成最终插件树。

这里有个很实用的原则:不要只看某个 Bundle 的 cordis.patch.yml 就断定系统最终配置。你的 Profile、Home 和命令行 Patch 可能已经把它改掉了。

7

用 dump-config 看“最后答案”

当配置层越来越多,最可靠的办法不是在脑中手动合并,而是让启动器输出最终结果:

dsh --profile web --dump-config

它会展示真正准备挂载的插件树。排查“为什么某插件没有启动”“为什么配置不是默认值”时,这通常是第一站。

如果只想看 Bundle 组成的默认结果,可以再使用 --dump-default-config。两者的区别正好体现了:默认方案与用户最终方案不是同一件事。

可以用一个小排查顺序减少迷路:

  1. 先运行 --dump-default-config,确认 Bundle 默认会产生什么;
  2. 再运行 --dump-config,看 Profile、Home 和命令行 Patch 改了什么;
  3. 根据配置行的 id 回到对应 cordis.patch.yml;
  4. 最后才进入插件源码,检查它收到的 config 与声明的 inject。

很多“插件为什么没生效”的问题,根源并不在插件内部,而是它根本没有进入最终树、被后层禁用,或配置行被整个替换了。

8

三个概念各用一句话记忆

  • Profile:为一次 Harness 启动选择完整的运行方案;
  • Bundle:打包并分发一组可复用的插件配置;
  • Patch:在不复制整套方案的前提下,替换或增加配置。

它们共同负责的是 宿主装配。至于某个 Session 要使用怎样的工具和提示词,那是 Preset 负责的第二次装配。

下一课,我们进入已启动的 Host 内部,看看 Agent Loop 周围到底有哪些稳定服务,以及“可替换 Provider”究竟替换的是什么。

下一课 · 06

Agent Loop 周围有哪些核心服务?

Agent Loop 不是万能机器,它究竟依靠哪些稳定服务才能工作?

0 人点赞,0 人看过