Browse this library

Chinese source article

5.1 · 状态机、故障恢复与追踪

以显式状态转移、提交语义和事件追踪管理长时间 Agent 运行。

View original Markdown ↗

1. Runtime状态机

1.1. 合法转移

常见状态包括就绪、推理、等待工具、等待审批、验证、完成和失败。每次转移由单一动作触发,并检查当前状态,非法转移拒绝执行。

显式状态机把不变量放进控制器。回调顺序或消息延迟发生变化时,系统不会凭时序猜测当前阶段。

1.2. 提交边界

模型输出、工具调用和环境写入具有不同提交点。只有权威状态完成更新后,运行才能进入下一阶段。

跨服务调用需要请求标识与状态查询。超时只表示调用方没有得到响应,不能推断环境没有执行。

2. 故障恢复

2.1. 重试条件

重试适用于明确未提交且具有暂时性的故障。参数错误、权限拒绝和确定性测试失败需要改变动作,重复原调用没有意义。

退避与次数上限由 Runtime 执行。模型可以建议重试,但不能覆盖系统预算或风险规则。

2.2. 检查点与中断

检查点保存可恢复的权威状态,不保存正在执行进程的虚假连续性。服务重启后,未知副作用任务应标记中断并等待人工判断。

恢复时验证外部资源版本,防止旧计划在变化后的环境继续运行。可复用成果按完成谓词重新检查。

3. Trace设计

3.1. 事件关联

每次运行、模型调用、工具调用和状态提交使用关联标识。父子关系与顺序号帮助还原并发过程,时间戳只作为辅助。

事件记录版本、耗时、token、结果摘要与原始产物指针。凭据和个人数据在写日志前处理。

3.2. 从Trace到诊断

诊断先定位首次偏离:上下文缺失、动作错误、执行故障、观察丢失或验证器误判。后续连锁错误不应掩盖最早原因。

Trace 也是轨迹评测和训练筛选的数据源,因此 schema 要稳定,并保留工具与环境版本。

状态不能只用一个 status 字符串表达。一次运行还要知道状态版本、占用中的动作、等待对象、剩余预算和已提交效果。例如 EXECUTING 必须绑定唯一动作 ID,WAITING_APPROVAL 必须绑定待批准的参数快照,VALIDATING 必须绑定被验证的提交版本。缺少这些伴随数据,名义上有状态机,实际上仍会靠散落的回调猜测上下文。

不变量要写进转移函数:任意时刻至多有一个副作用动作占用运行;终止态不能被迟到事件复活;批准只对申请时那组参数有效;验证通过只能提交它检查过的版本。UI 禁用按钮、调用方顺序和开发者约定都不能代替这些约束,因为它们无法阻止重放、并发和进程恢复带来的非法输入。

一次模型输出属于候选决定,尚未成为环境事实。Runtime 校验结构和策略并持久化动作意图,随后才允许执行器接触环境。工具返回后也不能立即宣告成功:系统保存原始 observation 和外部标识,更新已提交效果,最终由 verifier 判断任务谓词。读操作同样记录规范路径、内容版本和哈希,使后续推理依赖明确的观察版本。

重试决策包含四项:错误是否暂时、动作是否已经提交、是否存在幂等键、再次调用是否仍在预算和授权内。任何一项未知,都不能机械复制原调用。网络抖动下的只读查询通常可重试;参数错误回到规划;权限拒绝等待授权或改用低权限方案;外部写入超时先查询提交状态。指数退避只能降低拥塞,无法修复错误参数、过期凭据或已经发生的副作用。

检查点让系统能够在安全边界重新作决定,无法保证从任意进程指令处续跑。模型生成到一半的 token、浏览器尚未提交的表单、运行中的 shell 进程都不能被序列化成可信执行体。可恢复对象是已提交状态、事件游标、动作意图、外部交易标识和仍可验证的产物。恢复正确要求事实一致且没有重复副作用,界面重新出现 running 标签没有证明力。

关联结构同时包含树和序列。树回答工具调用由哪个模型决定触发,序列回答运行中哪个状态版本先提交;并行分支用各自序号和汇合依赖表达。只保留 wall-clock 时间会受到时钟漂移、批量写入和异步队列影响,不能判定因果。事件 schema 应把运行、主体、动作、资源、状态版本和父事件等稳定字段,与截图、长日志等可变产物分开。

