DeepSeek Harness 的配置目录:所有能配的项都在这张自动生成的表里
先把限定摆在前面:deepseek-harness 仓库的 README 里有一节标题就叫 Developer preview,正文写明该项目处于开发者预览阶段、正在快速迭代,并用大写强调 THERE WILL BE COMPATIBILITY-BREAKING CHANGES(会有破坏兼容性的变更)。下文提到的每一个键名、默认值、文件路径,都可能在下一次提交后变样。本文对应的是仓库快照 47f9438(2026-08-13,版本 0.1.0-rc.5),我们只读了源码与文档,没有安装也没有运行过它。
从一个很小的问题开始
假设你要改的是「bash 工具的前台默认超时」。这类需求在任何一个插件化框架里都会卡在同一个地方:键叫什么名字、写在哪个文件的哪一层、有没有默认值、改了之后还需不需要额外挂别的东西。
deepseek-harness 给这个问题准备了一份专门的文档:docs/config-catalog.md。它的第一行不是标题,而是一句注释——由 scripts/gen-config-catalog.ts 生成,不要手改。仓库的 package.json 里有两条对应脚本:gen-config-catalog 直接重新生成,verify-config-catalog 是同一个脚本加 --check,把已提交的文件和现场生成的内容做比对,不一致就打印「is stale」并以非零码退出(脚本里读文件失败也一并当作 stale);这条 check 挂在 doc-sync 闸门下。也就是说,这份文档如果和源码对不上,是会在流水线上炸的,而不是靠人记得更新。
这张表到底收了什么
我们自己数了一遍:docs/config-catalog.md 一共 3151 行、108 个二级标题。其中 105 个是「有配置的包」,每个都带一个 ```ts config-catalog 围栏,把该插件 apply 函数或服务构造函数接收的配置类型声明连同 JSDoc 原样粘进来,末尾一行 Source: 给出 包路径/文件:行号。105 段里有 81 段带 Requires: 行,列的是该插件 inject 的服务键——文档正文写明,你的 cordis.yml 树里还必须加载这些服务的提供者。
剩下 3 个二级标题是三份清单,没有围栏:Loadable plugins with no config 65 个(能从 cordis.yml 加载,但压根没有配置 API)、Seam packages (not directly loadable) 15 个(抽象服务类,得挂具体实现包)、Library packages (no plugin entry) 34 个(只能被别的包 import,cordis.yml 加载不了)。
105 + 65 + 15 + 34 = 219。而 packages/*/*/package.json 我们数出来正好是 219 个。这里提醒一句本产线踩过的坑:packages/* 只有 49 个,那是分组目录,不是包;数包必须走两层 glob。生成脚本自己也是按 packages/*/*/package.json 扫的,并且对无法分类的包直接抛错——分类是穷尽的,一个包只会落在四类中的一类。
所以第一条使用方法很直白:如果你在这份文档里搜一个包名搜不到 config 块,先去尾部三份清单里找它,多半是它压根没有配置项、或者它是个 seam。比如 @deepseek-ai/dsh-settings 就出现在 seam 清单里,标注是抽象 SettingsProvider;想让设置文件生效,你要挂的是具体实现 @deepseek-ai/dsh-settings-file。
cordis.yml 条目长什么样
仓库 examples/headless-agent/cordis.yml 是一份真实的组合文件,条目形状是 id + name + 可选的 config:
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 60000
这就是配置目录服务的那个位置——config: 块里能写哪些键,去查 @deepseek-ai/dsh-bash-local 那一段。以上片段原样抄自该文件,我们没有运行过它。
默认值经常不在这张表里
这是我翻这份目录时最容易被口径坑到的一点:表里粘的是类型声明和 JSDoc,不是运行时 schema。 而这个仓库里,默认值放在三个不同的位置都有先例。
第一种,默认值只写在 schema 上。packages/shell/bash-local/src/index.ts 里 Config 接口对 timeoutMs 的 JSDoc 只有一句「Default foreground timeout in milliseconds」,没有数字;数字在同文件的 static Config 里:timeoutMs 默认 120_000,maxTimeoutMs 默认 600_000,maxOutputBytes 默认 64_000,maxSpillBytes 与 graceMs 分别指向该文件里的常量 DEFAULT_MAX_SPILL_BYTES(64 * 1024 * 1024)与 DEFAULT_GRACE_MS(3_000)。光看目录,你只会知道有这几个键。
第二种,JSDoc 写了默认值,但 schema 里没有。@deepseek-ai/dsh-compaction-basic 的声明里写着 thresholdRatio 默认 0.8、retainRatio 默认 0.16,而 packages/compaction/compaction-basic/src/index.ts 里这两项的 schema 是裸的 z.number();真正的常量在 packages/compaction/compaction-basic/src/config.ts 顶部:DEFAULT_THRESHOLD_RATIO = 0.8、DEFAULT_RETAIN_RATIO = 0.16,在解析步骤里落地。
第三种,两边一致且直接挂在 schema 上。@deepseek-ai/dsh-jobs-local 的 maxConcurrentJobsPerOwner 声明里写「omission defaults to 10」,源码里 .default(DEFAULT_MAX_CONCURRENT_TASKS_PER_OWNER),常量就是 10。
结论只有一句:把配置目录当索引用,拿到键名之后顺着那一行 Source: 跳进源文件,找 static Config 或 export const Config,默认值以那里为准。另外提醒一句口径问题:这些默认值只是配置层面的默认取值,不等于你实际跑起来会得到的表现,我们也没有运行过它来验证。
声明块看不出来的另外两类约束
一是跨字段约束。还是 compaction:config.ts 里有一处校验,retainRatio 必须小于解析出来的 thresholdRatio,否则抛错;这条关系在两个字段各自的 JSDoc 里都读不出来。二是未知键。同一个文件里有个 validateKeys,遍历传入对象的键,只要不在允许集合里就抛 unknown key "..."。换句话说,在这个插件上把键名拼错,是加载期报错,不是被静默忽略——但这是该插件自己写的校验,不能推广到所有插件。
表里有的键,不一定 cordis.yml 能写
目录首段有一句需要读两遍的话:粘贴出来的是插件声明的完整配置类型,而运行时 schema 有意排除的字段属于「runtime-only seam」(其 JSDoc 会自己说明),不能从 cordis.yml 设置。
现成的例子是 @deepseek-ai/dsh-acp。它的 AcpConfig 声明里有三个字段,第三个是 stream,JSDoc 原文写着这是运行期用的传输覆写、生产环境走 stdio;而 packages/acp/acp/src/index.ts 里导出的 Config schema 只有 provider 和 model 两项。声明多一个,schema 少一个,这个差集就是文档说的那种 seam。
反方向则有硬保证。生成脚本会静态遍历 schemastery 的 schema 表达式,把每一个受校验的键路径(包括 agents[].id 这种嵌套加数组的路径)拿到声明类型上去找;找不到就报 violation,错误信息直说「the catalog paste would hide a loader-accepted field」(粘贴会藏掉一个加载器实际接受的字段)。而遍历不进去的外部类型只会判 unknown,不会误报 missing。合起来读:schema 收的键不会比表里少,表里有的键不保证 schema 都收。
第二条轴:settings.yaml
配置目录明确说自己是部署轴的参考,也就是 cordis.yml 这条线。但运行期还有第二层。packages/settings/settings/README.md 写明解析顺序是三层叠加:schema 默认值 → 注册方的组合 base(即它在 cordis.yml 里那个条目配置的子集)→ 用户文档里的对应 section。没挂 settings 提供者时,消费方就只解析条目配置,组合照常工作。
文件提供者的默认值我们核到这几处:packages/settings/settings-file/src/index.ts 里,文档路径默认是 harness home 下的 settings.yaml(config.path 省略时由 resolveSpec 拼出来),harness home 默认取 $DSH_HOME,再退到 ~/.dsh;watch 默认 true、debounceMs 默认 100,这两个的默认挂在 schema 上,而 path 与 dshHome 在 schema 里没有 .default(),默认发生在 resolveSpec 那一步。另一份 README——packages/settings/settings-file/README.md——还写明写入走「读-渲染-rename」并持有一个 wx 方式创建的 <file>.lock 兄弟文件、带指数退避与 2 秒获取截止时间(超时的一方不会去删既有锁,因为从锁的年龄分不出崩溃的持有者和暂停中的活写者,清理孤儿锁被列为运维动作);以及 YAML 写回是叶子级 diff,未改动节点上的注释、锚点、格式都保留,而被整体替换的数组内部注释会跟着一起没掉,JSON 则本来就不带注释。
不是所有插件都接了这层。我们在 packages/*/*/src/ 下 grep installSettingsSection(,命中 8 个包:agent-default-model、agent-loop、permission-presets、llm-deepseek、llm-pi-ai、bash-local、pwsh-local、web-search-deepseek。examples/headless-agent/cordis.yml 的注释里也写了这条链路:settings 文档里一个 llm-deepseek: section 可以覆盖下面那条适配器条目,不用重启。
还有一个纯属「表里查不到」的细节:section 名不等于包名。bash-local 注册的命名空间来自 packages/shell/shell/src/index.ts 里的 SHELL_SETTINGS_NAMESPACE,值是 shell。所以你在 settings.yaml 里要覆盖 bash 的 timeoutMs,section 名不是 bash-local。顺带提一句 Windows 侧:@deepseek-ai/dsh-pwsh-local 的配置键和 bash-local 几乎一样,只多一个 pwshPath,JSDoc 写明省略时按顺序探测 PowerShell 7 安装位置、PATH 条目(例如 Microsoft Store 安装)、再到 Windows PowerShell 5.1,最后退到 PATH 里的裸 pwsh;而它注册的也是同一个 shell 命名空间。settings 的 README 另一处写着重复命名空间会 fail loud——这两处白纸黑字放在一起是什么结果,我们没有运行过,不做判断,只是提醒:如果你的组合同时挂了这两个执行器,这是值得先去源码里确认一遍的地方。
一条可复用的查法
把上面几段压成操作顺序:在 docs/config-catalog.md 里搜键名或包名 → 找不到就去尾部三份清单确认它是 no-config、seam 还是 library → 找到了就顺 Source: 跳进源文件,用 static Config / export const Config 核默认值,同时看这个键在不在 schema 里 → 回头看该段的 Requires: 行,把需要的服务提供者一并加进 cordis.yml → 如果这个插件在那 8 个之列,再决定要不要改用 settings.yaml 的 section 做免重启覆盖。文档本身是生成物,本地跑 pnpm run gen-config-catalog 就能按当前源码重出一份,这也是判断你手上这份是不是过期的最直接办法。
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。