尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI Agent持久化协作系统:基于tmux与MCP的多Agent编排实践

AI Agent持久化协作系统:基于tmux与MCP的多Agent编排实践 1. 为什么需要把 AI Agent 从单次对话变成持久化协作系统1.1 从单 Agent 到多 Agent 的真实痛点我最早接触 AI Agent 的时候和大多数人一样都是从单 Agent 开始的。一个对话框一个系统提示词一个模型用户输入问题模型给出回答结束。这种模式在简单任务上确实好用比如写一段代码、翻译一篇文章、总结一份文档。但只要任务稍微复杂一点问题就暴露出来了。最典型的场景是我需要一个 Agent 帮我调研某个技术方案然后写一份对比报告最后再根据报告生成一份可执行的实施计划。单 Agent 做这件事的时候它会在一个上下文窗口里把所有事情都做完。结果是调研阶段消耗了大量 token等到写报告的时候前面的调研细节已经被压缩得七零八落等到写实施计划的时候报告的结构又开始影响它对调研内容的理解。整个过程就像让一个人连续开会八小时中间不休息、不做笔记最后还要他输出一份完整的会议纪要。多 Agent 的思路就是把这个过程拆开。调研 Agent 只负责调研输出结构化的调研结果写作 Agent 只负责根据调研结果写报告规划 Agent 只负责根据报告生成计划。每个 Agent 有自己的上下文窗口、自己的系统提示词、自己的工具集。这样做的好处是显而易见的每个 Agent 的上下文更干净职责更单一输出质量更稳定。但问题也随之而来。多个 Agent 之间怎么通信任务怎么传递状态怎么保存如果一个 Agent 执行到一半崩了怎么恢复如果两个 Agent 需要同时访问同一个资源怎么协调这些问题在单 Agent 模式下根本不存在但在多 Agent 模式下它们变成了必须解决的核心问题。1.2 OpenRig 的核心定位编排层而不是 Agent 框架OpenRig 这个项目我理解它的核心定位不是再造一个 Agent 框架而是做编排层。这个区别很重要。Agent 框架解决的是怎么让一个 Agent 更好地工作比如怎么调用工具、怎么管理记忆、怎么做推理链。而编排层解决的是怎么让多个 Agent 协同工作比如任务怎么分发、状态怎么持久化、通信怎么保证可靠、失败怎么恢复。打个比方Agent 框架像是培养一个优秀的员工而编排层像是设计一套公司的工作流程。员工再优秀如果没有好的流程多个员工在一起也会互相打架。OpenRig 要做的就是这套流程。它选择的技术路线也很有意思用tmux做进程隔离和会话管理用MCP做工具调用协议用持久化的方式保存 Agent 状态。这三个选择背后都有很实际的考量我后面会详细拆解。1.3 适合谁来参考这套实践这套东西不是给完全新手准备的。如果你还没写过任何 Agent建议先从一个简单的单 Agent 项目开始理解什么是系统提示词、什么是工具调用、什么是上下文窗口。但如果你已经写过单 Agent并且遇到了下面这些情况那 OpenRig 的思路就非常值得参考你的 Agent 任务越来越复杂单次对话已经装不下你需要多个 Agent 分别负责不同环节但不知道怎么让它们协作你的 Agent 执行时间很长中间可能中断你需要它能恢复你需要 Agent 调用外部工具但不想把工具调用逻辑硬编码在 Agent 里你想让 Agent 在后台持续运行而不是每次都要手动触发如果你有以上任何一个需求那这篇文章的内容应该能帮到你。我会从设计思路、核心细节、实操过程、常见问题四个层面把这套编排实践讲清楚。2. 核心设计思路拆解为什么是 tmux MCP 持久化2.1 用 tmux 做 Agent 进程隔离的底层逻辑很多人第一次听到用 tmux 管理 AI Agent的时候会觉得这个组合很奇怪。tmux 是一个终端复用工具跟 AI 有什么关系但如果你仔细想一下多 Agent 系统的需求就会发现 tmux 其实是一个非常自然的选择。多 Agent 系统需要什么需要每个 Agent 有独立的运行环境需要能够查看每个 Agent 的实时输出需要能够在 Agent 崩溃后重新附着上去需要能够在不影响其他 Agent 的情况下重启某一个 Agent。这些需求tmux 全都天然满足。具体来说每个 Agent 运行在一个独立的 tmux session 里。这个 session 有自己的终端、自己的环境变量、自己的工作目录。你可以用tmux attach -t agent-1附着到 agent-1 的 session 上实时看它在干什么。如果 agent-1 崩了你可以tmux kill-session -t agent-1然后重新启动其他 Agent 完全不受影响。更重要的是tmux session 是独立于你的 SSH 连接的。你关掉终端Agent 还在跑你重新连上来tmux ls就能看到所有还在运行的 Agent。这个特性对于需要长时间运行的 Agent 来说简直是刚需。我试过用 Docker 做隔离也试过用独立的进程管理器但最后发现 tmux 的平衡点最好隔离性够用开销极小调试极其方便。Docker 太重启动一个容器要好几秒独立的进程管理器又太轻没有终端交互能力。tmux 刚好卡在中间。2.2 MCP 协议在编排中的角色工具调用的标准化MCP 是 Model Context Protocol 的缩写简单说就是一套让 AI 模型调用外部工具的标准化协议。在没有 MCP 之前每个 Agent 框架都有自己的工具调用格式你在这个框架里写的工具换一个框架就不能用了。MCP 的出现相当于给工具调用定了一个USB 接口标准。在 OpenRig 的编排体系里MCP 的角色是解耦 Agent 和工具。Agent 不需要知道工具是怎么实现的它只需要知道有一个 MCP Server 提供了某个工具然后按照 MCP 协议去调用就行了。工具的实现可以随时替换只要 MCP 接口不变Agent 就不用改。这个设计的好处在实际操作中非常明显。比如我有一个 Agent 需要读写文件另一个 Agent 需要查询数据库第三个 Agent 需要调用某个 API。如果没有 MCP我可能需要在每个 Agent 里都写一套工具调用逻辑。有了 MCP我只需要启动三个 MCP Server每个 Server 提供对应的工具然后告诉 Agent 去哪个 Server 找工具就行了。而且 MCP 是跨语言的。MCP Server 可以用 Python 写也可以用 Node.js 写甚至可以用 Go 写。Agent 只关心协议不关心实现语言。这在多 Agent 系统里特别有用因为不同的 Agent 可能用不同的技术栈但通过 MCP 可以无缝协作。2.3 持久化协作系统的状态管理方案持久化是多 Agent 系统里最容易被低估的部分。很多人一开始会觉得Agent 嘛跑完就完了要什么持久化但实际用起来就会发现没有持久化的多 Agent 系统基本上没法用。想象一下这个场景你有五个 Agent 协作完成一个任务任务执行到第三步的时候机器重启了。如果没有持久化所有 Agent 的状态全丢了你得从头再来。如果有持久化每个 Agent 的状态都保存在磁盘上重启后可以从中断的地方继续。OpenRig 的持久化方案我理解是分层的。第一层是会话状态持久化每个 Agent 的对话历史、当前任务、已完成步骤都保存下来。第二层是任务状态持久化整个协作任务进行到哪一步、哪些子任务完成了、哪些还在进行这些信息也保存下来。第三层是工具调用结果持久化Agent 调用工具产生的输出比如读到的文件内容、查询到的数据也保存下来避免重复调用。这三层持久化配合起来才能实现真正的断点续跑。我实测下来有了这套机制之后即使中途手动重启某个 Agent整个协作流程也能继续不需要人工干预。3. 核心细节解析与实操要点3.1 Agent 会话的创建与命名规范创建 Agent 会话看起来简单但命名规范如果一开始没定好后面会非常痛苦。我踩过的坑是一开始随便起名字agent1、agent2、agent3结果 Agent 多了之后完全分不清谁是谁。后来我改成了一套命名规范{项目名}-{角色}-{序号}。比如research-writer-01、research-reviewer-01、report-writer-01。这样一看名字就知道这个 Agent 属于哪个项目、扮演什么角色、是第几个实例。创建会话的命令很简单tmux new-session -d -s research-writer-01 -c /path/to/workspace这里的-d表示后台创建-s指定 session 名字-c指定工作目录。工作目录很重要因为 Agent 读写文件都是相对于这个目录的。我建议每个 Agent 有自己独立的工作目录避免文件冲突。创建完 session 之后还需要在里面启动 Agent 进程。我通常会在 session 里跑一个启动脚本这个脚本负责设置环境变量、加载配置、启动 Agent 主循环。这样做的好处是如果 Agent 崩了我只需要重新跑这个脚本不需要重新创建 session。注意tmux session 的名字不能包含点号.和冒号:这两个字符在 tmux 里有特殊含义。我建议只用字母、数字和连字符。3.2 MCP Server 的配置与工具注册MCP Server 的配置是整套系统里最需要仔细处理的部分。每个 MCP Server 需要声明自己提供哪些工具每个工具需要什么参数返回什么格式。这些信息会以 JSON Schema 的形式暴露给 Agent。一个典型的 MCP Server 配置大概长这样{ name: file-tools, version: 1.0.0, tools: [ { name: read_file, description: 读取指定路径的文件内容, parameters: { type: object, properties: { path: { type: string, description: 文件路径 } }, required: [path] } } ] }Agent 启动的时候会先连接所有配置好的 MCP Server拉取工具列表然后把这些工具注册到自己的工具集里。这个过程是自动的不需要人工干预。我建议把 MCP Server 的配置写在一个统一的配置文件里比如mcp-servers.json然后所有 Agent 共享这个配置。这样新增工具的时候只需要改一个地方所有 Agent 都能用上。提示MCP Server 的启动顺序有讲究。如果 Agent A 依赖的工具在 MCP Server B 里那 B 必须先启动。我通常会在编排脚本里加一个健康检查确认所有 MCP Server 都就绪之后再启动 Agent。3.3 任务分发与结果回收的通信机制多 Agent 协作的核心是任务分发和结果回收。OpenRig 的做法我理解是基于文件系统的消息队列。每个 Agent 有一个收件箱目录任务以文件的形式投递到收件箱里Agent 处理完之后把结果写到发件箱目录里。这个方案听起来很土但实际用起来非常可靠。文件系统是操作系统里最稳定的东西之一不会因为网络问题丢消息不会因为进程崩溃丢数据。而且文件天然就是持久化的重启之后消息还在。任务文件通常是一个 JSON包含任务 ID、任务类型、输入参数、超时时间等信息。Agent 的主循环会不断扫描收件箱发现有新任务就取出来处理处理完把结果写到发件箱。import json import os import time INBOX /path/to/inbox OUTBOX /path/to/outbox def process_tasks(): while True: for filename in os.listdir(INBOX): if not filename.endswith(.json): continue filepath os.path.join(INBOX, filename) with open(filepath, r) as f: task json.load(f) result handle_task(task) result_path os.path.join(OUTBOX, filename) with open(result_path, w) as f: json.dump(result, f) os.remove(filepath) time.sleep(1)这个循环很简单但有几个细节需要注意。第一处理完任务之后要删除原文件否则会重复处理。第二结果文件的命名要和任务文件对应方便追踪。第三要加异常处理如果处理失败要把错误信息也写到结果里而不是让 Agent 直接崩掉。3.4 状态持久化的文件结构设计状态持久化的文件结构我建议按 Agent 和任务两个维度来组织state/ agents/ research-writer-01/ context.json history.jsonl current_task.json report-writer-01/ context.json history.jsonl current_task.json tasks/ task-001/ status.json input.json output.json logs.txtcontext.json保存 Agent 的当前上下文比如系统提示词、工具列表、环境变量。history.jsonl保存对话历史每行一条记录方便追加和查询。current_task.json保存当前正在处理的任务。任务目录下的status.json保存任务状态比如 pending、running、done、failed。input.json和output.json保存输入输出。logs.txt保存执行日志。这个结构的好处是任何时刻你都可以通过查看文件来了解系统状态。不需要连接数据库不需要调用 API直接cat就行。调试的时候特别方便。4. 实操过程与核心环节实现4.1 环境准备与依赖安装开始之前需要准备以下环境Linux 或 macOSWindows 建议用 WSL2tmux 3.0 以上Python 3.10 以上或者 Node.js 18 以上看你用什么写 Agent一个支持工具调用的模型 API安装 tmux 很简单# Ubuntu/Debian sudo apt-get install tmux # macOS brew install tmuxPython 依赖方面我建议用虚拟环境python -m venv venv source venv/bin/activate pip install mcp anthropic openai这里的mcp是 MCP 协议的 Python SDKanthropic和openai是模型客户端看你用哪个模型。注意不要用系统 Python 直接装依赖容易污染环境。虚拟环境是必须的尤其是当你同时跑多个 Agent 的时候不同 Agent 可能依赖不同版本的库。4.2 启动第一个 Agent 会话环境准备好之后就可以启动第一个 Agent 了。我建议从最简单的开始先跑通一个 Agent再考虑多 Agent 协作。# 创建工作目录 mkdir -p ~/openrig/workspace/research-writer-01 mkdir -p ~/openrig/state/agents/research-writer-01 # 创建 tmux session tmux new-session -d -s research-writer-01 -c ~/openrig/workspace/research-writer-01 # 在 session 里启动 Agent tmux send-keys -t research-writer-01 source ~/openrig/venv/bin/activate python ~/openrig/agent.py --role research-writer --id 01 Enter这几条命令做完Agent 就在后台跑起来了。你可以用tmux attach -t research-writer-01附着上去看它的输出。Agent 的主程序agent.py大概长这样import argparse import json import os import time from mcp_client import MCPClient from model_client import ModelClient def main(): parser argparse.ArgumentParser() parser.add_argument(--role, requiredTrue) parser.add_argument(--id, requiredTrue) args parser.parse_args() agent_id f{args.role}-{args.id} state_dir f/home/user/openrig/state/agents/{agent_id} inbox f/home/user/openrig/inbox/{agent_id} outbox f/home/user/openrig/outbox/{agent_id} os.makedirs(state_dir, exist_okTrue) os.makedirs(inbox, exist_okTrue) os.makedirs(outbox, exist_okTrue) mcp MCPClient(/home/user/openrig/mcp-servers.json) model ModelClient() while True: tasks [f for f in os.listdir(inbox) if f.endswith(.json)] for task_file in tasks: task_path os.path.join(inbox, task_file) with open(task_path) as f: task json.load(f) result execute_task(task, mcp, model, state_dir) with open(os.path.join(outbox, task_file), w) as f: json.dump(result, f) os.remove(task_path) time.sleep(1) if __name__ __main__: main()这个骨架很简单但已经包含了核心逻辑扫描收件箱、执行任务、写结果、删任务。你可以根据自己的需求扩展execute_task函数。4.3 配置 MCP Server 并注册工具MCP Server 的配置我建议单独写一个文件比如mcp-servers.json{ servers: [ { name: file-tools, command: python, args: [/home/user/openrig/mcp_servers/file_server.py], env: { WORKSPACE: /home/user/openrig/workspace } }, { name: web-tools, command: python, args: [/home/user/openrig/mcp_servers/web_server.py] } ] }每个 Server 用command和args指定启动方式用env传递环境变量。Agent 启动的时候会读取这个配置依次启动所有 Server然后拉取工具列表。一个最简单的文件工具 Server 大概长这样from mcp.server import Server from mcp.types import Tool, TextContent import os app Server(file-tools) WORKSPACE os.environ.get(WORKSPACE, /tmp) app.list_tools() async def list_tools(): return [ Tool( nameread_file, description读取工作目录下的文件, inputSchema{ type: object, properties: { path: {type: string} }, required: [path] } ), Tool( namewrite_file, description写入文件到工作目录, inputSchema{ type: object, properties: { path: {type: string}, content: {type: string} }, required: [path, content] } ) ] app.call_tool() async def call_tool(name, arguments): if name read_file: full_path os.path.join(WORKSPACE, arguments[path]) with open(full_path) as f: return [TextContent(typetext, textf.read())] elif name write_file: full_path os.path.join(WORKSPACE, arguments[path]) with open(full_path, w) as f: f.write(arguments[content]) return [TextContent(typetext, textOK)]这个 Server 提供了两个工具读文件和写文件。Agent 可以通过 MCP 协议调用它们不需要知道文件系统怎么操作。4.4 多 Agent 协作的完整流程演示假设我们要完成一个调研并撰写技术报告的任务涉及三个 Agent调研 Agent、写作 Agent、审核 Agent。第一步调研 Agent 收到任务调用 web-tools 搜索相关资料把结果写到research-output.json。第二步写作 Agent 扫描到research-output.json存在读取内容调用模型生成报告草稿写到report-draft.md。第三步审核 Agent 扫描到report-draft.md存在读取内容调用模型审核如果通过就写到report-final.md如果不通过就把修改意见写到report-feedback.md。第四步写作 Agent 扫描到report-feedback.md存在根据反馈修改报告重新写到report-draft.md。这个流程可以用一个简单的编排脚本协调import os import json import time def orchestrate(): while True: # 检查调研结果 if os.path.exists(research-output.json) and not os.path.exists(report-draft.md): task { type: write_report, input: research-output.json, output: report-draft.md } with open(inbox/report-writer-01/task-write.json, w) as f: json.dump(task, f) # 检查报告草稿 if os.path.exists(report-draft.md) and not os.path.exists(report-final.md): task { type: review_report, input: report-draft.md, output: report-final.md } with open(inbox/reviewer-01/task-review.json, w) as f: json.dump(task, f) time.sleep(2) if __name__ __main__: orchestrate()这个编排脚本不断检查文件状态根据状态投递任务。虽然简单但非常有效。我实测下来这套机制跑几十个任务都不会出问题。4.5 断点续跑与故障恢复的实操断点续跑的关键是每个 Agent 在处理任务之前先把当前状态写到磁盘处理完之后再更新状态。这样即使中途崩溃重启后也能从上次的状态继续。具体做法是在execute_task函数里加状态保存def execute_task(task, mcp, model, state_dir): task_id task[id] state_file os.path.join(state_dir, current_task.json) # 保存当前任务 with open(state_file, w) as f: json.dump({task_id: task_id, status: running}, f) try: result do_work(task, mcp, model) with open(state_file, w) as f: json.dump({task_id: task_id, status: done}, f) return result except Exception as e: with open(state_file, w) as f: json.dump({task_id: task_id, status: failed, error: str(e)}, f) raiseAgent 重启的时候先读current_task.json如果发现有未完成的任务就重新执行。如果任务已经完成就跳过。提示重新执行任务的时候要注意幂等性。如果任务有副作用比如写文件、发请求要确保重复执行不会产生错误结果。我的做法是给每个任务一个唯一 ID执行前先检查这个 ID 是否已经处理过。5. 常见问题与排查技巧实录5.1 Agent 会话丢失或无法附着最常见的问题是 tmux session 莫名其妙消失了。原因通常有几个一是机器重启tmux session 不会自动恢复二是 session 里的进程崩溃导致 session 退出三是磁盘满了tmux 无法写入。排查步骤tmux ls看还有哪些 session如果 session 不在了检查系统日志journalctl -xe看有没有 OOM 或磁盘错误检查磁盘空间df -h检查 Agent 日志看崩溃前最后在做什么预防措施我建议写一个守护脚本定期检查 session 是否存在不存在就重新创建。同时给 Agent 加异常捕获避免因为未捕获异常导致进程退出。5.2 MCP 工具调用超时或失败MCP 工具调用失败的原因很多常见的有Server 没启动、参数格式不对、工具内部报错、网络超时。排查的时候先确认 Server 是否在运行ps aux | grep mcp_server然后手动调用一次工具看返回什么echo {jsonrpc:2.0,method:tools/call,params:{name:read_file,arguments:{path:test.txt}},id:1} | python mcp_servers/file_server.py如果手动调用成功说明 Server 没问题问题在 Agent 这边。检查 Agent 的工具调用参数是否符合 Schema检查超时设置是否太短。我踩过的坑是工具返回的内容太大超过了模型的上下文窗口。解决办法是在 MCP Server 里加一个截断逻辑超过一定长度的内容只返回摘要。5.3 任务重复执行或状态不一致任务重复执行通常是因为任务文件没有及时删除或者 Agent 重启后重新扫描到了已经处理过的任务。解决办法是引入任务状态标记。每个任务处理前先在state/tasks/{task_id}/status.json里标记为 running处理完后标记为 done。Agent 扫描任务的时候先检查状态如果已经是 done 就跳过。状态不一致通常是因为多个 Agent 同时写同一个文件。解决办法是给文件加锁或者用不同的文件名避免冲突。我通常用{task_id}-{agent_id}.json这种命名方式确保每个 Agent 写的文件不冲突。5.4 上下文窗口溢出与 token 消耗过快多 Agent 系统里token 消耗比单 Agent 快得多因为每个 Agent 都有自己的上下文。如果不管控很容易超出预算。我的做法是第一给每个 Agent 设置独立的 token 预算超过就停止第二定期压缩历史记录只保留最近 N 轮对话第三把大块内容存到文件里上下文里只放文件路径和摘要。具体实现上可以在 Agent 主循环里加一个检查def check_budget(state_dir, max_tokens100000): history_file os.path.join(state_dir, history.jsonl) total 0 with open(history_file) as f: for line in f: record json.loads(line) total record.get(tokens, 0) return total max_tokens超过预算就暂停 Agent等人工介入或者自动压缩历史。5.5 常见问题速查表问题现象可能原因排查方法解决方案tmux session 消失机器重启/进程崩溃/磁盘满tmux ls、journalctl、df -h守护脚本自动重建MCP 调用超时Server 未启动/参数错误/内容过大手动调用测试检查 Server 状态、截断大内容任务重复执行任务文件未删除/状态未标记检查任务目录引入状态标记和幂等设计token 消耗过快上下文未压缩/历史未清理统计 history 文件设置预算、定期压缩Agent 输出乱码编码不一致检查文件编码统一用 UTF-8文件写入冲突多 Agent 同时写同一文件检查文件命名用 task_id agent_id 命名6. 几个我踩过的坑和实操心得6.1 不要一开始就上多 Agent我最早做多 Agent 的时候一上来就设计了五个 Agent 协作结果调试了两周都没跑通。后来退回到单 Agent先把单 Agent 跑稳再逐步增加 Agent反而顺利得多。我的建议是先用单 Agent 把任务跑通确认模型、工具、状态管理都没问题然后再拆分成多 Agent。拆的时候一次只加一个 Agent加完跑通了再加下一个。这样出问题的时候容易定位。6.2 日志要写详细但不要写太多日志是排查问题的关键但日志太多也会淹没关键信息。我的做法是分级ERROR 级别记录所有异常INFO 级别记录任务开始和结束DEBUG 级别记录详细的工具调用参数和返回。平时只开 INFO出问题的时候临时开 DEBUG。日志文件按天切割避免单个文件太大。6.3 给每个 Agent 设置超时Agent 卡死是常有的事可能是模型 API 挂了可能是工具调用死循环可能是网络问题。如果不设超时一个 Agent 卡死会拖垮整个协作流程。我的做法是给每个任务设置超时时间超过就标记为 failed然后由编排脚本决定是重试还是跳过。超时时间根据任务复杂度设置简单的任务 30 秒复杂的任务 5 分钟。6.4 定期备份状态目录状态目录是整个系统的核心丢了就全完了。我建议用 cron 定期备份0 * * * * tar -czf /backup/openrig-state-$(date \%Y\%m\%d\%H).tar.gz /home/user/openrig/state每小时备份一次保留最近 24 份。这样即使状态目录被误删也能快速恢复。6.5 模型选择要匹配任务不同的 Agent 可以用不同的模型。调研 Agent 需要强推理能力可以用大模型写作 Agent 需要强语言能力可以用另一个大模型审核 Agent 需要快速判断可以用小模型。我实测下来混合使用模型能显著降低成本同时保持输出质量。关键是要给每个 Agent 明确的任务边界让模型的能力和任务的需求匹配。这套编排实践我用了大半年从最初的单 Agent 到现在的多 Agent 协作中间踩了不少坑也积累了一些经验。OpenRig 这个项目的思路给了我很大启发尤其是用 tmux 做隔离、用 MCP 做工具标准化、用文件系统做持久化这三点看起来简单但组合起来非常实用。如果你也在做多 Agent 系统希望这些内容能帮你少走一些弯路。
返回列表