诊断时把失败拆成决定、执行、观察和验证四层。决定层可能选择错误动作,而执行器正确完成了它;工具已经完成,观察层可能截断输出;环境已经达到目标,验证器又可能读取旧版本并误判。若全部归为“Agent 失败”,修复会落在错误层。可复现轨迹还需保存代码提交、依赖锁、工具版本、策略版本、模型标识、提示模板哈希和输入资源版本。

4. 状态机与事件溯源

4.1. 转移函数

把运行状态写成 s=(phase,version,pending,effects,budget)s=(phase,version,pending,effects,budget),事件写成 e=(id,type,parent,expectedVersion,payload)e=(id,type,parent,expectedVersion,payload)。reducer 计算 δ(s,e)→(s′,effects′)\delta(s,e)\rightarrow(s',effects')。expectedVersion 不匹配时事件被记录为冲突,不能覆盖新状态;effects' 是待执行副作用,只有转移提交后才交给执行器。

模型请求、审批结果、工具开始、工具提交、验证结果、取消和超时使用不同事件。READY 只接受开始推理或取消,WAITING_APPROVAL 只接受批准、拒绝或取消,EXECUTING 接受工具结果、超时和取消请求。终止状态拒绝重新进入运行。

转移必须原子保存新状态和待发送事件,常用 transactional outbox 避免数据库已提交而消息未发。消费者按事件 ID 幂等处理。消息至少一次投递时,重复事件不会再次触发工具;至多一次投递则可能丢失,通常不适合关键状态。

4.2. 事件与物化状态

事件日志是不可变事实,物化状态是加速读取的投影。事件“付款请求已被供应商接受”不会因后来退款被删除;当前订单投影则变为已退款。时间问题读事件,当前 UI 读投影。

快照保存某个事件序号后的完整状态,恢复时加载快照并重放后续事件。快照哈希和 reducer 版本进入元数据。reducer 语义变化后,需要迁移或用旧版本回放,不能让同一事件在不同日期产生不同状态。

事件载荷区分控制字段和大产物。日志全文、截图与模型原始响应存入对象存储,事件只保存引用、哈希、大小与权限。删除敏感产物后,事件仍能证明发生过调用,却不能泄露原文。

4.3. 双写与因果顺序

工具执行和状态提交跨越两个系统,无法用单库事务包住。Runtime 先持久化意图与动作 ID,再调用外部系统,得到结果后持久化提交事件。崩溃发生在任意两步之间,都有可恢复状态。

时间戳不足以建立因果顺序,事件使用 parent ID、动作 ID 与单运行序号。并行工具各有子序列,汇合节点等待明确依赖。迟到结果可以进入审计,却要由状态机判断是否仍能推进运行。

取消请求与工具成功竞态时,二者都记录。若提交先发生,取消只停止后续动作;若取消使执行器在提交前终止,结果是未提交。UI 可以显示“已取消,但取消前动作已经完成”,不能把外部事实改成未发生。

5. 故障、恢复与可观测性

长时间运行还要处理并发与背压。调度器不能因为模型连续提出十个动作,就同时启动十个共享资源写入。每个资源可设置串行键,例如同一仓库分支、同一订单或同一文档;相同串行键的动作进入有界队列,不同资源才允许并行。队列已满时明确拒绝或暂停规划,不能无限占用内存,也不能丢弃最早动作后继续假装轨迹完整。

并行节点的完成不等于汇合节点可提交。汇合器检查每个依赖的状态版本、输出 schema 和资源版本;任一分支被取消、失效或重试,汇合结论随之失效。两个分支若修改同一文件,不能按完成时间覆盖,而要进入冲突状态,由重新规划或确定性合并器处理。模型生成的合并建议仍需基于双方确切版本执行。

预算也是状态机的一部分。token、时间、工具次数、外部费用和失败次数分别计量,预留验证与清理额度。若规划阶段耗尽全部预算,系统不应跳过测试后直接宣布完成;它应进入预算不足的终止态,保留已有成果和未满足谓词。预算变更由显式事件完成,使临时追加额度可审计。

