
1. 从一次线上事故说起Agent 为什么需要“运行机制”去年冬天我负责的一个自动化运维 Agent 在凌晨三点突然“失忆”了。它原本正在执行一个跨系统的数据同步任务前面 40 多分钟都跑得好好的结果在第 47 分钟的时候它把已经处理过的三个批次又重新处理了一遍导致下游系统出现了大量重复数据。事后复盘问题出在两个地方一是上下文在长任务中被截断Agent 丢失了“我已经做过什么”的记忆二是检查点机制形同虚设任务中断后没有从断点恢复而是从头再来。这次事故让我彻底意识到一个能真正上生产的 Agent光有“聪明的大脑”远远不够它还需要一套完整的运行机制——上下文怎么管、检查点怎么打、任务怎么恢复、循环怎么控制、资源怎么兜底。这套机制决定了 Agent 是“玩具”还是“工具”。这篇内容就是把我这两年踩过的坑、调过的参数、重构过的架构完整地拆一遍。核心关键词会围绕Agent、上下文、检查点、任务恢复、资源管控展开。不管你是刚接触 Agent 开发的新手还是已经在做多 Agent 协作的老手只要你的 Agent 需要长时间运行、需要处理复杂任务、需要在异常后还能爬起来继续干活这篇内容都能给你一套可以直接抄作业的方案。我先把结论摆在这儿Agent 的稳定性80% 取决于运行机制的设计而不是模型本身的能力。模型再强上下文丢了、状态乱了、资源爆了照样白搭。下面我按“设计思路 → 核心细节 → 实操实现 → 问题排查”的顺序一层层拆开讲。2. Agent 运行机制的整体设计与思路拆解2.1 为什么不能把 Agent 当成一个“大函数”来写很多人第一次写 Agent思路是这样的给一个大模型一个提示词让它输出下一步动作执行完再把结果塞回去循环直到任务完成。这个思路本身没错但它把 Agent 当成了一个“大函数”——输入进去输出出来中间状态全在内存里。问题在于真实任务往往要跑几分钟甚至几小时中间可能遇到网络抖动、API 限流、进程被杀、机器重启。一旦中断内存里的状态全没了任务只能从头再来。更麻烦的是有些操作是不可逆的比如已经发了邮件、已经扣了款、已经删了文件你不可能简单地“重跑一遍”。所以Agent 的运行机制必须解决四个核心问题上下文管理Agent 在每一步需要知道什么历史信息怎么压缩、怎么检索、怎么丢弃检查点机制在哪些关键节点把状态持久化存什么存哪里任务恢复中断后怎么判断从哪继续怎么保证不重复执行副作用操作循环与资源管控怎么防止 Agent 陷入死循环怎么限制 token、时间、调用次数这四个问题不是孤立的它们互相咬合。上下文设计得不好检查点就不知道该存什么检查点粒度太粗恢复时就得重做大量工作资源管控不到位Agent 可能烧光预算还在原地打转。2.2 三种主流架构的取舍ReAct、Plan-and-Execute、状态机在动手之前先选架构。我实际用过三种各有适用场景。ReAct 模式是最常见的思考-行动-观察循环。优点是灵活适合探索性任务缺点是容易跑偏长任务中上下文膨胀快。我一般用它做短任务或者需要动态决策的场景。Plan-and-Execute 模式是先让模型出一个完整计划再逐步执行。优点是全局可控检查点好设计缺点是计划一旦有误后面全错。适合流程相对固定的任务比如数据处理流水线。状态机模式是把任务拆成明确的状态节点每个节点有明确的输入输出和转移条件。优点是恢复能力最强每个状态都可以打检查点缺点是不够灵活需要预先定义状态。我现在的生产级 Agent 基本都用这种尤其是涉及外部副作用的场景。我的建议是如果你的 Agent 需要长时间运行、需要任务恢复优先考虑状态机 Plan-and-Execute 的混合模式。用计划做全局编排用状态机做单步执行和检查点兼顾灵活性和可控性。2.3 上下文、检查点、恢复三者的关系这三者的关系可以用一个类比来理解上下文是 Agent 的“工作记忆”检查点是“存档”恢复是“读档”。工作记忆容量有限所以需要分层短期记忆放当前步骤的细节中期记忆放任务进展摘要长期记忆放跨任务的知识。检查点不是每一步都打而是在状态发生实质变化的节点打比如完成一个子任务、调用了一个有副作用的接口之后。恢复时先加载最近的检查点再根据上下文判断下一步该做什么。这里有个关键设计原则检查点存的是“状态”不是“历史”。历史消息可以丢但状态必须完整。状态包括当前任务 ID、已完成步骤、待执行步骤、关键变量、副作用记录。这样恢复时不需要重放所有历史直接根据状态继续即可。3. 上下文管理的核心细节与实操要点3.1 上下文分层短期、中期、长期怎么划分上下文管理的第一个坑就是把所有东西都塞进一个消息列表。跑个十几步token 就爆了。我的做法是分三层短期上下文是当前步骤直接需要的比如当前要处理的这条数据、上一步的执行结果。这部分保留原始内容不压缩。中期上下文是任务级别的进展摘要比如“已完成 A、B、C 三个子任务当前在 D剩余 E、F”。这部分用模型生成摘要控制在几百 token 以内。长期上下文是跨任务的知识比如用户偏好、历史成功案例、领域规则。这部分存在外部存储里需要时通过检索注入不常驻上下文窗口。具体实现上我会维护一个ContextManager每一步根据当前状态决定注入哪些层的内容。短期内容直接拼中期摘要定期更新长期内容按需检索。注意中期摘要的更新频率很关键。更新太频繁浪费 token更新太慢会丢失关键信息。我的经验是每完成一个子任务更新一次或者每 5 到 8 步更新一次取两者中先到的。3.2 上下文压缩的三种策略与参数计算上下文压缩是绕不开的。我常用的三种策略滑动窗口最简单只保留最近 N 条消息。N 的取值要看任务复杂度我一般设 10 到 20。缺点是可能丢掉早期的关键信息。摘要压缩是用模型把旧消息总结成一段话。这里有个参数要算假设模型上下文窗口是 128K token当前用了 100K需要压缩到 60K那就要把大约 40K 的旧内容压成 10K 以内的摘要。压缩比 4:1 左右比较安全太高会丢信息。关键信息提取是只保留结构化的关键字段比如工具调用记录、决策结果、错误信息。这个最省 token但需要预先定义 schema。我实际用的时候是组合的短期用滑动窗口中期用摘要长期用提取。下面是一个压缩触发的判断逻辑def should_compress(context, max_tokens100000, threshold0.8): current count_tokens(context) if current max_tokens * threshold: return True return False def compress(context, target_ratio0.5): # 保留最近 10 条原始消息 recent context[-10:] older context[:-10] # 对旧消息做摘要 summary llm_summarize(older, max_tokensint(count_tokens(older) * target_ratio)) return [summary] recent3.3 上下文注入顺序对模型决策的影响这个细节很多人忽略但实测影响很大。同样的内容注入顺序不同模型的决策可能完全不同。我的经验顺序是系统指令 → 长期知识 → 中期摘要 → 短期细节 → 当前任务。系统指令放最前面因为模型对开头的内容注意力更强当前任务放最后面因为模型对结尾的内容也敏感这样能保证它聚焦在当前要做的事上。还有一个技巧把最关键的约束放在系统指令的最后一句。比如“不要重复执行已完成的步骤”放在系统提示的末尾比放在中间效果好很多。我做过对比测试同样的约束放末尾的遵守率比放中间高大约 30%。3.4 上下文污染的识别与清理上下文污染是指错误信息、无关内容、矛盾指令混入上下文导致模型决策异常。常见表现是 Agent 反复做同一件事、忽略明确指令、输出格式错乱。识别方法记录每一步的决策和上下文快照出问题时回溯。我一般会在检查点里存一份上下文摘要方便对比。清理策略一旦发现污染回滚到最近的干净检查点重新注入精简后的上下文。如果污染源是某个工具的错误输出就在注入前做过滤比如截断过长的错误堆栈、移除无关的调试信息。实操心得工具返回的内容一定要做长度限制和格式清洗。我见过一个 Agent 因为某个接口返回了 5 万字的 HTML直接把上下文撑爆后面全乱了。现在我的工具封装层统一做截断超过 2000 字符的内容只保留前 500 和后 500中间用省略标记。4. 检查点机制的设计与落地实现4.1 检查点该存什么状态快照的字段设计检查点存什么直接决定了恢复能力。我踩过的坑是存太少恢复时信息不够也存过太多序列化和反序列化开销大。经过几轮迭代我现在的检查点 schema 包含这些字段字段说明是否必需task_id任务唯一标识必需checkpoint_id检查点序号必需timestamp打点时间必需current_state当前状态机节点必需completed_steps已完成步骤列表必需pending_steps待执行步骤列表必需variables关键变量快照必需side_effects已执行的副作用记录必需context_summary上下文摘要建议resource_usage已消耗的 token、时间、调用次数建议side_effects这个字段特别重要。它记录了哪些不可逆操作已经执行过恢复时用来做幂等判断。比如“已发送邮件 ID 12345”恢复时如果发现这一步在记录里就跳过。4.2 检查点的触发时机不是越频繁越好检查点打得太频繁I/O 开销大还可能拖慢主流程打得太稀疏恢复时重做的工作多。我的触发策略是三类状态转移时必打。状态机每进入一个新节点打一个检查点。这是最基本的。副作用操作后必打。调用外部接口、写数据库、发消息之后立即打点。这样即使下一步崩溃也不会重复执行副作用。定时打点。对于长时间运行的单个步骤每隔一定时间比如 30 秒打一个轻量检查点只存进度不存完整状态。具体实现上我会用一个CheckpointManager提供save(state, checkpoint_type)方法内部根据类型决定存储策略。完整检查点存数据库轻量检查点存内存加定期刷盘。4.3 存储选型内存、文件、数据库怎么选存储选型要看任务的生命周期和恢复要求。内存最快但进程一挂就没了只适合轻量检查点或者临时缓存。文件适合单机场景实现简单但并发和查询能力弱。我早期用 JSON 文件后来任务多了之后管理很痛苦。数据库是生产环境的首选。关系型数据库适合结构化状态查询方便键值存储适合高频写入性能好。我现在用的是 PostgreSQL 存完整检查点Redis 存轻量检查点和锁。选型时还要考虑检查点的保留策略。我一般保留最近 10 个完整检查点更早的归档或删除。轻量检查点只保留最近 3 个。4.4 检查点的幂等性保证这是最容易被忽略但最致命的一点。检查点本身要保证幂等同一个检查点重复写入结果应该一致。实现上用(task_id, checkpoint_id)做唯一键写入时用 upsert 语义。恢复时读取最新的完整检查点如果发现检查点损坏或不完整回退到上一个。还有一个细节检查点的写入要和业务操作在同一个事务里。比如“扣款 打检查点”应该是一个原子操作否则可能出现扣了款但检查点没打恢复时又扣一次。如果做不到同事务就要用补偿机制比如先记录“准备扣款”扣款成功后更新为“已扣款”。5. 任务恢复的完整流程与关键实现5.1 恢复流程的五个阶段任务恢复不是简单地“读档继续”它有一套完整流程。我把它拆成五个阶段阶段一检测中断。进程启动时扫描未完成的任务判断哪些需要恢复。判断依据是任务状态不是“已完成”且最近检查点时间在有效期内。阶段二加载检查点。读取最新的完整检查点校验完整性。如果损坏回退到上一个。阶段三状态重建。根据检查点恢复状态机、变量、上下文摘要。这一步要特别注意副作用的处理。阶段四幂等校验。检查side_effects记录对即将执行的步骤做幂等判断。如果某步骤已执行跳过或做补偿。阶段五继续执行。从current_state的下一步开始注入重建的上下文继续循环。这五个阶段里阶段四最容易出问题。我见过太多 Agent 恢复后重复发消息、重复下单。核心原因是副作用记录不完整或者幂等判断逻辑有漏洞。5.2 断点续跑的幂等设计幂等设计的核心是每个有副作用的操作都要有一个唯一标识执行前先查这个标识是否已存在。具体做法给每个副作用操作生成一个operation_id格式可以是task_id step_id hash(参数)。执行前先查记录表如果存在就跳过不存在就执行并记录。对于不支持幂等的外部接口要用补偿机制。比如发邮件如果接口不支持去重就在本地记录“已发送”恢复时先查本地记录。如果本地记录也可能丢那就需要外部系统提供查询能力比如“查询该用户今天是否已收到该类型邮件”。注意幂等判断本身也有开销。对于高频操作查一次数据库可能比操作本身还慢。我的做法是本地缓存加定期同步缓存里存最近执行过的 operation_id命中就跳过未命中再查库。5.3 恢复时的上下文重建策略恢复时上下文不能简单地从检查点里的摘要重建因为摘要可能丢失细节。我的策略是摘要 关键原始记录。摘要提供全局视图关键原始记录提供细节。哪些算关键原始记录最近一次工具调用的完整输入输出、当前正在处理的原始数据、最近的错误信息。这些从检查点的context_summary和variables里恢复。如果检查点里没存这些就要从外部存储重新拉取。比如当前处理的数据 ID 存在变量里恢复时根据 ID 重新查询数据。重建后的上下文要重新做一次压缩和注入顺序调整确保符合当前步骤的需要。5.4 恢复失败的兜底方案恢复不是万能的。检查点损坏、状态不一致、外部依赖不可用都可能导致恢复失败。必须有兜底。我的兜底策略分三级一级回退检查点。当前检查点有问题回退到上一个。最多回退 3 个再失败就升级。二级人工介入。把任务标记为“需人工处理”记录详细的中断信息和已执行的操作通知运维人员。同时冻结相关资源防止误操作。三级安全终止。如果任务涉及敏感操作且无法确定状态直接终止执行清理逻辑比如释放锁、回滚未提交的事务。兜底方案的关键是可观测。每次恢复失败都要有详细日志包括失败原因、检查点内容、已执行操作列表。这些日志是后续排查的依据。6. 循环执行与资源管控的实战方案6.1 循环终止条件的四种设计Agent 循环最怕的是停不下来。我设计终止条件时会同时设置四道防线任务完成条件。这是正常的终止比如所有子任务完成、目标达成。判断逻辑要明确不能模糊。最大步数限制。防止无限循环。步数上限根据任务复杂度设我一般设 50 到 100。超过就终止并报警。最大时间限制。防止单步卡死。总时长和单步时长都要限制总时长比如 30 分钟单步比如 2 分钟。最大 token 消耗。防止烧钱。根据预算设比如 50 万 token。接近上限时降级或终止。这四道防线是“或”的关系任何一个触发都终止。终止时要记录原因方便后续优化。6.2 Token 预算的动态分配Token 是 Agent 最贵的资源。我的做法是给每个任务分配预算再动态调整。初始预算根据任务类型设简单任务 10 万复杂任务 50 万。执行过程中根据剩余步骤和已完成步骤的消耗动态调整每步的预算上限。如果发现某步消耗异常高比如超过平均值的 3 倍就触发告警检查是不是上下文膨胀或者模型跑偏了。还有一个技巧给不同步骤设不同的 token 上限。规划步骤可以多给执行步骤要控制总结步骤可以少给。这样整体预算更可控。6.3 并发控制与限流多 Agent 或者多任务并发时资源竞争很激烈。我的做法是任务级并发限制。同时运行的任务数不超过 NN 根据机器资源和外部接口限制定。接口级限流。对每个外部接口设 QPS 上限用令牌桶或漏桶算法。超过就排队或降级。模型调用限流。大模型 API 通常有速率限制要提前做队列和重试。重试用指数退避避免雪崩。并发控制的关键是公平性。不能让一个任务占满所有资源其他任务饿死。我用优先级队列高优先级任务先执行同优先级轮转。6.4 异常熔断与降级策略外部依赖不稳定是常态。必须有熔断和降级。熔断某个接口连续失败 N 次就暂时切断不再调用直接走降级逻辑。N 一般设 5 到 10熔断时间 30 秒到 5 分钟。降级接口不可用时用备用方案。比如模型调用失败降级到规则引擎数据库查询失败降级到缓存。降级要有明确的触发条件和恢复条件。恢复后要逐步放量不能一下子全切回去。实操心得熔断和降级一定要有开关能手动控制。我遇到过自动熔断误判的情况把正常接口切了结果任务全挂。后来加了手动开关和更保守的熔断阈值稳定多了。7. 常见问题与排查技巧实录7.1 上下文相关问题的排查问题Agent 反复执行同一步骤。排查思路先看上下文里是不是有重复的指令再看检查点里的completed_steps是不是没更新。常见原因是检查点写入失败但主流程没感知导致恢复时以为没做过。问题Agent 忽略明确指令。排查思路检查指令在上下文中的位置。如果被大量无关内容淹没模型可能忽略。把关键指令移到系统提示末尾或者用更强的格式标记。问题上下文突然爆掉。排查思路查工具返回内容的长度。我遇到过一次某个接口返回了完整数据库表结构几万字。现在所有工具返回都做截断。7.2 检查点与恢复问题的排查问题恢复后重复执行副作用操作。排查思路检查side_effects记录是否完整幂等判断逻辑是否有漏洞。重点看副作用操作和检查点写入之间是否有时间窗口。问题恢复后状态不一致。排查思路对比检查点里的状态和实际外部系统的状态。常见原因是检查点写入和业务操作不在同一事务。问题检查点文件损坏。排查思路检查存储介质和写入方式。我用过直接覆盖写断电时容易损坏。后来改成先写临时文件再原子重命名问题解决。7.3 资源管控问题的排查问题Token 消耗异常高。排查思路按步骤统计 token 消耗找出异常步骤。常见原因是上下文压缩没触发、工具返回内容过长、模型陷入循环。问题任务卡死不退出。排查思路检查终止条件是否都生效。我遇到过最大步数限制没生效原因是计数器在异常分支里没更新。现在所有分支都统一更新计数器。问题并发任务互相影响。排查思路检查共享资源是否有锁保护。常见的是共享上下文、共享检查点存储、共享外部接口配额。7.4 独家避坑清单坑表现避坑方法检查点存历史消息存储膨胀恢复慢只存状态不存历史副作用记录不完整恢复后重复操作副作用和检查点同事务上下文压缩太激进丢失关键信息压缩比不超过 4:1终止条件单一死循环四道防线同时设熔断阈值太敏感误切正常接口阈值设保守加手动开关恢复无兜底恢复失败后卡死三级兜底策略工具返回不截断上下文爆掉统一截断保留头尾并发无优先级任务饿死优先级队列加轮转8. 一套可直接复用的 Agent 运行机制模板把上面的东西串起来我给一个简化但完整的实现模板。核心是四个组件ContextManager、CheckpointManager、RecoveryManager、ResourceGuard。class AgentRuntime: def __init__(self, task_id, config): self.task_id task_id self.context ContextManager(config) self.checkpoint CheckpointManager(task_id, config) self.recovery RecoveryManager(task_id, config) self.guard ResourceGuard(config) self.state None def run(self): # 尝试恢复 if self.recovery.has_unfinished_task(): self.state self.recovery.restore() else: self.state self.init_state() while not self.is_done(): # 资源检查 if not self.guard.check(self.state): self.handle_resource_exhausted() break # 构建上下文 ctx self.context.build(self.state) # 模型决策 action self.decide(ctx) # 幂等校验 if self.is_duplicate(action): self.state self.next_state(action, skippedTrue) continue # 执行 result self.execute(action) # 更新状态 self.state self.next_state(action, result) # 打检查点 if self.should_checkpoint(action): self.checkpoint.save(self.state) self.finalize()这个模板的关键点恢复优先、资源检查前置、幂等校验在决策后执行前、检查点按需打。你可以根据自己的场景调整但骨架建议保留。最后分享一个我踩过最深的坑不要相信“任务一定会跑完”。任何长任务都要假设它会中断任何副作用都要假设它会重复。把这两个假设刻进设计里你的 Agent 才能真正上生产。我现在每个 Agent 项目启动前都会先问自己三个问题中断了怎么恢复重复了怎么幂等资源爆了怎么兜底这三个问题答不上来就不写代码。