Agent Platform · Detail Design

自修复机制设计

把异常从"人工救火"收敛为"可控自恢复流程":四层防护墙(Retry / Fallback / Circuit / Recovery),加上僵尸 Run 巡检与灰度一票否决回滚。

返回文档目录平台首页

四层防护墙

Retry → Fallback → Circuit → Recovery 由内向外,每一层只解决一类问题,组合形成自修复闭环

跨 Run 巡检

中间态停留超阈值的 Job 不再"挂在那儿",由巡检任务强制收敛到 TIMEOUT / CLOSED_FAILED 终态

灰度一票否决

失败率异常或 Trace 写入失败触发 feature flag 回切旧路径,所有动作进 Audit 留痕

Core Flow Diagram
Self-Healing
Error Detect→Retry→Fallback→Circuit Breaker→Recovery→Trace
Rollback
Grey Failure→Feature Flag→Old Path→Audit

详细设计说明

DESIGN DOCUMENT · SELF-HEALING

把异常从"人工救火"收敛为"可控自恢复流程"

这份文档定义 Harness 上线后的自修复能力:错误分类后选择重试、降级、熔断、终止,再加上跨 Run 巡检和灰度一票否决回滚。评审重点是异常发生后系统如何自动止损、留痕和恢复,而不是无限重试。

设计主张

自修复不是无限重试,而是按错误类型选择 Retry / Fallback / Circuit / Recovery 中的一档,并把每一次恢复动作写入 Trace。Run 内即时自愈、Run 间巡检收敛、灰度一票否决回滚——三层职责互补,不能互相替代。

RetryFallbackCircuit BreakerZombie RunCanary Rollback

评审关注点

  • 哪些错误允许重试,哪些立即失败
  • 熔断打开期间高优先级流量是否受影响
  • Fallback 路径是否仍满足业务目标
  • 僵尸 Run 是否一定收敛到终态
  • 灰度回滚的触发条件与回切边界
D1 错误分类可重试 / 不可重试分离
D2 有限重试指数退避且有上限
D3 熔断隔离三态 + 探测恢复
D4 终态收敛僵尸 Run 必须关闭
D5 一票否决灰度异常立即回切

第一章 · 定位与概念

SECTION GOAL

说明自修复在 Harness 中的边界、四层防护墙的关系、僵尸 Run 巡检和灰度回滚作为外围补丁的角色,回答"为什么不只是 try/except"。

状态:v1 草案 · 日期:2026-05-07 · 定位:Harness 内核运行期的稳态保障层;不替代 Policy 与 HITL,但为它们提供可恢复的执行底座 · 关联:harness-kernel-design(Run 状态机)、observability-spec(Trace / 告警)、testing-strategy(混沌测试与故障注入)

1.1 总体架构图

下图把自修复机制画成由内向外的四层防护墙,加上跨 Run 巡检和灰度回滚两条外围补丁。中间柱是 Harness 一次 Run 的执行路径,每一层防护墙拦住一类问题;越内层粒度越细(Tool 级),越外层范围越大(Run 级 / 灰度级)。所有恢复动作必须经 Trace 收口。

Self-Healing Architecture · 四层防护墙 + 巡检 + 灰度回滚
T1 · CANARY ROLLBACK · 一票否决 T2 · ZOMBIE RUN 巡检 · 跨 Run 收敛 T3 LAYER 4 · CIRCUIT 熔断 · 依赖隔离 T3 LAYER 3 · FALLBACK 降级 · 备路径 T3 LAYER 2 · RETRY 重试 · 指数退避 T3 LAYER 1 · RUN RECOVERY Tool / LLM Call Run 内单次外部 IO EXECUTE · STEP 6 D · TRACE · 必经收口 每一次恢复动作都写一行 Trace retry_count · fallback_used · circuit_state T1 · 健康看板 + 告警 Run 失败率 / Trace 写入失败率 / 熔断器 OPEN 阈值告警 + 灰度一票否决 trigger T2 · 跨 Run 巡检(Celery beat) 中间态停留超阈值 → 强制 TIMEOUT 每 60s 扫一次 · 写补偿 trace 所有恢复事件 巡检发现僵尸 → 强制收敛 LEGEND T3 · Run 内即时自愈(实线) T2 · 跨 Run 巡检(虚线) T1 · 看板告警 + 灰度一票否决
同心圆 = 防护层级(越内越细)·点击任意层标签跳到对应章节·所有恢复动作必经 Trace 收口·T1 看板 + T2 巡检 + T3 Run 内自愈三层互补

1.2 时序图(2 个典型恢复场景)

下面 2 张时序图对应自修复最常见的两条路径,分别可视化 Run 内即时自愈与跨 Run 巡检收敛的执行顺序。点击 lifeline 头部色块跳到对应章节。

