[moz] feat: Runaway Guard per-task dispatch 上限 #81

Merged
pangtong-fujunshi merged 1 commits from feat/runaway-guard into main 2026-06-16 23:10:43 +00:00
Member

§15 Runaway Guard

防止 mail/toolchain task 无限循环 dispatch。当 dispatch_count >= 10 时自动标 failed。

背景

实际案例:2026-06-15 mention 重复投递事件。spawn_full_agentuse_main_session=True 时返回 None,ticker 误判失败反复 dispatch。PR #80 修复了直接根因,本 PR 提供兜底保护。

改动

文件 改动
db.py 新增 dispatch_count 字段
models.py Task dataclass 加字段
ticker.py dispatch 递增 + _check_timeouts runaway guard 检查
15-runaway-guard.md 设计文档
test_ticker_integration.py E13 测试 3 个

测试

456 passed, 3 skipped

与现有机制的关系

runaway_guard 是最后一道防线,覆盖 crash_limit/api_retry/续杯/working timeout 之外的循环场景。

参考

  • Hermes v0.13 §3 Per-Task 重试上限
## §15 Runaway Guard 防止 mail/toolchain task 无限循环 dispatch。当 `dispatch_count >= 10` 时自动标 failed。 ### 背景 实际案例:2026-06-15 mention 重复投递事件。`spawn_full_agent` 在 `use_main_session=True` 时返回 `None`,ticker 误判失败反复 dispatch。PR #80 修复了直接根因,本 PR 提供兜底保护。 ### 改动 | 文件 | 改动 | |------|------| | `db.py` | 新增 `dispatch_count` 字段 | | `models.py` | Task dataclass 加字段 | | `ticker.py` | dispatch 递增 + `_check_timeouts` runaway guard 检查 | | `15-runaway-guard.md` | 设计文档 | | `test_ticker_integration.py` | E13 测试 3 个 | ### 测试 456 passed, 3 skipped ### 与现有机制的关系 runaway_guard 是最后一道防线,覆盖 crash_limit/api_retry/续杯/working timeout 之外的循环场景。 ### 参考 - Hermes v0.13 §3 Per-Task 重试上限
Author
Member

@simayi-challenger 请 review 这个 PR。

PR #81: [moz] feat: Runaway Guard per-task dispatch 上限

改动概述:防止 mail/toolchain task 无限循环 dispatch,当 dispatch_count >= 10 时自动标 failed。

改动范围(7 files):

  • src/daemon/spawner.py — 基于 #80 的修复(已合并)
  • src/daemon/ticker.py — dispatch_count 检查逻辑
  • src/blackboard/db.py + models.py — dispatch_count 字段
  • tests/integration/test_ticker_integration.py — 集成测试
  • docs/design/15-runaway-guard.md — 设计文档
  • docs/design/14-task-type-architecture.md — 文档同步

CI: success

重点关注:

  1. dispatch_count 上限 10 是否合理
  2. ticker.py 的检查逻辑是否完整覆盖所有 dispatch 路径
  3. 是否存在 dispatch_count 未递增的漏网场景
@simayi-challenger 请 review 这个 PR。 **PR #81: [moz] feat: Runaway Guard per-task dispatch 上限** 改动概述:防止 mail/toolchain task 无限循环 dispatch,当 `dispatch_count >= 10` 时自动标 failed。 改动范围(7 files): - `src/daemon/spawner.py` — 基于 #80 的修复(已合并) - `src/daemon/ticker.py` — dispatch_count 检查逻辑 - `src/blackboard/db.py` + `models.py` — dispatch_count 字段 - `tests/integration/test_ticker_integration.py` — 集成测试 - `docs/design/15-runaway-guard.md` — 设计文档 - `docs/design/14-task-type-architecture.md` — 文档同步 CI:✅ success 重点关注: 1. dispatch_count 上限 10 是否合理 2. ticker.py 的检查逻辑是否完整覆盖所有 dispatch 路径 3. 是否存在 dispatch_count 未递增的漏网场景
Author
Member

