# §15 Runaway Guard — Per-Task Dispatch 上限 > 设计文档 v1.0 | 2026-06-16 ## 问题 mail/toolchain task 走 handler auto-working(跳过 claim 阶段),不受 claim_timeout 的 3 次重试兜底保护。如果一个 auto-working task 反复 spawn 但永远到不了 done/failed,会无限循环消耗资源。 ### 实际案例 2026-06-15 mention 重复投递事件:`spawn_full_agent` 在 `use_main_session=True` 时返回 `None`,ticker `_process_mentions` 误判为失败,每次 tick(30s)都重试。同一 mention 投递了 4 次,直到 retry_count 达到 mention_queue 的 5 次上限才停止。 直接根因已由 PR #80 修复,但如果类似 bug 再次出现,当前没有任何机制阻止 task 层面的无限循环。 ## 设计 ### 机制 tasks 表新增 `dispatch_count` 字段,每次 ticker 成功 dispatch 一个 task 时递增。当 `dispatch_count >= 10`(全局默认)时,自动标 failed。 ### 默认值选择 全局默认 10 次。参考 Hermes v0.13 Best Practices §3 "Per-Task 重试上限": - 简单任务重试 1 次 - 复杂任务重试 3 次 - crash recovery(3 次)+ api_retry(3 次)余量 = ~10 次 ### 适用范围 所有 task 类型(task/mail/toolchain),所有非终态(pending/working/claimed)。 ### 检查时机 在 `_check_timeouts` 方法开头,先于现有的 claimed/working 超时检查执行。 ### 与现有机制的关系 | 机制 | 覆盖场景 | 触发动作 | |------|---------|---------| | claim_timeout retry_count >= 3 | 广播任务无人认领 | 升级庞统 | | crash_limit 3/30min | working 状态 crash | 标 failed | | api_retry_count | API 连续失败 | 标 failed | | 续杯 max_retries 3 | 续杯耗尽 | 标 failed | | working timeout | working 超时 | 标 failed 或 done | | **runaway_guard 10 次** | **任何状态的无限循环** | **标 failed** | runaway_guard 是最后一道防线,覆盖所有其他机制遗漏的循环场景。 ## 改动文件 | 文件 | 改动 | |------|------| | `src/blackboard/db.py` | `_safe_add_column(conn, "tasks", "dispatch_count", "INTEGER DEFAULT 0")` | | `src/blackboard/models.py` | Task dataclass 加 `dispatch_count: int = 0` | | `src/daemon/ticker.py` | `_dispatch_pending` / `_dispatch_reviews` 递增 dispatch_count;`_check_timeouts` 加 runaway guard 检查 | ## 参考 - Hermes v0.13 Kanban Best Practices §3 "Per-Task 重试上限" - 实际案例:2026-06-15 mention 重复投递事件(PR #80 修复了直接根因,runaway guard 作为兜底)