常见故障自愈有上限,超限收敛终态且可见:任务 attempts 上限、会话过期、 planning 双闸、executing 超窗兜底、触发失败计数判死、孤儿 agent 双管、 fire-and-forget 触发;open_session 预算硬闸门、settle 按终态区分、报告 scores 归一化;cron 池遗留面全删。
4.4 KiB
4.4 KiB
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)且用户可见,绝不无声卡死。具体机制:
- planning 双闸:planner 触发满 5 次或进入 planning 满 30 分钟仍未提交粗计划(触发失败计入次数),置
failed并落原因 + 决策日志。 - 会话过期:running 会话 60 分钟无新轮次由平台置
expired(启用既有 EXPIRED 枚举);submit_report校验从"全部 completed"放宽为"全部终态",expired 会话在报告中标注为不完整证据——不把超时会话伪装成 completed,证据链保持诚实。 - executing 兜底:最后一个会话到达终态后 10 分钟平台自动触发 analyst,上限 3 次;仍无报告或 executing 总时长超
time_window_hours + 2h,置failed。 - 任务重试上限:任务表加
attempts列,stale requeue 上限 3 次,超限置failed。 - 触发失败可见:子进程缺失/超时/非零退出记入决策日志(
cron_id=platform)并计入对应闸;同一评估连续 3 次触发失败直接判failed(结构性故障不等自然到期)。 - 孤儿 agent 双管:触发命令容器内包
timeout灭杀 agent 进程;平台侧 per-eval 触发冷却 10 分钟,兜住重启丢句柄留下的残余孤儿。 - 预算硬闸门:
open_session校验会话数 < 粗计划审批的虚拟用户数,超出 409。用户审批粗计划 = 审批预算,平台执行审批过的数字。 - 扫描节奏恢复:触发改 fire-and-forget(asyncio task,结果在回调中持久化),扫描循环恢复准确 60s,保住时段分布。
- Cron 池遗留面全删:摘除
/api/openclaw/cron-poolrouter 挂载,删除 openclaw_cron_pool / alerts / fault_tolerance / cron_pool 模块(AlertManager 无后台 tick,duration 告警永远不会触发,是假装在工作的死代码)。 - settle 语义诚实化:评估 cancelled/failed 时遗留任务置
failed,completed 时才置 completed;任务队列监控统计口径随之诚实。 - 报告边界归一化:
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 结构)。