diff --git a/docs/design/architecture-v3.0.md b/docs/design/architecture-v3.0.md index 0a40939..4a27e4c 100644 --- a/docs/design/architecture-v3.0.md +++ b/docs/design/architecture-v3.0.md @@ -28,6 +28,7 @@ | v3.0 | 2026-05-28 | **本版**:PRD 3.0 对齐 + 源码回溯 + 完整现状记录 | | v3.0.1 | 2026-05-28 | 补充6个讨论话题(§6.5/§10.4~10.6)+ 新增§21 T阶段规划(T1~T6) | | v3.0.2 | 2026-05-29 | §12.3 Mail API 防御 + Prompt 约束设计(纳入司马懿评审意见);§6.5 补充 spawn 改造方向 | +| v3.0.3 | 2026-05-30 | §6.3.1 Session Lock 实测结论 + L3a/L3b/L3c 有效性分析 + Bug 修复(outcome 白名单 + 详细日志) | ### 1.2 v3.0 定位 @@ -509,6 +510,80 @@ Ticker.tick() 6. wrapped_on_complete 闭包 → counter.release (try/finally 保证) ``` +### 6.3.1 Session Lock 实测结论 (v3.0.3) + +> 2026-05-30 基于实际 Gateway 日志 + daemon 日志的联合分析 + +#### 三层防线 (L3a/L3b/L3c) + +spawn 前检查 main session 状态(`_check_session_state`),三层防线: + +```python +# spawner.py spawn_full_agent() 内 +if use_main_session: + session_state = self._check_session_state(agent_id) + # L3a: lock 文件检查(唯一有效防线) + if session_state.get("lock_pid_alive"): + raise AgentBusyError(...) + # L3b: sessions.json status 检查(实测无效) + if session_state.get("status") == "running": + raise AgentBusyError(...) + # L3c: compact 检查 + if session_state.get("recent_compact"): + raise AgentBusyError(...) +``` + +#### 实测数据 (2026-05-30 双邮件测试) + +| 时间 | 事件 | status | lock_pid | alive | 拦截者 | +|------|------|--------|----------|-------|--------| +| 00:25:35 | spawn simayi (测试1) | done | None | False | ✅ 通过 | +| 00:26:05 | ticker 重试 simayi (测试2) | **done** | **75279** | **True** | **L3a** | +| 00:26:05 | spawn pangtong (处理回复) | done | None | False | ✅ 通过 | +| 00:26:36 | ticker 再试 simayi | done | 75279 | True | L3a | +| 00:30:38 | ticker 尝试 pangtong | **failed** | **75279** | **True** | **L3a** | +| 00:31:09 | spawn pangtong (回复) | done | None | False | ✅ 通过 | + +#### 关键发现 + +1. **`sessions.json` 的 `status` 全程未出现 `running`** + - Gateway 处理 Agent turn 时,`sessions.json` 的 status 在 daemon 30s 采样间隔内不会变为 `running` + - 唯一可靠信号是 lock 文件(L3a) + - L3b 的 `status == "running"` 检查在实际场景中**从未触发** + +2. **L3a(lock check)是唯一有效防线** + - 所有拦截都是 `lock_pid_alive=True`(L3a) + - L3b 修复(`"processing"` → `"running"`)是正确的但实测无效 + +3. **Gateway 内部存在二次 lock 竞争** + - Gateway 处理 CLI turn 时(持有 session write lock 5+ 分钟),内部会产生另一个请求也想写同一 session + - 表现为 `SessionWriteLockTimeoutError: timeout 60000ms` + - 这是 Gateway 内部并发问题,daemon 无法解决 + - 02 架构改造(统一 main session)是根本解法 + +4. **status 字段可能出现 `failed`** + - 00:30:38 检查 pangtong 时 `status=failed`(上一次 turn lock 超时后 Gateway 写入) + - L3b 不检查 `failed`,此时只有 L3a 在守 + +#### 已修复的 Bug + +| Bug | 问题 | 修复 | 影响 | +|-----|------|------|------| +| L3b 枚举值 | `"processing"` 应为 `"running"` | ✅ 已修复 | L3b 形同虚设,现恢复理论作用 | +| outcome 白名单 | `process_crash`/`unknown_status` 不在 CHECK 约束中 | ✅ 已映射到 `crashed`/`agent_error` | daemon 崩溃根因 | +| 无详细日志 | session state 检查通过时不打日志 | ✅ 已加 INFO 日志 | 下次出问题可追溯 | + +#### TOCTOU 竞态(已知限制) + +L3a 检查和 CLI subprocess 获取 write lock 之间存在时间差: +1. daemon 检查 `lock_pid_alive=False` → 通过 +2. spawn CLI subprocess +3. CLI 连接 Gateway 请求 session write lock +4. 此时用户通过 webchat 发消息 → Gateway 持有 lock +5. CLI 等 60s 超时 → `SessionWriteLockTimeoutError` + +这不是 daemon 能解决的问题,需要 02 架构改造(不再用 CLI spawn,统一投递到 main session)。 + ### 6.4 关键差异: 设计 vs 实现 | 设计文档 | 实际代码 | 状态 | @@ -1592,6 +1667,8 @@ PRAGMA busy_timeout=5000 # 写锁等待 5s | C4 | §17.1 #5 | **工具链集成** | PRD 差距 | P3 | | C5 | §17.1 #6 | **经验闭环 IMPROVE** | PRD 差距 | P3 | | C6 | §17.2 #16 | **per-provider 冷却** | 设计未落地 | P4 | +| C7 | §6.3.1 | **Gateway 内部 session lock 竞争**(已知限制,02 架构改造解决) | 已知限制 | P2 | +| C8 | §6.3.1 | **TOCTOU 竞态**(L3a 检查和 CLI 获取 lock 之间的窗口期) | 已知限制 | P2 | | D1 | HEARTBEAT | **v2.8 executor-prompt-design 漏洞**(3 个设计漏洞未修) | 设计暂停 | P3 | | D2 | HEARTBEAT | **Pipeline 架构调研**(暂停,已废弃——不搞 Pipeline 框架) | 设计暂停 | ~~P3~~ 📦 已归档 | | D3 | MEMORY | **M2 进程管理**(同步 vs 异步) | 待讨论 | P2 | @@ -1645,12 +1722,16 @@ PRAGMA busy_timeout=5000 # 写锁等待 5s **涉及文件**: `src/daemon/ticker.py`、`src/daemon/dispatcher.py`、`src/daemon/router.py` -**前置条件**: T1 完成 ✅ +**前置条件**: T1 完成 ✅ + #02 Main Session Delegation 架构方案确认 **bug 修复**(T2 前置): - B8: BUG-7 planning 暂停失败 — 需在功能开发前修复 - B9: BUG-8 cancel→resume 空图完成 — 需在功能开发前修复 +**已知限制**(T2 需解决): +- C7: Gateway 内部 session lock 竞争 — 02 架构改造(统一 main session)根治 +- C8: TOCTOU 竞态 — 同上,不再用 CLI spawn + **来源**: v2.8-direction-notes §一~§二 + §17 #2/#3 #### T3: 主动汇报 + 需求探索触发 📋 待开始 @@ -1788,6 +1869,8 @@ PRAGMA busy_timeout=5000 # 写锁等待 5s | C4 | 工具链集成 | T8 | ❌ | | C5 | 经验闭环 IMPROVE | T7 | ❌ | | C6 | per-provider 冷却 | T8 | ❌ | +| C7 | Gateway 内部 session lock 竞争 | T2 | ⚠️ 已知限制 | +| C8 | TOCTOU 竞态 | T2 | ⚠️ 已知限制 | | D1 | executor-prompt-design 漏洞 | T5 | ❌ | | D2 | Pipeline 架构调研 | — | 📦 已废弃 | | D3 | M2 进程管理 | T6 | ❌ | @@ -1812,3 +1895,5 @@ PRAGMA busy_timeout=5000 # 写锁等待 5s | 14 | prompt_templates 角色模板 | B6 | T5 | | 15 | CLI Schema 校验 | B7 | T5 | | 16 | per-provider 冷却 | C6 | T8 | +| — | Gateway 内部 session lock 竞争 | C7 | T2 (02 架构改造) | +| — | TOCTOU 竞态 | C8 | T2 (02 架构改造) |