# ADR-0011: 智能评估容错收敛——终态纪律与失败可见性 **状态**: 已接受 **日期**: 2026-08-19 **决策者**: 架构团队 **相关**: ADR-0009(触发式执行)、ADR-0010(独立 session)、ADR-0007(已被取代)、CONTEXT.md(智能评估会话、触发式执行) ## Context v1.1.1 将触发式执行在 t480 跑通后,对智能评估模块做容错盘点,发现一批"无声卡死"类缺口:评估级无 watchdog(worker 不关闭会话 → 报告永远 409 → 评估永久卡 executing)、任务与 planner 触发无限重试、子进程触发失败只写日志不落库、600s 超时杀不掉容器内 agent 形成孤儿、会话数无平台侧上限、串行 await 触发使 60s 扫描周期漂移、已删除前端页面的 Cron 池 API 面仍挂载。 根本问题不是单点 bug,而是缺少一条容错纪律:故障发生后系统该自愈到什么程度、什么时候必须放弃并让人知道。 ## Decision 确立**终态纪律**:常见故障自愈(有上限),超限或结构性失败收敛到终态(completed/failed/cancelled)且用户可见,绝不无声卡死。具体机制: 1. **planning 双闸**:planner 触发满 5 次或进入 planning 满 30 分钟仍未提交粗计划(触发失败计入次数),置 `failed` 并落原因 + 决策日志。 2. **会话过期**:running 会话 60 分钟无新轮次由平台置 `expired`(启用既有 EXPIRED 枚举);`submit_report` 校验从"全部 completed"放宽为"全部终态",expired 会话在报告中标注为不完整证据——不把超时会话伪装成 completed,证据链保持诚实。 3. **executing 兜底**:最后一个会话到达终态后 10 分钟平台自动触发 analyst,上限 3 次;仍无报告或 executing 总时长超 `time_window_hours + 2h`,置 `failed`。 4. **任务重试上限**:任务表加 `attempts` 列,stale requeue 上限 3 次,超限置 `failed`。 5. **触发失败可见**:子进程缺失/超时/非零退出记入决策日志(`cron_id=platform`)并计入对应闸;同一评估连续 3 次触发失败直接判 `failed`(结构性故障不等自然到期)。 6. **孤儿 agent 双管**:触发命令容器内包 `timeout` 灭杀 agent 进程;平台侧 per-eval 触发冷却 10 分钟,兜住重启丢句柄留下的残余孤儿。 7. **预算硬闸门**:`open_session` 校验会话数 < 粗计划审批的虚拟用户数,超出 409。用户审批粗计划 = 审批预算,平台执行审批过的数字。 8. **扫描节奏恢复**:触发改 fire-and-forget(asyncio task,结果在回调中持久化),扫描循环恢复准确 60s,保住时段分布。 9. **Cron 池遗留面全删**:摘除 `/api/openclaw/cron-pool` router 挂载,删除 openclaw_cron_pool / alerts / fault_tolerance / cron_pool 模块(AlertManager 无后台 tick,duration 告警永远不会触发,是假装在工作的死代码)。 10. **settle 语义诚实化**:评估 cancelled/failed 时遗留任务置 `failed`,completed 时才置 completed;任务队列监控统计口径随之诚实。 11. **报告边界归一化**:`submit_report` 将 scores 归一到单一规范结构落库,前端与 markdown 渲染删除双兼容分支。 ### Considered Options(已拒绝) - **全自动自愈**:任何故障都自行恢复。对单实例内部工具是过度工程,且"明确失败"本身是重要信号。 - **任务/触发不设上限,靠评估级超时兜底**:确定性失败的任会在兜底期内被重试约 24 次、触发约 24 个子进程,烧资源并刷垃圾数据。 - **超时会话强置 completed**:会让报告消费者(人和 AI)无法分辨完整证据与不完整证据,污染证据链。 - **预算软约束(自然语言指令)**:对会幻觉的 agent 不是约束;预算失控完全无声。 ## Consequences - 新增 schema 变更:`IntelligentEvalTaskQueueDB.attempts`(Alembic 迁移 + 全新库 parity 测试)。 - `EXPIRED` 会话状态从死代码变为真实终态,CONTEXT.md 已同步;报告校验口径变化需要前后端一起改。 - 冷却期把触发频率从"每 60s"降为"每评估每 10 分钟",时段推进节奏相应放缓,与 stale requeue 的 10 分钟对齐。 - 部署拓扑假设不变:单进程单实例(SQLite + StaticPool),多实例部署需另行复审触发/扫描的分布式互斥。 - 不变更:OpenClaw 三角色职责划分、时间窗口语义、报告业务 schema(仅归一化 scores 结构)。