diff --git a/docs/design/07-spawner-acquire-first.md b/docs/design/07-spawner-acquire-first.md index 902c4c9..17ecd42 100644 --- a/docs/design/07-spawner-acquire-first.md +++ b/docs/design/07-spawner-acquire-first.md @@ -263,7 +263,111 @@ if result["status"] in ("running",) and sf: **总计 ~70 行改动,单文件核心改动 ~50 行。** -## 六、不在这个方案范围内 +## 六、Executor/Review 统一(#07.2) + +### 6.1 问题 + +Executor 和 Review 本质上都是「agent 执行子任务」,spawner 层已完全统一。但 ticker 和 dispatcher 层存在大量 review-only 特殊判断,增加维护负担且逻辑不完整(executor 反而缺少某些保护)。 + +**冗余的特殊路径:** + +| 特殊路径 | 位置 | 问题 | +|----------|------|------| +| Fix-3b review_timeout | `_check_timeouts` 末尾 | 独立的 15min 超时回收,与上方 process_dead 检测功能重叠 | +| `_check_crash_limit` 只在 review | `_dispatch_reviews` | executor 路径没有 crash 上限保护 | +| review 的 crash rollback | dispatcher `on_complete` | executor crash 时没有回退 current_agent | + +### 6.2 统一原则 + +**所有非终态状态(claimed/working/review)的超时、crash、进程检测逻辑统一走 `_check_timeouts`,不因状态类型分叉。** + +Executor 和 Review 的唯二必须差异: +1. **completion_status**:executor 完成 → 标 review;review 完成 → 标 done +2. **dispatch 入口**:executor 从 pending dispatch;review 从 review 状态 dispatch(检查产出后再 dispatch) + +其余全是同一套逻辑,不应有 if-review 分支。 + +### 6.3 改动 + +#### 6.3.1 删除 Fix-3b(~40 行) + +`_check_timeouts` 末尾的 `review_timeout_minutes` 整段删除(约 40 行)。 + +**理由**: +- v2.7.2 的 process_dead 检测已覆盖「进程死 → 推回 pending」的场景 +- review agent crash → 进程死 → process_dead → 推回 pending → `_dispatch_reviews` 重新 dispatch → crash_limit 拦截 +- Fix-3b 用超时时间(15min)做同一件事,是冗余的特殊路径 + +#### 6.3.2 crash_limit 统一到 `_check_timeouts` + +把 `_dispatch_reviews` 里的 `_check_crash_limit` 调用移到 `_check_timeouts`,覆盖 working 和 review 状态: + +```python +# _check_timeouts 中,working/review 超时检测之前 +if status in ("working", "review"): + if self.dispatcher._check_crash_limit(task.id, db_path, limit=3, window_minutes=30): + # 标 failed,写 observation + ... + continue +``` + +同时删除 `_dispatch_reviews` 里的 crash_limit 检查(~15 行)。 + +#### 6.3.3 on_complete crash 回退统一 + +dispatcher `_task_on_complete` 中: +- 删除 `_is_review` 分支中的 `ROLLBACK_CURRENT_AGENT_OUTCOMES` 处理(~20 行) +- 改为 executor/review 共用统一的 crash 回退逻辑: + +```python +def _task_on_complete(aid, outcome): + try: + if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES: + # 统一 crash 回退:executor 和 review 都回退 current_agent + _dispatcher._rollback_current_agent(_task_db, _task_id, aid) + + if _is_review: + if outcome in ("completed", "session_revived"): + _dispatcher._mark_task_status(_task_db, _task_id, "done") + # else: crash/error,保持 review,等待 ticker 处理 + else: + _dispatcher._task_auto_complete(_task_id, _task_db) + except Exception as e: + logger.error("Task %s: on_complete error: %s", _task_id, e) +``` + +#### 6.3.4 `_dispatch_reviews` 精简 + +删除 crash_limit 检查后,`_dispatch_reviews` 只保留 review 特有的业务逻辑: +- 检查已有 review 记录 → 跳过 +- 检查活跃 routing → 防重复 +- 检查产出 → 无产出标 failed +- 调度 review agent + +### 6.4 改动范围 + +| 文件 | 改动 | 行数 | +|------|------|------| +| `ticker.py` `_check_timeouts` | 增加 crash_limit 覆盖 working/review | +15 行 | +| `ticker.py` `_check_timeouts` | 删除 Fix-3b 整段 | -40 行 | +| `ticker.py` `_dispatch_reviews` | 删除 crash_limit 检查 | -15 行 | +| `dispatcher.py` `_task_on_complete` | 统一 crash 回退 | ~25 行(净减 ~10 行) | +| `spawner.py` | 不变 | 0 | + +**总计:净减 ~30 行,简化逻辑。** + +### 6.5 验证 + +| 测试 | 预期 | +|------|------| +| executor crash 3 次 | 统一走 `_check_timeouts` → failed | +| review crash 3 次 | 同上(不再走 `_dispatch_reviews` 的 crash_limit) | +| review agent 进程死 | process_dead 检测 → 推回 pending(不再走 Fix-3b) | +| executor crash 后 current_agent | 回退到 assignee(统一 rollback) | +| review 正常完成 | 标 done(不变) | +| executor 正常完成 | 标 review(不变) | + +## 七、不在这个方案范围内 | 项目 | 说明 | 后续 | |------|------|------| @@ -271,7 +375,7 @@ if result["status"] in ("running",) and sf: | Session watcher | 检测 webchat 释放后自动 dequeue | 依赖 #08 | | counter.clear_cooldown | 正常完成后主动清除 cooldown | 可选优化 | -## 七、验证计划 +## 八、验证计划 1. **V1 基本功能**:创建任务 → broadcast → claim → 执行 → 完成(不变) 2. **V2 mozi 内部竞争**:同 agent 两个 pending 任务 → 第二个 AgentBusyError(reason=counter_blocked)