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

历史执行怎样重放和分叉:Replay、Determinism、Fork 与 Time Travel ​

前置:09-06 Checkpoint/Resume;理解 deterministic execution record
核心问题:重放历史时哪些步骤可以重新计算,哪些外部副作用绝不能再次执行,Replay、Resume 和 Fork 的边界在哪里?

“Replay”这个词最容易让人误解 ​

假设一个 Run 已经执行到:

text
read repository
→ model decides patch
→ write file
→ run tests
→ WAITING_APPROVAL

审批后恢复时,系统绝不能简单理解成“从函数开头再跑一遍”。write file、外部 API、随机数、当前时间、模型调用都可能产生新的事实。

因此先区分三件事:

text
State reconstruction
= 用 checkpoint/event 恢复已提交事实

Deterministic workflow replay
= 重新走控制代码,但复用历史 task/result,使同一个执行回到原路径

Live re-execution
= 真正重新调用 Model / Tool / External System

这三种行为的风险完全不同。

恢复的第一原则:已经提交的事实不要靠“再做一次”证明 ​

如果 Node test 已经有 durable completion record:

text
node_id = test
attempt = 2
status = SUCCEEDED
exit_code = 0

Resume 应读取这个事实,而不是重新跑测试来“确认它还是成功”。重新执行属于新的运行行为,可能受到代码、环境、时间或外部服务变化影响。

真正需要重新执行的通常是:

  • 尚未形成 completion record 的 attempt;
  • 明确声明可重新计算的纯步骤;
  • 用户主动创建的新 Fork;
  • reconciliation 后确认必须补做的操作。

Determinism 约束的是“恢复路径”,不是要求模型本身确定 ​

Durable workflow 常要求恢复时能够再次走到同一个逻辑位置。危险代码包括:

python
if random.random() > 0.5:
    ...

if datetime.now() > deadline:
    ...

result = requests.get(url)  # 直接嵌在 replay control flow

如果这些值没有被记录,resume 时可能走另一条分支。

更可靠的模型是:

text
non-deterministic operation
→ 作为显式 Task/Node 执行
→ result durable commit
→ control flow 只消费已记录结果

模型调用也属于 non-deterministic operation。不要要求 LLM 在 replay 时再次生成完全一样的文本;要持久化足以恢复控制流的规范化结果或 execution record。

不同 runtime 的具体实现不同:有的从 node boundary 恢复,有的会 replay workflow body。设计 Shadow Harness 时应明确自己的恢复粒度,而不是模糊使用“replay”一词。

Side Effect 必须从 Replay 边界中剥离 ​

外部副作用不能因为“历史控制流重新走到这里”就自动再次执行:

text
Replay reaches send_email
        ↓
lookup operation_id
        ↓
already committed? ─ yes → reuse fact
        │
        no
        ↓
execute with idempotency / reconciliation

对于无法可靠去重的高风险操作,恢复策略宁可进入 NEEDS_RECONCILIATION,也不要猜测第一次是否成功。

🔍 源码对照:Approval identity 让“同一次副作用”有可验证身份 ​

完整源码与通用分析见 11-05 HITL / Approval / Resume。这里不重复代码;这里关注 Replay / Fork:重放必须保留或显式重建 approval 与 side-effect identity,历史批准不能自动授权新的参数或新的动作。

源码说明

transcript mirror、initial transcript load 和 storage 能提供“历史如何被重新投影进运行环境”的案例,但它们不等价于 Event Sourcing。本文读源码时最重要的训练,是区分 transcript replay(重建模型可见历史)、state reconstruction(重建 Runtime facts)和 side-effect replay(通常不能简单重做)。

工程边界

不要因为存在 transcript/history 就宣称可以 deterministic time travel。模型 provider、tool world state、prompt/tool schema version 都可能让重放偏离原执行。

Shadow Harness 对照

  • Replay 输入绑定 RunManifest:model/prompt/tool schema/retrieval/code versions。
  • Fork 创建新 lineage,不修改原 execution history;external side effects 默认不 replay。

验证

对一个固定 ScriptedModel Run 做 replay,再改变 Tool schema version 做第二次 replay;第一条应相同,第二条必须被 manifest compatibility check 拒绝或显式迁移。

Fork / Time Travel 是新执行,不是修改旧历史 ​

Fork 从历史 checkpoint 创建新的分支:

text
original run: r-100
checkpoint: cp-7
        ↓ fork
new run/branch: r-101
parent = r-100@cp-7

新分支可以换 Plan、Context、Model 或人工修正状态,但必须:

  • 拥有新的 branch/run identity;
  • 保存 parent lineage;
  • 不覆盖原 trace/checkpoint;
  • 对外部副作用重新评估 operation identity,避免错误复用旧幂等键。

Time Travel 的价值主要是调试、对比策略和人工恢复,不应该把历史事实改造成“仿佛从未发生”。

Shadow Harness 的 Replay Policy ​

建议把“是否执行”显式化:

python
class ReplayPolicy(Protocol):
    def decide(self, execution: NodeExecution) -> Literal[
        "REUSE_COMMITTED_RESULT",
        "REEXECUTE",
        "RECONCILE_FIRST",
        "REJECT",
    ]: ...

Decision 依赖:

  • node 是否纯计算;
  • 是否已有 durable completion;
  • side-effect classification;
  • tool schema / runtime version 是否兼容;
  • operation id 是否仍有效;
  • 当前是 resume 还是用户主动 fork。

这样“replay”不会退化成一个危险的 run_from(checkpoint) 开关。

Failure Modes ​

Resume 时重新调用所有非确定性步骤 ​

后果: 模型结果、时间、随机数或远程数据变化,控制流偏离原 Run。

处理: 已完成步骤复用 durable result;非确定性操作放入可记录的 Task/Node boundary。

Replay 把外部副作用当普通函数 ​

后果: 邮件、支付、部署、写文件等再次发生。

处理: operation identity + idempotency/reconciliation;无法确认时 fail closed。

如何验证 ​

场景必须成立
checkpoint 前生成随机值,之后 crashresume 使用第一次已提交值,不改变分支
Model Node 已完成后 resume不再次调用 Provider
side effect 成功但本地 completion 未确认先 reconciliation,不直接重复执行
从旧 checkpoint fork新 branch 有独立 identity,原历史不可变

关联源码 ​

自测 ​

  1. 历史 Run 中包含真实外部写操作。Replay 为什么不能默认再次执行这些副作用?哪些步骤需要 deterministic substitute 或 recorded result?
  2. Fork 从某个历史点创建新分支时,哪些旧状态可以继承,哪些 environment facts 必须重新验证?

Sources ​

AI Engineering · Agent · Harness · Backend Systems