# Step 5 引擎接入 — 影响分析与逐点对照

## 方法论

逐行审查 dispatcher.py / spawner.py / ticker.py 中所有 `is_mail` / `_mail` / `project_id == "_mail"` 分支，
对照 handler 实现，确认每个特殊处理的去向。

---

## 一、dispatcher.py（985 行）

### 1.1 Guardrail 跳过（L127-129）

```python
is_mail = project_config.get("project_id") == "_mail" if project_config else False
if self.guardrails and not is_mail:
    violations = self.guardrails.check_task(task)
```

**特殊处理**：Mail 不做 guardrail 检查。

**Handler 覆盖**：设计文档 D6 "skip_guardrail 从接口删除，guardrail 自己判断"。Step 5 改为：`if self.guardrails and handler is None`（无 handler 时走 guardrail），或者用 handler.virtual_project 判断。handler 存在时跳过 guardrail。

**改动**：`is_mail` → `TaskTypeRegistry.get_by_project(project_id) is not None`

---

### 1.2 Mail on_checks_passed（L194-213）

```python
on_checks_passed = None
_mail_marked_working = False
if is_mail and db_path:
    def _mail_on_checks_passed():
        nonlocal _mail_marked_working
        if not _disp._mail_auto_working(_task_id, _mail_db):
            raise RuntimeError("mail_auto_working_failed")
        _mail_marked_working = True
    on_checks_passed = _mail_on_checks_passed
```

**特殊处理**：Mail spawn 前通过 on_checks_passed 回调标 working，标记成功后才 spawn，spawn 失败回退。

**Handler 覆盖**：MailHandler.pre_spawn 调用 `_auto_mark_working`，和 `_mail_auto_working` 逻辑完全一致。

**改动**：
- `on_checks_passed` 改为调用 `handler.pre_spawn(task_id, db_path)`
- `_mail_marked_working` 标记保留，用于 Exception 回退

---

### 1.3 Mail on_complete（L224-238）

```python
if is_mail:
    def _mail_on_complete(aid, outcome):
        _dispatcher._mail_auto_complete(_task_id, aid, _mail_db, _must_haves, outcome=outcome)
    on_complete = _mail_on_complete
```

**特殊处理**：Mail on_complete 调用 `_mail_auto_complete`（含 inform/request 分支、幻觉门控、重试 3 次、失败通知）。

**Handler 覆盖**：MailHandler 使用基类 post_complete 统一流程（crash→verify→mark→notify）。但现有 `_mail_auto_complete` 有几个细节差异需要注意：

| 现有逻辑 | Handler 覆盖 | 差异 |
|---------|-------------|------|
| request 无回复 → 重试 3 次标 failed | on_failure 标 failed + notify | ⚠️ 缺少 3 次重试 |
| inform 只在特定 outcome 标 done | verify 始终返回 True → 基类标 done | ✅ 简化了，合理 |
| 标 done 重试 3 次 | _mark_task_status 单次 | ⚠️ 缺少重试 |
| notify_mail_failed | on_failure 中调用 notify_mail_failed | ✅ 一致 |

**⚠️ 关键发现**：现有代码标状态时有 **重试 3 次** 机制（防止 DB 锁），handler 的 `_mark_task_status` 只做一次。需要把重试逻辑补到 `_mark_task_status` 或在 handler 层加。

**改动**：on_complete 改为调用 `handler.post_complete(task_id, agent_id, outcome, db_path)`

---

### 1.4 Task on_complete（L241-310）

```python
else:
    def _task_on_complete(aid, outcome):
        # #07.2: crash 回退
        if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
            _dispatcher._rollback_current_agent(_task_db, _task_id, aid)
        
        if _is_review:
            if outcome in ("completed", "session_revived"):
                # 读 verdict → approved 标 done / 非 approved @mention assignee
            else:
                logger.warning("review agent outcome=%s, NOT marking done", outcome)
        else:
            # executor: 三信号验证 → 标 review
            _dispatcher._task_auto_complete(_task_id, _task_db)
```

**特殊处理清单**：

1. **#07.2 crash 回退**：executor 和 review 都回退 current_agent → assignee
2. **review 分支**：outcome 必须是 "completed" 或 "session_revived" 才走 verdict 读取
3. **review verdict 读取**：approved → done，非 approved → @mention assignee + 保持 review
4. **review @mention**：通过 Blackboard.add_comment，comment_type="review"
5. **executor 分支**：走 _task_auto_complete → 三信号验证 → review

