TL;DR(核心总结)#

核心结论:状态落盘不能只停留在对话历史(Messages),必须下沉到执行状态(Tasks & Tool Progress),并结合工具的幂等性策略(Replay Policy)做分级恢复——即将“恢复给模型看的对话上下文”升级为“恢复给调度器看的执行状态机”。

实现 Agent 的 Durable(中断恢复)特性,核心在于将状态落盘从“对话历史层”下沉到“执行运行时层”。

在 Agent 运行过程中,不仅要把常规的 User、Assistant 消息及最终结果做历史落盘,更重要的是对每一轮模型生成和工具调用的执行进度做细粒度的状态持久化。当模型发起工具调用时,框架遵循“先落盘意图,再触发执行”的原则,在真正调用外部接口前先原子提交 Checkpoint,从而精确记录每个工具的生命周期状态——区分它是“尚未开始(Pending)”、“正在执行中(Executing)”还是“已经完成(Completed)”。

当进程崩溃重启后,系统不需要让大模型重新推理或整轮重跑,而是直接由运行时调度器(Scheduler)接管恢复:


一、从“恢复对话”到“恢复执行”的架构对比#

维度普通 Agent(仅消息落盘)Durable Agent(状态机落盘)
持久化粒度仅保存 User、Assistant、已完成的 Tool Result消息历史 + 请求生命周期(Submission)+ 任务树(Task)+ 阶段检查点(Checkpoint)
并发工具中途崩溃丢失在途状态,无法区分“没调”还是“调了一半”通过执行前 Checkpoint 精确识别每个子任务所处阶段
重启恢复主导方大模型(LLM)根据残缺历史去“猜”下一步,或整轮重跑运行时调度器(Scheduler)先走完已确定的程序步骤,收敛后再唤醒模型
副作用安全保障极易发生重复调用或状态不一致按 Replay Policy 分级恢复(安全重放 vs 异常交还)

二、通用 Durable Agent 的 4 个核心工程设计#

1. 显式建模:将 Agent Loop 拆解为可持久化的任务树与状态机#

把运行时的调度状态和给模型看的对话历史解耦。除了只追加(Append-only)的对话消息外,显式持久化三类运行时实体:

2. 边界原则:先落盘意图(Pre-execution Checkpoint),再触发外部副作用#

不能写完 Assistant 消息就凭内存去调工具,也不能等工具跑完了才落盘。必须严格卡住两个原子提交(Atomic Commit)边界:

3. 恢复调度:按工具幂等性策略(Replay Policy)分级收敛#

进程恢复后,调度器先扫描未完成的任务树,按状态与策略分类收敛,不急于调用大模型:

进程重启 -> 调度器扫描未完成 Generation Task 依赖的 Tool Tasks
├─ 状态为 Completed ──> 直接复用已落盘的 Tool Result(不重跑)
├─ 停留在 Call 阶段 ──> 尚未产生外部调用,正常启动执行
└─ 停留在 Execute 阶段(中途崩溃,外部状态未知)
├─ Replay: Safe(只读/幂等工具) ──> 使用落盘的最终参数自动重放
└─ Replay: Unsafe(写操作/发邮件等)──> 拒绝自动重试!写入“执行中断、状态未知”结果
──> 待所有工具收敛后交还 LLM(查询核实)或触发人工介入

只有当父任务等待的所有子工具任务都到达终态后,调度器才会在同一事务中完成当前生成轮次,并开启下一轮 Generation Task 请求模型汇总。

4. 明确边界:运行时控制流恢复 vs 外部副作用与工作区回退#

Durable Runtime 解决的是“已确定的程序控制流与已提交状态的精确接续”,但要清晰划定架构边界:


三、高频面试追问与回答模板#

1. 完整口播模板(1~2 分钟,适合“如何设计高可靠/可恢复 Agent”问题)#

在设计生产级 Agent Runtime 时,实现 Durable(中断恢复)的核心思想是:将状态落盘从“对话历史层”下沉到“执行运行时层”,把“恢复给模型看的对话上下文”升级为“恢复给调度器看的执行状态机”。
普通 Agent 只对 User、Assistant 和已完成的 Tool Result 做消息级落盘。如果模型并发调用了 3 个工具,前两个跑完、第三个跑到一半进程挂了,只恢复消息历史是没法自动接续的——要么丢失在途状态,要么得让模型重新推理猜进度。要实现通用的 Durable 机制,我们在架构上主要做四件事:
第一,将 Agent Loop 显式建模为可持久化的任务树与状态机。
我们把运行时的调度状态和对话历史分离。除了 Append-only 的对话消息,还会持久化三类执行状态:用户的请求生命周期(Submission 与持久化排队队列,带 RequestId 防客户端重试重复触发)、每一轮模型生成的父任务(Generation Task),以及每个具体工具调用的子任务(Tool Task)。当模型决定调用多个工具时,框架会在同一个原子提交里,同时落盘模型回复、创建所有工具子任务,并把当前生成任务标记为 Waiting 状态、记录依赖的子任务 ID 列表。
第二,严格遵循“先落盘 Checkpoint,再产生外部副作用”的边界原则。
每个工具调用内部还会细分阶段。在完成参数校验、真正执行外部 I/O 之前,必须先做一次 Commit,把状态推进到 Execute 阶段,并持久化最终参数和该工具的重放策略(Replay Policy)。这样崩溃重启后,调度器能精确区分一个工具是“还没开始调”还是“已经在执行中途被打断”。
第三,重启时由调度器按工具的幂等性策略分级恢复,而不是先调模型。
进程恢复后,调度器先扫描未完成的任务树:已完成的工具直接复用落盘结果,绝不重跑;还没开始执行的工具正常启动;而卡在 Execute 阶段(中途中断)的工具,则检查其重放策略——如果是只读或声明为幂等安全(Replay Safe)的工具,直接用落盘参数自动重放;如果是发邮件、写操作等非安全工具,框架绝不盲目重试,而是将其标记为“执行中断、状态未知”写回工具结果,等所有工具汇总完后交还给 LLM(让模型决定是调查询接口确认状态,还是请求人工介入)。只有当所有依赖的工具子任务都收敛到终态后,调度器才会唤醒下一轮模型生成。
第四,明确框架与外部环境的边界。
Durable Runtime 保证的是“控制流与已提交状态”的精确接续,而不是魔法般地回滚外部世界。对于外部 API 副作用,需要结合工具层的幂等键(Idempotency Key)去保障;而对于本地编码场景下的“代码改错了想撤回”,这属于工作区文件快照回退(Workspace History),和负责“未完成任务继续往前走”的 Durable 执行恢复是正交的两套机制,实际工程中通常会配合使用。

2. 常见深挖追问#