第 9 章 架构进阶:为什么"一切皆插件"不是口号
这一章给想深入源码的读者。不用背,当"地图"读即可——遇到具体问题时知道去哪查。
9.1 Cordis:底层插件框架
dsh 底层是 Cordis(以 vendor 方式引入的插件框架),它的设计来自论文《A Programming Paradigm for Spatiotemporal Composability》。五个核心概念:
插件是实现 Service 的对象:函数形式、对象形式或 Service 子类;
上下文是服务的容器:服务占据稳定的 ctx.
inject 声明依赖:加载顺序通过服务依赖表达,而非手动编排启动序列;
类型化事件用于通信:emit(观察)、waterfall(中间件/短路)、parallel(并行)、serial(按序传值);
注册是可逆副作用:一切注册在 reload 和 teardown 时自动撤销。
这五条你已经在前面的章节里用过前三条,现在知道它们是框架级约定,不是 dsh 独有的临时设计。
9.2 核心包:一张地图
#lTHoTsBDrbUs text { white-space: pre; }#lTHoTsBDrbUs .fb0 { stroke: var(--N900-FG, 0.15); }#lTHoTsBDrbUs .fb0 {stroke: rgba(31,35,41, 0.15)}#lTHoTsBDrbUs .fx0 { stroke-miterlimit: 10; }#lTHoTsBDrbUs .fx0 {stroke-miterlimit: 10}#lTHoTsBDrbUs .fa1 { fill: rgb(187, 191, 196); }#lTHoTsBDrbUs .fa1 {fill: rgb(187, 191, 196)}#lTHoTsBDrbUs .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#lTHoTsBDrbUs .fx1 {font-style: normal}#lTHoTsBDrbUs .fx2 { text-anchor: middle; }#lTHoTsBDrbUs .fx2 {text-anchor: middle}#lTHoTsBDrbUs .fx3 { dominant-baseline: central; }#lTHoTsBDrbUs .fx3 {dominant-baseline: central}#lTHoTsBDrbUs .fx4 { stroke: none; }#lTHoTsBDrbUs .fx4 {stroke: none}#lTHoTsBDrbUs .fc0 { fill: var(--text-caption); }#lTHoTsBDrbUs .fc0 {fill: #646a73}#lTHoTsBDrbUs .fa2 { fill: var(--text-link-disabled); }#lTHoTsBDrbUs .fa2 {fill: rgb(187, 191, 196)}#lTHoTsBDrbUs .fa3 { fill: rgb(232, 239, 250); }#lTHoTsBDrbUs .fa3 {fill: rgb(232, 239, 250)}#lTHoTsBDrbUs .fx5 { font: 700 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#lTHoTsBDrbUs .fx5 {font-style: normal}#lTHoTsBDrbUs .fx6 { text-anchor: start; }#lTHoTsBDrbUs .fx6 {text-anchor: start}#lTHoTsBDrbUs .fx7 { dominant-baseline: alphabetic; }#lTHoTsBDrbUs .fx7 {dominant-baseline: alphabetic}#lTHoTsBDrbUs .fc1 { fill: rgb(30, 42, 58); }#lTHoTsBDrbUs .fc1 {fill: rgb(30, 42, 58)}#lTHoTsBDrbUs .fx8 { font: 400 12px LarkHackSafariFont, LarkEmojiFont, Consolas, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#lTHoTsBDrbUs .fx8 {font-style: normal}#lTHoTsBDrbUs .fc2 { fill: rgb(26, 58, 122); }#lTHoTsBDrbUs .fc2 {fill: rgb(26, 58, 122)}#lTHoTsBDrbUs .fx9 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#lTHoTsBDrbUs .fx9 {font-style: normal}#lTHoTsBDrbUs .fb1 { fill: var(--N900-FG, 0.15); }#lTHoTsBDrbUs .fb1 {fill: rgba(31,35,41, 0.15)}#lTHoTsBDrbUs .fb2 { fill: rgb(201, 212, 230); }#lTHoTsBDrbUs .fb2 {fill: rgb(201, 212, 230)}#lTHoTsBDrbUs .fa4 { fill: var(--bg-body); }#lTHoTsBDrbUs .fa4 {fill: #fff}ABC12345678包职责ctx 键core/sessioncore/system-promptcore/toolscore/agentcore/agent-loopcore/scopellm/llmctx.sessionsctx.systemPromptctx.toolsctx.agentsctx.agentLoopctx.llm仅追加的会话事件日志提示词片段与工具 schema 组装作用域化的工具注册表 + 带把关的执行流水线Agent 接口与活跃 agent 注册表默认的 agent 循环驱动器按 agent 划分作用域的注册原语消息与流式词汇表、适配器 seam库,无 ctx 键
注意最后一行:agent loop 本身也是一个可替换的包。这就是"没有特权内核"最有力的证据——连循环都能换。
9.3 事件域与轮次流程
一次对话的"轮次"是这样流转的(简化版):
代码块
Plain Text
turn/start
→ 领取输入
→ 组装提示词片段 + 工具 schema
→ agent/pre-step ← 可以改写或拒绝输入
→ step/start
→ 写入 user/message
→ 从日志推导模型历史
→ agent/request ← 可以替换模型/推理档位
→ llm/stream ← 流式响应
→ assistant 消息
→ 工具调用: tools/pre-execute → tools/execute → tools/post-execute
→ step/end
→ 若还有未完成工作或新输入 → 进入下一个 step
→ agent/turn-stopping
turn/end
关键理解:
•
一个步骤 = 一次模型请求 + 它调用的工具;
•
一个轮次 = 零个或多个步骤,从领取首条输入开始,到不再欠任何工作为止;
•
agent/pre-step、agent/request、llm/stream 和三个 tools/* 事件是 waterfall(瀑布式)事件,监听器必须调用 next() 才能委托下去;
•
agent/turn-stopping 是 serial 事件,没有 next(),用来决定是否停。
9.4 会话日志:模型可见即已记录
dsh 有一条运行时不变式:
模型可见即已记录。 抵达模型请求的一切,都必须能从会话日志重建。
因此:
•
会话日志是模型所见上下文的唯一来源;
•
原始 assistant/chunk 事件保证回放和 UI 保真;
•
fork、恢复、transcript、遥测都派生自这条事件流;
•
想新增一项"模型可见"的输入,就必须新增一个会话事件类型,并从日志渲染它。
对插件作者的意义:别绕过日志偷偷给模型塞上下文——那既违反设计,也会在回放和审计时穿帮。
9.5 能力 seam 三件套
第 2 章讲过 seam = Service Definition + Service Provider + Consumer。架构上的推论:
•
替换一个 provider,就改变整个产品。文件系统与进程 provider 共享同一个执行世界,把文件系统换成远程沙箱,Bash、PTY、LSP 一并搬走,不需要 provider 专属 fork;
•
subagent 也是 seam:从新建一个子 agent,到把一个轮次委派给另一个产品,背后是同一个接口。
9.6 新行为挂哪里:扩展点速查表
9.7 本章小结
•
Cordis 五概念:Service、Context、inject、类型化事件、可逆副作用;
•
核心包地图里最值得注意的是 core/agent-loop——循环本身可替换;
•
轮次 = 多个步骤,事件是扩展点,waterfall 事件要 next();
•
"模型可见即已记录"是 dsh 的审计底线,别绕过日志;
•
seam 三件套让"换 provider = 换世界"成为可能;
•
想加什么能力,先查扩展点速查表,再决定挂哪。
下一章,三个实战案例,把前面所有知识串起来。