上个月我搭了个单 Agent 跑自动化测试,效果还行。然后我天真地想着,"再加两个 Agent 并行干活,效率不就三倍了吗?"
结果是三个 Agent 抢着改同一个文件,一个说要升级依赖版本,另一个说把那个版本锁死------项目直接崩了。
这就是单 Agent 到多 Agent 的"死亡率最高"的一步。不是模型不够聪明,是没有编排的大脑。
我花了两周时间,把市面上能用的编排方案翻了个底朝天,最后用 OpenClaw + MCP 搭了一套能跑通的全流程方案。这篇就是完整的手把手教程------从架构设计到代码实现,从单 Agent 到三 Agent 协作,步步有代码。
先搞清楚:多 Agent 到底需要什么
单 Agent 的核心问题是上下文窗口:一个 prompt 装不下所有事。
多 Agent 的核心问题是协调:多个"大脑"各自决策,需要一个仲裁者。
我总结出三个逃不掉的组件:
| 组件 | 职责 | 类比 |
|---|---|---|
| Orchestrator(编排器) | 拆解任务、分派给子 Agent、汇总结果 | 项目经理 |
| Memory Store(记忆存储) | 跨会话持久化上下文、决策历史和中间结果 | 项目文档 |
| Tool Registry(工具注册表) | 统一管理 Agent 能调用的工具和服务 | IT 资产清单 |
你说的那三个 Agent 为什么打架?因为每个 Agent 都有自己"觉得对的"判断,但没有一个统一的记忆层告诉它们------"十分钟前隔壁兄弟刚改了这个文件"。
Step 1:搭记忆层------用 MCP Server 做统一存储
所有编排的起点是记忆。先搭一个轻量级 MCP 记忆服务器,让所有 Agent 共享同一块"黑板"。
python
# mcp_memory_server.py --- 基于 SQLite + 向量的轻量记忆服务器
import json
import sqlite3
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
DB_PATH = "agent_memory.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE,
value TEXT,
tags TEXT,
created_at TEXT,
updated_at TEXT
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_key ON memories(key)
""")
conn.commit()
conn.close()
class MemoryHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('Content-Length', 0))
body = json.loads(self.rfile.read(length))
if self.path == '/store':
conn = sqlite3.connect(DB_PATH)
now = datetime.now().isoformat()
conn.execute(
"INSERT OR REPLACE INTO memories (key, value, tags, created_at, updated_at) "
"VALUES (?, ?, ?, COALESCE((SELECT created_at FROM memories WHERE key=?), ?), ?)",
(body['key'], json.dumps(body['value']),
json.dumps(body.get('tags', [])),
body['key'], now, now)
)
conn.commit()
conn.close()
self._respond({"status": "ok"})
elif self.path == '/search':
conn = sqlite3.connect(DB_PATH)
query = f"%{body.get('query', '')}%"
rows = conn.execute(
"SELECT key, value, tags, updated_at FROM memories "
"WHERE key LIKE ? OR value LIKE ? OR tags LIKE ? LIMIT 10",
(query, query, query)
).fetchall()
conn.close()
results = [{"key": r[0], "value": json.loads(r[1]),
"tags": json.loads(r[2]), "updated_at": r[3]} for r in rows]
self._respond({"results": results})
def _respond(self, data, code=200):
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
if __name__ == '__main__':
init_db()
server = HTTPServer(('localhost', 8090), MemoryHandler)
print("Memory server running on :8090")
server.serve_forever()
不到 80 行代码,一个可用的记忆服务器就启动了。这个服务器支持两个操作:
POST /store--- 写入记忆(key-value + tags)POST /search--- 全文搜索记忆
启动它:
bash
python3 mcp_memory_server.py &
测试写入和读取:
bash
# 写入一条记忆
curl -X POST http://localhost:8090/store \
-H "Content-Type: application/json" \
-d '{"key": "project:ecommerce:db_config", "value": {"host": "localhost", "port": 5432, "db": "shop"}, "tags": ["postgres", "ecommerce"]}'
# 搜索记忆
curl -X POST http://localhost:8090/search \
-H "Content-Type: application/json" \
-d '{"query": "ecommerce"}'
有这个记忆层之后,多个 Agent 就不会"各自为政"了。它们写入和读取的是同一个数据源。
Step 2:写编排器(Orchestrator)
有了记忆,下一步是编排器。它不直接做具体工作------它负责把任务拆成子任务、派给子 Agent、收集结果、写回记忆。
完整的解决方案就在后半部分,包含可直接复用的代码模板【关注后可见】📦
python
# orchestrator.py --- 任务编排器
import json
import urllib.request
import time
MEMORY_URL = "http://localhost:8090"
class Orchestrator:
def __init__(self, workflow_id: str):
self.workflow_id = workflow_id
self.state = {"tasks": [], "results": {}, "errors": []}
def define_workflow(self, tasks: list):
"""定义工作流步骤"""
self.state["tasks"] = tasks
# 把工作流定义写入记忆
self._store(f"workflow:{self.workflow_id}:definition", {
"tasks": tasks,
"created_at": time.time()
})
def execute(self, agent_executor):
"""按顺序执行任务"""
for i, task in enumerate(self.state["tasks"]):
print(f"[{i+1}/{len(self.state['tasks'])}] 执行: {task['name']}")
# 1. 从记忆读取上下文
context = self._search(task.get("context_query", ""))
# 2. 执行任务
result = agent_executor(task, context)
# 3. 保存结果到记忆
self.state["results"][task["name"]] = result
self._store(f"workflow:{self.workflow_id}:result:{task['name']}", {
"result": result,
"completed_at": time.time()
})
# 4. 检查是否需要继续
if result.get("status") == "blocked":
print(f" ⚠️ {task['name']} 被阻塞,需要人工介入")
self.state["errors"].append({
"task": task["name"],
"reason": result.get("reason", "unknown")
})
break
# 5. 汇总写入最终记忆
self._store(f"workflow:{self.workflow_id}:final", {
"total_tasks": len(self.state["tasks"]),
"completed": len(self.state["results"]),
"errors": self.state["errors"],
"status": "complete" if not self.state["errors"] else "partial"
})
return self.state
def _store(self, key, value):
data = json.dumps({"key": key, "value": value, "tags": [self.workflow_id]}).encode()
urllib.request.urlopen(f"{MEMORY_URL}/store", data=data)
def _search(self, query):
if not query:
return []
data = json.dumps({"query": query}).encode()
resp = urllib.request.urlopen(f"{MEMORY_URL}/search", data=data)
return json.loads(resp.read()).get("results", [])
这个编排器实现了"定义 → 执行 → 存储"的闭环。每个子 Agent 的执行结果被自动保存到记忆服务器,后续步骤可以随时查看上下文。
使用示例:
python
# 定义一个三步骤工作流
orch = Orchestrator("deploy-v2")
orch.define_workflow([
{"name": "代码审查", "context_query": "project:规范", "agent": "reviewer"},
{"name": "运行测试", "context_query": "project:配置", "agent": "tester"},
{"name": "部署预览", "context_query": "workflow:deploy-v2:result:运行测试", "agent": "deployer"},
])
Step 3:子 Agent 接入 MCP 工具
编排器有了,子 Agent 需要能调用工具。MCP 协议在这里发挥作用------每个子 Agent 通过 MCP 访问记忆服务器和其他工具。
在 OpenClaw 的 config.yaml 中注册 MCP 记忆工具:
yaml
# ~/.config/openclaw/config.yaml
mcpServers:
memory:
command: python3
args:
- /path/to/mcp_memory_client.py
env:
MEMORY_SERVER_URL: "http://localhost:8090"
filesystem:
command: npx
args:
- -y
- "@modelcontextprotocol/server-filesystem"
- /home/ubuntu/projects
对应的 MCP 客户端封装:
python
# mcp_memory_client.py --- 将记忆服务器包装为 MCP 工具
import json
import sys
import urllib.request
MEMORY_URL = "http://localhost:8090"
def handle_request(request):
method = request.get("method", "")
if method == "tools/list":
return {
"tools": [
{
"name": "memory_store",
"description": "存储记忆到持久化层",
"inputSchema": {
"type": "object",
"properties": {
"key": {"type": "string"},
"value": {"type": "object"},
"tags": {"type": "array", "items": {"type": "string"}}
}
}
},
{
"name": "memory_search",
"description": "搜索已有记忆",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "number", "default": 5}
}
}
}
]
}
elif method == "tools/call":
tool = request["params"]["name"]
args = request["params"]["arguments"]
if tool == "memory_store":
data = json.dumps({
"key": args["key"],
"value": args["value"],
"tags": args.get("tags", [])
}).encode()
urllib.request.urlopen(f"{MEMORY_URL}/store", data=data)
return {"content": [{"type": "text", "text": "stored"}]}
elif tool == "memory_search":
data = json.dumps({"query": args["query"]}).encode()
resp = urllib.request.urlopen(f"{MEMORY_URL}/search", data=data)
results = json.loads(resp.read()).get("results", [])
return {"content": [{"type": "text", "text": json.dumps(results, indent=2)}]}
# 标准 MCP stdio 协议
if __name__ == "__main__":
while True:
line = sys.stdin.readline()
if not line:
break
request = json.loads(line)
response = handle_request(request)
response["id"] = request.get("id")
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
这个 MCP 客户端通过 stdio 与 OpenClaw 通信,提供 memory_store 和 memory_search 两个工具。子 Agent 可以在 prompt 中随意调用它们。
Step 4:实战------三 Agent 协作完成代码迁移
理论说够了,来一个真实的编排案例。假设我们要把项目从 JavaScript 迁移到 TypeScript,分为三步走。
python
# migration_workflow.py --- 完整的多Agent编排
from orchestrator import Orchestrator
import subprocess
import json
def run_agent(agent_name: str, task: dict, context: list):
"""调用 OpenClaw 执行子任务"""
prompt = f"""
你是一个 {agent_name} Agent。
你的任务: {task['name']}
上下文: {json.dumps(context, indent=2)[:500]}
请完成任务并返回 JSON: {{"status": "ok|blocked", "summary": "...", "files_changed": [...]}}
"""
# 用 OpenClaw CLI 执行
result = subprocess.run(
["openclaw", "run", "-p", prompt],
capture_output=True, text=True, timeout=120
)
try:
return json.loads(result.stdout.strip())
except:
return {"status": "blocked", "reason": f"Agent 输出解析失败: {result.stdout[:200]}"}
# 定义工作流
orch = Orchestrator("js-to-ts-migration")
orch.define_workflow([
{
"name": "扫描项目结构,列出所有需要迁移的 JS 文件",
"context_query": "project:目录结构",
"agent": "scanner"
},
{
"name": "分析依赖关系,确定迁移顺序",
"context_query": "project:依赖关系",
"agent": "analyzer"
},
{
"name": "逐个文件从 JS 迁移到 TS,添加类型定义",
"context_query": "workflow:js-to-ts-migration:result:分析依赖关系",
"agent": "migrator"
}
])
# 执行(依次启动子 Agent)
state = orch.execute(lambda task, ctx: run_agent(task["agent"], task, ctx))
print(f"✅ 完成 {len(state['results'])}/{len(state['tasks'])} 个任务")
if state["errors"]:
print(f"❌ {len(state['errors'])} 个错误需要处理")
三个 Agent 各司其职:
| Agent | 职责 | 输入 | 输出 |
|---|---|---|---|
| Scanner | 扫描项目 | 项目路径 | 文件清单 + 类型统计 |
| Analyzer | 分析依赖 | 文件清单 | 迁移顺序图 |
| Migrator | 逐个迁移 | 迁移顺序 + 记忆 | 类型定义文件 |
关键的设计决策:Analyzer 的输出自动存入记忆,Migrator 从记忆读取 Analyzer 的结果。这就避免了那个"三个 Agent 抢文件"的问题。
Step 5:状态持久化------让工作流"断电不停"
多 Agent 工作流的一个常见场景是跑着跑着 Agent 崩了(或者你关掉了终端)。如何在恢复后继续?
python
# 恢复工作流 --- 从记忆读取上一个状态
def resume_workflow(workflow_id: str):
"""从中断点恢复工作流"""
orch = Orchestrator(workflow_id)
# 从记忆读取工作流定义
final = orch._search(f"workflow:{workflow_id}:final")
if final:
print(f"ℹ️ 工作流 {workflow_id} 已完成,跳过恢复")
return None
# 读取已完成的步骤
completed_results = orch._search(f"workflow:{workflow_id}:result:")
# 读取工作流定义
definition = orch._search(f"workflow:{workflow_id}:definition")
if not definition:
print("❌ 找不到工作流定义")
return None
tasks = json.loads(definition[0]["value"])["tasks"]
completed_count = len(completed_results)
print(f"🔄 恢复工作流: 已完成 {completed_count}/{len(tasks)}")
remaining_tasks = tasks[completed_count:]
# 从断点继续执行
orch.state["tasks"] = remaining_tasks
orch.state["results"] = {t["name"]: json.loads(r["value"])
for t, r in zip(tasks[:completed_count], completed_results)}
return orch
# 使用
orch = resume_workflow("js-to-ts-migration")
if orch:
agent_fn = lambda task, ctx: run_agent(task["agent"], task, ctx)
state = orch.execute(agent_fn)
这段代码的关键是:所有中间结果都通过 _store 写入了记忆服务器。所以即使主进程挂了,重启后读取记忆就能知道"干到哪了、干了什么、下一步是什么"。
架构总览
把以上组件串起来,完整的架构图如下:
┌─────────────────────────────────────────────────────┐
│ Orchestrator │
│ 拆任务 → 派 Agent → 收结果 → 决定下一步 │
└────┬────────┬────────┬────────┬──────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐┌────────┐┌────────┐┌──────────────┐
│Agent 1 ││Agent 2 ││Agent 3 ││MCP Tool 注册 │
│Scanner ││Analyzer││Migrator││memory/filesys │
└───┬────┘└───┬────┘└───┬────┘└──────┬───────┘
└──────────┴──────────┴─────────────┘
│
▼
┌────────────────┐
│ MCP Memory │
│ Server (:8090)│
│ SQLite + Tags │
└────────────────┘
所有 Agent 读写同一个记忆后端,Orchestrator 控制流程,MCP 提供工具接口。
踩过的坑
坑 1:Agent 之间锁竞争
两个 Agent 同时 memory_store 同一个 key,后写覆盖先写。解法很简单------在编排器中加一个写锁:
python
import threading
_write_lock = threading.Lock()
def safe_store(self, key, value):
with _write_lock:
self._store(key, value)
坑 2:Agent 上下文爆炸
每个 Agent 在执行前会加载所有记忆,但如果记忆太多,prompt 会爆炸。需要在编排器里加一个记忆摘要层:
python
def summarize_context(self, results: list, max_tokens=2000):
"""压缩记忆上下文"""
total = sum(len(json.dumps(r)) for r in results)
if total > max_tokens:
# 只保留最近的3条结果
return results[-3:]
return results
坑 3:Agent "忘记"调用工具
不是每个 Agent 都记得调用 memory_search。我加了一条系统 prompt 指令来完成这个任务:
在你执行任何操作前,先调用 memory_search 检查是否有相关上下文。
完成操作后,调用 memory_store 保存结果。
适合什么场景
这套架构不是万能的,但它解决了一个真实的问题:你的 Agent 不需要每次都在空白状态下工作。
| 场景 | 适合 | 不适合 |
|---|---|---|
| 代码迁移/重构 | ✅ 分步执行,记忆衔接 | ❌ 实时聊天(延迟高) |
| CI/CD 流水线 | ✅ 按步骤执行+断点续传 | ❌ 毫秒级响应要求 |
| 数据管道处理 | ✅ 分阶段处理+中间结果持久化 | ❌ 单次简单查询 |
| 多 Agent 并行开发 | ✅ 协调多个 AI 编码助手 | ❌ Agent 之间频繁通信 |
写在最后
回到开头那个"三个 Agent 打架"的故事。问题的根源不是 Agent 不够聪明,是没有给它们一套协同的规则。
我搭完这套系统之后,最大的感受不是"哇三倍效率",而是------每个 Agent 都知道自己在干什么,以及别人干了什么。这种感觉比单纯的效率提升重要得多:Agent 不再是孤立运行的"黑箱",而是被编排进了可观察、可恢复、可控制的工作流。
如果你已经在用单 Agent 做日常开发,下一步不是"再加一个 Agent",而是先搭好记忆层。有了共享记忆,多 Agent 协作就是一个顺理成章的结果。
下一步我会尝试把 LLM 本身的推理也纳入编排------让 Orchestrator 不只是"按顺序派活",而是根据子 Agent 的反馈动态调整任务分解策略。这个坑挖好了,下次填。
延伸阅读:Dynamic Workflows教程:Claude Opus 4.8的编排机制、Kimi Work 300Agent实测:多Agent并行的真实体验
📌 系列文章
- 从零搭建多Agent协作系统:2026年最实用的智能体工作流实战指南
- AI Agent 记忆方案横评:Memoria vs OpenClaw vs MCP,让Agent记住你的3种方式
- 搭AI Agent到底该选哪个框架?OpenClaw、DeerFlow、MCP生态我全测了一遍
看完有收获?点个关注 👆 我会持续分享更多AI编程实战教程。