验证失败不能统一回到任务起点。verifier 输出结构化失败:哪个完成谓词未满足、观察来自哪个版本、失败是否可局部修复、已有提交是否仍有效。编译失败可以让相关补丁节点重新规划;生产健康检查失败则要先判断部署是否已经生效,再决定回滚、修复还是停止。验证器本身异常与被验证对象失败是两类事件,前者不应污名化有效产物。

Runtime 还需区分控制面可用性和数据面执行。调度服务失联时,已经交给隔离执行器的命令可能继续运行;执行器把结果写入本地耐久信箱,控制面恢复后按动作 ID接收。若没有这条回传路径,控制面会把暂时失联误认为工具未执行。对于无法安全离线完成的高风险动作,租约过期后执行器在提交点前停止。

租约不是幂等的替代品。工作器领取动作时得到带期限的租约和 fencing token,写回结果必须携带当前 token。旧工作器网络恢复后提交过期结果,会被权威状态拒绝;新工作器即使重新处理,也仍使用同一逻辑动作 ID 去重。租约解决谁有资格推进,幂等键解决重复请求是否制造第二个效果。

运行级心跳只证明进程仍在,不证明任务有进展。每个阶段保存最近进展事件,例如新增 token、工具输出偏移、外部状态变化或验证步骤。监控分别检测失联、无进展和超出阶段时限。无进展告警可以触发诊断或取消,但不能在没有提交信息时直接另起同一写动作。

人工介入也必须进入事件流。操作员可以确认外部动作已提交、标记结果无法确认、批准补偿或终止运行;每项操作都携带依据和作用版本。直接改数据库中的状态会破坏历史与投影,使后续恢复无法理解为何跳过某一步。管理界面只是事件命令的入口,不是绕过状态机的后门。

归档运行时,控制事件、业务产物和调试细节采用不同保留期。状态转移与外部交易引用通常长期保留,含个人数据的工具原文按策略删除,调试采样可更短。删除产物后保留墓碑、哈希和删除原因,使事件引用不会变成无法解释的空洞,也避免为可观测性无限复制敏感内容。

容量测试要模拟大量同时等待审批、工具长时间无响应、事件消费者落后以及投影重建。评测重点包括队列是否有界、优先级是否公平、取消能否及时传播、恢复是否会形成重试风暴,峰值请求数只是其中一项。低风险读取可以降级排队,高风险写入则宁可明确暂停,也不能在状态服务拥塞时绕过持久化边界。

恢复演练需要使用生产等价的外部依赖语义。内存假服务总能精确返回成功或失败,覆盖不了真实 API 的异步受理、分页查询、最终一致和重复回调。测试替身应能返回“已受理但尚不可查”、在查询之间改变状态,并重复发送旧通知。系统面对这些序列时仍要维持单一逻辑动作和单调状态版本。

对文件类任务,崩溃测试同时检查临时文件、原子重命名和版本控制状态。写到一半的文件不能成为已提交结果,已成功重命名但回执丢失则应由内容哈希识别。对数据库类任务,验证事务提交记录与业务幂等表;对浏览器任务,区分页面点击、服务端受理和最终业务完成。每类工具都应声明自己的提交探针,通用 Runtime 不凭返回文案猜测。

任务完成后生成效果清单:读取过哪些受版本约束的输入,创建或修改了哪些资源,哪些动作得到批准,哪些补偿仍可执行,哪些未知状态需要关注。清单来自事件投影,可以与最终答复分别生成。即使模型总结遗漏某个副作用,系统记录仍能向用户和后续恢复器呈现真实环境变化。

5.1. 故障模型

故障至少分为策略错误、输入校验失败、权限拒绝、暂时执行故障、永久执行故障、未知提交、状态冲突、验证失败和基础设施中断。类别决定后继动作:修参数、请求授权、有限重试、查询、重规划或停止。

超时不是结果。客户端没有收到响应时,外部服务可能未接收、正在处理或已经提交。只读动作通常可重试,写动作进入 UNKNOWN_COMMIT,只能用幂等键查询或人工核对。把所有超时映射为失败会制造重复副作用。

进程崩溃使内存推理、连接和子进程丢失。启动恢复把僵尸 running/queued 标记中断,不自动重建执行体。已经提交的环境事实从事件和外部查询恢复,用户可明确发起新尝试。

