框架信号路由:一条总线当中枢#
事件层(event-layer.md)定义了事件是什么。这篇讲它对整个框架的影响:谁来路由信号、
各子系统往总线里放什么、以及这套设计刻意不碰哪些部分。
1. 总线要解决的问题#
没有总线时,信号存在的目的只有一个:让前端看到。这个目的把每个信号都硬连到 webui server 的
_broadcast——task_status、channel_turn、skills:changed 各用各的 JSON 直连,agent 事件经
dispatcher 回调链到达。webui 是个 UI 组件,让它当路由点就等于把 UI 关注点放进了框架的信号
链路。比耦合更麻烦的是缺口:auth 把自己的事件建模对了却几乎没人订阅,memory 和文件改动
根本不发信号,hooks 的返回值被丢弃(能观察不能拦截),EventBus 闲置。
代价落在任何一个新消费者身上。proactive 是第一个:没有总线,它得分别对接五六套机制,而有 些时机根本没有信号可以对接。
2. 总线当中枢:三个角色#
| 角色 | 谁 | 这个角色是什么 |
|---|---|---|
| 中枢 | EventBus | 唯一路由点:进程级单例、统一 Event 格式、按类型订阅 |
| 订阅者 | webui server | 一个普通订阅者:订阅总线,转发前端 WS |
| 订阅者 | proactive 及未来任何功能 | 同样只是订阅者,一行 subscribe(types=…) 接入 |
总线旁边有一处同步问询点 tool.before——全框架唯一的拦截位。它复用 _approval,对 subagent
同样生效。其余一切与总线的交互都是异步观察。
3. 各子系统往总线里放什么#
| 子系统 | 它在总线设计里的角色 |
|---|---|
| agent loop | 保留内部 AgentEvent 流,同时在关键节点 emit 到总线;tool.before 带同步问询 |
| dispatcher | 保留 on_event 回调链,同时 emit user.prompt_submitted 等事件 |
| task runner | emit subagent.*;前端广播由 webui 订阅转发 |
| auth | 内部不动;一段桥接把 AuthEvent 翻成 Event emit 进总线 |
| context | 在已有回调里顺手 emit context.* |
| channels | emit channel.*;前端直连并存 |
| memory | 处理起止 emit,把定时 poll 包装成事件 |
| 文件改动 | 在 checkpoint_before_edit 处 emit file.changed |
| plugin hooks | 内部统一走总线;hooks 保留为插件 API,包一层订阅 |
| webui server | 订阅总线,而不是接收各路直连 |
| EventBus | 中枢本身:按类型订阅 + 单例访问 |
auth 的桥接值得单说,它是"子系统自己已经把事件建模对了"这一类的通用做法:子系统保留自己的 机制,一段桥接把它的事件翻译成统一格式。auth 内部不需要知道总线的存在。
4. 新旧路径并行#
总线并行于现有路径运行,而不是一次性替换掉它们。每个源独立迁移,每次迁移都能单独验证、 单独回退。
两个性质让这件事成立。把一个源接进总线是纯加法:总线收到一份拷贝,旧路径原样跑,行为不变。 把 webui 切成订阅者确实动了旧路径,所以它用透传信封——webui 广播出去的帧和原来逐字节一致, 前端不用改、也感知不到切换。这也是设计选信封而不选影子比对的理由:帧完全相同就不需要比对。
5. 刻意不动的东西#
不动什么和动什么一样重要。总线设计不伸手进这些地方:
- dispatcher 的七阶段 turn 编排,以及
process_user_turn的对外签名 - session git DAG 存储与 contextgit
- TaskRunner 的线程池模型
ApprovalRegistry批准机制——问询点复用它,而不是改写它- AuthStore 自身——用桥接,不修改
- 前端 WS 协议——webui 广播的帧不变,所以对前端透明
实现状态#
总线已启用并接入 A 类源,file.changed 与 tool.before 同步问询点已就位,B 类源已桥接
(auth 经 events/bridges.py,context / channels / memory / webui watcher 在源头 tap),
webui 已是总线订阅者——五个外部源(task runner、sub_agent、worktree、functions watcher、
channels)改为 emit ws.frame 事件,不再 import webui。剩余工作是 proactive 规则层,
它消费总线,不新增源。
可视化版本:
framework-evolution.html。