点击下方任意场景卡片跳到对应时序图。

SCENARIO · A Tool 调用失败 → 重试 → Fallback → 熔断 Run 内即时自愈:失败先重试,连续失败切 fallback skill,再失败熔断 OPEN,HALF_OPEN 探测恢复。 SCENARIO · B 僵尸 Run 巡检 → 强制 TIMEOUT 收敛 中间态停留超阈值的 Run 由 Celery beat 巡检发现 → 强制写 TIMEOUT → 经 TRACING → CLOSED_FAILED。
Scenario A · Tool 调用失败的恢复路径

一次 Tool 调用从失败到走 fallback、到触发熔断、到 HALF_OPEN 探测恢复的完整路径。红色箭头 = 失败 / 拒绝路径,绿色 = 恢复路径。

Run 管理器RUN STATE Tool 调用控制器RETRY · FALLBACK Circuit BreakerCLOSED / OPEN / HALF 主 Skill / 模型PRIMARY EXT Fallback Skill / 模型FALLBACK EXT TraceAUDIT — PHASE A · RETRY 指数退避(max 3 次)— 1 invoke_tool(skill_id, args) 2 查熔断状态 CLOSED · 通行 3 attempt 1 503 Service Unavailable(可重试) 4 ↻ 退避 1s · retry_count=1 5 attempt 2 · 退避 2s 仍 503 · retry 上限耗尽 — PHASE B · FALLBACK 切备路径 — 6 读 manifest.fallback_skill_id 切备 备路径返回成功(fallback_used=true) — PHASE C · 主路径连续失败累计 → CIRCUIT OPEN — 7 fail_count++ 连续 5 失败 → state=OPEN(30s) — PHASE D · 30s 后 HALF_OPEN 探测 → CLOSED 恢复 — 8 ↻ 30s 计时到 → HALF_OPEN(放 1 个探测) 9 探测调用 attempt 1 200 OK · state=CLOSED 恢复 10 写 ToolInvocationTrace · retry_count=2 · fallback_used=true · circuit_event=OPEN/HALF/CLOSED
绿色 = 成功路径 · 红色 = 失败路径 · 紫色 = fallback 切备路径 · ↻ = 同层自循环 · 所有恢复事件最终都写一行 Trace
Scenario B · 僵尸 Run 巡检 → 强制终态收敛

中间态停留超阈值的 Run 没有自然终态。Celery beat 巡检任务每 60s 扫一次 Run 表,超阈值的强制写入 TIMEOUT,再经 TRACING 收口到 CLOSED_FAILED。

Celery BeatPATROL · 60s 巡检 WorkerSCAN · DECIDE DB · Run 表RUN STATE Run 管理器STATE TRANSITION Trace + 告警AUDIT · ALERT — PHASE A · 巡检定时触发(每 60s)— 1 cron · run_zombie_scan 2 查 state IN (PREPARING, EXECUTING) AND updated < now-1h 命中 N 条僵尸 Run 3 ↻ 按停留时长分级(warn / kill) — PHASE B · 强制收敛(kill 档)— 4 force_timeout(run_id, reason=ZOMBIE) 5 UPDATE state=TIMEOUT 6 ↻ TIMEOUT → TRACING(必经收口) 7 写 RunTrace · close_reason=ZOMBIE 8 ↻ TRACING → CLOSED_FAILED — PHASE C · 告警(P1 oncall)— 9 飞书 oncall
紫色 = 巡检触发与决策路径 · 红色 = 强制终态路径 · ↻ = 同层自循环 · 凡进入 TIMEOUT 必经 TRACING 才能到 CLOSED_FAILED

1.3 为什么自修复

不做自修复时常见的 4 类痛点:

痛点不做自修复时的表现做了自修复后的表现
瞬时网络抖动一次 503 直接 RunFailed,用户看到红色错误指数退避自动重试 2~3 次,对用户完全不可见
外部依赖故障故障期间所有 Run 全部失败,雪崩拖垮整个 Harness熔断 OPEN 隔离故障依赖,其他 Skill 正常服务
Run 卡中间态消费者放着不管,DB 中堆积大量 PREPARING / EXECUTING巡检定时强制 TIMEOUT,终态收敛,监控指标干净
灰度路径异常新路径失败率突涨但未发现,业务受损扩大失败率超阈值即一票否决回切旧路径,损失锁定在分钟级

1.4 三层职责

自修复的三层职责按"颗粒度从细到粗"排列;每一层只解决自己擅长的问题,不能用一层替代另一层。

