# 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 结构）。
