Skip to content

CH 08 · 插件树心智模型:万物皆插件

全文字数4074 字预估耗时约 15 分钟前置CH 02 概念、CH 05 实操难度理解篇

本章目标

CH 02 说"一切皆插件",这一章把它讲成能带走的心智模型:运行中的 dsh 到底是什么,为什么每个部分都能换,以及"换一个 Provider 就是换一个产品"这句话是怎么成立的。

一句话先立起来:运行中的 dsh = 一棵插件树

官方架构文档的原话是:运行中的 dsh 是一棵插件树,启动时从有序层组合。把它拆成三层来记,整个架构就立住了:

运行中的 dsh = 一棵插件树(示意)

图里从上往下看就是它的逻辑:① 先选一个 Profile → ② 按顺序一层层叠插件 → ③ 所有层都跑在同一个 Cordis 内核上。对应的三个要点:

  • 最底层是 Cordis 内核:一个共享的 context,负责让插件注入服务、广播事件、挂载效果。
  • 中间是插件树本身:各种插件叠成树,每个插件干一件具体的事。
  • 最顶层是 Profile:它不新增能力,只是决定"这棵树长什么样"——web、headless,或你自己的自定义组合(下一节讲它是什么、怎么自建)。

Cordis:驱动一切的框架

dsh 跑在一个叫 Cordis 的插件框架上(官方 README 明确写着"由 Cordis 驱动")。Cordis 的核心约定只有三条:

  1. 插件往共享 context 里注入服务。想给系统加能力,就写个插件注册进去。
  2. 插件广播类型化事件。别人监听这些事件来协作,不用直接 import 对方。
  3. 插件的注册是可逆效果——插件卸载时,它注册的东西自动回卷,不留脏东西。

最关键的是最后一句,它是"一切皆插件"能成立的根因: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/agentAgent 的接口和实时注册表
core/agent-loop默认的"请求 → 工具调用"循环驱动
llm/llm消息和流词汇 + 模型适配器接缝

注意最后一行的"接缝"(Seam)——那是下一个关键概念。

Profile 与 Bundle:插件怎么组合成"模式"

插件 = 菜品(蛋炒饭、例汤、小菜);bundle = 套餐包(把米饭、主菜、餐具打包好);profile = 菜单单(单子上写要哪些菜、按什么顺序上);后厨那口锅,就是下一节的 Cordis 内核。

一棵树由什么组成,由 Profile 决定——把它想成你手里的菜单单

  • Profile 是一个命名的组合,存在你的 Harness 目录里。它列出要叠哪些 Bundle、装哪些额外的插件,还保留你自己的一份 cordis.patch.yml。单子不炒菜,只写"这顿上哪些":官方出厂带了 webheadless 两张现成菜单单,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 都见过。

把加载过程串成一条时间线,前面这些就全落地了

  1. 启动时 dsh 先读你选好的 Profile——它就是一张加载清单,写着"我要按什么顺序叠哪些层";
  2. 按清单顺序逐层加载:先装第 1 层基础(dsh-base),再装第 2 层追加(web-app / headless / 你的插件),最后第 3 层覆盖(cordis.patch.yml + --patch)——越后加载的越能覆盖先加载的;
  3. 所有层都装到同一个 Cordis 内核上,在那里注入服务、互相通信。

一句话收口:Profile = 启动时的点单顺序;插件树 = 按这个顺序装菜;Cordis = 装菜的那口锅。

事件与能力接缝:插件之间怎么说话

插件之间不互相 import,靠两类东西协作:

事件是扩展点,分三个领域:

  • Session 事件:持久事实,追加进日志并广播,重启了也还在(比如"这条消息发过了")。
  • Agent 事件agent/*):携带活着的 Agent,用来观察或拦截正在跑的工作(比如"这一步要开始前拦一下")。
  • 能力事件fs/*tools/*telemetry/*):把策略和适配器挂到某个接缝上,不用改主循环。

能力接缝(Seam)是可替换的能力,它有三个固定角色:

  • Service Definition:声明接口长什么样;
  • Service Provider:真正实现它;
  • Consumer:使用它(通常是模型要调的工具)。

为什么说"换一个 Provider 就是换一个产品"?因为文件系统和子进程的 provider 共享同一个"执行世界"——把它们指向一个远程沙箱,Bash、PTY、LSP 会一起跟着搬过去,不需要为每种能力单独写一套。模型适配器同理:ctx.llm 上注册哪个适配器,整个产品就用哪个模型,别的都不用动。

把心智模型收进一句话

如果只能带走三句,记住这三句(配着上面那家小馆子一起记):

  1. dsh 没有核心——每一部分都是插件,包括它自己;往旁边加一道菜就行,拔掉会自动回卷。
  2. Profile 决定树长什么样——web / headless / 自定义,都是同一棵树的不同"菜单单";它不干活,只点菜。
  3. 事件和接缝是插件协作的接口——挂上就生效,拔下就回卷,所以随便换、随便加;换 Provider 就是换产品。

这一章你学到了什么

  • [ ] 能说出"运行中的 dsh = 一棵插件树"和它从有序层组合的含义
  • [ ] 能说出 Cordis 的三个核心约定(服务注入 / 事件 / 可逆效果),以及为什么没有特权核心
  • [ ] 能从 --dump-config 的输出里认出核心插件(llm / session / credentials / tools 等)
  • [ ] 能说清 Profile 和 Bundle 的关系,以及插件叠加的层顺序
  • [ ] 能用"点菜"比方说清插件 / bundle / profile 的关系,知道自建 profile 是"抄单子改",plugin add / --patch 是最轻量的改法
  • [ ] 能说出能力接缝的三个角色(Definition / Provider / Consumer),并解释"换 Provider 就是换产品"

Open Source · MIT · Community Driven