跳过主要内容
全部文章
指南

DeepSeek Harness 配置指南:cordis.patch.yml、--patch 与 --dump-config

DeepSeek Harness 如何合并四层配置、为什么 patch 是整行替换而不是深度合并配置项,以及如何用 --dump-config 调试最终结果。

DeepSeek-Harness(dsh)的配置是一叠按固定顺序应用的 YAML patch——先是各 bundle 的 patch,再是你所在 profile 的 patch,然后是全机级的 patch,最后是任意 --patch 参数——而且每一层都是整体替换目标行的配置,而不是逐字段合并。理解这一条规则,就能解释绝大多数让人意外的配置行为。

四层顺序

每一处配置改动都存在一个 cordis.patch.yml 形状的文件里:一个 YAML 数组,每一项要么向插件树里 insert 新行,要么按 id 覆盖一行已有的配置。dsh 按顺序合并以下四层:

#位置作用范围
1Bundle 的 patch每个已安装 npm 包内部(dsh.bundle.patch该 bundle 贡献的内容,按 dsh.profile.bundles 顺序
2Profile 的 patch$DSH_HOME/profiles/<name>/cordis.patch.yml仅限当前 profile
3机器级 patch$DSH_HOME/cordis.patch.yml这台机器上的所有 profile
4命令行 patch--patch <path>,可重复仅限本次调用

关于 profile 和 bundle 从何而来、以及为什么 web/headless 是特殊处理的名字,参见DeepSeek Harness 的 Profile 与 Bundle 详解。这里要讲的是四层都生效之后的合并行为。

insert 与按 id 覆盖

一条 patch 要么新增行,要么定位一行已有的行:

# 插入一行新的插件行
- insert:
    - id: my-tool
      name: '/absolute/path/to/my-tool.ts'
      config:
        greeting: hello

# 按 id 覆盖某一行已有的 config
- id: my-tool
  config:
    greeting: goodbye

id 是贯穿每一层的关联键。如果某个 bundle 插入了一行 id: dsh-shell,而你的 $DSH_HOME/cordis.patch.yml 里后来也有一条同样 id 的条目,你的机器级条目会生效——因为第 3 层在第 1 层之后应用。

最容易踩的坑:整行替换,而不是深度合并

当后面的层按 id 定位某一行时,它会整体替换该行的 config——不会把你的覆盖内容深度合并进已有对象里。如果某个 bundle 提供了:

- id: my-tool
  config:
    greeting: hello
    timeoutMs: 5000

而你写的覆盖只动了 greeting

- id: my-tool
  config:
    greeting: goodbye

结果会是 { greeting: goodbye }——timeoutMs 消失了,并没有被保留下来。要安全地覆盖某一个字段,你必须把这一行的 config 需要的所有字段都重写一遍,而不只是你要改的那一个。这是"我的覆盖把其他东西弄坏了"这类问题反馈里最常见的一种,而且是 patch 层叠设计的直接结果,不是 bug。

机器级层真正有用的地方

$DSH_HOME/cordis.patch.yml 会应用到这台机器上运行的每一个 profile,且排在各 profile 自己的 patch 之上(第 3 层压过第 2 层)。这让它成为放"无论启动哪个 profile 都想生效"的设置的正确位置——比如一个权限覆盖、一个共享插件配置项、一次目录选择器后端切换——而不用把同一份覆盖粘贴进每个 profile 各自的 patch 文件里。这一层的一个具体真实用例:针对 Windows 上原生目录选择器损坏的文档化绕过方案,就是禁用 directory-picker、改插入 directory-picker-browse,并写在机器级层,这样无论你在哪个 profile 里都会生效。

一个贯穿四层的完整例子

为了让层叠顺序变得更具体,我们跟踪一个设置走完全部四层。假设某个 bundle 插入了一个带默认超时时间的 shell 工具:

# 第 1 层——某个已安装 bundle 内部
- insert:
    - id: dsh-tool-bash
      name: '@some-org/dsh-bash-tool'
      config:
        timeoutMs: 5000
        allowNetwork: false

你所在 profile 自己的 patch,针对这个具体 profile 把超时时间调紧了:

# 第 2 层——profile 的 cordis.patch.yml
- id: dsh-tool-bash
  config:
    timeoutMs: 2000
    allowNetwork: false

你的机器级 patch——因为你决定这台笔记本上的每个 profile 都应该允许 shell 命令发起出站网络访问——又一次覆盖了它:

# 第 3 层——$DSH_HOME/cordis.patch.yml
- id: dsh-tool-bash
  config:
    timeoutMs: 2000
    allowNetwork: true

在这台机器上运行的任何 profile 里,dsh-tool-bash 最终的 config 都是 { timeoutMs: 2000, allowNetwork: true }——第 3 层的行整体替换了第 2 层的行,而第 2 层的行也早已整体替换了第 1 层的行。注意第 3 层不得不重新写一遍 timeoutMs: 2000,尽管它本来只关心改 allowNetwork——如果省略它,就会悄悄把超时时间重置成第 3 层自己对这个字段的默认值,而不是第 2 层设定的值。这个"必须重写全部字段"的要求,正是上面所说的整行替换规则带来的直接、实际的后果,也是你第一次写机器级覆盖时最容易被咬一口的细节。

--dump-config--dump-default-config 调试

因为四层是悄无声息地叠加的,想知道一个正在运行的 profile 到底解析出了什么,最快的办法是直接让 dsh 把它打印出来,而不是靠猜:

# 只看 bundle 层——这个 profile 在你覆盖之前自带的内容
dsh --profile web --dump-default-config

# 完整组合树——bundle + profile patch + 机器级 patch + --patch 参数
dsh --profile web --dump-config

这两个 flag 都会打印组装好的配置树然后退出,不启动应用——不会开 session,不会占端口。--dump-default-config 适合在你动手改之前看看一个全新 profile 长什么样;--dump-config 则是在某个东西表现得不符合你的配置预期时用来做 diff 的工具。注意这类 launcher flag 必须出现在 launcher 无法识别的第一个 token 之前——那个边界就是"launcher 自身的 flag"和"app 专属参数"的分界线(完整 flag 参考见 DeepSeek Harness CLI 速查表)。

--patch 应用临时覆盖

--patch <path> 会在其他所有层之上再叠加一份 YAML 文件,作用范围仅限本次调用——不会被持久化到任何地方:

dsh --profile web --patch ./scratch-plugin/cordis.yml

这也是本地插件开发时常用来加载一个尚未安装的插件的方式:insert 一行,name 指向你的插件文件的绝对路径,然后用 --patch 指向这个文件来运行。多个 --patch 参数按命令行给出的顺序依次生效。

.env 层与凭据解析

配置和凭据是分开解析的。模型 provider 的凭据查找顺序是:继承的进程环境 → $DSH_HOME/.credentials.yaml → 你执行 dsh 时所在目录下的 .env 文件 → $DSH_HOME/.env。托管的凭据文档永远不会被写进 process.env;两个 .env 文件是普通的启动环境层,不属于 cordis.patch.yml 这条链路。环境变量完整表格(DSH_HOMEDSH_PERMISSION_MODEDSH_TOOLS_MODE、遥测相关变量等)见 DeepSeek Harness CLI 速查表

FAQ

--patch 会在多次运行之间持久化吗,还是每次都要重新传?

只作用于单次调用。如果想要永久性的改动,写进该 profile 自己的 cordis.patch.yml(对这个 profile 持久生效)或 $DSH_HOME/cordis.patch.yml(对这台机器上的所有 profile 持久生效)。--patch 是给一次性覆盖和本地插件开发用的。

如果两个 bundle 插入了同一个 id 的行会怎样?

dsh.profile.bundles 列表顺序,后加载的 bundle 里同 id 的行会覆盖先加载的,遵循和其他任何层一样的整行替换规则——两个 bundle 定位同一行时不会有自动合并。

我能在不启动 dsh 的情况下查看配置吗?

可以——--dump-config(完整组合树)和 --dump-default-config(仅 bundle 层)都是打印后即退出,不涉及任何 session 或端口。

为什么我只改了一个字段,其他设置却在同一行里消失了?

因为 patch 覆盖是整体替换一行的 config 对象,而不是深度合并。要重写这一行需要的全部配置,而不只是你要改的那个字段。

下一步

如果还没读过,先看DeepSeek Harness 的 Profile 与 Bundle 详解,然后用 DeepSeek Harness CLI 速查表查完整的 flag 和环境变量参考。如果你想在调试自己的覆盖之前先确认某个插件的 manifest 和 patch 格式是否本身就写对了,开发与运行时分类下的 dsh-plugin-check 就是针对这类问题的零依赖、只读检查工具。