auto-sync: 2026-05-15 12:31:35
This commit is contained in:
@@ -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 生命周期
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user