
我把运行了大半年的“全能 AI 助手”拆了。这大概是我最近在 AI Agent 上做过最正确的一个决定——之前为了让一个 Agent 兼顾内容选题、资料检索、初稿生成和评论回复我在系统提示词里堆了几千字规则上下文一长就开始遗忘和乱来改一个环节就得重新调全部。后来我用 OpenRig 做了完整的多智能体编排改造把能力拆成职责清晰的小 Agent用事件总线和持久化状态把它们织成一个可以跨天运行、崩溃恢复、任务可回放的协作系统。这篇文章是这次实践的过程复盘适合那些手里已经跑过单 Agent 应用、正准备往多智能体方向迈进的同学也适合所有被“一个 Agent 干所有事”折磨过的人。OpenRig 本身并不是又一个 Agent 框架而是一个多智能体编排运行时。它不限制你用什么模型、什么语言写 Agent只负责把离散的 Agent 组织成流程并把协作过程中产生的状态、事件、上下文沉淀到磁盘上。这套思路让我第一次觉得多智能体系统不再是几个 LLM 之间互相调来调去的“千层饼”而是一个可以像软件工程一样被设计、测试、恢复的正式系统。1. 为什么要把 Agent“拆开”再“织起来”——OpenRig 的编排思路在讲具体配置之前我想先聊聊方案选型。这个阶段如果没想清楚后面写再多 workflow 也只是把单体垃圾换一种方式摊开。1.1 单体 Agent 的问题上下文拥挤、职责耦合、状态丢失我之前那个“全能助手”就是一个典型的单体 Agent。它要处理的事情太多了系统提示词越来越长工具列表越来越膨胀每次对话都要把全部工具 schema 塞给模型。结果就是模型选择工具时经常出错明明让它查天气它去调了文章生成器明明让它总结评论它开始自己编新闻。这是职责耦合的必然代价。更麻烦的是上下文拥挤。单体 Agent 的每次调用都要携带完整的历史消息、任务描述、工具结果。任务一多上下文窗口很快被无关信息占满。我试过把历史消息摘要压缩但摘要本身也占 token而且压缩会丢掉细节。一旦某个任务夹杂在几百条历史消息中间模型的表现会断崖式下跌。还有状态丢失。单体 Agent 的状态通常跟进程内会话绑定进程一重启之前的任务进度、中间结果、决策理由全没了。我们做内容流水线的时候经常要跨天执行任务比如周一收集选题、周三生成初稿、周五发布。如果中间服务器重启一次整个流程就得从头再来。这就是我决定引入多智能体编排的导火索。1.2 OpenRig 的编织模型调度器 事件总线 状态仓库OpenRig 的编排模型不复杂核心就三部分调度器、事件总线、状态仓库。调度器负责按照 recipe你可以理解为 workflow 剧本把步骤分发给对应的 AgentAgent 之间不直接互相调用而是把结果以事件形式写到总线上状态仓库保存每个 Agent 的会话快照、任务进度和事件日志。这个设计最大的价值是把“协作”和“通信”解耦了。每个 Agent 只需要关心自己的输入输出契约不需要知道上游是谁、下游是谁。比如我的“资料检索 Agent”只负责根据选题返回引用列表它根本不知道这份引用会被“初稿 Agent”用还是被“评论回复 Agent”用。调度器编排时只是把前一个步骤的输出作为后一个步骤的输入注入。这种方式比让 Agent 之间直接调用稳定得多因为你不需要在提示词里反复强调“调用链”。同时状态仓库让整个系统变成可暂停、可恢复的。所有关键数据都会落盘而不是只在内存里转瞬即逝。我后来模拟了一次kill -9进程重启后能从上一次中断的位置继续跑这在单体 Agent 时代是想都不敢想的。1.3 为什么编排层用 Rust 实现热词里总在提“基于 Rust 语言 ai agent”我猜大家关心的是为什么要用 Rust以及自己是不是也要学 Rust。我的答案是OpenRig 用 Rust 构建了编排层但你使用它时基本不需要写 Rust。选择 Rust 做编排运行时一是因为并发能力强。多智能体系统天然是一个高并发系统多个 Agent 可能同时执行、同时回传结果。Rust 的异步运行时和所有权模型让任务队列、事件投递这些核心逻辑不容易出现数据竞争和内存安全问题。二是因为部署形态干净。一个 Rust 二进制扔到服务器上就能跑没有 Python 解释器版本问题也没有 Node 依赖地狱。三是性能。编排层要做大量 JSON Schema 校验、状态快照序列化、事件日志追加写Rust 在这种 IO 密集和 CPU 密集混合的场景下表现很稳。我常用一个类比OpenRig 之于 Agent就像 Nginx 之于 Web 应用。我们写业务系统不需要会写 C但 Nginx 的高性能和可配置性一直在托底。OpenRig 把底层调度、持久化、重试这些脏活接过去了你需要掌握的只是 Agent 契约和 workflow 定义。2. 持久化协作系统的四个核心细节如果只是让几个 Agent 通过 HTTP 互相调用那不叫系统那叫“脚本”。真正让它称得上“持久化协作系统”的是下面四个细节。它们是我在实践里踩过无数坑才慢慢补齐的。2.1 会话状态持久化把 Agent 的“记忆”从内存挪进磁盘Agent 默认是“失忆”的。每次调用模型看起来它记得对话实际只是你把它之前的内容当作历史消息重新喂给了它。一旦进程重启这些历史全丢。OpenRig 的做法是为每个 Agent 实例维护一个state store把关键业务状态落盘而不是把全文历史无脑存下来。我实践中保存的状态包括当前任务的run_id、任务的输入输出摘要、已经完成到 workflow 的哪一步、重试过几次、上下文压缩后的决策记忆。这些和模型 token 无关更像是业务系统的数据库记录。比如资料检索 Agent 完成后我会保存一份“已检索关键词 引用列表”而不是保存它调用搜索 API 时的原始 HTML。一个很重要的注意点是幂等性。每次执行步骤前先检查run_id是否已经有过成功记录如果有就直接复用输出而不是重新调用 Agent。这个习惯能救你命——因为 Agent 调用是有成本和延迟的而且模型输出有随机性重复执行同一任务既费钱又可能导致结果漂移。OpenRig 在状态仓库里会标记每个步骤的执行状态我只需要确保自己的业务逻辑不做重复写入。2.2 事件日志与任务队列把“协作”变成一条可回放的事件流事件日志是我最喜欢 OpenRig 的部分。整个协作过程中每个 Agent 的调用开始、调用结束、成功、失败、条件分支判断都会追加到一份 append-only 的事件日志里。每条事件带自增序号可顺序回放。为什么需要可回放因为多智能体系统出问题的时候你很难当时就定位到原因。有了事件日志你可以在事后把整个流程像看回放一样重演一遍哪一步投递失败哪一步返回了非法 JSON哪一步条件分支走错了都清清楚楚。这个思路和数据库的 WAL 日志一样是系统可靠性的一道安全网。任务队列本身也可以从事件日志重建避免队列里积压的消息在进程崩溃后烟消云散。我通常会定期对事件日志做归档压缩。比如一周之前的日志只保留摘要和异常标记因为完整事件量积累起来也很大。但千万不要在调试阶段清理日志否则回放功能就废了。2.3 Agent 注册与能力声明先知道自己有什么工具再谈协作多智能体系统里每个 Agent 都需要有一个注册项声明自己的能力边界和输入输出契约。OpenRig 的注册表类似这样agent: name: research_agent description: 根据主题收集3到5条带引用的资料片段 input: topic: string max_results: integer output: citations: array model: gpt-4o-mini request_timeout_ms: 30000为什么需要固定 schema因为编排层要负责任务路由调度器读到 “research_agent” 的 description 和 input schema才知道什么任务能投递给它。Agent 返回的结果如果不符合 output schemaOpenRig 会在编排层直接拦截标记结构错误而不是把这些脏数据交给下游 Agent。这个校验层极大减少了模型输出格式漂移带来的连锁反应。我强烈建议把 Agent 的输入输出契约设置得严格一些。宁可让无效请求早点失败也不要等数据在链路里流转了三层之后才发现字段缺失。实际做内容流水线的时候我把每个 Agent 的输出都定了 JSON Schema检索 Agent 必须返回{citations: [{title, url, snippet}]}审查 Agent 必须返回{verdict: pass|revise, feedback: string}。这样下游的初始化逻辑就变得非常朴素根本不需要写一堆防御式解析代码。2.4 上下文与 Token 管理多 Agent 协作时的钱和内存账本“ai agent token 是什么意思”这个话题最近很火。简单说token 就是模型处理文本的最小计费单位也是上下文窗口的刻度。单 Agent 对话里token 消耗还可以靠对话长度估算多 Agent 协作里同一份资料可能会被复制多份在多个 Agent 的上下文中传递token 会指数级膨胀。我的经验是每个 Agent 只保留与当前步骤相关的局部上下文不要让它处理完整历史。比如资料检索 Agent 的职责只是根据 topic 返回引用那它的输入就只有 topic 和 max_results没有其他上下文。初稿 Agent 的输入是上一步输出的引用摘要而不是原始网页全文。另一个有效策略是摘要接力一个 Agent 完成工作后先由 summary 环节把结果压缩到几百 token再传递给下一个 Agent。这样做既省钱也能让下游 Agent 的注意力聚焦在最关键的信息上。OpenRig 还支持配置每个 Agent 的 context 配额达到阈值就自动做摘要或者截断。我一度很迷信加模型窗口大小后来发现更大的窗口只会让 Agent 看到更多噪声。合理分配上下文预算比换大模型立竿见影得多。3. 实操用 OpenRig 搭一套可复用的多智能体协作流水线理论讲完上点硬货。我以自己反复验证过的一套“内容生产流水线”为例完整演示怎么用 OpenRig 编排两个工作 Agent并实现崩溃恢复。3.1 环境准备与项目结构OpenRig 的安装出人意料地简单。如果你不想从源码折腾可以直接去官方发布页下载对应平台的预编译二进制解压后把rig命令放到 PATH 里。想尝鲜也可以用cargo install openrig但 Rust 编译时间够你喝两杯咖啡赶时间就别选了。项目结构我推荐这样组织content-pipeline/ ├── recipes/ │ ├── weekly_content.toml │ └── agents.toml ├── agents/ │ ├── research_agent.py │ └── review_agent.py ├── state/ │ ├── events/ # 事件日志落盘目录 │ └── checkpoints/ # 步骤级状态快照 └── rig.toml # 全局配置包括状态目录和默认Agent池写清晰一点recipes目录放 workflow 定义agents目录放各 Agent 的独立服务state目录放 OpenRig 运行时的持久化数据。这个结构看起来平平无奇但坚持下来之后新增一个 Agent 或者新增一条流水线都不会污染已有目录。3.2 定义两个最小 Agent检索型 Agent 和审查型 AgentOpenRig 不限制 Agent 的编写语言。我这里用 Python 写一个最小检索 Agent它本质上是一个 HTTP 服务接收 OpenRig 转发的任务参数返回固定结构的结果。# agents/research_agent.py from flask import Flask, request, jsonify app Flask(__name__) def run_research(topic, max_results): # 这里通常封装搜索 API / 向量库 / LLM 二次加工 # 示例直接返回一条模拟结果 return { citations: [ {title: f{topic} 入门教程, url: https://example.com/intro, snippet: 一篇讲入门概念的资料} ] } app.post(/invoke) def invoke(): payload request.get_json(forceTrue) params payload[parameters] try: result run_research(params[topic], params.get(max_results, 3)) return jsonify({output: result, success: True}) except Exception as exc: return jsonify({output: {error: str(exc)}, success: False}) if __name__ __main__: app.run(host0.0.0.0, port8901)审查 Agent 的结构几乎一样只是内部逻辑不同。这里的关键是你得让 Agent 保持“无状态”所有需要持久化的东西都放在 OpenRig 侧Agent 本身只负责“拿到输入产出输出”。这样 Agent 进程随时可以被替换、重启、水平扩展而不会破坏协作流。注册信息放在recipes/agents.toml里[[agents]] name research_agent description 根据主题收集资料引用列表 endpoint http://127.0.0.1:8901/invoke input_schema { topic string, max_results integer } output_schema { citations array } timeout_ms 30000 [[agents]] name review_agent description 检查初稿内容返回通过或不通过 endpoint http://127.0.0.1:8902/invoke input_schema { draft string } output_schema { verdict string, feedback string } timeout_ms 30000在这里就把超时和重试相关参数注册好后面流程定义里就不用重复配置。我一开始把这些参数散落到每个步骤里维护起来非常痛苦后来统一收敛到注册表里改动一次全局生效。3.3 编排剧本用 Workflow DSL 描述协作流程OpenRig 的编排剧本可以用 TOML 写。我这条流水线包含四个步骤检索资料、生成初稿、审查、发布。为了减少篇幅示例里只展示两个核心 Agent审查失败会回退到生成初稿重来。# recipes/weekly_content.toml name weekly_content_flow trigger cron: 0 9 * * 1 steps [ { id collect_brief, agent research_agent, params { topic {{input.topic}}, max_results 5 } }, { id draft, agent writer_agent, params { references {{steps.collect_brief.output.citations}} } }, { id review, agent review_agent, params { draft {{steps.draft.output.text}} }, on_fail { goto draft, max_retries 2 } }, { id publish, agent publisher_agent, params { text {{steps.review.output.approved_text}} } }, ]注意{{steps.collect_brief.output.citations}}这种引用语法。OpenRig 会把上一步的输出结构化解引用到下一步的参数里。我在一开始没有明确指出字段层级导致多次拿到 undefined。后来学乖了每个 Agent 的输出 schema 里都会写清楚哪些字段是必须的引用的时候就照着 schema 写。on_fail { goto draft, max_retries 2 }是回退机制。如果审查不通过整个流程回到“生成初稿”这一步并且限制重试两次避免死循环。我还试过配置多个发布渠道并行只需要把最后一个步骤改成并行组OpenRig 会自动把任务投递给多个目标 Agent并等待全部完成。3.4 运行、模拟崩溃与恢复运行这条流水线的命令很简单rig run recipes/weekly_content.toml \ --param topic多智能体编排入门 \ --state-dir ./state启动后能看到调度器逐步创建事件、把任务投递给对应 Agent、把 Agent 返回结果写入事件日志。这时如果我们模拟一次进程崩溃让调度器死掉pkill -9 rig观察state/events目录会发现事件日志停在某个序号。然后重新拉起并指定从上一次中断的位置恢复rig resume recipes/weekly_content.toml \ --state-dir ./state \ --resume-from 42这里--resume-from 42的含义是从序号为 42 的事件继续执行。OpenRig 先读取事件日志找到未完成的任务再根据任务状态重新调度。实际执行时我推荐不要手动填序号直接用rig resume不传--resume-from让它自动选择最后一个完整事件。手动指定容易跳过一些必要的补偿操作。这一步是整个持久化系统的真正价值所在。以前我用别的编排方案Agent 进程倒是活着呢但调度器一崩全乱了。OpenRig 因为所有关键状态都在磁盘恢复起来就像打开一个浏览器标签页上下文还在那里。4. 常见问题与排查技巧实录任何编排系统跑久了都会暴露各种奇怪问题。我把自己踩过的一些坑和排查思路整理成了速查式内容希望你能少走弯路。4.1 Agent 超时与重试风暴最经典的问题是重试风暴。一个上游 Agent 请求卡住了下游多个 Agent 因为收不到结果开始不停重试几秒钟内就能打满你的模型配额和 API 并发。我一开始天真地设置了固定重试 5 次结果系统雪崩得特别快。现在的配置是给每个 Agent 设置独立超时并在全局开启指数退避[retry] max_attempts 3 backoff_base_ms 500 backoff_multiplier 2一个更稳健的兜底是设置死信队列。当任务重试超过max_attempts后OpenRig 不再自动重试而是把事件任务标记成dead_letter需要人工介入处理。这样至少不会再让失败的 Agent 反复占用资源。我实际的排查经验是先看事件日志里失败事件的 error message再判断是模型输出不规范、网络超时还是下游 Agent 自身 bug。这三种情况的处理方式完全不同。如果是模型输出偶发不规范加重试没意义应该调整 prompt 或者加 schema 校验容错如果是网络超时调大 timeout 比调重试更有效。4.2 消息乱序与状态回滚并行执行步骤时事件日志的顺序和 Agent 实际完成顺序不一定一致。比如步骤 A 和步骤 B 同时开始B 先完成A 后完成但下游步骤 C 需要同时依赖 A 和 B 的结果。这时候如果单靠“最新事件”判断状态C 就会拿到 B 的产出但缺少 A 的产出导致错误。OpenRig 的解决办法是给每条事件分配全局自增seq每个步骤也会记录自己消费到哪个seq。我在自己写的 Agent 代码里也遵循同样的原则每个任务消息都带着seq字段如果当前消息的 seq 小于等于已处理的 seq说明是重复或乱序消息直接忽略。这个逻辑非常简单但能挡住很多隐形 bug。关于状态回滚多智能体环境下最好不要设计复杂的分布式回滚。我的做法是让每个 Agent 尽量保持幂等失败时从错误事件处重放而不是让上游 Agent 撤销之前的输出。重放比回滚简单可靠得多而且结合事件日志整个过程是可审计的。4.3 上下文被撑爆的典型误判很多人在多 Agent 系统里把上下文撑爆第一反应是“模型窗口不够大”。其实大多数情况根本不是窗口不够而是你把所有资料像倒垃圾一样全部塞给了 Agent。我有次让资料检索 Agent 把几十篇参考文章全文都放进了输出下游初稿 Agent 的上下文直接爆炸而真正有用的信息只有每篇的结论和观点。正确的做法是资料检索 Agent 只输出摘要和引用路径。正文内容写入文件或向量数据库下游 Agent 需要一个用户信息时通过工具按需检索。OpenRig 支持context_paths为 Agent 注入文件引用但我实际用下来觉得直接传摘要才是性价比最高的方案。上下文管理的目的从来不是“不超窗口”而是让 Agent 在信息密度足够高的环境里做决策。把注意力集中到关键信息上比你换一个 128k 窗口的模型更有效。4.4 日志和调试技巧调试多智能体系统最关键的是要能追踪一次完整调用链。OpenRig 的事件日志本身就支持查看运行记录我的习惯是每次运行都记录run_id然后通过rig inspect run run_id查看步骤级追踪信息。OpenRig 还会把trace_id注入到 Agent 请求的 Header 里这样你在自己的 Agent 服务日志中也能通过同一个 trace_id 关联到编排层的状态。我自己加日志时会遵守三条纪律一是不在日志里打印完整用户私密信息和长文本原文只打印 hash 和截断摘要既省磁盘又避免敏感信息泄露二是每个 Agent 返回时必须带success字段和具体错误信息哪怕某一步失败也要让日志明确说明失败在哪三是定期检查事件日志文件大小并设置归档策略否则跑上一个月光日志就能塞满整块磁盘。4.5 发布渠道接入时的额外注意点再补充一个和发布相关的小坑。如果你把这条流水线接到外部平台自动发布一定要在发布 Agent 里增加“预检”和“人工确认”两个控制点。预检是指发布前模拟请求目标平台接口确认认证信息没有过期人工确认是指把最终发布的文本送进一个待审核队列等人工点确认之后再真正对外发布。多 Agent 系统太容易因为上游小概率错误导致一篇未成形的稿子被发出去我至少遇到过一次审查 Agent 把一篇包含占位符的草稿放行了好在因为预检失败才没造成事故。写在最后的几句真心话多智能体编排做到后面瓶颈往往不在模型能力而在工程治理。消息契约、状态恢复、可观测性这些才是把一堆 Agent 从 demo 推向系统的关键。OpenRig 用 Rust 把这层底座打磨得还算顺手但它不是银弹它把状态、重试、回放这些脏活接了过去你仍然得非常清楚每个 Agent 的职责边界和上下文预算。我个人的体会是拆 Agent 这事不要太激进。最开始不要追求十个 Agent 协同工作先挑一条最痛的业务链路比如从“选题到初稿”或“评论收集到回复”开始用编排层把一个旧流程重构成两个、三个 Agent 协作的小系统。跑通一次崩溃恢复、一次事件回放、一次重试降级之后你才会真正理解什么叫“持久化协作系统”。等到你习惯了这种把 AI 当工程组件来治理的方式再往更复杂的编排方向扩容就不会手忙脚乱。