架构
BubFramework:创建插件管理器、加载插件并执行process_inbound()。BubHookSpecs:定义所有 hook 契约(src/bub/hookspecs.py)。HookRuntime:以 sync/async 兼容方式执行 hook(src/bub/hook_runtime.py)。Agent:内置的模型与工具运行时(src/bub/builtin/agent.py)。ChannelManager:启动 channel、缓冲入站消息并路由出站消息(src/bub/channels/manager.py)。
Turn 生命周期
Section titled “Turn 生命周期”BubFramework.process_inbound() 当前按以下顺序执行:
- 通过
resolve_session(message)解析 session(为空时回退到channel:chat_id)。 - 使用
BubFramework.workspace中的_runtime_workspace初始化状态。 - 合并所有
load_state(message, session_id)返回的字典。 - 通过
build_prompt(message, session_id, state)构建 prompt(为空时回退到入站content)。 - 执行
run_model_stream(prompt, session_id, state)。 - 对每个流事件,调用
OutboundChannelRouter.dispatch_event(...),当目标 channel 存在时转发到channel.on_event(event, message)。 - 始终在
finally块中执行save_state(...)。 - 通过
render_outbound(...)渲染出站批次,然后展平。 - 如果没有出站消息,生成一条回退出站。
- 通过
dispatch_outbound(message)分发每条出站消息。
如果没有插件实现 run_model_stream,HookRuntime 会回退到 run_model(prompt, session_id, state),并将返回的文本适配为仅含一个文本块的流。
Hook 优先级语义
Section titled “Hook 优先级语义”注册顺序:
- 内置插件
builtin - 外部 entry point(
group="bub")
执行顺序:
HookRuntime反转 pluggy 的实现顺序,因此后注册的插件先执行。call_first返回第一个非None值。call_many收集所有实现的返回值(包括None)。
合并/覆盖细节:
load_state在合并前再次反转,使高优先级插件在键冲突时胜出。provide_channels由BubFramework.get_channels()收集,第一个 channel 名称胜出,因此高优先级插件可以覆盖内置 channel 名称。
- 对于普通 hook,
HookRuntime不会吞掉实现错误。 process_inbound()捕获顶层异常,通知on_error(stage="turn", ...),然后重新抛出。on_error本身是观察者安全的:一个观察者失败不会阻塞其他观察者。- 在同步调用(
call_first_sync/call_many_sync)中,可等待的返回值会被跳过并记录警告。
内置运行时说明
Section titled “内置运行时说明”内置 BuiltinImpl 的行为包括:
build_prompt:支持逗号命令模式;非命令文本可能包含context_str。run_model_stream:委托给Agent.run()。system_prompt:将默认 prompt 与工作区AGENTS.md组合。register_cli_commands:安装run、gateway、chat以及隐藏的诊断命令。provide_channels:返回telegram和clichannel 适配器。provide_tape_store:返回基于文件的 tape store,位于~/.bub/tapes。
Channel 事件流
Section titled “Channel 事件流”Channel 有两种不同的出站接口:
send(message):处理最终渲染的出站消息。on_event(event, message):处理模型仍在运行时的原始流事件。
on_event 是可选的。当 channel 可以从增量渲染、输入指示器、进度更新或部分文本显示中受益时,请实现它。message 参数是原始入站消息,因此 channel 实现通常用它来恢复路由元数据,如目标 channel、chat id、session id 或消息类型。
如果 channel 不需要任何特殊的事件行为,可以忽略 on_event,完全依赖 send()。
Envelope有意保持弱类型(Any+ 访问器辅助方法)。- 跨插件
state没有全局强制的 schema。 - 本文档中描述的运行时行为与当前源码一致。