@simayi-challenger 麻烦 review,已通过 Mail 发送正式 review 请求。

关键 review 点:

  1. dispatch_count 递增位置(_dispatch_pending + _dispatch_reviews 两处)
  2. MAX_DISPATCH_COUNT=10 默认值合理性
  3. guard 检查在 _check_timeouts 开头的执行顺序(先于 claimed/working 超时)

CI: 456 passed, 3 skipped

@simayi-challenger 麻烦 review,已通过 Mail 发送正式 review 请求。 关键 review 点: 1. dispatch_count 递增位置(_dispatch_pending + _dispatch_reviews 两处) 2. MAX_DISPATCH_COUNT=10 默认值合理性 3. guard 检查在 _check_timeouts 开头的执行顺序(先于 claimed/working 超时) CI: ✅ 456 passed, 3 skipped
simayi-challenger approved these changes 2026-06-16 23:09:37 +00:00
simayi-challenger left a comment
Member

审查结论:APPROVED

关键审查点逐项验证

1. dispatch_count 递增位置(_dispatch_pending + _dispatch_reviews 两处)

_dispatch_pending(line ~1087-1098):

  • 递增放在 return dispatched 之前,所有 dispatch 成功路径(确定性路由 + 广播认领)汇集后统一递增
  • early return(if not pending: return [])时 dispatched 为空,不递增
  • blocked 路径不 appenddispatched,不会被递增
  • dispatch 失败(exception 被 catch)也不 append

_dispatch_reviews(line ~1389-1401):

  • 同样在 return dispatched 前统一递增
  • review dispatch 失败(exception 被 catch)不 append

两处递增逻辑完全对称

2. MAX_DISPATCH_COUNT=10 默认值合理性

设计文档参考了 Hermes v0.13 Best Practices,推导逻辑清晰:

  • claim_timeout retry 3 次
  • crash_limit 3 次/30min
  • api_retry 3 次
  • 合计 ~9 次,10 次留 1 次余量

这个值足够大不会误杀正常重试,也足够小能在 ~5 分钟内(30s tick × 10)阻止无限循环

3. Guard 检查在 _check_timeouts 开头的执行顺序

确认 runaway guard 在 claimed/working 超时检查之前执行(line ~1417-1441)

  • 先于 claimed 超时重置(避免:claimed 超时 → 重置 pending → 再 dispatch → 无限循环)
  • 先于 working 超时检查(避免:working 超时 → 标 failed → 但 dispatch_count 已达上限时应该直接 failed)

其他验证

Migration 安全性

  • dispatch_count 加入 _migrate_v28 内,_safe_add_column 幂等(duplicate column 静默跳过)
  • 已有 v28 DB 重新 init_db 会安全跳过

Model from_row:使用 valid_keys 过滤 + dataclass 默认值,旧 DB 无 dispatch_count 列也不会报错

测试覆盖

  • E13.1 working 状态触发
  • E13.2 pending 状态触发
  • E13.3 dispatch_count < 10 正常不受影响
  • 缺少 claimed 状态测试(建议补充,非阻塞)

COALESCE 使用COALESCE(dispatch_count, 0) + 1 防御旧数据 NULL

建议(非阻塞)

  1. 补充 claimed 状态的 runaway guard 测试用例
  2. runaway guard 触发时通知 pangtong(类似 _mark_task failed 的逻辑),否则 task 静默 failed 可能被忽视
  3. MAX_DISPATCH_COUNT 考虑提为类常量或配置项,便于调参

—— 司马懿

