从单Agent到多Agent集群:7x24小时稳定运行的架构设计与实践
半夜两点二十三分我的单 Agent 自动化流程终于把自己玩死了上下文窗口撑爆之后它开始对着同一个任务反复确认把状态标记改得乱七八糟任务队列里还堆着两百多条没处理的数据。那一刻我意识到想让它 7x24 小时不停工作需要的不只是一个更听话的 Agent而是一整套能管住 Agent 的架构。这就是后来这套多 Agent 集群的起点。这篇文章把我从单 Agent 到多 Agent 集群的完整过程写出来包括架构怎么分层、二十四小时不停机的关键保障机制是什么、实测阶段踩过的四个大坑以及跑起来之后的真实成本数据。如果你也在做 Agent 工程化、定时自动化、复杂任务流水线或者正被单 Agent 不够稳这件事折磨这篇文章应该能帮你省掉不少试错时间。1. 单 Agent 撑不起 7x24 小时一次深夜事故的复盘先聊聊我最初是怎么挂的。当时的需求其实很朴素每小时从几个固定的信息源抓取数据做清洗、摘要、分类然后写入数据库并推送通知。我直接用了一个单 Agent让它按提示词循环执行抓取-整理-输出这套流程。前三小时一切正常第五小时开始不对劲Agent 的响应速度明显变慢原本 20 秒能完成的单轮操作拖到 40 秒以上。第七小时它开始出现重复行为——同一个信息源被连续抓了三遍数据库里出现了一堆重复记录。到第九小时整个流程直接卡死一次 API 调用超时后状态没写对后面所有任务都开始排队等待最终把队列彻底堵住。事后我总结了单 Agent 长期运行的三个死因。第一上下文窗口是有上限的。对话历史、中间结果、工具返回内容全堆在一个上下文里时间一长必然逼近窗口边界。一旦触顶模型要么开始忽略早期内容导致逻辑断裂要么产生幻觉式补全开始自己编造任务状态。我那次数据库重复记录就是因为 Agent 把旧任务内容当成新任务来处理了。第二单点故障没办法自愈。单 Agent 跑一天总会遇到 API 限流、网络超时、返回值格式异常。普通异常只要重试一次还能恢复但如果 Agent 自身状态已经错乱那它连重试这个动作都不会做——它只会用错误的状态继续推流程。你想靠多写几句提示词来解决这个问题基本不可能。第三串行执行没有吞吐弹性。单 Agent 同一时间只能处理一个任务链路前面卡住后面全部排队。哪怕你把它并发调用底层还是一个大脑、一套工具上下文本质上还是在串行思考。所以问题很清楚要支撑 7x24 小时不间断运转核心不是怎么让一个 Agent 更聪明而是怎么设计一套机制让 Agent 挂了也不影响整体让它出错能被发现让它跑完任务能干净退出。这套机制就是一个多 Agent 集群。2. 我采用的集群架构调度层、工作层与记忆层各司其职很多人一听多 Agent 集群就以为是开十个进程每个进程里跑一个 Agent然后随机派任务。真这样做了大概率会收获十个各自为政的熊孩子——它们之间没有任何协调状态互相覆盖职责还重叠。我的设计思路是三层架构调度层只负责派活工作层只负责干活记忆层只负责存状态。每一层各管各的互不越权。2.1 调度层任务队列、优先级与派发策略调度层是整个集群的中枢本质上是一个持久化的任务队列加上一个调度器。所有外部进来的任务先落队列调度器再按照优先级和依赖关系把它们分发给工作层的 Agent。任务队列我选的是支持持久化的消息中间件原因很简单进程重启之后队列数据不能丢。之前用内存队列吃过亏一次重启两百多个任务全没了从那以后所有队列数据必须落盘。调度器要维护的信息不复杂任务 ID、任务类型、入队时间、优先级、依赖状态、当前归属的 Agent。依赖状态很关键因为部分任务需要等前序任务完成之后才能执行。实现上我用的是一个状态表任务完成后回调更新依赖计数计数归零才允许被派发。派发策略我试过两种轮询和按能力标签派发。轮询实现简单但效率一般因为不同类型的 Agent 能力边界不一样你让摘要 Agent 去做采集很容易出错。最终我采用的是能力标签 负载水位的方式每个 Agent 启动时声明自己能处理的任务类型调度器看当前各 Agent 的队列深度把任务派给同类里积压最少的那一个。2.2 工作层不同角色的 Agent 各自独立工作层是真正执行任务的 Agent 组我按职责拆成了四类采集 Agent、清洗 Agent、摘要 Agent、分发 Agent。这么拆的目的不是为拆而拆而是为了让每个 Agent 的上下文和提示词足够短、足够专一。采集 Agent 的上下文里只需要工具使用规范和信息源列表清洗 Agent 只需要处理异常数据格式的规则摘要 Agent 只需要知道怎么提炼要点。它们各自维护一套专属的提示词、工具集和上下文清理策略。每一类 Agent 可以起多个副本副本数量按任务量动态调整。高峰期摘要任务是瓶颈我就把摘要 Agent 副本从 2 个扩到 5 个半夜采集任务多采集 Agent 就优先扩容。扩缩容的实现不复杂任务队列长度超过阈值时调度器自动拉起新的 Agent 进程并注册到工作池里队列空了超过一段时间空闲副本自动退出。2.3 记忆层共享存储与上下文隔离记忆层解决两个问题一是任务中间结果放哪二是 Agent 运行状态怎么持久化。任务中间结果我统一存在共享库里任务 ID 作为主键。这样任意一个 Agent 挂掉新拉起的 Agent 可以根据任务 ID 读取上次已经处理好的中间结果不用从头再来。清洗到一半的任务如果清洗 Agent 挂了新的清洗 Agent 直接从断点处接着处理。Agent 运行状态则包含两层。第一层是每个任务会话的上下文存在独立的会话存储里与其他任务隔离第二层是 Agent 的长期记忆比如它用着顺手的工具调用偏好、经常出现的字段格式特征存在向量库里按 Agent 角色做命名空间隔离。2.4 为什么这样分层这样分层带来的最大改变是故障隔离。以前单 Agent 一个错误会传染到整条链路上现在采集 Agent 挂了调度器重启它就行清洗和摘要完全不受影响。同时并行度大幅提升——四个不同类型的任务可以同时执行而不是像单 Agent 那样被上下文切换反复打断。另一个优点是扩展性。想支持新任务类型只需要新增一种 Agent 角色然后在调度器里注册它的能力标签。不需要改任何已有 Agent 的逻辑。3. 让集群 7x24 小时不睡觉的五个保障机制架构只是骨架真正让集群不停工的是下面这几个保障机制。可以说没有这些机制多 Agent 集群比单 Agent 还容易出事故——因为你把故障面从一个 Agent扩大到了一批 Agent。3.1 心跳检测与守护进程自动拉起每个 Agent 进程必须每隔固定时间向调度器上报心跳。心跳包里包含进程 ID、当前任务 ID、已运行时长、最近一次工具调用的时间戳。调度器侧有一个守护逻辑连续三个心跳周期没收到某个 Agent 的心跳就判定它失联。失联后的处理分两步先把它的任务标记为待重新派发然后自动拉起一个新的同类型 Agent 进程从注册中心拿到配置重新开始干活。这里有个细节心跳超时阈值不能设太短。我一开始设了 15 秒结果 Agent 在处理一个特别长的文本摘要任务时单次工具调用超过了 30 秒心跳没能及时发出去被误杀好几次。后来我把阈值调到单次工具调用平均耗时的五倍误杀问题才消失。具体来说如果你的工具调用平均耗时小于 10 秒阈值可以设 60 秒如果有超过 30 秒的长时间调用阈值建议设 180 秒。3.2 任务幂等重启之后不重复执行这是整个集群最容易被忽略、后果却最严重的机制。没有幂等保护Agent 挂掉重启后任务会被重新执行一遍——数据库里出现重复记录API 被重复调用下游通知重复发送。我的做法是给每个任务分配全局唯一的任务 ID并在共享库里维护一张任务状态表。状态只有四种pending、running、succeeded、failed。调度器派发任务时会先更新状态为 runningAgent 执行完写结果时如果状态已经是 succeeded就直接丢弃本次结果不再写入。这样实现后即使同一个任务被不小心派发给两个 Agent最终也只有一个 Agent 的结果会被真正写入。现在我的所有 Agent 在写任何外部依赖之前都要先执行一次状态检查-写入的原子操作确保重复执行不会产生副作用。3.3 断点续跑长任务拆步骤中间结果落盘有些长任务单个就要跑十几分钟比如采集上千个信息源再逐条清洗。如果中间一次异常整个任务从头再来成本很高。我把这类长任务改造成步骤可中断的模型任务执行过程中的关键中间结果每完成一步就写入共享库。Agent 重启后会先从共享库加载已有中间结果跳过已完成步骤只执行剩余步骤。举个例子一个采集任务需要翻 500 个信息源。我让采集 Agent 每处理 50 个信息源就记录一次检查点记录内容包括已完成 50 个的 ID 列表、下 50 个的起始位置、上次运行的上下文摘要。这样哪怕 Agent 在第 253 个信息源时挂掉重启后也只需要从第 251 个继续跑而不是把 500 个全部重来一遍。实测下来断点续跑让长任务的整体失败重试成本下降了约 70%。3.4 优先级队列与超时熔断7x24 小时运行的集群总会遇到突发状况某条链路异常导致大量任务积压或者外部接口突然开始慢速响应。如果没有优先级和熔断机制普通低价值任务会把高优任务的资源全部占掉。调度器给每个任务分配一个优先级高优任务入队时插到低优任务前面。这样即便队列里有三百个普通任务紧急修复任务也可以在 10 秒内被派发出去。熔断逻辑在调度器侧当某类任务连续失败次数超过阈值比如 10 次调度器会暂停派发这类任务进入冷却期。冷却期内任务会进入延迟重试状态等外部接口恢复后再重新派发。冷却时间按指数退避递增第一次重试间隔 30 秒第二次 60 秒第三次 120 秒上限 10 分钟。3.5 上下文隔离与定期清理多 Agent 集群运行时间长了每个 Agent 的会话上下文都会积累大量历史信息。如果不做隔离和清理上下文会越来越长成本越来越高而且不同任务之间的信息会互相污染。我的做法是强制直播每个任务独立保存自己的上下文快照任务完成 上下文清空归零。Agent 进程之间不共享任何会话上下文。如果一个 Agent 需要参考其他任务的输出它必须显式通过记忆层的隔离接口去读取而不是在上下文里顺带带着别的任务的信息。同时每隔一段时间比如每天凌晨三点记忆层的清理任务会把向量库里超过 90 天未访问的长期记忆归档到冷存储只保留最近活跃的部分。这样能控制向量库体积查询速度也不会随着运行时间变慢。4. 实测中的四个坑每一个都差点让集群崩溃架构搭好、机制写齐之后我以为二十四小时不停工只是启动一按的事。结果真实跑起来前两周几乎每周都有一场小事故。下面这四个人坑是最典型的也是新人最容易反复踩的。4.1 并发失控一口气放开 8 个副本换来一片限流集群上线第一天我心情很好想着任务量不小直接把四个 Agent 角色的副本数都扩到 2 个一共 8 个并发进程。结果半小时之后外部模型接口开始大量限流任务失败率一下子冲到 30%。定位过程我先看的是任务队列有没有堵发现队列没有异常失败的任务集中在模型调用环节。再看错误日志返回的是限流错误。这时候我才意识到8 个 Agent 进程同时调用同一个商用大模型接口总量早就超过接口配额了。修复办法调度器把模型接口的并发上限纳入派发考量。我在调度器里加了一个全局令牌桶每秒只允许一定次数的模型调用任何 Agent 调用前都先来这里拿令牌拿不到就等待。同时 Agent 侧加了退避重试逻辑遇到限流错误时等 30 秒后再试而不是立刻重试导致雪崩。调整完之后集群的并发稳定在单接口配额的四分之三左右几乎不再触发限流。4.2 重复执行凌晨重启后数据库冒出 40 条重复记录这是一个我一开始完全没想到的坑。凌晨三点某次模型接口故障采集 Agent 进程崩溃。守护进程自动拉起新 Agent任务重新派发。表面上一切正常但第二天早上我发现数据库里有一批记录重复了。定位到根因新 Agent 拉起来的时候它读取的已完成任务状态还是旧值——旧进程还没来得及把状态从 running 更新成 succeeded 就挂掉了所以新进程认为任务没完成又重新跑了一遍。修复方案我在任务状态表里增加了一个版本号字段。每次任务开始执行时先读取版本号执行完写入结果时携带这个版本号。写入前检查版本号是否发生变化没变化才允许更新有变化说明任务已经被另一个进程执行过了直接丢弃本次结果。这个方案本质上是乐观锁用版本号来防止并发下的状态覆盖。4.3 上下文污染一个 Agent 把上个任务的信息带到下个任务有一段时间摘要 Agent 输出的摘要偶尔会出现串味——给 A 信息源做的摘要里混进了 B 信息源的结论。我排查了很久最后发现是上下文清理时机的问题。问题出在任务切换的瞬间。Agent 处理完 A 任务时上下文还没来得及清空调度器就已经把 B 任务派过来了。Agent 带着 A 任务的尾巴开始处理 B模型就把两段信息混在一起。我的解决方法是引入一个显式的任务结束信号Agent 写完结果后必须执行清理动作把当前上下文打出存档并清空然后再向调度器发送空闲就绪信号。调度器只有在收到空闲就绪之后才派发新任务。这个规则的严格执行彻底根除了上下文污染问题。4.4 任务流转死锁A 等 BB 等 A最后全部超时集群跑了一周之后我开始加入一些有依赖关系的任务采集 A 做完之后要触发清洗 A清洗 A 做完之后要触发摘要 A。看起来没什么问题但有一天凌晨整批任务全都卡在 pending 状态一个都不动弹。排查发现依赖方向写反了某类任务的依赖条件包含了自己所属的类型导致它永远在等自己的另一个实例完成——形成了循环等待。修复方法调度器在解析依赖关系时强制要求依赖图必须是有向无环图。我加了一个构建时的检查逻辑每次注册新任务类型时检查一遍依赖关系成环则拒绝注册。同时所有任务的状态更新带超时保护如果某个任务在 pending 状态超过预设的最大等待时间调度器自动把它标记为 failed 并触发告警而不是无限期等下去。5. 集群跑起来之后的真实数据与优化空间经过两周的反复调整集群终于稳定下来。它每天 24 小时连续运行、不需要手动干预。这里放一些我在实际运行里记录的数据供大家参考。任务吞吐量单 Agent 时代每天能处理约 60 个完整链路任务采集到分发。多 Agent 集群稳定下来之后每天处理约 1200 个链路任务吞吐提升大约 20 倍。失败率单 Agent 时代一天内失败率在 15% 到 40% 之间波动同一个任务失败三次以上的情况很常见。集群稳定后失败率降到 1% 以下绝大多数失败来自外部接口的偶发超时重试一次就能恢复。资源占用8 个 Agent 进程的总内存占用约 3.5GB加上队列和数据库整体控制在 6GB 以内。我用一台 8 核 16GB 的云主机就能跑整套集群硬件开销并不夸张。成本控制方面有几个经验值得分享。第一个是模型分层高价值的摘要、分类、判断任务用强推理模型低价值的采集、清洗、格式转换任务用便宜模型。第二个是上下文压缩每次任务完成就把上下文归档成短摘要而不是把全量上下文存进记忆库。第三个是批量处理低优先级任务合并成批次执行减少模型的空转调用。整体算下来每天的综合成本比单 Agent 时代的暴力重试模式低了 30% 左右。后续我还想尝试几个方向一是根据任务队列长度自动扩缩容目前还是手动配置副本数二是给 Agent 增加主动告警能力让它可以自己发现异常并发出通知三是探索多个 Agent 之间更复杂的协作方式比如任务协商和分配而不仅仅是调度器单向派发。就我个人的实际体会而言做多 Agent 集群最大的收获不是让它 24 小时跑起来这个结果而是过程中想清楚了很多工程化的问题任务状态如何管理、故障如何隔离、并发如何控制、成本如何收敛。如果你正准备把自己的单 Agent 流程改成集群模式我的建议是先从任务幂等和中间结果落盘这两个机制做起它们是整个集群的稳定地基其他机制都是在这两个基础上加瓦添砖。