**Handler 覆盖**：
- crash 回退：✅ BaseTaskHandler.post_complete 第一步
- review verdict：⚠️ **TaskHandler.handle_review_complete 存在但未被 dispatcher 调用**。现有 dispatcher 直接在闭包里做了，不走 handler。
- @mention：⚠️ handler 用 `conn.execute("INSERT INTO comments")` 直接插入，dispatcher 用 `Blackboard.add_comment`（会做更多处理，如 comment_type="review"）
- executor 三信号：✅ TaskHandler.verify_completion

**⚠️ 关键发现**：
1. dispatcher 的 review @mention 用 `bb.add_comment(..., comment_type="review")`，handler 直接 INSERT 不带 comment_type。需要修复 handler。
2. dispatcher 对 review outcome 有白名单检查（只处理 "completed"/"session_revived"），handler 的 post_complete 没有 outcome 白名单——crash 已在基类处理，其他 outcome 都会走 verify。
3. dispatcher review 非 approved 时**保持 review 状态**，handler 的 handle_review_complete 标回 working。这是**行为差异**。

**改动**：需要先修复 handler 的 review 分支，再替换 on_complete。

---

### 1.5 Mail spawn 失败回退（L355-358）

```python
except Exception as e:
    if _mail_marked_working:
        self._mail_revert_to_pending(task.id, db_path)
```

**特殊处理**：spawn 失败（subprocess 启动失败）回退 working → pending。

**Handler 覆盖**：❌ handler 没有这个。这是 dispatcher 级别的异常处理，和 handler 无关。但 toolchain 也需要类似逻辑。

**改动**：保留在 dispatcher 中，改为 `_mail_marked_working` → `handler_marked_working`。

---

### 1.6 Legacy dispatch（L584-660）

```python
is_mail_legacy = project_config.get("project_id") == "_mail"
if is_mail_legacy:
    if not self._mail_auto_working(task.id, db_path_legacy):
        return error
```

**特殊处理**：legacy 路径（router=None 时触发）也有 mail 特殊处理。

**Handler 覆盖**：同 1.2/1.3，用 handler 替代。

**改动**：同样用 handler.pre_spawn 和 handler.post_complete 替代。

---

### 1.7 现有 Mail 辅助方法（L658-870）

`_mail_auto_working` / `_mail_revert_to_pending` / `_mail_auto_complete` / `_mail_check_reply`

**改动**：Step 5 不删这些方法（安全起见保留，标记 deprecated），只改调用方。确认稳定后再删。

---

## 二、spawner.py（1704 行）

### 2.1 _build_prompt 中的 mail 分支（L282-284）

```python
if project_id == "_mail":
    return self._build_mail_prompt(task_id, title, description, must_haves, agent_id)
```

**特殊处理**：Mail 用专用精简模板。

**Handler 覆盖**：MailHandler.build_prompt 通过 PromptComposer 拼 3 个 section。

**改动**：查注册表 → handler.build_prompt(context)。需要构建 PromptContext 传入。

---

### 2.2 _build_api_section（L321-325）

```python
success_status = '"done"' if project_id == "_mail" else '"review"'
```

**特殊处理**：Mail 的 success_status 是 done。

**Handler 覆盖**：已由 handler 的 PromptSection 处理（TaskApiSection hardcode review，MailApiSection 不含 status 回写指令）。

**改动**：如果 handler 存在，跳过 _build_api_section（handler.build_prompt 已包含）。

---

### 2.3 classify_outcome 中的 handler 调用

spawner 在 classify_outcome 后调 on_complete(outcome)。on_complete 是 dispatcher 传入的闭包。

**改动**：on_complete 闭包改为调用 handler.post_complete。spawner 本身不直接查注册表。

---

## 三、ticker.py（1897 行）

### 3.1 虚拟项目扫描（L218-229）

```python
mail_db = Path(self.registry.root) / "_mail" / "blackboard.db"
if mail_db.exists() and "_mail" not in active_projects:
    pr = await self._tick_project("_mail", {...})
```

**特殊处理**：_mail 硬编码扫描。

**Handler 覆盖**：TaskTypeRegistry.virtual_projects() 返回 ["_toolchain", "_mail"]。