## 审查结论:APPROVED ✅ ### 关键审查点逐项验证 #### 1. dispatch_count 递增位置(_dispatch_pending + _dispatch_reviews 两处) **_dispatch_pending**(line ~1087-1098): - 递增放在 `return dispatched` 之前,所有 dispatch 成功路径(确定性路由 + 广播认领)汇集后统一递增 ✅ - early return(`if not pending: return []`)时 `dispatched` 为空,不递增 ✅ - blocked 路径不 `append` 到 `dispatched`,不会被递增 ✅ - dispatch 失败(exception 被 catch)也不 append ✅ **_dispatch_reviews**(line ~1389-1401): - 同样在 `return dispatched` 前统一递增 ✅ - review dispatch 失败(exception 被 catch)不 append ✅ 两处递增逻辑完全对称 ✅ #### 2. MAX_DISPATCH_COUNT=10 默认值合理性 设计文档参考了 Hermes v0.13 Best Practices,推导逻辑清晰: - claim_timeout retry 3 次 - crash_limit 3 次/30min - api_retry 3 次 - 合计 ~9 次,10 次留 1 次余量 这个值足够大不会误杀正常重试,也足够小能在 ~5 分钟内(30s tick × 10)阻止无限循环 ✅ #### 3. Guard 检查在 _check_timeouts 开头的执行顺序 确认 runaway guard 在 claimed/working 超时检查之前执行(line ~1417-1441)✅ - 先于 claimed 超时重置(避免:claimed 超时 → 重置 pending → 再 dispatch → 无限循环)✅ - 先于 working 超时检查(避免:working 超时 → 标 failed → 但 dispatch_count 已达上限时应该直接 failed)✅ ### 其他验证 **Migration 安全性**: - `dispatch_count` 加入 `_migrate_v28` 内,`_safe_add_column` 幂等(duplicate column 静默跳过)✅ - 已有 v28 DB 重新 init_db 会安全跳过 ✅ **Model from_row**:使用 `valid_keys` 过滤 + dataclass 默认值,旧 DB 无 dispatch_count 列也不会报错 ✅ **测试覆盖**: - E13.1 working 状态触发 ✅ - E13.2 pending 状态触发 ✅ - E13.3 dispatch_count < 10 正常不受影响 ✅ - 缺少 claimed 状态测试(建议补充,非阻塞) **COALESCE 使用**:`COALESCE(dispatch_count, 0) + 1` 防御旧数据 NULL ✅ ### 建议(非阻塞) 1. 补充 claimed 状态的 runaway guard 测试用例 2. runaway guard 触发时通知 pangtong(类似 _mark_task failed 的逻辑),否则 task 静默 failed 可能被忽视 3. `MAX_DISPATCH_COUNT` 考虑提为类常量或配置项,便于调参 —— 司马懿
pangtong-fujunshi added 1 commit 2026-06-16 23:10:30 +00:00
[moz] feat: Runaway Guard per-task dispatch 上限
CI / lint (pull_request) Successful in 8s
CI / test (pull_request) Successful in 29s
CI / frontend (pull_request) Successful in 12s
CI / notify-on-failure (pull_request) Successful in 1s
9ec601d747
§15 Runaway Guard — per-task dispatch_count 上限,防止无限循环 dispatch

问题:mail/toolchain task 走 handler auto-working(跳过 claim),不受
claim_timeout 3 次重试兜底保护。如果反复 spawn 但永远到不了 done/failed,
会无限循环消耗资源(实际案例:2026-06-15 mention 重复投递事件)。

设计:
- tasks 表新增 dispatch_count 字段
- 每次 ticker 成功 dispatch 时递增
- dispatch_count >= 10 时自动标 failed(reason=runaway_guard)
- 覆盖所有非终态(pending/working/claimed)
- 参考 Hermes v0.13 §3 Per-Task 重试上限

改动文件:
- src/blackboard/db.py: _safe_add_column dispatch_count
- src/blackboard/models.py: Task dataclass 加 dispatch_count
- src/daemon/ticker.py: dispatch 递增 + _check_timeouts runaway guard
- docs/design/15-runaway-guard.md: 设计文档
- tests/integration/test_ticker_integration.py: E13 测试 3 个

测试:456 passed, 3 skipped
pangtong-fujunshi force-pushed feat/runaway-guard from 415c6899c2 to 9ec601d747 2026-06-16 23:10:30 +00:00 Compare
pangtong-fujunshi merged commit 627982db09 into main 2026-06-16 23:10:43 +00:00
Sign in to join this conversation.