Agent从Checkpoint恢复后为什么重复调用工具?提交边界、幂等键与副作用对账完整排查

发布时间:2026/8/20 14:22:19
Agent从Checkpoint恢复后为什么重复调用工具?提交边界、幂等键与副作用对账完整排查 文章摘要Checkpoint能保存Agent运行状态却不能自动保证外部工具只执行一次。生产环境最危险的故障窗口是工具已经成功创建订单、发送邮件或发放优惠券但进程在写入Checkpoint之前崩溃。恢复后状态仍显示该Step未完成Executor会再次调用工具造成重复订单、重复通知和重复扣款。反过来如果系统先把Step标记为成功再调用外部工具进程可能在工具执行前崩溃恢复后Agent会错误地跳过从未执行的操作。无论“先执行后保存”还是“先保存后执行”跨数据库和外部系统都无法仅靠本地事务实现原子提交。本文从Checkpoint语义、Step Ledger、幂等键、外部操作ID、Intent记录、UNKNOWN状态、Outbox、Inbox、租约、重试、补偿和恢复对账等方面逐层排查给出副作用工具安全恢复的完整方案。一、最典型的重复调用窗口Agent执行S4向区域负责人发送销售报告时序1. Executor调用邮件服务 2. 邮件服务返回SENT 3. 进程崩溃 4. Step成功状态尚未写入数据库 5. Agent从上一个Checkpoint恢复 6. S4仍然是READY 7. 再次发送邮件Checkpoint没有错它忠实保存了最后一次成功提交的状态。问题在于外部副作用 和 本地状态提交 之间 存在不可消除的故障窗口二、Checkpoint保证了什么Checkpoint通常保证保存图状态保存下一步节点保存变量与Artifact引用支持进程重启后恢复支持人工中断支持历史状态和重放。Checkpoint通常不保证外部API与数据库原子工具仅执行一次重放不会触发副作用Provider调用不会重复业务系统幂等旧审批仍有效。三、至少一次执行是现实默认值分布式环境中最可靠的假设是Step可能被执行多次 事件可能被投递多次 回调可能被接收多次目标不是追求不现实的“绝对只调用一次”而是实现至少一次执行 业务幂等 效果只发生一次四、错误方案一先执行工具再保存成功优点工具失败不会误标成功。风险工具成功 →Checkpoint失败 →恢复后重复执行五、错误方案二先保存成功再执行工具风险成功状态已提交 →进程崩溃 →工具未执行 →恢复后跳过这会造成“幽灵成功”。六、正确思路Intent—Execute—Reconcile推荐三阶段记录执行意图 ↓ 调用外部工具 ↓ 记录结果并对账状态机NOT_STARTED → INTENT_RECORDED → EXECUTING → SUCCEEDED EXECUTING → UNKNOWN → RECONCILING → SUCCEEDED / FAILED七、Step执行模型publicrecordStepExecution(StringrunId,intplanVersion,StringstepId,StepStatusstatus,intattempt,StringidempotencyKey,StringinputHash,StringexternalOperationId,StringoutputArtifactRef,StringerrorCode,InstantleaseUntil,longrowVersion){}八、Step状态publicenumStepStatus{PENDING,READY,INTENT_RECORDED,EXECUTING,SUCCEEDED,FAILED,UNKNOWN,RECONCILING,WAITING_APPROVAL,CANCELLED}九、幂等键必须稳定同一业务操作的所有重试必须使用同一Key。publicStringbuildIdempotencyKey(StringtenantId,StringrunId,intplanVersion,StringstepId,StringoperationGroup){returnsha256(String.join(|,tenantId,runId,String.valueOf(planVersion),stepId,operationGroup));}错误做法每次重试生成随机UUID这样外部系统无法识别重复请求。十、什么情况下可以换新幂等键只有确认业务意图已经变化原操作明确失败且不可恢复人工批准新操作Replan生成新的业务动作输入Hash发生变化旧操作已补偿并关闭。普通网络重试不能换Key。十一、输入Hash审批和幂等都要绑定具体输入。publicrecordToolIntent(StringidempotencyKey,StringtoolName,StringtoolVersion,StringinputHash,StringapprovalTokenId,InstantcreatedAt){}如果Plan修订后邮件收件人变化旧Intent不能继续使用。十二、外部系统支持幂等时请求POST /orders Idempotency-Key: 8dbf...外部服务保存tenant idempotency_key → operation_result重复请求返回原结果不再创建新订单。十三、外部系统不支持幂等时可以增加Tool GatewayAgent → Tool Gateway → 外部系统Gateway负责幂等表-请求锁-外部操作ID-结果缓存-状态查询-审计-补偿。十四、幂等表createtabletool_operation(tenant_idvarchar(64)notnull,idempotency_keyvarchar(128)notnull,run_idvarchar(128)notnull,plan_versionintegernotnull,step_idvarchar(128)notnull,tool_namevarchar(128)notnull,tool_versionvarchar(64)notnull,input_hashvarchar(128)notnull,statusvarchar(32)notnull,external_operation_idvarchar(128),result_refvarchar(512),error_codevarchar(64),created_at timestamptznotnull,updated_at timestamptznotnull,primarykey(tenant_id,idempotency_key));十五、Intent记录必须先提交TransactionalpublicToolOperationprepare(StepExecutionstep,ToolRequestrequest){ToolOperationexistingrepository.find(request.tenantId(),request.idempotencyKey()).orElse(null);if(existing!null){validateSameIntent(existing,request);returnexisting;}returnrepository.save(ToolOperation.intent(request));}提交后才能调用外部工具。十六、同Key不同输入必须拒绝if(!existing.inputHash().equals(request.inputHash())){thrownewIdempotencyConflictException(request.idempotencyKey());}否则同一个Key可能错误复用旧结果。十七、执行工具publicToolOperationexecute(ToolOperationoperation,ToolRequestrequest){if(operation.status()ToolOperationStatus.SUCCEEDED){returnoperation;}if(operation.status()ToolOperationStatus.EXECUTING!leaseExpired(operation)){thrownewOperationInProgressException(operation.idempotencyKey());}ToolOperationclaimedrepository.claim(operation,workerId,leaseDuration);try{ExternalToolResultresultclient.execute(request,operation.idempotencyKey());returnrepository.markSucceeded(claimed.id(),result.externalOperationId(),result.resultRef());}catch(TimeoutExceptionex){returnrepository.markUnknown(claimed.id(),TIMEOUT_AFTER_SEND);}catch(Exceptionex){returnrepository.markFailed(claimed.id(),classify(ex));}}十八、为什么超时应进入UNKNOWNHTTP超时只代表客户端没有收到结果不代表服务端没有执行如果直接标记FAILED并重试可能重复副作用。UNKNOWN必须进入对账流程。十九、外部操作ID外部系统返回order_id message_id payment_id job_id必须保存。恢复时优先使用external_operation_id查询状态而不是再次创建。二十、没有外部操作ID怎么办可使用幂等Key查询-业务唯一键-请求时间窗口-收件人主题内容Hash-订单业务号-人工核对。但可靠性低于原生外部操作ID。二十一、恢复时决策矩阵本地SUCCEEDED直接复用结果INTENT_RECORDED尚未确认执行 →领取租约后执行EXECUTING且租约未过期其他Worker仍可能运行 →等待EXECUTING且租约过期不能直接重做 →查询外部状态UNKNOWN执行ReconcileFAILED按错误类别决定重试、补偿或人工二十二、对账服务publicReconciliationResultreconcile(ToolOperationoperation){OptionalExternalStatusstatusexternalStatusClient.find(operation.externalOperationId(),operation.idempotencyKey());if(status.isEmpty()){returnReconciliationResult.unresolved();}returnswitch(status.get().state()){caseSUCCEEDED-markSucceededFromExternal(operation,status.get());caseFAILED-markFailedFromExternal(operation,status.get());caseRUNNING-ReconciliationResult.stillRunning();};}二十三、工具结果与Checkpoint提交工具操作成功写入Tool Ledger后Agent再推进StepToolOperationSUCCEEDED ↓ StepExecutionSUCCEEDED ↓ Artifact引用保存 ↓ Checkpoint提交即使最后一步失败恢复时也能从Tool Ledger发现成功结果。二十四、双重事实来源不要只依赖图Checkpoint。需要Checkpoint 保存当前计算状态 Step Ledger 保存步骤业务状态 Tool Operation Ledger 保存外部副作用事实恢复时三者对账。二十五、Checkpoint的提交边界建议在Plan冻结后 Step领取后 Tool Intent提交后 Tool结果写入后 Step成功后 审批结果后 Reviewer决策后保存。但Checkpoint越密集存储和延迟越高需要按风险调整。二十六、读工具与写工具不同只读工具重复查询通常只增加成本。策略Query Cache-短TTL-允许重试-结果版本。写工具重复会产生业务影响。策略强制幂等-审批-Intent-对账-补偿-审计。二十七、LLM调用也可能重复恢复后重复模型调用会增加成本-得到不同结果-改变计划-改变Tool参数。可保存model_call_id input_hash model_profile structured_output对于确定的已完成Planner或Reviewer调用可直接复用输出。二十八、模型调用缓存与业务幂等不同模型缓存解决重复计算工具幂等解决重复效果不能混为一谈。二十九、Pending Writes并行节点中部分节点成功、一个节点失败。恢复时应复用已成功节点的写入避免全组重跑。如果框架支持Pending Writes要确认哪些状态已保存-哪些Tool副作用已记录-Reducer是否幂等-恢复从哪个Superstep开始。三十、租约避免两个Worker同时领取同一Step。updateagent_step_executionsetstatusEXECUTING,lease_owner:worker,lease_until:leaseUntil,row_versionrow_version1whererun_id:runIdandstep_id:stepIdandstatusin(READY,INTENT_RECORDED)and(lease_untilisnullorlease_untilnow());受影响行数为1才领取成功。三十一、租约过期不代表任务没执行Worker可能仍在运行但心跳失败-外部工具已成功-网络分区-GC暂停。所以过期后进入Reconcile不直接重复调用。三十二、重试分类publicenumFailureCategory{TRANSIENT_BEFORE_SEND,TRANSIENT_AFTER_SEND,PERMANENT_INPUT,PERMISSION,RATE_LIMIT,UNKNOWN_OUTCOME,BUSINESS_REJECTED}BEFORE_SEND可安全重试。AFTER_SEND先对账。PERMANENT_INPUTReplan或人工。PERMISSION重新授权不自动重试。三十三、补偿无法实现原生幂等的业务可以设计补偿重复创建订单 →取消重复订单但补偿不是完美回滚邮件无法真正撤回-通知已被阅读-价格可能变化-外部系统可能拒绝取消。优先幂等补偿作为第二道防线。三十四、审批与幂等绑定Approval Token绑定plan_hash step_id input_hash idempotency_key expires_at恢复后如果输入变化旧审批不得继续使用。三十五、Replay与副作用时间旅行或Checkpoint重放可能重新触发模型-API-Interrupt-工具。Replay模式必须明确publicenumReplaySideEffectMode{DISABLED,RECORDED,SANDBOX,LIVE_WITH_NEW_APPROVAL}生产默认不允许Live副作用重放。三十六、OutboxStep状态变化后需要通知UI、审计和监控。状态更新与Outbox同事务TransactionalpublicvoidcompleteStep(StepExecutionstep,ToolOperationoperation){stepRepository.markSucceeded(step.id(),operation.resultRef());outboxRepository.save(AgentEvent.stepSucceeded(step,operation));}三十七、Inbox工具回调和事件消费者可能重复。createtableevent_inbox(consumer_namevarchar(128)notnull,event_idvarchar(128)notnull,processed_at timestamptznotnull,primarykey(consumer_name,event_id));三十八、自动化测试崩溃点注入至少测试Intent提交前崩溃 Intent提交后、发送前崩溃 发送后、响应前崩溃 响应后、Tool Ledger前崩溃 Tool Ledger后、Step成功前崩溃 Step成功后、Checkpoint前崩溃每个点都验证业务效果只发生一次。三十九、测试同Key同输入重复调用返回同一外部操作ID和结果。四十、测试同Key不同输入必须报冲突不能复用。四十一、测试UNKNOWN模拟服务端成功、客户端超时。恢复后通过查询状态标记成功不再次创建。四十二、测试双Worker两个Worker同时领取同Step只能一个成功。四十三、测试审批过期旧审批Token过期后恢复不能执行副作用。四十四、监控指标agent_step_recovery_total{ state } agent_tool_idempotency_hit_total{ tool } agent_tool_idempotency_conflict_total{ tool } agent_tool_unknown_total{ tool } agent_tool_reconciliation_total{ result } agent_duplicate_side_effect_total{ tool } agent_step_lease_expired_total agent_checkpoint_after_tool_lag_seconds agent_replay_side_effect_blocked_total四十五、告警duplicate_side_effect 0 UNKNOWN长时间未解决 同Key不同输入 租约过期率异常 Tool成功到Step提交Lag过高 恢复后工具调用量激增 审批已过期但尝试执行四十六、最终排查清单□ 不把Checkpoint等同于Exactly Once □ 所有写工具有稳定幂等键 □ 同一操作重试不更换Key □ 幂等键绑定输入Hash □ Tool Intent先提交再执行 □ 外部操作ID被持久化 □ 超时后进入UNKNOWN而非直接FAILED □ UNKNOWN先对账再重试 □ Step Ledger与Tool Ledger独立存在 □ Tool成功后可恢复原结果 □ 两个Worker通过租约和乐观锁互斥 □ 租约过期不会直接重复副作用 □ 审批绑定Plan、输入和幂等键 □ Replay默认禁止真实副作用 □ Outbox和Inbox都支持幂等 □ 崩溃点注入覆盖全部提交窗口 □ 重复副作用率是零容忍指标总结Agent从Checkpoint恢复后重复调用工具不是Checkpoint失效而是系统错误地把“状态恢复”当成了“副作用只执行一次”。可靠方案必须同时具备Checkpoint Step Ledger Tool Intent 稳定幂等键 外部操作ID UNKNOWN对账 审批绑定恢复时Executor不能只问“Checkpoint里这个Step完成了吗”还要问这个业务意图是否已经提交 外部系统是否已经产生效果 现有结果能否安全复用只有回答完这三个问题Agent才能在崩溃、重试和重放中保持业务效果一致。