DeepSeek Harness 源码精读 01:先把它跑起来——数字校准与插件树全景

第 01 章 先把它跑起来:数字校准与插件树全景

代码基线:commit 8f86b979a1(2026-10-01)| 0.2.0-rc.1 | 本系列讲次:M0

本章导读

这一章要解决一个很朴素的问题:你在读什么?

代码基线是 commit 8f86b979a1、版本 0.2.0-rc.1。这个仓库有 328 个包、35.6 万行 TypeScript、2 万多次提交。任何人第一次打开它都会晕——不是难,是大。所以第一讲不讲任何机制,只做三件事:把数字校准、把运行时摊开给你看、把最容易混淆的一组概念分开。

具体做三件事。其一,数字校准。 你手上那份《黑鲸出,见全貌》的书稿写于 v0.1.x 时期,它说"219 个包、五十万行"——这是当时的真话,现在既不是真的、也不重要了。真正重要的是建立一条纪律:任何数字主张都必须标注基线版本,否则它会随版本腐坏,而读者无法察觉。

其二,把插件树摊开。 --dump-config 会打印你这台机器此刻真正会启动的东西,每一层都标注了谁 patch 了它。你自己的机器上有 14 层,官方 bundle、第三方插件、你自己写的补丁,在 YAML 里的待遇完全一样。这不是修辞,是可以逐行验证的事实。

其三,分清 profile 与 preset。 这是全书第一个真正的概念陷阱。profile 回答"这个进程由什么组成"(5 个),preset 回答"这一个 Agent 由什么组成"(4 个),二者正交。书稿把它们混成了"四种模式",而这个混淆有实际后果:以为"切 PTC 模式"和"用 headless 启动"是同一层级的操作。

学习要点

  • 任何规模数字,先问"哪个版本、怎么算的、能不能复现"
  • 亲手 dump 一次运行时配置,比读十页架构文档更有用
  • 遇到"官方/第三方/自己地位平等"这种说法,去 YAML 层验证它是不是字面成立
  • profile 与 preset 是正交的两层,不是同一个列表

本章目标

  1. 把"dsh 到底有多大"从印象变成可复算的数字
  2. 亲眼看见这台机器此刻会启动的那棵插件树
  3. 分清 profile 与 preset —— 书稿在这里把两个东西混成了一团

一、回读书稿 §1.4 / §1.7 的原文主张

书稿第一章给了三条硬主张,M0 要逐条验:

# 书稿主张 出处
A "大约 219 个 npm 包" §1.4
B "TypeScript 源码规模在 五十万行以上" §1.4
C "web 和 headless 是官方随附的两个模板 profile" §1.7
D "Base、web-app、headless 是三个基础 Bundle" §1.7
E "官方提供的四种模式:standard / minimal / PTC / creative,本质是四份不同的插件配置清单" §1.6

二、数字校准:实测 vs 书稿

export PATH="/opt/homebrew/bin:$PATH"   # 本机 node 26.8.1 / pnpm 11.7.0 不在默认 PATH  
find packages -maxdepth 3 -name package.json -not -path '*/node_modules/*' | wc -l  
find packages -path '*/src/*.ts' -not -path '*/node_modules/*' | xargs wc -l | tail -1  
node -p "require('./package.json').version"  
git log -1 --format='%h %ad %s' --date=short  
指标 书稿(v0.1.x 时期) 实测(0.2.0-rc.1) 结论
npm 包数量 ~219 328 涨了 109 个,+50%
packages 源码 TS 行数 "五十万行以上" 356,402 行 书稿偏高
版本 未标注 0.2.0-rc.1 已跨一个大版本
仓库提交 — 20,526 commits 首提交 2026-06-10

读法:书稿 §1.4 的"219 个包 / 五十万行"是当时的真话,现在既不是真的、也不重要了。真正重要的是下面这条规模判断仍然成立——这是一个需要数百人-月维护的严谨工程,而且包数量还在以每版上百的速度增长。

⚠️ 顺带提醒:书稿 §1.4 提到的 code-runtime 组、self-modification 包名,在 0.2.0-rc.1 里对应的是 ptc-runtime 与 extensions。这类命名漂移后面还会遇到很多次。


三、五个 Profile 实测(本章核心材料)

pnpm dsh --profile <name> --dump-config  
profile 配置行数 唯一插件数 定位
web 214 206 浏览器应用(含全部 client-ui-*)
sdk 98 97 JSON-RPC SDK 服务器
headless 97 95 一次性 runner,无服务器
acp 96 95 自动化专用 ACP
sdk-minimal 32 31 刻意不套 dsh-base 的裸树

