docs(events): align hook design with runtime observation

Mark the original hook design as an early record and update observer examples to pkg/events runtime events and hook.runtime_event.

Validation: make lint
This commit is contained in:
Hoshina 2026-04-26 16:33:18 +08:00
parent b2249df3ea
commit fce800414d

View file

@ -1,11 +1,15 @@
# PicoClaw Hook 系统设计(基于 `refactor/agent` # PicoClaw Hook 系统设计(基于 `refactor/agent`
> 当前状态:本文是 hook 系统的早期设计记录。事件系统升级后,观察型 hook 的主路径已经切到
> `pkg/events.Event``RuntimeEventObserver` 和进程 hook 的 `hook.runtime_event`
> 文中提到的 `agent.Event``EventKind``hook.event` 只代表迁移期兼容层,不应作为新代码接口。
## 背景 ## 背景
本设计围绕两个议题展开: 本设计围绕两个议题展开:
- `#1316`:把 agent loop 重构为事件驱动、可中断、可追加、可观测 - `#1316`:把 agent loop 重构为事件驱动、可中断、可追加、可观测
- `#1796`:在 EventBus 稳定后,把 hooks 设计为 EventBus 的 consumer而不是重新发明一套事件模型 - `#1796`:在 runtime event bus 稳定后,把 hooks 设计为事件 consumer而不是重新发明一套事件模型
当前分支已经完成了第一步里的“事件系统基础”,但还没有真正的 hook 挂载层。因此这里的目标不是重新设计 event而是在已有实现上补出一层可扩展、可拦截、可外挂的 HookManager。 当前分支已经完成了第一步里的“事件系统基础”,但还没有真正的 hook 挂载层。因此这里的目标不是重新设计 event而是在已有实现上补出一层可扩展、可拦截、可外挂的 HookManager。
@ -52,8 +56,9 @@ pi-mono 的核心思路更接近当前分支:
当前分支已经具备 hook 系统的地基: 当前分支已经具备 hook 系统的地基:
- `pkg/agent/events.go` 定义了稳定的 `EventKind``EventMeta` 和 payload - `pkg/events` 定义 runtime event envelope、kind、scope、source、severity 和 fan-out bus
- `pkg/agent/eventbus.go` 提供了非阻塞 fan-out 的 `EventBus` - `pkg/agent/event_payloads.go` 保留 agent domain payload
- `pkg/agent/eventbus.go` 只作为迁移期兼容层存在
- `pkg/agent/loop.go` 中的 `runTurn()` 已在 turn、llm、tool、interrupt、follow-up、summary 等节点发射事件 - `pkg/agent/loop.go` 中的 `runTurn()` 已在 turn、llm、tool、interrupt、follow-up、summary 等节点发射事件
- `pkg/agent/steering.go` 已支持 steering、graceful interrupt、hard abort - `pkg/agent/steering.go` 已支持 steering、graceful interrupt、hard abort
- `pkg/agent/turn.go` 已维护 turn phase、恢复点、active turn、abort 状态 - `pkg/agent/turn.go` 已维护 turn phase、恢复点、active turn、abort 状态
@ -62,7 +67,7 @@ pi-mono 的核心思路更接近当前分支:
当前分支还缺四件事: 当前分支还缺四件事:
- 没有 HookManager只有 EventBus - 没有 HookManager只有旧 agent EventBus
- 没有 Before/After LLM、Before/After Tool 这种同步拦截点 - 没有 Before/After LLM、Before/After Tool 这种同步拦截点
- 没有审批型 hook - 没有审批型 hook
- 子 agent 仍走 `pkg/tools/SubagentManager + RunToolLoop`,没有接入 `pkg/agent` 的 turn tree 和事件流 - 子 agent 仍走 `pkg/tools/SubagentManager + RunToolLoop`,没有接入 `pkg/agent` 的 turn tree 和事件流
@ -73,19 +78,19 @@ pi-mono 的核心思路更接近当前分支:
## 设计原则 ## 设计原则
- Hook 必须建立在 `pkg/agent` 的 EventBus 和 turn 上下文之上 - Hook 必须建立在 `pkg/events` runtime event bus 和 turn 上下文之上
- EventBus 负责广播HookManager 负责拦截,两者职责分离 - runtime event bus 负责广播HookManager 负责拦截,两者职责分离
- 项目内挂载要简单,项目外挂载必须走 IPC - 项目内挂载要简单,项目外挂载必须走 IPC
- 观察型 hook 不能阻塞 loop拦截型 hook 必须有超时 - 观察型 hook 不能阻塞 loop拦截型 hook 必须有超时
- 先覆盖主 turn不把 sub-turn 一次做满 - 先覆盖主 turn不把 sub-turn 一次做满
- 不新增第二套用户事件命名系统,优先复用 `EventKind.String()` - 不新增第二套用户事件命名系统,新观察点统一使用 `pkg/events.Kind`
## 总体架构 ## 总体架构
分成三层: 分成三层:
1. `EventBus` 1. `pkg/events` runtime event bus
负责广播只读事件,现有实现直接复用 负责广播只读事件,覆盖 agent、channel、gateway、bus、MCP 等运行时组件
2. `HookManager` 2. `HookManager`
负责管理 hook、排序、超时、错误隔离并在 `runTurn()` 的明确检查点执行同步拦截 负责管理 hook、排序、超时、错误隔离并在 `runTurn()` 的明确检查点执行同步拦截
@ -97,7 +102,7 @@ pi-mono 的核心思路更接近当前分支:
换句话说: 换句话说:
- EventBus 是“发生了什么” - runtime event bus 是“发生了什么”
- HookManager 是“谁能介入” - HookManager 是“谁能介入”
- HookMount 是“这些 hook 从哪里来” - HookMount 是“这些 hook 从哪里来”
@ -113,11 +118,11 @@ pi-mono 的核心思路更接近当前分支:
```go ```go
type EventObserver interface { type EventObserver interface {
OnEvent(ctx context.Context, evt agent.Event) error OnRuntimeEvent(ctx context.Context, evt events.Event) error
} }
``` ```
这类 hook 直接订阅 EventBus 即可 这类 hook 直接订阅 runtime event bus 即可。旧 `OnEvent(ctx, agent.Event)` 仅用于迁移期兼容
适用场景: 适用场景:
@ -156,7 +161,7 @@ type ToolApprover interface {
## 对外暴露的最小 hook 面 ## 对外暴露的最小 hook 面
V1 不需要把所有 EventKind 都变成可拦截点。 V1 不需要把所有 runtime event kind 都变成可拦截点。
建议只开放这些同步 hook 建议只开放这些同步 hook
@ -168,19 +173,19 @@ V1 不需要把所有 EventKind 都变成可拦截点。
其余节点继续作为只读事件暴露: 其余节点继续作为只读事件暴露:
- `turn_start` - `agent.turn.start`
- `turn_end` - `agent.turn.end`
- `llm_request` - `agent.llm.request`
- `llm_response` - `agent.llm.response`
- `tool_exec_start` - `agent.tool.exec_start`
- `tool_exec_end` - `agent.tool.exec_end`
- `tool_exec_skipped` - `agent.tool.exec_skipped`
- `steering_injected` - `agent.steering.injected`
- `follow_up_queued` - `agent.follow_up.queued`
- `interrupt_received` - `agent.interrupt.received`
- `context_compress` - `agent.context.compress`
- `session_summarize` - `agent.session.summarize`
- `error` - `agent.error`
`subturn_*` 在 V1 中保留名字,但不承诺一定触发,直到子 turn 迁移完成。 `subturn_*` 在 V1 中保留名字,但不承诺一定触发,直到子 turn 迁移完成。
@ -369,7 +374,7 @@ PicoClaw 启动外部进程,并在其 stdin/stdout 上跑协议。
### 观察链路 ### 观察链路
```text ```text
runTurn() -> emitEvent() -> EventBus -> observers runTurn() -> emitEvent() -> runtime event bus -> observers
``` ```
### 拦截链路 ### 拦截链路
@ -464,13 +469,13 @@ V1 不做复杂自动发现。
最适合 PicoClaw 当前分支的方案,不是直接复制 OpenClaw 的 hooks也不是完整照搬 pi-mono 的 extension system而是 最适合 PicoClaw 当前分支的方案,不是直接复制 OpenClaw 的 hooks也不是完整照搬 pi-mono 的 extension system而是
- 以现有 `EventBus` 为只读观察面 - 以 `pkg/events` runtime event bus 为只读观察面
- 以新增 `HookManager` 为同步拦截面 - 以新增 `HookManager` 为同步拦截面
- 项目内通过 Go 对象直接挂载 - 项目内通过 Go 对象直接挂载
- 项目外通过 `stdio JSON-RPC` 进程通信挂载 - 项目外通过 `stdio JSON-RPC` 进程通信挂载
这样做有三个好处: 这样做有三个好处:
- 和 `#1796` 一致hooks 只是 EventBus 之上的消费层 - 和 `#1796` 一致hooks 只是 runtime event bus 之上的消费层
- 和当前 `refactor/agent` 实现一致,不需要推翻已有事件系统 - 和当前 `refactor/agent` 实现一致,不需要推翻已有事件系统
- 同时满足“仓内简单挂载”和“仓外进程通信挂载”两个硬需求 - 同时满足“仓内简单挂载”和“仓外进程通信挂载”两个硬需求