一个内容 Agent 已经完成检索、核验、写作和两张配图,最后上传封面时进程崩了。页面上只有一个“重试”按钮。

如果重试等于从第一步重新执行,问题就来了:搜索结果可能变化,事实表可能产生新版本,图片会重复计费,更危险的是,某个已经成功的外部发布动作可能被执行第二次。

所以,retry() 不是恢复策略。真正的断点续跑至少需要三层状态:追加式事件日志、经过验收的阶段快照,以及不可重复副作用的幂等边界。

Google 在 2026 年 5 月公开 Agent Executor 时,直接指出长时间 Agent 工作流脆弱、难以可靠管理,并把 event log、snapshot 与 resumption 作为运行时能力。它的开源仓库同时明确警告项目仍在早期开发,协议还会变化。因此本文不把 AX 当成现成答案,而是提炼一套小团队可以自己实现的最小协议。

一、先区分三种“状态”

很多实现只保存消息数组:

[
  {"role":"user","content":"发布这篇文章"},
  {"role":"assistant","content":"已经开始处理"}
]

它能恢复对话,却不能回答:哪张图片上传成功了?草稿 ID 是多少?最后一次发布请求有没有真正到达平台?

建议把状态拆成三类:

状态作用是否能直接重放
Conversation保留意图和解释可以作为上下文,但不能当事实凭证
Execution Event记录工具调用、结果和状态迁移只追加,不覆盖
Artifact Snapshot保存已验收的状态与产物引用恢复的起点

事件日志连续保存轨迹,阶段快照提供可恢复起点

事件可以用一份简单结构表示:

type RunEvent = {
  runId: string;
  seq: number;
  stage: "research" | "draft" | "visual" | "prefill" | "publish";
  type: "STARTED" | "TOOL_CALLED" | "TOOL_RESULT" | "VERIFIED" | "FAILED";
  inputVersion: string;
  payloadRef?: string;
  artifactRefs?: string[];
  idempotencyKey?: string;
  occurredAt: string;
};

关键不是字段多,而是 seq 单调、事件只追加、重要输入有版本、外部结果有引用。

二、检查点不是定时保存,而是验收通过的边界

每隔 30 秒保存一次内存,并不等于有可用检查点。进程可能恰好在半写入、半上传或外部请求未确认时被快照。

更稳妥的检查点有两个条件:

  1. 当前阶段的输出可独立验证;
  2. 恢复后不需要猜测上一阶段是否完成。

例如,一篇文章的检查点可以是:

checkpointId: run-9269:visual-verified
runId: run-9269
lastSeq: 48
stage: visual
inputs:
  facts: artifact://facts/v3
  draft: artifact://draft/csdn-v2
artifacts:
  - uri: artifact://images/cover.jpg
    sha256: 7c1d...
  - uri: artifact://images/body-1.jpg
    sha256: 2b4a...
verifiedBy:
  - rule: image.count == 3
  - rule: image.watermark == false
nextStage: prefill

这份清单说明“配图阶段已完成并通过检查”,而不是仅仅记录“Agent 说图做好了”。

三、恢复流程应该先重放事实,再唤醒模型

恢复时不要先把旧对话塞给模型,问它“你做到哪一步了”。模型的回答不是事务日志。

推荐顺序:

load run
  -> scan event log
  -> find latest VERIFIED checkpoint
  -> verify referenced artifacts still exist
  -> reconcile external side effects
  -> acquire single-writer lease
  -> resume next stage

伪代码如下:

async function resume(runId: string) {
  const events = await eventStore.scan(runId);
  const checkpoint = latestVerifiedCheckpoint(events);
  await assertArtifacts(checkpoint.artifacts);
  await reconcileEffects(events);

  return lease.withLock(runId, async () => {
    return executeFrom(checkpoint.nextStage, checkpoint);
  });
}

single-writer lease 很重要。恢复任务和原任务可能同时醒来;如果两边都继续执行,就会产生双写。Google AX 的公开仓库也把 single-writer architecture 列为一致性能力之一。

四、把纯计算和外部副作用分开

生成摘要失败了,可以安全重跑。发布文章、支付费用、发送邮件、删除文件则不同。

可以给每个动作声明重放策略:

type ReplayPolicy = "SAFE" | "IDEMPOTENT" | "RECONCILE_FIRST" | "NEVER";

const policy = {
  generateDraft: "SAFE",
  renderImage: "IDEMPOTENT",
  uploadAsset: "IDEMPOTENT",
  publishArticle: "RECONCILE_FIRST",
  chargeCard: "NEVER"
} satisfies Record<string, ReplayPolicy>;

RECONCILE_FIRST 动作,恢复器必须先查询外部系统:如果文章 ID 已存在且标题、内容哈希匹配,就记录成功并继续,不能再次点击发布。

幂等闸门保证重复指令只产生一次外部副作用

一个发布请求可以携带稳定幂等键:

deliveryKey = sha256(accountId + platform + canonicalDraftId + revision)

幂等键的作用域必须包含账号和平台,否则不同账号可能误判为同一次投递;revision 变化后也应该生成新键。

五、恢复后不要悄悄改变输入

最隐蔽的错误不是重复执行,而是“恢复后换了资料”。例如研究阶段使用 facts-v3,故障恢复时系统自动拿到 facts-v4,却继续沿用旧验收结果。

恢复协议必须二选一:

  • 严格恢复:继续使用检查点绑定的输入版本;
  • 显式升级:创建新 run 或新分支,重新验证受影响阶段。

不要把输入升级藏在重试按钮后面。

六、最小落地版本

小团队不需要先造分布式运行时。可以从四张表开始:

runs(run_id, status, current_stage, version)
events(run_id, seq, type, payload_ref, created_at)
checkpoints(checkpoint_id, run_id, last_seq, manifest_ref)
effects(delivery_key, target, status, external_id, updated_at)

再加四条纪律:

  1. 状态只通过追加事件推进;
  2. 阶段验收通过后才能写检查点;
  3. 高风险工具调用前先写 effect intent,调用后回写 external ID;
  4. 恢复前先做 reconcile,不允许盲目重放。

这也是我们做 Tipkay 定时任务和多岗位接力时很在意的边界。面向一人公司和小团队的 AI 员工,不应该只会生成一次答案;研究、文案、配图、排版和发布准备之间的产物要能保留,中途失败时也不该让用户从头培养一遍。按实际使用量计费的前提之一,就是尽量别让同一段已完成工作被无意义重跑。

边界同样要承认:十秒钟能重做、没有外部副作用的低风险转换,重新执行可能比维护检查点更便宜。检查点优先放在耗时长、成本高、可验收或会改变外部世界的阶段。专业不是给每一步加复杂基础设施,而是知道哪一步绝不能“再来一次”。

参考资料

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