一、两种世界观Two Worldviews
传统框架:核心 + 扩展点
- 特权核心塞满内置功能
- 扩展 = 核心预留的"插座",位置数量固定
- 想换内置功能?改核心源码,或祈祷它留了口子
- 越成功越臃肿,"牵一发动全身"
dsh:一切皆插件(微内核)
- 核心只剩最小调度机制
- 连模型适配器、工具注册表、会话日志、agent 循环本身都是插件
- 扩展 = 在别的插件旁边再挂一个你的插件
- 任何部分都能从配置替换,没有谁动不得
传统框架像精装房:装修队留了几个插座,你只能在那儿插。dsh 像毛坯 + 标准化构件:墙都能拆,因为每面墙都是同样规格的挂件。
最有说服力的证据:驱动整个智能体的 agent-loop(循环本身)也是普通插件,挂在 packages/core/agent-loop/,可被配置替换——循环之上没有任何特权代码。
二、好处一:没有特权核心No Privileged Core
扩展 dsh 的方式永远只有一种:挂插件,不存在"内置的改不了"——换权限策略、换模型、换持久化后端,都是挂一个自己的插件。官方文档原话:"There is no privileged core to patch"——没有一个需要打补丁的特权核心。
三、好处二:注册即效果Registration is an Effect
registrations are effects —— 一切注册都是"可撤销的效果"。
插件注册的每样东西——工具、事件监听器、提示词段落、定时器——都通过 ctx.effect() 或 ctx.on() 安装,自动附带"卸载逻辑",卸载时按相反顺序干净消失。
export function apply(ctx: Context) {
// 传统写法:注册了就收不回来,卸载后监听器还挂着(内存泄漏/重复触发)
setInterval(heartbeat, 5000)
// dsh 写法:卸载时 clearInterval 自动执行
ctx.effect(() => {
const timer = setInterval(heartbeat, 5000)
return () => clearInterval(timer) // ← 卸载时自动调用
})
}
像酒店房卡:入住(注册)拿房卡,退房(卸载)房间自动恢复原状——不用挨个把家具搬回去。
四、好处三:热重载HMR
前两个好处叠加得到 HMR(热重载):改插件代码不用重启进程。
保存代码my-plugin.ts 改动
→
旧插件卸载注册全部自动撤销
→
新插件挂载重新注册
→
进程不重启会话状态不丢
别的框架很难做到——注册是"泼出去的水"(无撤销逻辑),卸载必留幽灵监听器;注册即效果让"拔插件"变安全,这是"一切皆插件"的隐藏红利。
五、配套纪律:改插件,不改循环Plugins, not Loop Changes
新行为挂在已有扩展点上(事件、服务),不改 agent-loop 本身。官方规定:改 agent-loop 必须同步更新架构文档——这条高门槛让循环保持极简稳定,复杂度全外移到插件层。写插件也一样:先找扩展点,找不到再考虑别的。
这让 dsh 成为"反向依赖"系统:循环不知道插件的存在,插件却知道循环的一切扩展点——耦合方向决定可演化性。
微内核 microkernel
只保留最小调度核心、一切功能皆可插拔模块的架构。
扩展点 extension point
官方文档化的挂载位置:事件、服务、注册表。
✏️ 动手练习
- 打开
docs/architecture.zh.md 的"Where new behavior goes"表,挑 3 行没见过的机制抄下来(后续课程的地图)。
- 用"酒店房卡"和"精装房 vs 毛坯房"两个比喻,向自己复述"注册即效果"和"没有特权核心"。
📝 自测(点击展开答案)
1. "一切皆插件"最极端的证据?
agent-loop——驱动智能体的循环本身也是插件(packages/core/agent-loop/),可整体替换;循环之上没有特权代码。
2. 为什么 dsh 的 HMR 能干净工作?
注册即效果:所有注册经 ctx.effect()/ctx.on() 安装、自带撤销逻辑,卸载时逆序回收。传统框架注册无撤销路径,卸载必留残留。
3. 加新行为的正确思路顺序?
先查"Where new behavior goes"表找扩展点,做成插件挂上去;改 agent-loop 是最后手段(还触发架构文档更新义务)。
4. "耦合方向决定可演化性"指什么?
循环不 import 任何插件,插件却知道循环的全部扩展点;增删插件循环零改动,系统可无限演化。