OpenProgram Docs

Proactive Layer 接入代码 — 设计#

Proactive 本身的设计在 ../proactive/。本文只讲那套设计 与现有 OpenProgram 代码的交界:复用了哪些机制、事件从哪里发出、gate 挂在哪里、 它的强制力实际覆盖到什么程度。有冲突以设计文档为准。

1. 代码落点#

事件层是 openprogram/events/bus.py 的就地升级:Event、按事件类型订阅、 进程级单例。taps 加在各个本来就知道「刚才发生了什么」的源文件里,而不是集中到一个 收集器。openprogram/proactive/ 包装规则层,等第一个规则消费者出现时才建;在那之前 事件层本身已经有用。

事件模型是核心三样加一个开放的 metadata 口袋(设计 event-layer.md §1)。 turnsession 走 metadata 而不是固定字段,这样没有 turn 概念的源不必硬造一个。

2. 复用而非重建的机制#

设计里描述的每个角色,代码库里都已经有一份能用的实现:

设计中的角色 现有机制 位置
进程内事件扇出 EventBus——已实现但闲置,dispatcher 和 agent_loop 用直接回调绕过了它 openprogram/events/bus.py
gate 的 ask 路径 ApprovalRegistry + _wrap_with_approval:发起请求、阻塞等待、批准或拒绝,拒绝时回一个 is_error 的 tool result openprogram/agent/internals/_approval.py
observer 的 Prepare 后台 task TaskRunner.spawn_task——ThreadPoolExecutor、状态机、task_status 广播 openprogram/agent/task/runner.py
Inject 的落地槽位 注入 system prompt 的 memory prefetch,以及 steering messages openprogram/agent/agent_loop.py
事件因果、rewind、分支 session git DAG,节点带 parent_id / caller openprogram/contextgit/
gate 的强制点 所有 chat tool 调用都要过的那一个点 agent_loop.py _execute_tool_calls

3. 事件 tap#

事件从本来就能察觉到相应事实的位置发出。多数 tap 是把现有回调或内部事件转成规范事件, 而不是新增一套探测:

Event 来源 tap 的性质
user.prompt_submitted dispatcher/__init__.py,持久化用户消息处 加在已有的 chat_ack / chat_response 广播旁;放在持久化分支之外,两条路径都会发
model.response_started agent_loop.py 的 AgentEventMessageStart 转换现有事件
model.response_completed agent_loop.py 的 AgentEventMessageEnd 转换现有事件
tool.before agent_loop.py,每次 tool.execute() 之前 一份事件同时喂 notify emit 与 gate 问询
tool.after agent_loop.py,每次工具调用结束之后 notify emit,附结果文本通道
subagent.started / completed task/runner.py 的 task_status 广播 转换,经 _broadcast_task_status 汇总
permission.requested _approval.py 的 approval_request 信封 新增 tap
artifact.file.changed file_backup.backup_before_editproject_commit 写成功后新增发送

4. gate 的挂载点与如实声明的覆盖率#

gate 挂在两个地方,两处的保证强度确实不同,这个差别是明说的,不掩盖。

chat 路径——强制。 gate 串在 agent_loop.py_execute_tool_calls 里、 tool.execute 之前。所有 chat tool 调用都过这一个点,绕不过去。

agentic 嵌套路径——可选挂载。 function.py_pre_invocation_hooks, cancel 检查已经挂在这里。它是一个挂载点而非咽喉点;覆盖率如实声明,不对嵌套调用 宣称全覆盖。

gate 对 subagent turn 生效,且独立于 permission_mode。特别地,它不会被 sub_agent_run.py 里设的 permission_mode="bypass" 关掉——那个 bypass 是本设计 要堵的现有漏洞(设计 invariants.mdexecution-model.md §2)。

5. Prepare 的执行#

Prepare 复用 TaskRunner.spawn_task,但注入一个不含 bash、write、network 的受限 tool allowlist。它跑在一个独立小池里,并发 1–2,可被用户任务抢占,遇 429 让路 (设计 execution-model.md §3)。

6. 一个已知缺口,但不阻塞#

@function 的 tool 执行不写 DAG 节点,只有 @agentic_function 写,因此 DAG 树作为 因果记录是不完整的。如果审计必须靠 DAG 做因果回溯,就得先补上这一块。本设计改为在 events.jsonl 里独立记录全量事件,于是 DAG 的缺口成了一个已知项,而不是前置条件。

7. 验证方式#

每一处接线改动都按同一套查:py_compile、相关单测、openprogram worker restart/healthz 正常,以及经 web UI 发一条真实消息(前端改动要先 cd web && npm run build)。

事件顺序的验证方式是跑一个带工具调用的 turn,读该会话的 events.jsonl (事件日志常开)。日志必须依序出现 user.prompt_submitted → model.response_started → tool.before → tool.after → model.response_completed,且每条的 metadata 里带 session 和 turn。

附录:实现状态#

迁移分五步,已落地四步。先启用总线并接入 agent 内部的事件源;接着是 file.changedtool.before 同步问询点;然后是外部源桥接(一座真的 auth 桥,加上 context、 channels、memory、web UI 的源头 tap);再然后把 web UI 从发送方改成订阅方,外部源 发 ws.frame 信封、web UI 原样转发。前四步中除第四步改动了旧路径外都是纯加法。 第五步——openprogram/proactive/ 规则层及其 Policy、挡路、旁观行为——尚未落地, 验收标准是 proactive 不碰任何子系统内部,纯靠订阅工作。

已落地的各部分位置:

位置
Event / make_event / emit_safe / subscribe(types=) / get_event_bus / 事件日志订阅者 openprogram/events/bus.py
同步问询点:register_tool_gate / decide_tool_gate / ToolGateDenied openprogram/events/tool_gate.py
tool.before 观察与问询、tool.aftermodel.* taps openprogram/agent/agent_loop.py
user.prompt_submitted openprogram/agent/dispatcher/__init__.py
subagent.started / ended openprogram/agent/task/runner.py _broadcast_task_status
file.changed,写成功后经懒 import 发出 write / edit / apply_patch 三个工具中的五处
外部源桥,worker 启动时幂等安装 openprogram/events/bridges.py + worker/runner.py
外部源头 taps context/engine.py(compaction ×2)、channels/_conversation.pymemory/session_watcher.py(×2)、webui/server.py(skills / plugins)
emit_ws_frame 透传信封 + _subscribe_event_bus 订阅转发 openprogram/events/bus.pywebui/server.py
外部源不再 import web UI task/runner.pysub_agent_run.pyworktree/manager.pyfunctions/watcher.pychannels/_broadcast.py
单测(30 个) tests/agent/test_event_bus.pytest_tool_gate.pytest_event_bridges.py

实测验证覆盖了真实 turn 上的完整事件序列、file.changed、gate 的端到端测试、 skills.changed,以及一次 WebSocket 探针——确认 task_status 的四个状态都经新链路 到达前端。有一条环境注意事项要带下去:worker 的工作目录是 home,因此项目 skills 目录解析成 ~/skills

Last updated · 2026-08-13