profile 是 $DSH_HOME/profiles/<名字> 下的一份命名组装。patch 应用到一个空条目列表上,顺序固定:profile 列出的每个 bundle、profile 自己的 cordis.patch.yml、home 级的 $DSH_HOME/cordis.patch.yml、最后是 --patch 覆盖。后面的层赢,dsh --profile <名字> --dump-config 打印组装结果。
DeepSeek Harness 上手第一周,多数人栽的是分层问题:改了个配置文件,毫无反应,还看不出为什么。答案基本 只有两种——被更靠后的层覆盖了,或者改错了那两个同名文件中的一个。
这套组装模型小到能装进脑子里,值得花十分钟。
profile 是什么
profile 是 harness home 目录下的一份命名组装,位置在 $DSH_HOME/profiles/<名字>。它干三件事:
- 列出自己要叠哪些 bundle
- 存放自己安装的 out-of-tree 插件
- 保管用户自己的
cordis.patch.yml
预览版自带两个模板:web 和 headless。用 --profile 启动:
dsh --profile web
dsh --profile headless "总结一下失败的测试"dsh web 是前者的简写。headless 那种形态接一个任务字符串,跑完一次会话、输出结果、退出——这就是你在
CI 里要的形状。
bundle 是什么
bundle 是 Cordis 配置行及其挂载代码的分发格式。定义里最关键的性质是:bundle 插入的任何东西, 仍然能被它上面的层 patch 掉。bundle 不是黑盒,它只是一组你之后能覆盖的起始行。
自带的三个:
| Bundle | 提供 |
|---|---|
dsh-base | 模型适配器、工具、持久化、沙箱与审批策略、设置、凭证、遥测 |
dsh-web-app | 浏览器 UI 层 |
dsh-headless | 单次执行,无服务端 |
dsh-base 是所有 profile 的地基。web profile 本质上就是 dsh-base + dsh-web-app,headless
就是 dsh-base + dsh-headless。
patch 顺序
这段要背下来。patch 应用到一个空条目列表上,顺序如下:
- 每个 bundle,按 profile 列出的次序
- profile 的
cordis.patch.yml——$DSH_HOME/profiles/<名字>/cordis.patch.yml - home 级的
cordis.patch.yml——$DSH_HOME/cordis.patch.yml - 运行时传入的
--patch覆盖
后面的层赢。patch 按标识符命中某一行,要么整行替换其配置,要么插入新行。
由此直接推出两个实用结论:
你的 home 级文件压过 profile 文件。 如果你按 profile 设了某项却被忽略,先查你几个月前是不是也在 home 级设过、然后忘了。这是「我配了但完全没生效」最常见的成因。
--patch 压过一切。 所以它是一次性实验的正确工具——换个模型、收紧审批策略,只对这一次运行生效,
磁盘上任何文件都不动:
dsh --profile web --patch ./experiments/strict-approval.yml看真正启动了什么
永远别靠读源文件去推组装结果。打印它:
dsh --profile web --dump-config这会显示四层全部应用之后、每一行可被 patch 修改的配置。行为和预期打架时,以这份输出为准。
它的兄弟回答的是另一个问题:
dsh --profile web --dump-default-config--dump-default-config 不初始化 profile,直接显示组装出来的树——一个干净 profile 长什么样,
不受你本地改动污染。两份输出 diff 一下,就是你到底改了些什么。
往 profile 里装插件
Out-of-tree 插件住在 profile 自己的 node_modules 里。launcher 提供了一个直接把参数转发给 pnpm 的
子命令:
dsh plugin --profile web add some-cordis-plugin底层就是 pnpm,所以 pnpm 的全套词汇都能用——add、remove、update、版本号。bundle 的解析顺序是
先从 dsh 安装目录找,再从 profile 自己的 node_modules 找,这正是本地装的插件能遮盖或扩展自带
插件的机制。
参数顺序有讲究
launcher 先解析自己的 flag。遇到第一个它不认识的 token,后面全部算 app 的参数。 所以这样是对的:
dsh --profile web --port 3100因为 --port 是透传给启动的 app 的,不被 launcher 吃掉。
无效命令、属于别的模式的选项、配置错误和启动失败,一律非零退出。如果你要用脚本包 dsh,记得检查
退出码——放错位置的 flag 会大声失败,而不是悄悄干错事。
一个好用的心智模型
把它当成 agent 运行时的 CSS 层叠。bundle 是浏览器默认样式表:给合理默认值,完全可覆盖。profile 的
patch 文件是你的样式表。home 级 patch 文件是你的 !important。--patch 则是审查器——先试,再决定要
不要落盘。
常见问题
profile 和 mode 有什么区别?
profile 是磁盘上那份会被启动的组装,自带 web 和 headless 两个模板。mode 是组装出来的 agent 呈现的工具面。profile 是机制,mode 是这个机制产出的其中一种结果。
profile 级和 home 级的 cordis.patch.yml 谁赢?
home 级赢。patch 按 bundle → profile patch → home patch → --patch 的顺序应用,后面的层覆盖前面的。
怎么看真正启动起来的配置?
dsh --profile <名字> --dump-config,打印组装后每一行可被 patch 命中的配置。--dump-default-config 则不初始化 profile,显示干净基线。
第三方插件装到哪?
装进 profile 自己的 node_modules。用 dsh plugin --profile <名字> <pnpm 参数>,它转发给 pnpm。