CH 08 · 插件树心智模型:万物皆插件
本章目标
CH 02 说"一切皆插件",这一章把它讲成能带走的心智模型:运行中的 dsh 到底是什么,为什么每个部分都能换,以及"换一个 Provider 就是换一个产品"这句话是怎么成立的。
一句话先立起来:运行中的 dsh = 一棵插件树
官方架构文档的原话是:运行中的 dsh 是一棵插件树,启动时从有序层组合。把它拆成三层来记,整个架构就立住了:
图里从上往下看就是它的逻辑:① 先选一个 Profile → ② 按顺序一层层叠插件 → ③ 所有层都跑在同一个 Cordis 内核上。对应的三个要点:
- 最底层是 Cordis 内核:一个共享的 context,负责让插件注入服务、广播事件、挂载效果。
- 中间是插件树本身:各种插件叠成树,每个插件干一件具体的事。
- 最顶层是 Profile:它不新增能力,只是决定"这棵树长什么样"——web、headless,或你自己的自定义组合(下一节讲它是什么、怎么自建)。
Cordis:驱动一切的框架
dsh 跑在一个叫 Cordis 的插件框架上(官方 README 明确写着"由 Cordis 驱动")。Cordis 的核心约定只有三条:
- 插件往共享 context 里注入服务。想给系统加能力,就写个插件注册进去。
- 插件广播类型化事件。别人监听这些事件来协作,不用直接 import 对方。
- 插件的注册是可逆效果——插件卸载时,它注册的东西自动回卷,不留脏东西。
最关键的是最后一句,它是"一切皆插件"能成立的根因:dsh 没有特权核心需要打补丁。模型的适配器是插件、工具的注册表是插件、会话日志是插件、连 agent 主循环本身也是插件。扩展 dsh 的方式就是"在旁边挂一个插件",而不是"去改某个核心文件"。这对你想改造成本极低:改不动就换,换不掉就加。
这套思路和别的东西差在哪?拿两个最常用的 CLI Agent 摆一起,差别一眼就出来:
| 对比 | 扩展方式 | 能不能动"骨架" |
|---|---|---|
| Claude Code | 工具能扩展(MCP、技能),但模型和主循环换不了 | 半开:能加工具,但模型适配、会话、agent loop 是焊死的骨架,模型也被自家生态锁住 |
| Codex | 工具能扩展,但模型和主循环换不了 | 半开:能加工具,但模型适配、会话、agent loop 是焊死的骨架,模型被锁在自家生态 |
| dsh | 旁边挂插件,改配置就能换 | 全开:连会话日志、agent loop、模型适配器都是插件,没有特权核心 |
一句话总结:Claude Code 和 Codex 的"骨架"是焊死的,dsh 的"骨架"也是插件。这就是为什么模型随便接(CH 06 说过的 Codex 对比就是例子)、会话想换成自己的实现也行——骨架本身可替换,剩下的只有你想不想改。
从 --dump-config 看一棵真实的树
理论说十遍,不如看一眼真的。CH 05 你跑过一次 dsh --profile headless --dump-config,打印出来的就是一长串 @deepseek-ai/dsh-* 插件叠出来的树——llm(模型)、session(会话)、credentials(密钥)、session-persistence-jsonl(会话持久化)……这些插件各管一摊,叠在一起就是一个能跑的 dsh。
官方把核心插件列得很清楚,每个都对应一棵树上的一个"大枝":
| 核心插件 | 管什么 |
|---|---|
core/session | 追加式的会话日志(模型看到的一切都被记录) |
core/system-prompt | 系统提示词怎么拼、工具 schema 怎么组装 |
core/tools | 工具注册表和受控执行管道 |
core/agent | Agent 的接口和实时注册表 |
core/agent-loop | 默认的"请求 → 工具调用"循环驱动 |
llm/llm | 消息和流词汇 + 模型适配器接缝 |
注意最后一行的"接缝"(Seam)——那是下一个关键概念。
Profile 与 Bundle:插件怎么组合成"模式"
插件 = 菜品(蛋炒饭、例汤、小菜);bundle = 套餐包(把米饭、主菜、餐具打包好);profile = 菜单单(单子上写要哪些菜、按什么顺序上);后厨那口锅,就是下一节的 Cordis 内核。
一棵树由什么组成,由 Profile 决定——把它想成你手里的菜单单:
- Profile 是一个命名的组合,存在你的 Harness 目录里。它列出要叠哪些 Bundle、装哪些额外的插件,还保留你自己的一份
cordis.patch.yml。单子不炒菜,只写"这顿上哪些":官方出厂带了web和headless两张现成菜单单,CH 02 说的"四种运行模式"底层就是四套不同的插件组合预设,等于另外几张单子。 - Bundle 是"一组插件 + 它们挂载的配置"的分发格式,等于打包好的套餐包:
dsh-base是每个 profile 的第一层(地基:模型、工具、持久化、沙箱、审批、设置、密钥、遥测);web-app给基础套餐加"堂食环境"(浏览器界面);headless加"打包带走"(命令行一次跑,无服务器)。
先说一个容易混的点:Profile 不是插件。 插件是"会干活的东西"(菜品),Profile 只是"单子"(只点菜、不炒菜)。你选"极简模式"不是装了什么新插件,而是把插件树换成一组更精简的组合——换了张单子,后厨没变。
插件的叠加有严格顺序(官方),就像后厨按单子备菜、后写的备注还能覆盖前面的:
每个 bundle(按 profile 里列的顺序) ← 先上套餐包
→ profile 自带的 cordis.patch.yml ← 单子上的备注
→ Harness 目录级的那份 ← 老板的统一要求
→ 命令行 --patch 覆盖层 ← 临时再加的备注,最新优先每一层都可以覆盖上一层——patch 按 id 定位一行、替换它的整个配置,或插入新行。所以 CH 05 那个 --patch 不是"额外开洞",而是正大光明地改这棵树:--dump-config 打印出来的任何一行,都能被你自己的 patch 替换。
自建 Profile 也不用从零写代码,本质是"抄一张单子改成自己的":
- 想比 web 少点什么 → 抄一份 web 的配置,删掉不要的层("堂食,但不要例汤");
- 想比 headless 多点什么(比如默认多挂一个你写的工具插件)→ 抄一份 headless,在它的 bundle 列表里加一行("打包,再加份你带来的小菜");
- 最轻量的是不改单子、只加备注:给现有 profile 装插件用
dsh plugin --profile <名字> add <包>("在现有单子上加份小菜"),临时覆盖用--patch("这顿备注:例汤换雪碧")——CH 05 都见过。
把加载过程串成一条时间线,前面这些就全落地了:
- 启动时 dsh 先读你选好的 Profile——它就是一张加载清单,写着"我要按什么顺序叠哪些层";
- 按清单顺序逐层加载:先装第 1 层基础(dsh-base),再装第 2 层追加(web-app / headless / 你的插件),最后第 3 层覆盖(cordis.patch.yml +
--patch)——越后加载的越能覆盖先加载的; - 所有层都装到同一个 Cordis 内核上,在那里注入服务、互相通信。
一句话收口:Profile = 启动时的点单顺序;插件树 = 按这个顺序装菜;Cordis = 装菜的那口锅。
事件与能力接缝:插件之间怎么说话
插件之间不互相 import,靠两类东西协作:
事件是扩展点,分三个领域:
- Session 事件:持久事实,追加进日志并广播,重启了也还在(比如"这条消息发过了")。
- Agent 事件(
agent/*):携带活着的 Agent,用来观察或拦截正在跑的工作(比如"这一步要开始前拦一下")。 - 能力事件(
fs/*、tools/*、telemetry/*):把策略和适配器挂到某个接缝上,不用改主循环。
能力接缝(Seam)是可替换的能力,它有三个固定角色:
- Service Definition:声明接口长什么样;
- Service Provider:真正实现它;
- Consumer:使用它(通常是模型要调的工具)。
为什么说"换一个 Provider 就是换一个产品"?因为文件系统和子进程的 provider 共享同一个"执行世界"——把它们指向一个远程沙箱,Bash、PTY、LSP 会一起跟着搬过去,不需要为每种能力单独写一套。模型适配器同理:ctx.llm 上注册哪个适配器,整个产品就用哪个模型,别的都不用动。
把心智模型收进一句话
如果只能带走三句,记住这三句(配着上面那家小馆子一起记):
- dsh 没有核心——每一部分都是插件,包括它自己;往旁边加一道菜就行,拔掉会自动回卷。
- Profile 决定树长什么样——web / headless / 自定义,都是同一棵树的不同"菜单单";它不干活,只点菜。
- 事件和接缝是插件协作的接口——挂上就生效,拔下就回卷,所以随便换、随便加;换 Provider 就是换产品。
这一章你学到了什么
- [ ] 能说出"运行中的 dsh = 一棵插件树"和它从有序层组合的含义
- [ ] 能说出 Cordis 的三个核心约定(服务注入 / 事件 / 可逆效果),以及为什么没有特权核心
- [ ] 能从
--dump-config的输出里认出核心插件(llm / session / credentials / tools 等) - [ ] 能说清 Profile 和 Bundle 的关系,以及插件叠加的层顺序
- [ ] 能用"点菜"比方说清插件 / bundle / profile 的关系,知道自建 profile 是"抄单子改",
plugin add/--patch是最轻量的改法 - [ ] 能说出能力接缝的三个角色(Definition / Provider / Consumer),并解释"换 Provider 就是换产品"
