diff --git a/docs/design/architecture-v2.6.md b/docs/design/architecture-v2.6.md index 22d9f05..2cad861 100644 --- a/docs/design/architecture-v2.6.md +++ b/docs/design/architecture-v2.6.md @@ -18,20 +18,20 @@ | v2.6.2.1 | 2026-05-15 | 司马懿评审反馈:L2/L3区分标准、timeout修正、outputs关联attempt、Scope Guard异步、risk_level自动 | | v2.6.3 | 2026-05-15 | 课题2设计决策:双层事件架构(EventBus+Signal File+Tick)、L1/L2/L3上下文传递、黑板是索引不是仓库、依赖驱动并行/串行、Phase规划更新 | -### 课题 1-2 遗留 TODO(需后续课题解决) +### 课题 1-2 遗留 TODO(需后续课题解决) | # | 待解决事项 | 归属课题 | 说明 | |---|----------|---------|------| -| ~~T1-1~~ | ~~spawn sub 是否阻塞?需要调查~~ | ~~课题 2~~ | ✅ 课题 2 解决:不阻塞,signal file 异步 | -| ~~T1-2~~ | ~~事件驱动取代 polling tick~~ | ~~课题 2~~ | ✅ 课题 2 解决:双层事件架构 | -| ~~T1-3~~ | ~~依赖推进(done→自动解锁下游)~~ | ~~课题 2~~ | ✅ 课题 2 解决:task_completed 事件即时解锁 | -| ~~T2-1~~ | ~~files_modified 冲突检测~~ | ~~课题 2~~ | ✅ D2-4 决策:不做,Agent 评论自然协调 | -| ~~T2-2~~ | ~~Auto-compact~~ | ~~课题 2~~ | ✅ D2-6 决策:不做,隔离 session 天然无 context rot | +| ~~T1-1~~ | ~~spawn sub 是否阻塞?需要调查~~ | ~~课题 2~~ | ✅ 课题 2 解决:不阻塞,signal file 异步 | +| ~~T1-2~~ | ~~事件驱动取代 polling tick~~ | ~~课题 2~~ | ✅ 课题 2 解决:双层事件架构 | +| ~~T1-3~~ | ~~依赖推进(done→自动解锁下游)~~ | ~~课题 2~~ | ✅ 课题 2 解决:task_completed 事件即时解锁 | +| ~~T2-1~~ | ~~files_modified 冲突检测~~ | ~~课题 2~~ | ✅ D2-4 决策:不做,Agent 评论自然协调 | +| ~~T2-2~~ | ~~Auto-compact~~ | ~~课题 2~~ | ✅ D2-6 决策:不做,隔离 session 天然无 context rot | | T1-4 | Agent 间自主协商机制 | 课题 3 | 事件驱动 + 挑战体系共同支撑 | | T1-5 | Scope Guard 的 Skill 定义 | 课题 4 | scope_declaration 格式、检查 prompt 模板 | -| T1-6 | truths 验证的具体实现 | 课题 4 | AI 级别验证,怎么让 AI 判断 truths 达成 | +| T1-6 | truths 验证的具体实现 | 课题 4 | AI 级别验证,怎么让 AI 判断 truths 达成 | | T1-7 | outputs attempt_number 过滤规则 | 课题 4 | 重试时 Agent 看到之前 attempt output 的规则 | -| T1-8 | 状态机细化(review 轮次、sub_status) | 课题 3 | 挑战体系引入 review 内部状态 | +| T1-8 | 状态机细化(review 轮次、sub_status) | 课题 3 | 挑战体系引入 review 内部状态 | | T2-3 | blackboard.py 写操作自动写 signal file | Phase 1 实现 | CLI 层自动完成 | | T2-4 | EventBus + Signal File Watcher 实现 | Phase 1 实现 | Daemon 核心改造 | | T2-5 | L2/L3 分层读取 API | Phase 2 实现 | blackboard.py read --level L2/L3 | @@ -465,25 +465,25 @@ def add_comment(task_id: str, author: str, body: str, mentions: list = None): - Claude Code 的 file reference 模式:不内联,只引用 - agent-chorus 的 Context Pack 实验证明:结构化上下文让 Agent 文件打开量降 70%、token 消耗降 60%、零生产风险答案 - Opal-Bridge 的 Fidelity 三档:无损/摘要/混合,传递时按需降档 -- 一个典型任务全量黑板信息 ~1100-1750 tokens,极端 ~4000 tokens——远小于 128K context +- 一个典型任务全量黑板信息 ~1100-1750 tokens,极端 ~4000 tokens--远小于 128K context - 问题不是空间不够,而是**信号噪声比**:全量注入让 Agent 在无关信息上浪费注意力 -**AI Native 内容规范——不做硬限制,做软引导:** +**AI Native 内容规范--不做硬限制,做软引导:** 传统做法是给每个字段设长度上限(如 comments.body ≤ 2000 字符),这是 CRUD 应用的思维。 -AI Native 的做法是:Agent 是智能体,有能力判断“这段分析应该写文件还是直接写评论”。规范是指导性的,不是强制性的。 +AI Native 的做法是:Agent 是智能体,有能力判断"这段分析应该写文件还是直接写评论"。规范是指导性的,不是强制性的。 -- **不做硬限制**——不设字段长度上限,不截断,不报错 -- **做软引导**——Agent 的 Skill 中写“评论应简洁明了,大段分析写文件后在评论中给路径” -- **做传递优化**——L1 传递时自动截取(最近 3 条评论、每条 100 字符),这是传递层面的优化,不是存储层面的限制 -- **做信息分层**——黑板上的 comments 表存完整内容(不截断),但 L1 传递时只取摘要 +- **不做硬限制**--不设字段长度上限,不截断,不报错 +- **做软引导**--Agent 的 Skill 中写"评论应简洁明了,大段分析写文件后在评论中给路径" +- **做传递优化**--L1 传递时自动截取(最近 3 条评论、每条 100 字符),这是传递层面的优化,不是存储层面的限制 +- **做信息分层**--黑板上的 comments 表存完整内容(不截断),但 L1 传递时只取摘要 **为什么这样做是 AI Native**: -1. Agent 是智能体,不是 API 客户端——它有能力判断“这段分析应该写文件还是直接写评论” +1. Agent 是智能体,不是 API 客户端--它有能力判断"这段分析应该写文件还是直接写评论" 2. 如果硬限制导致信息丢失,Agent 会绕过限制(拆成多条评论、用文件存储),反而更混乱 3. 真正需要控制的是传递时的信息量(L1 预算),不是存储时的信息量 -**黑板上“必要信息”的定义(指导性)**: +**黑板上"必要信息"的定义(指导性)**: | 类别 | 上黑板 | 不上黑板 | |------|--------|----------| @@ -542,82 +542,82 @@ Daemon **不做**: 这个区分决定了 spawn 时的消息内容--L2 传数据,L3 传指针。 -### 4.2 事件驱动架构(课题 2 设计决策) +### 4.2 事件驱动架构(课题 2 设计决策) #### 设计推导过程 -**三个参考系统的做法**: +**三个参考系统的做法**: | 系统 | 架构 | 事件通知方式 | 启示 | |------|------|------------|------| -| **open-multi-agent** | 单进程 TypeScript | 纯 EventEmitter——`queue.on('task:ready', handler)`。TaskQueue 内部维护 listeners Map,complete() 时同步触发 emit。零基础设施 | 和我们的黑板同构:TaskQueue.complete() = 我们的任务完成,unblockDependents() = 我们的依赖解锁 | -| **agent-chorus** | 多进程(Claude/Codex/Gemini 各自独立) | 本地 JSONL 文件队列——`chorus send` 写入 `.agent-chorus/messages/.jsonl`,`chorus messages --agent --clear` 读并清空。纯文件系统,无网络 | Standup+Conclude 模式:Agent 开始时读 inbox,结束时广播状态。JSONL inbox 做跨进程通信 | -| **Edict** | 分布式(API Gateway + Orchestrator + Agent Pool) | Redis Streams Event Bus——`task.created` 等主题 + WebSocket 推送 Dashboard | 我们是单机单进程,不需要 Redis | +| **open-multi-agent** | 单进程 TypeScript | 纯 EventEmitter--`queue.on('task:ready', handler)`。TaskQueue 内部维护 listeners Map,complete() 时同步触发 emit。零基础设施 | 和我们的黑板同构:TaskQueue.complete() = 我们的任务完成,unblockDependents() = 我们的依赖解锁 | +| **agent-chorus** | 多进程(Claude/Codex/Gemini 各自独立) | 本地 JSONL 文件队列--`chorus send` 写入 `.agent-chorus/messages/.jsonl`,`chorus messages --agent --clear` 读并清空。纯文件系统,无网络 | Standup+Conclude 模式:Agent 开始时读 inbox,结束时广播状态。JSONL inbox 做跨进程通信 | +| **Edict** | 分布式(API Gateway + Orchestrator + Agent Pool) | Redis Streams Event Bus--`task.created` 等主题 + WebSocket 推送 Dashboard | 我们是单机单进程,不需要 Redis | -**推导结论**: -1. open-multi-agent 证明:单进程内 EventEmitter 完全够用,但它是单进程,我们是跨进程 -2. agent-chorus 证明:跨进程通信用 JSONL 文件就行,不需要 HTTP/Redis/MQ -3. Edict 的 Redis Streams 是分布式场景所需,我们不需要 -4. **真正需要即时响应的场景只有 4 个**:task_completed / task_failed / @mention / user_action。其他 ≤30s 延迟完全可接受 -5. **60s Tick 本身不是问题,问题是 Tick 的效率**——应该 Tick 是核心,加速器可选 +**推导结论**: +1. open-multi-agent 证明:单进程内 EventEmitter 完全够用,但它是单进程,我们是跨进程 +2. agent-chorus 证明:跨进程通信用 JSONL 文件就行,不需要 HTTP/Redis/MQ +3. Edict 的 Redis Streams 是分布式场景所需,我们不需要 +4. **真正需要即时响应的场景只有 4 个**:task_completed / task_failed / @mention / user_action。其他 ≤30s 延迟完全可接受 +5. **60s Tick 本身不是问题,问题是 Tick 的效率**--应该 Tick 是核心,加速器可选 -**用户反馈与设计迭代**: -- 初始设计:Signal File 跨进程通知 → 用户质疑"Signal File 存在的意义是什么" -- 第二版:HTTP 端点 → 用户要求"基于优秀实践推导,不是拍脑袋换方案" -- 最终版:Tick 核心 + Inbox JSONL 加速(agent-chorus 模式)——基于三个参考系统的实际代码推导 +**用户反馈与设计迭代**: +- 初始设计:Signal File 跨进程通知 → 用户质疑"Signal File 存在的意义是什么" +- 第二版:HTTP 端点 → 用户要求"基于优秀实践推导,不是拍脑袋换方案" +- 最终版:Tick 核心 + Inbox JSONL 加速(agent-chorus 模式)--基于三个参考系统的实际代码推导 -#### D2-1:Tick 核心 + Inbox 加速(最终方案) +#### D2-1:Tick 核心 + Inbox 加速(最终方案) ``` ┌──────────────────────────────────────────────────────────────────┐ │ Daemon (Python asyncio) │ │ │ -│ 核心:Tick Loop(30s 主循环) │ +│ 核心:Tick Loop(30s 主循环) │ │ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ 读黑板全量状态(SQLite 查询) │ │ -│ │ 发现需要处理的(mention / blocked / done → pending) │ │ -│ │ 执行对应操作(spawn / 通知 / 清理) │ │ -│ │ 健康检查(zombie / stale / heartbeat) │ │ +│ │ 读黑板全量状态(SQLite 查询) │ │ +│ │ 发现需要处理的(mention / blocked / done → pending) │ │ +│ │ 执行对应操作(spawn / 通知 / 清理) │ │ +│ │ 健康检查(zombie / stale / heartbeat) │ │ │ │ │ │ -│ │ 设计推导:Hermes 60s tick 证明 polling 可靠稳定。 │ │ -│ │ 我们从 60s 降到 30s,因为黑板查询比 Hermes 轻量。 │ │ +│ │ 设计推导:Hermes 60s tick 证明 polling 可靠稳定。 │ │ +│ │ 我们从 60s 降到 30s,因为黑板查询比 Hermes 轻量。 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↑ │ -│ 加速:Inbox JSONL(agent-chorus 模式) │ +│ 加速:Inbox JSONL(agent-chorus 模式) │ │ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Agent 写黑板后,可选:追加一行 JSON 到 daemon inbox │ │ +│ │ Agent 写黑板后,可选:追加一行 JSON 到 daemon inbox │ │ │ │ Daemon 主循环每 1s 检查 inbox 是否有新内容 │ │ -│ │ 有新内容 → 立即执行一次 mini-tick(只处理触发的事件) │ │ +│ │ 有新内容 → 立即执行一次 mini-tick(只处理触发的事件) │ │ │ │ 处理完清空 inbox │ │ │ │ │ │ -│ │ 设计推导:agent-chorus 用 JSONL inbox 做跨 Agent 通信, │ │ +│ │ 设计推导:agent-chorus 用 JSONL inbox 做跨 Agent 通信, │ │ │ │ 我们用 JSONL inbox 做 Agent→Daemon 通知。同理同构。 │ │ -│ │ inbox 是加速器,不是核心。Tick 兜底所有场景。 │ │ +│ │ inbox 是加速器,不是核心。Tick 兜底所有场景。 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↑ │ -│ 恢复:启动时全量扫描 │ +│ 恢复:启动时全量扫描 │ │ ┌─────────────────────────────────────────────────────────────┐ │ -│ │ Daemon 重启后立即做一次完整 Tick(PM2 自动重启) │ │ +│ │ Daemon 重启后立即做一次完整 Tick(PM2 自动重启) │ │ │ │ 消除重启后的 30s 空窗 │ │ -│ │ 不需要 EventBus 持久化——黑板(SQLite)是唯一真相源 │ │ +│ │ 不需要 EventBus 持久化--黑板(SQLite)是唯一真相源 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` -**为什么不选的替代方案**: -- EventBus + Signal File(初始设计):Signal File 需要额外的扫描/读/删循环,增加了耦合链 -- HTTP 端点(第二版):引入网络依赖,Daemon 需要跑 HTTP 服务,不够简单 -- Redis pub/sub(Edict 方案):引入新依赖,v2.6 已去掉 Redis;我们不需要分布式 -- SQLite update-hook:需要 C API 绑定 -- fswatch/watchdog:跨平台兼容性差 +**为什么不选的替代方案**: +- EventBus + Signal File(初始设计):Signal File 需要额外的扫描/读/删循环,增加了耦合链 +- HTTP 端点(第二版):引入网络依赖,Daemon 需要跑 HTTP 服务,不够简单 +- Redis pub/sub(Edict 方案):引入新依赖,v2.6 已去掉 Redis;我们不需要分布式 +- SQLite update-hook:需要 C API 绑定 +- fswatch/watchdog:跨平台兼容性差 -**Inbox JSONL 具体实现**(参考 agent-chorus `chorus send` 模式): +**Inbox JSONL 具体实现**(参考 agent-chorus `chorus send` 模式): ```python -# blackboard.py 写完 SQLite 后,可选追加一行 JSON +# blackboard.py 写完 SQLite 后,可选追加一行 JSON INBOX_PATH = Path("~/.sanguo_projects/sanguo_moziplus_v2/inbox/daemon.jsonl") -# 写入格式(参考 agent-chorus message schema: from/to/timestamp/content/cwd) +# 写入格式(参考 agent-chorus message schema: from/to/timestamp/content/cwd) async def notify_daemon(event_type: str, task_id: str, agent: str): line = json.dumps({ "from": agent, @@ -634,50 +634,50 @@ async def check_inbox(): if not INBOX_PATH.exists(): return lines = INBOX_PATH.read_text().strip().split('\n') - INBOX_PATH.unlink() # 原子读取+删除(参考 agent-chorus --clear 模式) + INBOX_PATH.unlink() # 原子读取+删除(参考 agent-chorus --clear 模式) for line in lines: msg = json.loads(line) await handle_event(msg['event'], msg['task_id'], msg['from']) ``` -**Daemon 主循环**: +**Daemon 主循环**: ```python async def daemon_main_loop(): # 启动时全量扫描 await full_tick() - + while True: - # 1. 检查 inbox(每 1s) + # 1. 检查 inbox(每 1s) await check_inbox() # 有内容则立即执行 mini-tick - - # 2. 定期 Tick(每 30s) + + # 2. 定期 Tick(每 30s) if time.time() - last_tick > 30: await full_tick() last_tick = time.time() - + await asyncio.sleep(1) ``` -#### D2-2:依赖声明的并行/串行自动决策 +#### D2-2:依赖声明的并行/串行自动决策 -> **设计推导**:open-multi-agent 的 TaskQueue.complete() → unblockDependents() 是核心模式——complete→auto-unlock,纯依赖声明驱动。其 scheduler.ts 还提供了 4 种调度策略(round-robin / least-busy / capability-match / dependency-first)。 +> **设计推导**:open-multi-agent 的 TaskQueue.complete() → unblockDependents() 是核心模式--complete→auto-unlock,纯依赖声明驱动。其 scheduler.ts 还提供了 4 种调度策略(round-robin / least-busy / capability-match / dependency-first)。 -**串行触发链**(Tick + Inbox 加速版): +**串行触发链**(Tick + Inbox 加速版): ``` Agent A 完成 task-001 → 写黑板 outputs + 更新 status → done + 写 handoff comment - → 通知 Daemon(inbox JSONL) - → Daemon 下次循环(~1s 内)收到通知 → mini-tick: + → 通知 Daemon(inbox JSONL) + → Daemon 下次循环(~1s 内)收到通知 → mini-tick: 查询所有 depends_on 包含 task-001 的 pending 任务 - → task-002 depends_on: [task-001],检查 task-001 done ✅ + → task-002 depends_on: [task-001],检查 task-001 done ✅ → spawn Agent B 执行 task-002 - (如果 inbox 通知丢失 → 30s Tick 兜底补上) + (如果 inbox 通知丢失 → 30s Tick 兜底补上) ``` -**并行**:`depends_on` 为空且 assignee 不同的任务,自然并行(Daemon 分别 spawn)。不需要额外逻辑。 +**并行**:`depends_on` 为空且 assignee 不同的任务,自然并行(Daemon 分别 spawn)。不需要额外逻辑。 -**不做 files_modified 冲突检测**(D2-4):Agent 通过黑板评论自然协调("我在改 main.py,你别碰"),不需要系统强制。Scope Guard(课题 1)作为兜底。实际覆盖:depends_on 覆盖 80%+ 的显式依赖场景,边角场景通过黑板评论 + 庞统仲裁补充。 +**不做 files_modified 冲突检测**(D2-4):Agent 通过黑板评论自然协调("我在改 main.py,你别碰"),不需要系统强制。Scope Guard(课题 1)作为兜底。实际覆盖:depends_on 覆盖 80%+ 的显式依赖场景,边角场景通过黑板评论 + 庞统仲裁补充。 #### 与课题 1 的兼容性 @@ -686,7 +686,7 @@ Agent A 完成 task-001 | 续杯机制 | task_completed 通知加速依赖解锁 | @mention 从 ≤60s 降到 ≤1s | | retry 由 AI 决策 | task_failed 通知加速 retry 链 | 庞统更快介入 | | Guardrail 吹哨人 | observation 写入后通知 Daemon | Daemon 即时感知问题 | -| 三层执行模型 | 不变,Tick/inbox 处理仍按 L1/L2/L3 分层 | ✅ 一致 | +| 三层执行模型 | 不变,Tick/inbox 处理仍按 L1/L2/L3 分层 | ✅ 一致 | ### 4.3 Session 生命周期 @@ -714,11 +714,21 @@ Agent A 完成 task-001 - Gateway WS `sessions.delete` 需要 `operator.admin` scope(token 模式不授予,不可用)❌ - 回退方案:直接编辑 `sessions.json` 是安全可靠的 ✅ -### 4.4 Agent Spawn 的上下文分层传递(课题 2 设计决策) +### 4.4 Agent Spawn 的上下文分层传递(课题 2 设计决策) -> **设计推导**:GSD Wave Execution 证明隔离 session + 新鲜 context > 单一 session + 压缩。Claude Code 的 file reference 模式证明"引用而非内联"是最优策略。问题不是 context 不够大,而是信号噪声比。 +> **设计推导**:GSD Wave Execution 证明隔离 session + 新鲜 context > 单一 session + 压缩。Claude Code 的 file reference 模式证明"引用而非内联"是最优策略。Opal-Bridge 的 Fidelity 三档提供了理论框架。agent-chorus 的 Context Pack 实验证明结构化上下文让 Agent 效率提升 60-70%。问题不是 context 不够大,而是信号噪声比。 -**D2-5:三层上下文传递(L1 必传 / L2 按需 / L3 按需)** +**L1/L2/L3 对应 Opal-Bridge Fidelity 三档**: + +| Opal-Bridge Fidelity | 我们的映射 | 场景 | +|---------------------|----------|------| +| Mode A:无损(完整上下文) | L1 + L2 + L3 | 复杂任务,Agent 需要完整了解讨论历史和产出 | +| Mode B:LLM 摘要 | L1 + L2 | 标准任务,核心信息 + 扩展摘要 | +| Mode C:混合保留最近N条 | L1 | 简单任务,只传核心,Agent 按需取更多 | + +Agent 自己决定用哪个 Fidelity 档位——收到 L1 后判断信息是否足够,不够就 L2/L3。 + +**D2-5:三层上下文传递(L1 必传 / L2 按需 / L3 按需)** | 层级 | 内容 | Token 估算 | 谁决定 | |------|------|-----------|--------|