5.2. 幂等与补偿

逻辑动作 ID 由 Runtime 生成,绑定运行、节点、参数哈希和资源版本。相同 ID 与相同参数重复请求返回第一次结果;相同 ID 与不同参数返回冲突。模型不能自行换键绕过去重。

跨系统很难保证 exactly-once,常见语义是至少一次投递加幂等消费。无法幂等的动作采用查询、人工恢复或补偿。邮件已发送无法撤回为“未发送”,补偿只能再发更正;退款是新交易,不是删除付款事件。

Saga 为每个已提交步骤定义补偿动作。订单创建、库存锁定、付款成功后通知失败,可发送补发通知;付款失败则释放库存并取消订单。补偿也可能失败,需要自己的状态、权限与 verifier。

5.3. 检查点与恢复算法

恢复器读取非终止运行并检查最近状态。模型执行体丢失会使 THINKING 中断。WAITING_APPROVAL 保持等待并检查有效期。EXECUTING 使用动作 ID 查询,VALIDATING 可重跑无副作用 verifier,UNKNOWN_COMMIT 则保留人工核对或查询路径。所有判断都写成状态事件,保证下次恢复得到相同事实。恢复失败也会形成终止原因和处置入口,避免运行永久停在模糊的进行中状态。

检查点保存任务、状态版本、计划、已提交效果、待处理动作、预算和事件游标。它不保存“模型正在想什么”的虚假连续性。恢复后上下文从权威状态重建,旧临时推理仅作历史产物。

资源版本也要核对。暂停期间文件、网页或数据库可能变化,依赖旧版本的计划节点失效。恢复器传播失效并保留仍有效成果,而非全量重做。

5.4. Trace因果链

一条完整链从用户请求到 run ID、模型 call ID、候选动作、授权、tool call ID、外部 transaction ID、observation、state version 和 verifier ID。每个 ID 有 parent 与时间,能够回答谁触发谁。

OpenTelemetry span 可承载调用关系,但业务事件仍需记录提交语义。HTTP 200 只表示协议响应,不能代表订单已完成;业务 span 属性包含动作类型、资源、幂等键和提交状态,敏感值使用哈希或受控引用。

诊断从首次偏离开始。最终测试失败后,沿因果链查看补丁基于哪个文件版本、模型看到哪些 observation、工具实际改了什么、验证器运行在哪个提交。相关日志无需靠全文搜索拼接。

完整故障轨迹。

Agent 请求创建云实例,Runtime 写入动作 op-7 后调用云 API。云端创建成功,网络响应丢失,本地进入未知提交。服务随即重启,恢复器读取 op-7 并按客户端令牌查询,得到实例 i-9,补写提交事件,再运行健康检查。

若恢复器直接重试,会创建两个实例;若把超时标失败并结束,系统又会遗漏真实资源。查询使本地状态与环境重新一致。健康检查失败不否认实例存在,而是产生修复或销毁的新动作。

另一个轨迹中,用户在测试执行期间取消。测试成功事件晚于取消请求到达。reducer 保存成功 observation,却禁止开启部署节点,最终状态为取消。事件顺序和状态规则同时保留事实与用户意图。

可靠性评测。

故障注入在意图提交前、外部提交后、事件写入前、验证期间和 UI 通知时杀进程。每个注入点检查外部资源数、运行终局、重复事件和恢复动作。属性测试随机排列批准、取消、成功与超时事件,验证不变量。

指标包括恢复成功率、重复副作用率、未知提交停留时间、平均恢复步骤、事件丢失、投影延迟和人工介入。单纯“服务重新启动”不能证明恢复正确,必须核对环境终局。

Trace 评测给定故障,要求定位首次偏离及其父事件;删掉关键关联字段后比较诊断时间。可观测性价值体现在因果定位和安全恢复,不在日志行数。 分布式 Runtime 还要处理控制面与执行面失联。调度器把动作意图持久化后交给工作器,工作器可能继续运行,而控制面暂时不可达。执行结果写入本地耐久信箱,恢复连接后按动作 ID 回传。控制面没有收到心跳,只能判断失联,不能推断动作未执行。