层谁负责颗粒度关键能力
T1 · 健康看板 + 告警Prometheus / Grafana / 飞书 oncall平台级失败率 / 熔断 OPEN 时长 / Trace 写入失败率持续超阈值即告警;为灰度一票否决提供触发信号
T2 · 跨 Run 巡检 + 灰度回滚Celery beat 巡检任务 / Feature Flag 控制器Run 级 / 路径级中间态停留超阈值的 Run 强制收敛;灰度路径失败率异常一票否决回切
T3 · Run 内重试 + Skill 熔断Tool 调用控制器 / LLM RouterTool 调用级错误分类后选择重试、降级或熔断;HALF_OPEN 探测自动恢复

三层互为补充:T3 是第一道防线(毫秒~秒级自愈),T2 是兜底巡检(分钟级收敛),T1 是异常的最后红线(人工介入 / 全局回滚)。任何一层失效,下一层接管。


第二章 · 重试策略

SECTION GOAL

说明哪些错误允许重试、哪些必须立即失败、退避算法、重试上限,并把每一次重试都写进 Trace。

2.1 错误分类与重试决策

所有外部 IO(Tool / LLM / Skill / 沙箱)的错误必须先经过错误分类,再决定是否重试。原则是:瞬时性 / 服务侧问题可重试,语义性 / 调用方问题立即失败。

类型判断依据动作原因
可重试网络错误(DNS / TCP reset / 连接超时)指数退避重试瞬时性故障,重试通常成功
可重试HTTP 5xx · 上游 unavailable指数退避重试服务侧问题,常自愈
可重试HTTP 429 · rate_limit退避后重试(按 Retry-After)限流是瞬时拒绝,退避后通常放行
不可重试HTTP 4xx(除 429)· 参数错误立即失败语义错误,重试无意义
不可重试auth / billing 错误立即失败账号 / 配额问题,重试只会重复触发
不可重试context_length_exceeded立即失败 · 上抛 Plan需要走压缩降级而非重试

2.2 退避算法与上限

维度策略
退避公式指数退避 1s → 2s → 4s(基础值乘 2 的 retry_count 次方)
抖动(jitter)退避值上叠加 ±20% 随机抖动,避免重试雪崩
Tool 调用上限3 次(含首次);超过 → CLOSED_FAILED
LLM 调用上限3 次 + 主备切换(下一次切到备模型,仍可继续 2 次重试)
退避总耗时上限不超过 Skill manifest 声明的 timeout_per_call

2.3 Trace 留痕

每次重试都在 ToolInvocationTrace 上累加 retry_count,并记录每次失败的错误码与退避耗时。Trace 写入失败本身不阻塞 Run,但会触发 P0 告警(见第七章灰度回滚的触发条件)。


第三章 · Fallback

SECTION GOAL

说明 Fallback 触发条件、目标路径来源、与重试 / 熔断的协同关系,以及 Fallback 路径自身的退化处理。

3.1 三类 Fallback

类型触发目标来源
模型 Fallback主模型 5xx / timeout / 配额耗尽 / 重试上限耗尽切到备模型继续推理LLM Router 配置(按 endpoint 优先级)
Skill Fallback主 Skill 重试上限耗尽 / 熔断 OPEN切到 manifest 声明的备 SkillSkill manifest 中的 fallback_skill_id 字段
功能降级全局压力高(队列积压 / 成本接近上限)关闭非核心 Skill,只保留核心能力Skill manifest 中的 priority 字段

3.2 Fallback 与重试 / 熔断的协同

3.3 Fallback 路径退化处理

当备路径也失败时分两种情形:


第四章 · 熔断恢复

SECTION GOAL

说明熔断器三态状态机、触发与恢复阈值、HALF_OPEN 探测策略,以及熔断与重试 / Fallback 的边界。

4.1 三态状态机

状态语义请求处理
CLOSED正常态所有请求放行;累计失败计数
OPEN熔断态所有请求立即拒绝(短路);不再打到目标依赖
HALF_OPEN探测态放 1 个探测请求;成功 → CLOSED,失败 → OPEN 续时

4.2 触发与恢复阈值

事件阈值转移
累计失败连续 5 次失败(同一 endpoint / Skill 维度)CLOSED → OPEN
OPEN 时长初始 30s · 连续触发指数退避到上限 5minOPEN → HALF_OPEN(计时到)
HALF_OPEN 探测单请求成功HALF_OPEN → CLOSED · 重置计数
HALF_OPEN 探测单请求失败HALF_OPEN → OPEN · 续时

4.3 熔断与重试 / Fallback 的边界

4.4 熔断与 RunState 的关系

熔断不直接改变 RunState;它只决定单次外部调用是否放行。Run 内即使遇到熔断 OPEN,仍按 Fallback / 拒绝执行的常规路径走,最终 Run 仍由 Run 管理器写入 SUCCEEDED / CLOSED_FAILED。