3.1 纠正 C 与 D:profile 和 bundle 都变多了

书稿 §1.7 写"web 和 headless 是官方随附的两个模板 profile""Base、web-app、headless 是三个基础 Bundle"。

0.2.0-rc.1 的 docs/architecture.md 明确写着五个 profile:web、headless、sdk、sdk-minimal、acp;bundle 则是 dsh-base、dsh-web-app、dsh-headless、dsh-sdk-app、dsh-acp-app,外加那个"刻意例外"的 dsh-sdk-minimal。

书稿这一节的结论(Profile/Bundle/Patch 分层组装、patch 按 id 整行替换、层叠顺序)依然完全正确,只是清单落后了一代。

3.2 sdk-minimal 为什么只有 31 个插件 —— 它是全场唯一一次"硬编码"

它的 31 个插件全部列在下面:

dsh-agent-loop  dsh-agent  dsh-session  dsh-session-log-deepseek  
dsh-session-persistence-jsonl  dsh-session-projection  dsh-session-title  
dsh-system-prompt  dsh-tools  dsh-scope/invariant  dsh-agent/invariant  
dsh-agent-loop/invariant  dsh-session/invariant  dsh-invariants  
dsh-llm  dsh-llm-deepseek-api-key  dsh-llm-retry  dsh-deepseek-llm-api-extensions  
dsh-subprocess-local  dsh-sandbox-local  dsh-sandbox-policy  
dsh-terminal  dsh-terminal-bash  dsh-tool-bash-persistent  dsh-tool-pwsh-persistent  
dsh-jobs-local  dsh-mcp-resources  dsh-plugin-package-inventory-deepseek  
dsh-sdk-app  dsh-sdk-jsonrpc-server  cordis-plugin-timer  

架构文档的原话是:dsh-sdk-minimal 是刻意的例外——"one bundle owns its complete explicit SDK tree and does not apply dsh-base"。

为什么这是本章最值得记住的一件事:整个系统的主题是"一切皆插件、组合优于硬编码",而 sdk-minimal 是唯一一个把所有依赖逐个写死在 YAML 里的组合。它存在的理由也很硬——它是跑 benchmark 的裸模型环境,你要的就是"除了 agent loop 和会话,别的什么都没有",任何一层隐式继承都会污染基准数据。

教学点:架构里"刻意的例外"往往比"统一的规则"信息量更大。看到 sdk-minimal,就该问一句:为什么这里可以破例?


四、你这台机器此刻的 web 插件树(M0 最值钱的一屏)

--dump-config 打印的不是配置模板,而是你这台机器此刻真正会启动的东西,每一层都标注了谁 patch 了它。

口径说明(2026-10-03 补):原始输出里 # == 分段头会因为同一来源的行组交错而重复出现(dsh-base 一个来源就出现 16 次头)。下面是为可读性做的去重版,每来源只列一次。本机实测:52 个分段头 / 20 个不同来源。按 M2 §三的定义,真正的"层"= 参与 layers.flat() 的一个 patch 列表。

去重后的来源列表:

# == @deepseek-ai/dsh-base                                   ← 地基:模型/工具/持久化/沙箱/审批/设置/凭据/遥测  
# == @deepseek-ai/dsh-base, patched by ~/.dsh/profiles/web/cordis.patch.yml  
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app   ← 浏览器应用叠上来  
# == @deepseek-ai/dsh-host-sysadmin                            ← 你的  
# == @deepseek-ai/dsh-client-ui-settings-sysadmin               ← 你的  
# == @tt-a1i/archify-dsh                                       ← 第三方:archify 画图  
# == @deepseek-ai/dsh-wechat                                   ← 你的  
# == dsh-better-sidebar                                        ← 你的  
# == dsh-ego-browser                                           ← 你的  
# == @captain1275/dsh-client-ui-skin-aurora                    ← 第三方:皮肤  
# == @captain1275/dsh-client-ui-skin-suite                      ← 第三方:皮肤  
# == dshmarket                                                 ← 插件市场  
# == @deepseek-ai/dsh-experimental-schedule-bundle             ← 实验特性  
# == @deepseek-ai/dsh-experimental-auto-review  
# == @deepseek-ai/dsh-experimental-agent-team-profile  
# == dsh-knowly-weclaw                                         ← 你的:knowly/weclaw 桥  
# == ~/.dsh/profiles/web/cordis.patch.yml                      ← 你的补丁,永远在最后  