高风险工作器使用租约与 fencing token。领取动作时得到单调 token,提交状态必须携带当前值;旧工作器网络恢复后,其过期结果进入审计但被 reducer 拒绝。租约决定谁有推进资格,幂等键决定重复外部请求是否产生第二个效果。

心跳和进展要区分。进程仍在发送心跳,却可能卡在无进展循环;长工具调用没有频繁事件,也可能正常执行。每个阶段声明进展信号和时限,监控分别检测进程失联、阶段超时与状态无变化。自动处置必须尊重提交语义。

队列背压是可靠性的一部分。执行槽已满时继续让模型产生动作,会形成大量过期意图和资源版本冲突。Runtime 暂停规划、限制每个运行的在途节点,并按优先级与等待时间公平调度。队列长度有硬上限,拒绝原因对用户可见。

事务 outbox 解决状态与事件双写。权威数据库在同一事务中写入状态和待发布事件,投递器异步发送;消费者用 inbox 记录事件 ID 幂等处理。投递器在发送后、标记前崩溃会产生重复事件,但不会重复业务效果。直接改库后再发消息会留下永久静默窗口。

外部系统不参与本地事务时,动作日志承担协调。流程为 intent persisted、request sent、external accepted、result observed、local committed、verified。崩溃可能发生在任意边界,恢复器根据最近一项可信证据查询或人工核对。省略中间状态会把真实不确定性压成失败。

投影落后不等于权威状态错误。UI 列表、搜索索引和成本面板各自消费事件,记录游标与代码版本。重建时从快照和后续事件生成新投影,比较关键状态哈希后切换。缓存损坏可以重建,事件事实不随投影删除。

快照频率在恢复时间与写入成本之间取舍。事件量小可从头重放,长运行按事件数或状态大小快照。快照包含 reducer 版本与事件序号,生成过程中仍有新事件时,以明确边界截取。无法验证哈希的快照不能作为恢复起点。

状态迁移需要演练。reducer 语义升级后,对历史事件离线重放,比较终局、已提交效果和待处理动作。若新旧差异是有意修正,使用一次性迁移或更正事件,并保留映射说明。读路径长期猜测多个旧格式会让恢复不可预测。

可靠性目标应按用户可见结果定义。可用性、恢复时间和数据持久性分别设置 SLI,例如运行状态查询成功率、从进程崩溃到明确终局的 P95、重复副作用率和未知提交持续时间。服务进程在线率无法代表 Agent 任务可靠。

错误预算可以指导发布速度,但安全不变量不进入可交换预算。少量页面延迟可以容忍,越权执行和重复付款不能因“本月额度尚有”被接受。不同任务风险分层,低风险读取与不可逆写入拥有不同 SLO 和降级策略。

降级路径要提前设计。模型服务不可用时,系统保留状态并明确暂停;事件推送故障时,页面通过短轮询拉取;策略服务不可用时,高风险动作默认拒绝。降级不能绕过持久化、权限和 verifier,否则可用性提升建立在错误终局上。

灾难演练在外部提交前后、事件落库前后、快照生成、投影切换和通知发送时杀进程。每个注入点核对环境资源数、事件序列、恢复动作与用户界面。只测“应用能重新启动”无法发现重复效果和僵尸运行。

网络分区演练让控制面和工作器各自认为对方失联,随后恢复。fencing token 应阻止双写,迟到 observation 应被保存但不能推进旧版本。若两个分区都能批准并提交同一资源,说明所有权没有被权威状态强制。

容量故障也需注入。大量任务同时等待审批、外部 API 限流、事件消费者落后和日志存储变慢时,队列保持有界,高风险动作不丢审计。恢复后避免所有任务同时重试形成惊群,使用按租户和服务的速率控制。

完整恢复案例可以是批量发布。十个对象逐项提交,前六个成功后服务崩溃。事件日志保存每个对象的独立动作 ID,恢复器查询十个状态,只对明确未提交且可幂等的四个继续。整体任务最终完成,但审计保留中断和恢复过程。

若工具只返回一个批次布尔值,恢复器无法知道哪些对象已经发布,只能全量重试或人工检查。由此可见,工具契约决定 Runtime 可达到的恢复上限。可靠性设计必须下沉到动作粒度,不能只在调度器外围增加重试。