**改动**：循环 `TaskTypeRegistry.virtual_projects()` 替代硬编码。_toolchain 如果也需要 ticker 扫描就自动发现。但需确认 _toolchain 是否需要 ticker——当前 toolchain 任务创建和完成都在 toolchain_routes.py 中处理，可能不需要 ticker 扫描。

---

### 3.2 _transition_status 中 mail assignee 不清空（L953-960）

```python
if new_status == "pending":
    if self._current_project_id == "_mail":
        # Mail 的 assignee 是收件人，永不清空
        conn.execute("UPDATE tasks SET status=?, updated_at=? WHERE id=?", ...)
    else:
        conn.execute("UPDATE tasks SET status=?, assignee=NULL, ...", ...)
```

**特殊处理**：Mail 重置到 pending 时不清空 assignee（assignee 是收件人）。

**Handler 覆盖**：❌ handler 不管 ticker 的状态转换逻辑。这是 ticker 内部逻辑。

**改动**：用 `TaskTypeRegistry.get_by_project(project_id)` 判断替代硬编码。

---

### 3.3 Mail 跳过 claimed 状态（L1029-1043）

```python
if project_id == "_mail":
    conn.execute("UPDATE tasks SET current_agent=? WHERE id=?", ...)
    # 跳过 claimed，直接 working
```

**特殊处理**：Mail 不走 claimed 中间态（已在 dispatcher 中标 working）。

**Handler 覆盖**：handler.pre_spawn 的 _auto_mark_working 跳过了 claimed。

**改动**：用 handler 判断替代硬编码。

---

### 3.4 _dispatch_reviews 跳过 mail（L1304）

```python
if project_id == "_mail":
    return []
```

**特殊处理**：Mail 不走 review 流程。

**Handler 覆盖**：MailHandler.target_success_status = "done"，不走 review。但 ticker 的 _dispatch_reviews 是看项目级。

**改动**：用 handler 判断。

---

### 3.5 Mail 幻觉门控兜底（L1474-1492）

```python
if self._current_project_id == "_mail":
    has_reply = self._mail_check_reply(task.id, db_path)
    if has_reply:
        # working → done
```

**特殊处理**：Ticker 超时检查时，如果 mail 有回复，标 done 而非 failed。

**Handler 覆盖**：❌ handler 的 check_completion 只返回 bool，不做状态标记。

**改动**：调用 handler.check_completion 替代 _mail_check_reply。状态标记逻辑保留在 ticker 中。

---

### 3.6 _mail_check_reply（L1555-1575）

和 dispatcher 版本一致。

**改动**：用 handler.check_completion 替代。

---

### 3.7 虚拟项目 init + recovery 扫描（L1625-1643）

```python
for virtual_id in ("_general", "_mail"):
    ...
    # _mail 项目不清空 assignee
```

**改动**：virtual_projects() + _general 硬编码。

---

## 四、Handler 缺陷（需在 Step 5 前修复）

| # | 缺陷 | 影响 | 修复方案 |
|---|------|------|---------|
| H1 | BaseTaskHandler._mark_task_status 无重试 | DB 锁时标状态失败，任务卡住 | 加 3 次重试（和 dispatcher 现有行为一致） |
| H2 | TaskHandler.handle_review_complete 中 @mention 不带 comment_type="review" | review comment 无类型标记 | INSERT 加 comment_type |
| H3 | dispatcher review 非 approved 保持 review 状态，handler 标 working | **行为差异** | handler 改为保持 review 状态（和 dispatcher 一致） |
| H4 | dispatcher review outcome 有白名单（"completed"/"session_revived"），handler 无 | crash 之外的异常 outcome 也会走 verify | handler 的 post_complete 已在基类处理 crash，其余 outcome 走 verify 是合理的 |

**H3 最关键**——dispatcher review 非 approved 保持 review 状态（等 assignee 自己处理），handler 标 working 会触发 ticker 重新 dispatch executor，这不是预期行为。

## 五、改动策略

**不删旧代码，只改调用方**：
1. dispatcher 中 is_mail → handler 判断，on_checks_passed/on_complete → handler.pre_spawn/post_complete
2. spawner 中 _build_prompt → handler.build_prompt
3. ticker 中虚拟项目扫描 → registry.virtual_projects()，mail 特殊判断 → handler 判断
4. 旧方法（_mail_auto_working 等）标记 @deprecated 保留，不删

**先修 handler 缺陷（H1-H3），再改引擎**。
