Skip to content
预计 1 分钟右侧目录可快速定位

局部失败如何传播:Partial Failure 与 Runtime Invariants ​

前置:运行中的并发怎么收住:Concurrency Limit、Cancellation 与 Deadline
核心问题:并行 Graph 中只有一个节点失败时,哪些成功结果可以保留、哪些下游必须阻断,谁来决定这次 Run 是否还能继续?

三路搜索一条失败可用两路结果继续;支付链路某关键写失败则不能简单返回 partial success。A 失败导致依赖 A 的 C BLOCKED,但独立 B 仍可完成并保存 artifact,便于 replan。

Partial Failure ​

并行执行时,一个 branch 失败并不自动意味着整个 Run 都失败。Runtime 必须根据依赖关系和任务语义决定影响范围。

例如:

text
A ─┐
B ─┼→ Merge
C ─┘

如果 B 只是“补充第二个数据源”,可以把它标记为 FAILED_OPTIONAL,让 A/C 继续进入降级版 Merge;如果 B 是 Merge 的强依赖,则只应 block 依赖 B 的节点,不需要重跑已经成功且无关的 A。

因此 partial failure policy 至少要区分:fail-fast、block dependents、keep partial result、degrade 或触发 replan。

Failure Propagation ​

传播规则必须显式:失败影响哪些 downstream node,哪些 sibling 可以继续,父 Run 何时进入 terminal failure。否则同一图在不同执行路径上会得到不可预测状态。

Failure 应沿 dependency/parent-child 边传播,并保留原始 category/cause。下游可能变 BLOCKED,独立 branch 可以继续,最终 Run 状态由 policy 汇总。

Runtime Invariants ​

Invariant 如 terminal monotonicity、no double execution、dependency correctness、checkpoint/trace consistency 可以用 property/invariant tests 自动验证。

Invariant 是所有执行路径都必须成立的程序规则,例如终态单调、依赖满足后才 READY、副作用节点不重复提交。应通过 property/fault-injection test 验证。

🔍 源码对照:Checkpoint 写入本身也有 invariant 和失败窗口 ​

完整源码与通用分析见 09-06 Checkpoint / Pause / Resume。这里不重复代码;这里关注 invariant:checkpoint 主存储与 transcript mirror 不能被视为一个原子动作,两个事实之间的失败窗口必须进入 partial-failure policy。

源码说明

turn-observation.ts 中 instrumentation 路径自己包在错误隔离里,注释明确 instrumentation failure 不应改变 turn execution。这是一个非常干净的 Runtime invariant 示例:辅助系统失败不能自动升级为业务执行失败。把它与 transient stream error / crash guard 一起读,可以练习区分 control-path failure、observation failure 与 execution failure。

工程边界

“辅助失败不影响主链”不是无条件吞错;如果 trace/checkpoint 本身承担合规或恢复保证,它可能必须 fail closed。是否降级取决于该组件是不是 correctness-critical。

Shadow Harness 对照

  • 为每类 subsystem 标注 correctness-critical / best-effort。
  • Partial failure reducer 明确哪些 sibling 继续、哪些取消、哪些 terminal result 保留。

验证

分别让 metrics exporter、checkpoint store、一个并行 read-only tool 失败;断言三者触发的 Run policy 不相同。

Failure Propagation 最好成为状态机规则,而不是异常冒泡 ​

假设图是 A → C,同时 B 与 A 独立。A 失败后,Runtime 可以把 C 标记为 BLOCKED,但 B 仍可完成。Python exception 是否跨 coroutine 抛出只是实现细节;真正需要持久化的是 Node/Run 的 domain state。

text
A = FAILED
B = SUCCEEDED
C = BLOCKED(reason=A_FAILED)

最终 Run 是 FAILED、PARTIAL 还是进入 REPLAN_REQUIRED,由 graph/policy 定义。这样重启后仍能从状态重建,而不是依赖当时的异常调用栈。

Invariant 是 Runtime 最值得做 Property Test 的地方 ​

典型 invariant 可以写成程序断言:

text
terminal node never returns to RUNNING
READY node has all required dependencies satisfied
one logical side effect has at most one committed success
attempt belongs to exactly one node/run
cancelled run starts no new node
state version increases monotonically

Fault injection 可以随机在 schedule、tool return、checkpoint、worker crash 等位置打断,再检查这些 invariant 是否仍成立。这样测试的是 Runtime 的普遍正确性,而不是只列几个预想得到的 happy path。

关联源码 ​

自测 ​

  1. 并行三个 Node 中一个失败,另外两个已经成功。为什么“整个 Run 失败”不等于把已成功副作用当作没发生?
  2. 设计一个 invariant,区分 node attempt 的执行事实、Run 最终状态和后续补偿决策。

Sources ​

AI Engineering · Agent · Harness · Backend Systems