另一个案例是 verifier 自身崩溃。外部写入已提交,验证进程丢失。恢复器重新运行无副作用 verifier,并引用同一提交版本;它不会重放写动作。验证失败则新增修复或补偿节点,已发生效果仍保持 committed。

追踪系统要能关联用户请求、计划节点、模型调用、工具动作、外部交易、状态版本和 verifier。采样不能随机丢掉高风险调用的关键 span;详细内容可按权限单独存储,控制链 ID 与结果分类应完整保留。

日志保护同样重要。命令行、错误栈和 HTTP 头可能含凭据,进入 trace 前过滤;个人数据按字段标记与保留期处理。诊断人员通过受控引用访问必要产物,普通指标系统只看到聚合标签。可观测性不能成为旁路数据湖。

运行手册从状态和证据出发。面对 unknown_commit,操作员看到动作参数哈希、外部查询入口和已尝试步骤;面对冲突,看到相关资源版本;面对中断,看到安全重试条件。让人直接编辑数据库 status 会破坏事件因果,应提供受控管理命令生成新事件。

最终验收要求重复执行恢复测试,并对性质做自动检查:版本单调、终止态不可复活、同一逻辑动作至多提交一次、未批准写动作不可执行、投影可从事件重建。单元测试覆盖 reducer,集成测试覆盖 outbox 与工作器,故障注入覆盖真实进程边界。

备份与恢复覆盖事件、快照、产物元数据和加密密钥。只有数据库备份却缺少对象产物,事件引用会失效;只备份产物而没有事件顺序,又无法重建状态。恢复演练在隔离环境进行,核对运行终局、权限和删除墓碑。

跨区域复制可能延迟。故障切换后,最近事件尚未复制,系统不能在旧状态上重新派发高风险动作。恢复点目标明确说明可能丢失的窗口;对不可重复效果,使用外部查询和人工核对弥补。

时间语义需要单调时钟和业务时间分开。墙钟用于用户展示与跨系统记录,单调时钟用于本进程超时。NTP 调整不应让租约突然延长或缩短。跨进程因果依赖事件 ID 与版本,不依赖毫秒时间戳排序。

成本失控也是运行故障。模型在循环中持续调用昂贵工具,系统仍可能技术上可用,却已经违反预算。每个动作在派发前预估费用,提交后记实际值;异常增长触发暂停,并保留当前成果与恢复选项。

用户可见错误包含发生位置、当前事实和可行动建议。仅显示“任务失败”会让用户重复触发未知动作;显示“支付响应丢失,正在按交易 ID 查询”则能阻止重复操作。内部堆栈放入受控诊断产物。

可靠性回归集收集历史中断、竞态和迟到事件的最小轨迹。每次 reducer、队列或工具协议变更都重放,比较外部效果与状态哈希。随机性质测试寻找新排列,历史回归保证已经修过的事故不再出现。

部署采用分阶段流量和影子投影。新投影先消费相同事件但不服务用户,与旧投影比较;新调度器先处理低风险任务。发现差异时停止扩大范围,保留原事件用于复现。状态迁移不靠线上用户承担首次试验。

Runtime 的可靠性最终体现为错误被转化为明确状态。超时变成未知提交,冲突变成重新规划,崩溃变成中断与恢复证据。可解释状态是自动恢复和人工介入共同依赖的基础。

发布前还要验证关闭流程。服务收到终止信号后停止接收新动作,等待可安全完成的只读任务,把未越过提交点的写动作标中断,并刷新 outbox。直接杀死进程会增加 unknown_commit;无限等待又阻塞部署。每类工具声明可中止阶段,关闭控制器据此设置有界期限。

部署完成后,从事件与数据库抽样核对在途运行,没有旧实例仍持有有效租约。新版本先消费积压通知并追上投影游标,再对外报告 ready。健康检查若只看 HTTP 端口,会在状态尚未水合时过早接流量。

这些操作流程也进入自动演练。滚动升级、数据库短暂只读、消息系统重复投递和对象存储不可用分别测试,确认系统选择正确降级。运行手册与代码使用同一状态名称,避免事故时人工把 interrupted 误改为 completed。

演练结果绑定版本和环境,失败项在下次发布前复测。没有定期执行的恢复文档只能说明设计意图,不能证明当前系统仍具备恢复能力。