请注意最后三行的顺序:官方 bundle → 第三方插件 → 你自己的 patch。架构文档里那句"官方和第三方地位平等,唯一区别只是层叠的位置更靠下还是更靠上",在这里是字面意义上成立的——@tt-a1i/archify-dsh 和 @deepseek-ai/dsh-wechat 在 YAML 里的待遇没有任何差别。

而你 ygsdoc/开发大坑_dshmarket重复挂载bundle层已注册的包_2026-10-01.md 里记录的那个坑——dshmarket 把 bundle 层已注册的包重复挂成 shim,导致 60+ 个 client 插件全部 pending——根子就在这张表里:dshmarket 是第 12 层,它会去重扫前面 11 层已经挂好的条目。

4.1 一条 patch 长什么样

看 dump 里被 patch 过的行,patch 的粒度是"按 id 锁定一整行,替换它的整个 config":

# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app  
- id: workflow-ptc  
  name: '@deepseek-ai/dsh-workflow-ptc'  
  config:  
    provider: spawn  
  disabled: true          # ← 整行被替换,连 disabled 一起  

以及你的补丁直接改别人的 config:

# == @deepseek-ai/dsh-base, patched by /Users/ygs/.dsh/profiles/web/cordis.patch.yml  

这就是"没有特权内核"最具体的一个演示:你可以把官方 bundle 里的一行整个换掉,它不会反抗。


五、书稿 §1.6"四种模式":数目对,名字错

本节第一版我判成"preset 只剩三个,creative 已消失",这是错的。广山哥当场用 preset 定义把我纠正了。事实是四个,一个不少。下面是核实后的准确版。

5.1 实测:四个 preset,第四个叫 cordis

packages/bundle/web-app/presets/ 下正好四个文件,每个文件定义一个 @deepseek-ai/dsh-agent-preset 行:

preset 文件 行 id preset id order
standard.patch.yml preset-standard standard 1
ptc.patch.yml preset-ptc ptc 2
minimal.patch.yml preset-minimal minimal 3
cordis.patch.yml preset-cordis cordis 4

pnpm dsh --profile web --dump-config 里四条都在(本次运行落在第 771 / 953 / 1141 / 1221 行)。

⚠️ 这是活 dump 的绝对行号,会漂移。 同一台机器 2026-10-03 重跑得到的是 774 / 956 / 1144 / 1224(统一 +3),因为 home 级 cordis.patch.yml 是你自己的文件,任何一次编辑都会平移后续行号。稳定的锚点是"四条 preset 行按 order 依次为 1/2/3/4",不是行号。

所以书稿 §1.6 说"四种模式"——数目完全正确。唯一的错是第四个的名字:书稿写 creative,代码里叫 cordis。

方法论教训:我第一次 grep 出的 preset id 列表里明明有 cordis,却只数出三个。一手数据的读法比二手结论更可靠 —— 这一条要记进勘误附录。

5.2 "创造模式"没有消失,它只是换了名字——而且它的真身是两组工具

cordis preset 的定义在 packages/bundle/web-app/presets/cordis.patch.yml。它和 standard 的 persona 逐字相同,注释写得很清楚:

# Same persona as `standard`: tool descriptions and the skill catalog carry  
# the operating detail, and the skills own the preset-authoring rules.  

"创造模式"与"标准模式"的差别,几乎完全落在两行工具声明上:

工具 standard preset cordis preset
tool-cordis(@deepseek-ai/dsh-tool-cordis) ❌ 不挂 ✅ 挂 —— 运行时 API 勘查(cordis_inspect_list / cordis_inspect_query)
tool-plugin-manager(@deepseek-ai/dsh-plugin-manager/tools) disabled: true(硬关) disabled: !!js "!ctx.get('profileContext')" —— 在 preset 作用域内 profileContext 存在,因此它是开的

这就是全篇最值得记住的一句:创造模式不是一个模式名,而是一组工具的开关差异。

它也顺手解释了你 开发大坑_cordis只写命名空间服务漏了容器座位 里那类 remote.* 服务为什么会出岔子——cordis preset 里挂的这套勘查工具,背后是 dsh-tool-cordis/host + cordis-host-runner / cordis-client-runner 一整条链(packages/extensions/)。

tool-cordis README 的一句限定值得记住,因为它解释了"为什么单挂 preset 行没用":

