架构总览:插件树与无特权内核
本教程共 32 篇 · 第 27 篇 · 更新于 2026-08-15 · 约 6 分钟阅读
本节目标:看懂 dsh 的整体架构——为什么说它「没有特权内核」,插件怎么挂载、怎么卸载回退,以及它和普通 Agent 框架差在哪里。
前 26 章一直在「用」dsh。这一章开始往深里看:一个跑起来的 dsh,里面到底是什么结构?答案是四个字:一切皆插件。这不仅是口号,也是架构事实。
整个产品都是插件
官方架构文档的第一句话就是:产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身。
这句话怎么理解?拿你熟悉的 web profile 来说,里面跑的 LLM 接入(llm-deepseek)、文件工具(tool-fs)、bash 执行(tool-bash)、会话日志(core/session)、Web 界面(dsh-web-app),没有一样是「写死在内核里的」。它们全部以插件身份挂在同一棵 Cordis 插件树上。
推论很直接:每一部分都可以从配置替换。想换模型接入方式,换掉 LLM 适配器插件;想换会话存储,换掉持久化后端插件。没有哪个组件享有「我改不了」的特权。
无特权内核:没有要打补丁的核
这是 dsh 架构最反直觉的一点。官方原话:
不存在需要打补丁的特权内核:扩展 dsh 的方式是把插件挂载到其他插件旁边。
传统框架的思路是「一个核心 + 一圈插件」,核心是神圣的,扩展只能通过核心预留的钩子。dsh 的思路是「没有核心,只有树」。你想加功能,不需要请求内核开洞,只需要在树里挂一个新插件;你想改现有行为,就在目标插件旁边挂一个监听事件的插件,把行为拦下来改掉。
代价是纪律:一切注册都是副作用(effect)。插件注册一个工具、监听一个事件、声明一个服务,这些动作全部登记为 effect;插件卸载时,这些 effect 自动撤销,系统回到插件挂载前的状态。这就是「可逆」。
Tip可逆 effect 是热重载(HMR)的基石。开发者改插件代码时,Cordis 可以卸载旧实例、挂载新实例,而不会残留半截注册。官方文档把这种能力概括为「时空可组合性」(Spatiotemporal Composability)。
运行中的 dsh 是一棵插件树
一个跑起来的 dsh,本质是一棵由多层配置叠加出来的插件树。层与层之间靠三个概念配合:
| 概念 | 是什么 | 例子 |
|---|---|---|
| profile | 存放在 Harness home 里的具名组装,列出要叠放的组合包 | web、headless |
| bundle | 组合包:一批 Cordis 配置项 + 挂载代码的分发格式 | dsh-base(第一层)、dsh-web-app |
| patch | 按条目 id 定位并替换配置的覆盖层 | cordis.patch.yml、--patch |
叠加顺序是固定的,从空条目列表开始:
profile 列出的 bundle(按序) → profile 的 cordis.patch.yml → home 级 patch → --patch 覆盖层
dsh-base 是每个 profile 的第一层,提供模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。dsh-web-app 在其上增加浏览器应用,dsh-headless 增加一次性运行器且不带服务器。
看实际配置树用一条命令:
dsh --profile web --dump-config
它打印出的任何条目,都可以由你自己的 patch 按 id 替换。这是「一切可替换」落到配置层的体现。
Warning0.1.0-rc.6 处于 Developer Preview,插件名与配置项会快速变化,以
dsh --version实测为准。
三大扩展点:会话 / Agent / 能力事件
新行为不写进循环,而是挂到事件上。官方把事件分成三个域,选对域是改动的第一个决定:
- Session 事件:追加到会话日志的持久事实(
user/message、assistant/chunk)。需要重载后仍存在的事实用它。 - Agent 事件(
agent/*):携带活跃 Agent 的实时事件(inbox、步骤、状态、请求)。要观察或拦截进行中的工作用它。 - Capability 事件(
fs/*、tools/*、telemetry/*):向能力缝附加策略和适配器,无需导入循环。
三个域的分工在第 12 章讲过,这里记住一句话:事件就是扩展点。架构文档把「新行为归属哪里」整理成了一张映射表,例如加模型提供方去 ctx.llm 注册适配器、加面向模型的能力去 ctx.tools 注册、限制进程用 ctx.sandbox 后端、拦截请求或工具用对应的 agent/* 或 tools/* 事件。照着表找位置,就不会把功能写进循环里。
与常规 Agent 框架的差异
把 dsh 和常见的单体 Agent 框架摆在一起,差异是结构性的:
| 维度 | 常规框架 | dsh |
|---|---|---|
| 核心 | 有特权内核,功能内嵌 | 无特权内核,一切皆插件 |
| 扩展方式 | 内核预留的钩子/配置项 | 在树里挂插件、监听事件 |
| 卸载 | 不一定有概念 | effect 自动回退,卸载即还原 |
| 换实现 | 换插件但内核行为不变 | 换一个 Provider,整个产品行为跟着换 |
最后一条最值得展开。一个 seam(能力缝) 由三种角色组成:声明接口的 Service Definition、实现它的 Service Provider、使用它的 Consumer(通常是面向模型的工具)。Consumer 只认接口。所以把文件系统 Provider 从本地换成远程沙箱,Bash、PTY、LSP 这些共享同一执行世界的消费方会一起搬过去,不需要为每个工具写 Provider 专用分支。替换一个提供方,就是替换整条能力链。
Note第三方解读流传过「八层可替换架构」的说法,那是外部抽象,不是官方术语。官方架构分类是 Capability Seams(能力缝)+ Core Services(核心服务),服务通过
ctx.*键暴露。讨论架构请用官方说法,下一章给出完整的服务地图。
小结
记住三句话:没有特权内核,扩展靠挂载插件;所有注册是可逆 effect,卸载自动回退;换一个 Provider 就换整个产品行为。下一章把 ctx.* 服务地图铺开,回答「哪些东西能换、怎么换」。