第五章 · 失败 Run 恢复

SECTION GOAL

说明已经写入失败终态的 Run 在什么条件下可以重新发起,避免"失败即终结"也避免"无限自动重试"。

5.1 恢复决策依据

已经写入终态的 Run 不能简单"原地重启",必须基于 RunState 与 Trace 重新评估:

判据来源用途
失败原因分类RunTrace.close_reason · ErrorTrace.error_class判断是瞬时还是语义错误
已产生的副作用ToolInvocationTrace 中已写库 / 已外发的 Tool 调用避免重复执行不可重入的 Tool
HITL 状态HITLTrace人工已拒绝的不能自动重启
成本已消耗CostTrace判断是否在用户预算范围内重试

5.2 四档恢复处置

档条件动作
自动恢复瞬时错误(5xx / network) · 无副作用 · 成本未超预算新建 child Run,复用原 input + ctx;旧 Run 保留终态
降级恢复主路径连续失败 · 业务允许降级新建 child Run 走 Fallback 路径
拒绝恢复语义错误 / HITL 拒绝 / 成本超限不重启;返回原失败结果给调用方
人工恢复核心场景但所有自动路径已尝试挂起到工单 / oncall 群,等待运维介入

5.3 恢复路径的不变量


第六章 · 僵尸 Run 检测

SECTION GOAL

说明哪些 Run 算"僵尸"、巡检如何发现、强制收敛的状态转移路径,以及补偿动作的边界。

6.1 僵尸 Run 定义

停留在非终态中间态且updated_at 超过阈值的 Run 即为僵尸 Run。可能的原因:worker 崩溃、网络分区、外部依赖死锁、HITL 长期无人响应。

中间态停留阈值(默认)处置
PREPARING5min强制 TIMEOUT
PLANNING10min强制 TIMEOUT
EXECUTING1h(受 Skill 上限影响)强制 TIMEOUT
AWAITING_HITLHITL request 自带 timeout(默认 24h)到时即超时(不算僵尸,走 HITL 超时路径)
TRACING5minP0 告警 + 强制 CLOSED_FAILED(Trace 写入失败时的最后兜底)

6.2 巡检机制

维度策略
触发器Celery beat · 每 60s 一次
查询条件state IN (中间态) AND updated_at < now - 阈值
分级处置warn 档(接近阈值)只告警;kill 档(超过 1.5×阈值)强制收敛
批量上限单次巡检最多处理 100 条,避免巡检自身打挂数据库
幂等巡检任务用 Redis 锁防止重复执行

6.3 强制收敛路径

强制收敛必须遵循 RunState 的"必经 TRACING"约束:

  1. Worker 调 Run 管理器的 force_timeout(run_id, reason=ZOMBIE) 接口。
  2. Run 管理器原子转移:当前中间态 → TIMEOUT → TRACING。
  3. Trace 写入 close_reason=ZOMBIE 与停留时长。
  4. TRACING → CLOSED_FAILED(最终关闭态)。
  5. P1 告警(Job 卡 running > 1h)。

6.4 补偿动作的边界


第七章 · 灰度回滚

SECTION GOAL

说明灰度路径异常的判定信号、一票否决回滚的触发与执行、回切到旧路径后的留痕与复盘。

7.1 触发信号(一票否决)

灰度路径上线后,下列任一信号超阈值即立即一票否决,无需多指标复合判定:

信号阈值原因
新路径 Run 失败率5min 滑窗 > 旧路径 × 1.5新路径明显劣化
Trace 写入失败率> 1%(持续 5min)审计完整性受损,不可接受
熔断器 OPEN 数> 历史均值 × 3新路径触发依赖故障扩散
首字延迟 P95> 旧路径 × 2用户体验显著退化
成本超限触发频率> 10 次 / hour新路径成本失控

7.2 回滚动作

步骤动作
1Feature Flag 控制器读告警事件,把 USE_HARNESS_PIPELINE 切回 false(或灰度百分比归零)
2新进入的 Run 全部走旧路径;已在新路径执行的 Run 不强制中断(让其自然完成或失败)
3飞书 oncall 群 P0 告警,附本次触发的具体信号与阈值
4把回滚事件写入 AuditLog(actor=system · action=canary_rollback · reason=信号名称)
5触发自动复盘:拉过去 30min 的关键 Trace 与告警,生成报告挂到事件单

7.3 回切边界

7.4 与僵尸 Run 巡检的关系

灰度回滚发生后,新路径上仍有少量在飞 Run。如果这些 Run 因新路径已下线而无法继续推进,会变成僵尸 Run,由第六章的巡检兜底强制收敛。两层互补:灰度回滚阻止新增问题,僵尸巡检收敛存量问题。