Creator mode includes this toolset. Other compositions mount @deepseek-ai/dsh-tool-cordis/host once in the host composition... a preset row alone registers no Host providers.

注意它是"在 web profile 里挂载"(web 命中 3 处,headless / sdk / sdk-minimal / acp 全是 0 处)——所以创造模式事实上只有 Web GUI 有,无头和 SDK 端拿不到这套工具。

5.3 书稿 §1.6 里仍然成立的部分

书稿对创造模式的机制描述基本准确,只有一处需要加限定:

  • ✅ "它允许 Agent 检查自己的插件" —— 对,cordis_inspect_*
  • ✅ "热加载新插件" —— 对,tool-plugin-manager 在此 preset 内是开启的,Agent 能发起真实的插件安装(经 bundled pnpm 事务)
  • ⚠️ "甚至自己写一个自定义 preset 然后创作出来" —— tool-plugin-manager 管的是安装 bundle / MCP 配置(README 原话:"Use Plugin Manager to install bundles containing plugin code or MCP configuration");自己写 preset 属于编辑 profile 配置这条路,不是这个工具的职责
  • ✅ "目前这种自进化还停留在实验阶段" —— 判断准确

5.4 两个概念的准确划分

profile preset
回答的问题 这个进程由什么组成? 这一个 Agent 由什么组成?
定义在 Harness home 的 profile 目录 packages/bundle/*/presets/*.patch.yml 里的普通 Cordis YAML 行
组合单位 bundle / patch / 第三方插件 persona / 工具 / 子插件列表
数量 5(web/headless/sdk/sdk-minimal/acp) 4(standard/ptc/minimal/cordis)
谁选它 命令行 --profile 会话创建时选

书稿 §1.6 的真正问题不是"四种模式错了",而是没把 profile 和 preset 这两层分开说。 这个混淆有实际后果:以为"切 PTC 模式"和"用 headless 启动"是同一层级的操作,而它们其实正交——你可以跑 web profile 的界面,选中一个 ptc preset 的 Agent。



本章实验(附录)

六、动手验证(M0 的作业)

export PATH="/opt/homebrew/bin:$PATH"  
cd /Users/ygs/ygs/deepseek-harness  
  
# 1. 五个 profile 各导一份,数行数与唯一插件数  
for p in web headless sdk sdk-minimal acp; do  
  pnpm dsh --profile $p --dump-config > /tmp/dump-$p.yml  
  echo "$p rows=$(grep -c '^- id:' /tmp/dump-$p.yml) plugins=$(grep -oE "name: '[^']+'" /tmp/dump-$p.yml | sort -u | wc -l)"  
done  
  
# 2. 看你的 web 树有哪些层(谁 patch 了谁)  
grep '^# ==' /tmp/dump-web.yml  
  
# 3. 看 web 比 headless 多了什么  
comm -23 <(grep -oE "name: '[^']+'" /tmp/dump-web.yml | sed "s/name: '//;s/'//" | sort -u) \  
         <(grep -oE "name: '[^']+'" /tmp/dump-headless.yml | sed "s/name: '//;s/'//" | sort -u)  
  
# 4. 亲手改一行 patch,验证它真的生效(M2 §S-12 的反面教材)  
  
# 4a. 先确认目标 id 存在 —— 这是唯一可靠的检查手段  
pnpm dsh --profile web --dump-config | grep -n 'dsh-client-ui-goal'  
  
# 4b. 在你的 patch 里按 id 关掉它(先备份)  
cp ~/.dsh/profiles/web/cordis.patch.yml /tmp/patch.bak  
cat >> ~/.dsh/profiles/web/cordis.patch.yml <<'YAML'  
- id: client-ui-goal  
  disabled: true  
YAML  
  
# 4c. 立刻验证(不要靠"感觉界面变了",靠 dump)  
pnpm dsh --profile web --dump-config | grep -A2 'id: client-ui-goal'  
  
# 4d. 还原  
cp /tmp/patch.bak ~/.dsh/profiles/web/cordis.patch.yml  

风险提示:对正在运行的 3080 实例改 client 插件会当场改变界面(这正是 HMR 的意义)。
若 id 拼错,patch 会只 warn 不报错,界面毫无变化——所以 4c 那一步不能省。
先备份再改,出问题用 4d 还原。

通过标准:能指着第 2 步的输出,说出"第 12 层那个 dshmarket 会重扫前 11 层"这句话,并解释它为什么会引发 那 60+ 个插件 pending 的坑。



本章的勘误条目见 附录 A · 勘误总表。