当前任务已经中断时,恢复顺序应该是:先确认没有第二个恢复执行器正在领取同一个 run_id,再从日志或状态库中保住 run_id、输入快照、最后成功节点和工具回执;对已经发出的外部请求,先用稳定幂等键或业务键查询它是否已经生效,最后才从最近的可信节点继续。直接再点一次“运行”,最容易留下的不是“任务失败”,而是重复发布、重复发消息或重复建单。
禁用 Windows Update 只能降低任务被打断的概率,不能替代任务恢复机制。V2EX 帖描述的典型事故是:一整晚安排的 Agent 自动任务没有完成,醒来后看到的却是 Windows 登录页面,等待的上线发布也没有出现(V2EX 原帖)。但登录页面本身并不能证明系统刚刚重启,更不能证明这次中断一定由更新引起。可靠的恢复设计应在机器重启后仍然知道任务做到了哪里,并能安全地决定跳过、重试还是交给人确认。
登录页面就说明 Windows 刚重启了吗
不能只看登录页面。锁屏、睡眠或休眠恢复、注销后重新登录,都可能把用户带到类似 Windows 登录界面的画面;Agent 子进程崩溃、模型调用超时、计划任务提前退出,也可能发生在机器根本没有重启的时候。
先把开机时间、计划任务运行时间和系统事件对齐。下面命令中的 AgentNightly 是占位任务名,需要替换成真实名称:
Get-CimInstance Win32_OperatingSystem |
Select-Object LastBootUpTime
Get-ScheduledTask -TaskName "AgentNightly" |
Get-ScheduledTaskInfo
schtasks /Query /TN "AgentNightly" /V /FO LIST
LastBootUpTime 只能作为证据之一。如果它晚于任务原定的结束时间,并且系统事件、Windows 更新历史和任务停止时间能够互相对应,才比较支持“任务运行期间发生过重启”。如果开机时间没有变化,而电源事件更接近锁屏、睡眠或唤醒,就应继续检查 Agent 进程、模型请求和外部工具的日志,而不是先修改更新策略。
计划任务的历史同样重要。Get-ScheduledTaskInfo 和 schtasks /V 可以帮助核对任务是否真的启动过、最近一次运行结果以及是否仍在运行;但退出结果只能说明计划任务观察到的进程状态,不能说明发布是否成功。Agent 启动后自行崩溃、任务在恢复过程中启动了两份,都会让“最近结果”看起来正常。
事件查看器中的系统事件、电源事件和任务计划程序历史应一起看。不同 Windows 版本的事件编号和显示内容可能不同,因此这里不写死事件编号,应该以目标机器实际记录为准。资料 提到 Application Recovery and Restart:应用支持并正确配置时,它可以在应用因更新而退出前保存数据或状态;但同一资料也指出,没有一个托管 API 可以在事后直接判断某次重启是否由更新导致。 这条资料年代较早,具体能力仍应按目标 Windows 版本核对。恢复逻辑不应建立在“重启后先猜原因,再决定怎么恢复”上。
该改 Windows、任务计划程序还是 Agent
这三层都要看,但解决的问题不同。Windows 更新策略负责降低任务运行期间被打断的概率;任务计划程序负责在合适的身份和条件下重新拉起进程;Agent 自己负责保存工作流位置、闭合工具调用,并处理外部操作是否已经生效。计划任务能够重新启动一个进程,不等于已经恢复了被打断的业务任务。
Windows 侧应优先使用当前系统版本和受管环境已经支持的更新通知、延迟重启与维护窗口机制。具体支持范围取决于 Windows 版本、设备管理方式以及更新类型,不能把某个旧版组策略或注册表值直接复制到 Windows 10/11。本文不提供跨版本通用的更新开关路径,落地时应核对目标版本对应的 Microsoft 官方文档和组织策略优先级。
网上常见的旧做法不能直接照搬。Windows XP/Vista 时代的资料曾记录过“重新提示计划安装重启”和“禁止计划安装自动重启”等组策略,其中重新提示间隔被记录为默认 10 分钟。 这属于旧版本环境的做法。另一份针对 Windows 10 1703 的旧答案曾建议停止并禁用 wuauserv:
sc stop wuauserv
sc config wuauserv start= disabled
这最多可以作为历史临时处置的识别依据,不应作为当前生产机的长期方案。停止更新服务并不会消除更新,只会让更新继续积压,也让组织无法通过正常策略控制安装与重启窗口。真正需要解决的是任务可恢复性,而不是让机器从此不再安装补丁。
任务计划程序这一侧,要检查运行身份、是否依赖用户会话、失败后的重新启动策略、是否允许并发实例、执行时限以及整机启动后的触发条件。若任务依赖交互式桌面,“未登录也运行”并不代表 GUI 自动化仍然可用;锁屏后的交互会话是否满足自动化要求,需要单独验证。核心的上线发布任务更适合放在持续在线的 Windows 服务、虚拟机或其他运行节点上,但 GUI 自动化不能因为改用服务就自动获得交互能力。
还要给计划任务加一道应用层互斥。即使任务计划程序配置了失败重试或系统启动后的触发条件,恢复进程也必须通过 run_id 对应的锁或租约,避免两个执行器同时领取同一任务。否则所谓“自动恢复”很可能只是启动了两份相同工作流。
断点到底要保存什么
普通 checkpoint 和一段对话摘要都不够。训练任务中的 checkpoint 通常用于保存模型、优化器等训练状态,可以作为断点续训的参考;但 Agent 长流程恢复还必须绑定业务节点和工具回执。 Windows 自动更新让长任务半路停止后,内存里尚未持久化的上下文和已取得数据也无法继续使用。
一个实用的最小状态结构可以先按下面的方式设计:
{
"run_id": "<任务实例ID>",
"input_ref": "<输入快照或内容摘要>",
"node_id": "<当前业务节点>",
"node_state": "running",
"tool_call_id": "<工具调用ID>",
"idempotency_key": "<稳定幂等键>",
"tool_receipt": null,
"config_version": "<配置版本>"
}
这是结构示意,不是真实任务数据。running 表示节点在中断前仍处于执行中,tool_receipt: null 表示结果尚未确认,不能直接解释为调用失败。
其中至少要保住任务 ID、原始输入、当前节点状态和已经完成的工具回执。 原始输入最好保存快照、文件版本或内容摘要,否则恢复时重新读取的文件可能已经变化,后半段任务会建立在另一份输入上。配置也要记录版本,并在恢复前校验模型参数、工具开关、安全策略和依赖环境。配置中心场景中,可以在运行时使用 JSON Schema 做一致性校验,这个思路可以借鉴,但不代表必须引入特定配置中心产品。
恢复决策取决于“调用前意图”和“调用后回执”。在发出有副作用的请求前,应先持久化工具调用 ID、幂等键和请求参数摘要;外部系统确认执行后,再写回返回标识、结果和错误码。若外部请求已经发出,但进程在回执落盘前被重启,状态就属于“结果不确定”,而不是简单的失败或成功。
自然语言摘要可以帮助模型理解上下文,但不能单独承担恢复状态。摘要可能漏掉字段,也可能把“准备调用”描述成“已经完成”。结构化记录至少应能回答:这是哪一次运行、当前在哪个节点、准备执行什么、是否已经产生外部影响、拿到了什么回执。
恢复时先对账,再决定跳过还是重试
恢复器看到 node_state 仍是 running 时,不能直接重跑。应先取得该 run_id 的执行锁,再读取检查点,并按工具调用状态分类处理。
如果只有待执行记录,尚未持久化调用意图,且对应动作按业务定义可以安全重试,例如读取文档、查询数据库或调用搜索,可以按配置好的重试次数和退避策略重新执行。重试次数和退避参数应来自任务配置,本文不设定统一数值。
如果已经存在完整回执,说明该动作已经完成,恢复器应记录结果并进入下一个节点,而不是再执行一次。若只有调用意图,没有回执,则必须先使用稳定请求标识或业务键查询外部系统。查到结果,就补写回执并跳过原动作;明确查不到,才允许使用原来的幂等键重试。幂等键必须在第一次调用前生成并持久化,不能在重启后重新生成一个看起来相同、实际却不同的键。
发送通知、创建工单、写入记录等操作不能只凭超时判断失败。请求可能已经到达外部系统,只是响应丢失或回执尚未写入。恢复前先查,可以避免第二条重复消息或第二张重复工单。 反过来,付款、删除、批量修改权限等高风险动作一旦出现不确定状态,应直接转人工,不让 Agent 自行猜测。
如果外部系统既不支持幂等键,也不能按业务键查询,就不具备可靠的自动恢复条件。此时自动重试与自动跳过都有风险,唯一合理的动作是保留现场并交给人确认。
发布任务也可以用同样的思路处理。把“上传制品”和“切换发布版本”拆成两个有明确状态的节点,恢复时先检查目标版本和制品摘要是否已经存在。相同摘要的重复激活应当是幂等操作;如果发布平台不提供这种查询能力,就需要从流水线记录或发布记录中确认,而不是看到 HTTP 请求失败就重新发布。
文件写到一半或审批悬空时怎么处理
文件输出应当设计成“要么完整,要么尚未开始”。写入正式路径前,先在同目录写临时文件,完成刷新和关闭后再替换正式文件,并保存预期摘要。临时文件与正式文件位于同一目录、同一个文件系统,才能把最终替换设计成原子操作。重启后遗留的临时文件不能直接算作成功产物,应根据检查点中的文件版本和摘要判断是重新生成、隔离旧文件,还是确认已有正式文件确实匹配。
如果正式文件存在,但节点回执尚未写入,也不能仅凭“文件存在”认定成功。应比较文件摘要与构建清单;摘要一致才补写完成回执,不一致则继续恢复该节点。这样可以避免把半写文件当成可部署制品。
模型工具调用也需要成对保存。持久化记录中如果只有“准备调用”,却没有对应的完成回执,残缺的 tool_use / tool_result 历史可能导致下一次加载失败。 对模型请求本身,可以在安全点重新发起;对会产生外部副作用的工具,则仍要按幂等键先查询再决定是否重试。
等待人工审批时,应把状态保存为 waiting_approval,同时记录审批请求 ID。恢复后应继续等待原审批,而不是重新发起一轮审批。如果审批已经通过但回执没有写回,也要先查询审批系统状态。审批等待、模型等待和外部调用等待是三种不同状态,不能都简化成 running。
用户主动停止和机器突然消失也不完全相同。主动停止可以在工具调用之间设置安全点,让短暂的写文件操作结束;机器重启或断电则不能依赖退出回调来完成最后一次落盘。 因此,检查点必须提前写,而不是等任务结束时再写。对于子进程、流式回复和外部副作用,也要分别记录,不能指望系统关闭时统一收尾。
怎么证明恢复成功而不是只是进程启动
恢复验证至少要覆盖计划任务、Agent 工作流和业务结果三层。计划任务退出码为零,只能证明进程以它认为成功的方式退出,不能证明部署成功。
计划任务层可以再次检查运行时间、最近结果和实例状态:
Get-ScheduledTask -TaskName "AgentNightly" |
Get-ScheduledTaskInfo
schtasks /Query /TN "AgentNightly" /V /FO LIST
这里要确认的不是单纯“最近结果为零”,还包括任务是否在预期时间启动、是否已经退出、是否仍残留第二个实例,以及恢复器是否正确释放了锁。
Agent 层要核对同一个 run_id 是否从最后可信节点继续,而不是重新创建一次运行;未闭合的工具调用是否完成对账;恢复时使用的 config_version 和输入快照是否与原任务一致;外部系统是否只产生了一次预期副作用。还要检查恢复路径有没有跳过必要节点,例如制品生成成功但尚未激活发布版本时,不能因为模型生成阶段结束就直接报告部署完成。
业务层要核对真实制品和发布结果。下面命令中的文件路径、域名和健康检查路径都必须替换成真实值:
Get-FileHash -Path 'C:\release\<artifact-file>' -Algorithm SHA256
Invoke-WebRequest -Uri 'https://<域名>/<健康检查路径>' -UseBasicParsing
制品摘要应与构建流水线记录的摘要一致,站点响应中的发布版本也应与本次 run_id 对应。Invoke-WebRequest 返回成功只说明地址能够访问,不能证明页面内容是新版本。如果项目没有健康检查接口,就改为核对流水线记录、制品摘要和页面中的发布版本标记,并留意代理或 CDN 缓存仍返回旧页面的情况。
只有这三层证据能够串起来,才能说“任务恢复”同时也是“部署成功”。
怎样用一次重启演练证明方案有效
在测试环境里,不能只模拟“进程还能启动”。应分别在模型调用之后、外部操作已经生效但回执尚未写入时、文件写入过程中以及等待人工审批时制造中断,检查恢复行为。
模型调用后中断时,Agent 应能从最后可信节点继续;如果调用本身可安全重试,不应丢失已经完成的业务节点。外部操作生效但回执未写入时,恢复器必须先查询外部系统,确认已经生效后跳过,不能再发一次。文件写入过程中断后,临时文件不能被当成正式产物,正式文件摘要必须与预期清单一致。等待审批时断,恢复后应保持原审批的等待状态,不能重复创建审批请求。
演练还要覆盖同一请求连续提交两次,以及两个执行器同时尝试领取同一任务。前者检查业务去重,后者检查锁、租约或原子状态更新能否阻止并发执行;这些也是长任务恢复测试中需要主动覆盖的场景。
最后再模拟一次真实的 Windows 重启或进程终止,重新核对计划任务结果、Agent 检查点和最终部署制品。如果这次演练只能证明“机器开机后任务又跑了一遍”,却无法证明未完成动作被正确对账、半写文件没有被误用、等待审批没有被重复发起,那么恢复机制仍然没有完成。
稳定不等于永不重启
这类系统上线前,我会把“受控中断后能从可信节点恢复,并且不产生重复副作用”列为硬门槛。Windows 是否在半夜重启,不应决定 Agent 是否具备可恢复性;重启后,Agent 仍需说清任务进度,并能安全地选择跳过、重试还是交给人工确认。













暂无评论内容