auto-sync: 2026-05-15 12:31:35

This commit is contained in:
cfdaily
2026-05-15 12:31:35 +08:00
parent a9ca21a019
commit f118ec6dfc
+104 -53
View File
@@ -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: EventBusasyncio.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: Tick30s 简化版,兜底 + 健康检查) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ • 僵尸检测、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` | EventBusDaemon 内部) | ≤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-5Signal File 规范
```python
async def daemon_tick():
"""每 60 秒执行一次"""
# Agent 操作黑板后写 signal fileCLI 自动完成)
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. 处理评论中的 @mentionL2/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. 清理完成的 sessionL1: 文件操作)
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 生命周期
```