diff --git a/docs/design/architecture-v2.6.md b/docs/design/architecture-v2.6.md index d2ec752..2f90823 100644 --- a/docs/design/architecture-v2.6.md +++ b/docs/design/architecture-v2.6.md @@ -504,70 +504,121 @@ Daemon **不做**: 这个区分决定了 spawn 时的消息内容——L2 传数据,L3 传指针。 -### 4.2 Daemon Tick 循环 +### 4.2 双层事件架构(课题 2 设计决策) -参考 Hermes Dispatcher,但更轻量: +#### D2-1:架构总览 -**Tick 频率:60 秒(默认),可通过 CLI 手动触发立即执行。** +> **设计推导**:v2.6 原设计为 60s polling tick,但 @mention 响应延迟、依赖解锁延迟、用户操作无法即时响应等痛点要求事件驱动。open-multi-agent 证明纯 EventEmitter 零基础设施即可实现,Network-AI 证明 file-backed 信号可实现跨进程通知。用户确认“事件驱动这块需要设计完一起实施”。 -```bash -# 手动触发一次 tick(用于需要立即响应的场景) -python3 ~/.sanguo_projects/sanguo_moziplus/cli/daemon.py tick +**双层架构**: + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Daemon (Python asyncio) │ +│ │ +│ Layer 1: EventBus(asyncio.Queue) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 进程内事件总线,~0ms 延迟 │ │ +│ │ • task_completed → 解锁下游依赖 + spawn 对应 Agent │ │ +│ │ • task_failed → 触发 retry 链(spawn 庞统) │ │ +│ │ • comment_added → @mention 检测 → spawn 被提及者 │ │ +│ │ • user_action → 即时响应 │ │ +│ │ • task_ready → 依赖满足 → spawn 对应 Agent │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ ↑ │ +│ Layer 2: Signal File Watcher(~500ms) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Agent(外部进程)写 SQLite 后,同时写 signal file │ │ +│ │ Daemon 每 500ms 扫描 signal 目录 │ │ +│ │ 读取信号 → emit 到 EventBus → 即时处理 │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ ↑ │ +│ Layer 3: Tick(30s 简化版,兜底 + 健康检查) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ • 僵尸检测、stale 任务回收 │ │ +│ │ • Signal 遗漏先底(万一 signal file 处理失败) │ │ +│ │ • 低优先级事件批量处理(task_created, output_written) │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ ``` -**§4.2.1 Tick 循环中的 L1/L2/L3 标注说明:** -- L1(几乎零成本):健康检查、状态回收、session 清理、SQLite 查询、文件存档 -- L2(轻量):spawn 单任务 sub,如 @mention 通知、待领取任务通知、scope guard -- L3(完整):spawn 完整 Agent,如庞统仲裁 blocked 任务、AI 纠错协商、审核 +**不选的替代方案**: +- SQLite update_hook:~0ms 但需要 C API 绑定,且只能在 SQLite 操作时触发 +- Redis pub/sub:引入新依赖,v2.6 已去掉 Redis +- fswatch/watchdog:跨平台兼容性差,signal file 更简单 + +#### D2-2:事件类型与优先级 + +| 事件类型 | 触发方式 | 延迟要求 | 处理方式 | +|---------|---------|---------|----------| +| `task_completed` | Signal File + EventBus | ~0ms | EventBus 即时:解锁下游依赖 | +| `task_failed` | Signal File + EventBus | ~0ms | EventBus 即时:触发 retry 链 | +| `comment_added` | Signal File + EventBus | ~0ms | EventBus 即时:@mention 检测 → spawn | +| `user_action` | 直接 API 调用 | ~0ms | EventBus 即时处理 | +| `task_ready` | EventBus(内部事件) | ~0ms | EventBus 即时:spawn 对应 Agent | +| `task_created` | Signal File | ≤30s | Tick 批量处理 | +| `task_claimed` | EventBus(Daemon 内部) | ≤30s | Tick 批量处理 | +| `output_written` | Signal File | ≤30s | Tick 批量处理 | + +**关键洞察**:真正需要即时响应的场景只有 4 个(task_completed / task_failed / @mention / user_action),其他 60s 延迟完全可接受。 + +#### D2-3:依赖声明的并行/串行自动决策 + +> **设计推导**:open-multi-agent 的核心模式——complete→auto-unlock,纯依赖声明驱动。不需要额外的冲突检测或 AI 判断并行性。 + +**串行触发链**(事件驱动版): +``` +Agent A 完成 task-001 + → 写黑板 outputs + 更新 status → done + → 写 signal file: task_completed + → Daemon EventBus 即时处理: + 查询所有 depends_on 包含 task-001 的 pending 任务 + → task-002 depends_on: [task-001],检查 task-001 done ✅ + → 触发 task_ready 事件 + → spawn Agent B 执行 task-002 +``` + +**并行**:`depends_on` 为空且 assignee 不同的任务,自然并行(Daemon 分别 spawn)。不需要额外逻辑。 + +**不做 files_modified 冲突检测**(D2-4):Agent 通过黑板评论自然协调(“我在改 main.py,你别碰”),不需要系统强制。Scope Guard(课题 1)作为兜底。 + +#### D2-5:Signal File 规范 ```python -async def daemon_tick(): - """每 60 秒执行一次""" +# Agent 操作黑板后写 signal file(CLI 自动完成) +SIGNAL_DIR = Path("~/.sanguo_projects/sanguo_moziplus_v2/signals") - # 1. 健康检查(L1: Daemon 直接操作) - # 详见 §4.5 续杯与心跳 - check_agent_heartbeats() # L1: 检查 observations 有无新进展 - reclaim_stale_tasks() # L1: 超时的 working 任务回收(硬上限 = 3x 预估工时) - detect_zombie_sessions() # L1: 进程死了但 session 还在的 +# Signal file 格式: {event_type}.signal +# 内容: {task_id}:{agent}:{timestamp} +# 示例: task_completed.signal → "task-001:zhangfei-dev:1715750400" - # 2. 读黑板(L1: SQLite 查询) - board = read_blackboard() # SQLite 查询 - - # 3. 处理评论中的 @mention(L2/L3: spawn agent) - # Agent A 在评论中 @AgentB → daemon spawn AgentB 来看评论 - for comment in board.get_unprocessed_mentions(): - target_agent = comment.mentioned_agent - if not is_agent_active(target_agent): - async_spawn_agent(target_agent, - message=f"黑板上有给你的新评论(task-{comment.task_id}),请查看。") - mark_comment_processed(comment.id) - - # 4. 处理待领取的任务(L2: spawn agent)庞统在黑板上分配了但 agent 还没领) - for task in board.get_assigned_unclaimed(): - if not is_agent_active(task.assignee): - async_spawn_agent(task.assignee, - message=f"黑板上有分配给你的任务({task.title}),请查看并认领。") - - # 5. 处理 blocked 任务(L3: spawn 庞统决策)agent 请求帮助) - for task in board.get_blocked_tasks(): - mentions = get_latest_comment_mentions(task.id) - if mentions: - # 评论中 @ 了某人 → spawn 那个人 - for agent_id in mentions: - if not is_agent_active(agent_id): - async_spawn_agent(agent_id, - message=f"任务 {task.id} 被 block,需要你的协助。") - else: - # 没有 @ → spawn 庞统来决定找谁帮忙 - async_spawn_agent('pangtong-fujunshi', - message=f"任务 {task.id} 被 block,没有指定协助者,请决定如何处理。") - - # 6. 清理完成的 session(L1: 文件操作) - for session in get_completed_sessions(): - archive_session(session) # mv jsonl → task 目录 - cleanup_sessions_json(session) # 编辑 sessions.json 删除记录 +# Daemon Watcher: 每 500ms 扫描 SIGNAL_DIR +async def watch_signals(): + while True: + for signal_file in SIGNAL_DIR.glob("*.signal"): + payload = signal_file.read_text() + parts = payload.split(":") + event_type = signal_file.stem + await event_bus.emit(Event( + type=EventType(event_type), + task_id=parts[0] or None, + agent=parts[1] or None, + )) + signal_file.unlink() # 处理完删除 + await asyncio.sleep(0.5) ``` +**关键**:Signal file 写入由 `blackboard.py` CLI 自动完成,Agent 无需额外操作。任何 `blackboard.py` 的写操作(comment/output/claim/status update)都会同步写 signal file。 + +#### 与课题 1 的兼容性 + +| 课题 1 设计 | 事件驱动后变化 | 改善 | +|-----------|--------------|------| +| 续杯机制 | task_completed 事件即时触发依赖解锁 | @mention 从 ≤60s 降到 ≤1s | +| retry 由 AI 决策 | task_failed 事件即时触发 retry 链 | 庞统更快介入 | +| Guardrail 吹哨人 | observation 写入触发 signal file | Daemon 即时感知问题 | +| 三层执行模型 | 不变,事件处理仍按 L1/L2/L3 分层 | ✅ 一致 | + ### 4.3 Session 生命周期 ```