6. 多 Agent 编排
上一篇文章实现了 SubAgent:主 Agent 把子任务委派给一个临时工,临时工干完活、返回结果、然后消失。
用了一阵之后会发现一个明显的局限:SubAgent 没有身份,也没有记忆。
你让 SubAgent 去检查 /src/api/ 的安全问题,它干完汇报了。五分钟后你又想让它”再查一遍,看看上次漏了什么”——它一脸茫然。它不是同一个人,是一个全新的临时工,只是恰好接了相似的活。
现实中的团队不这样。你找同事 A 帮忙看代码,十分钟后你再找他,“刚才那个问题你再确认一下”——他知道你在说什么。有身份,有记忆,你们的协作是持续的。
SubAgent 是函数调用,调完就忘。多 Agent 团队需要的是对象——活的,有状态的。
从一次性委派到持久团队
把 SubAgent 变成团队成员,需要补齐三样东西:
1. 持久化。 Agent 跨多轮 chat() 调用存活。它记得上一轮做了什么、结果是什么——不需要每次都从零开始。
2. 身份与生命周期。 Agent 有名字、有角色。它被创建、持续存活、最终解散。生命周期是显式管理的,而不是”函数返回就没了”。
3. 通信通道。 Agent 之间可以互相发消息。点对点,或者广播。不是彼此隔离的孤岛。
这三样东西,用两个类就能装下。
Agent 类
SubAgent 的实现是一个函数——输入 task,输出结果,中间状态全是局部变量,函数返回就释放。
现在把它升级为一个类:
class Agent {
name: string;
role: string;
private messages: ChatMessage[];
private inbox: Message[];
constructor(name: string, role: string, systemPrompt: string) {
this.name = name;
this.role = role;
this.messages = [{ role: "system", content: systemPrompt }];
this.inbox = [];
}
receive(from: string, content: string) {
this.inbox.push({ from, content, timestamp: Date.now() });
}
async chat(task?: string): Promise<string> {
// 干活之前,先把收件箱里的消息同步进对话历史
while (this.inbox.length > 0) {
const msg = this.inbox.shift()!;
this.messages.push({
role: "user",
content: `[消息来自 ${msg.from}]: ${msg.content}`,
});
}
if (task) {
this.messages.push({ role: "user", content: task });
}
const result = await agentLoop(this.messages, {
tools: TEAM_TOOLS,
maxIterations: 15,
});
this.messages = result.messages; // 保留对话历史,下次 chat 继续
return extractFinalAnswer(result.messages);
}
}跟 SubAgent 的关键区别就一个:messages 从局部变量变成了实例属性。
函数版的 SubAgent:每次调用创建新数组,返回时销毁。下一次调用从头开始。
类版的 Agent:messages 挂在 this 上,跟对象同生命周期。第一次 chat() 做的事情,第二次 chat() 还记得。
inbox 同理——其他 Agent 发来的消息先存在这里,chat() 执行前一口气读完。如果在 chat() 中途塞新消息进来,Agent 正在思考的上下文就被污染了。消息只在执行边界消费。
Team 类
Agent 类解决了单个成员的持久化和通信接收端。还需要一个容器来管理一群 Agent——创建、通信路由、销毁。
class Team {
private members: Map<string, Agent> = new Map();
private leader: string;
constructor(leader: string) {
this.leader = leader;
}
hire(name: string, role: string, systemPrompt: string): Agent {
const agent = new Agent(name, role, systemPrompt);
this.members.set(name, agent);
return agent;
}
send(from: string, to: string, content: string) {
const target = this.members.get(to);
if (!target) throw new Error(`Agent ${to} not found`);
target.receive(from, content);
}
broadcast(from: string, content: string) {
for (const [name, agent] of this.members) {
if (name !== from) {
agent.receive(from, content);
}
}
}
get(name: string): Agent | undefined {
return this.members.get(name);
}
disband() {
this.members.clear();
}
}Team 不做决策。它不替 LLM 判断谁该干什么、什么时候通信、通信内容是什么。它只提供两个能力:成员管理(hire、disband)和消息路由(send、broadcast)。
决策权在 LLM 手里——它控制每个 Agent 的 chat() 什么时候调、传什么 task、给谁发消息。Team 只是管道。
这是对 SubAgent 那篇”代码提供能力,LLM 控制流程”的延续。代码提供的是类——Agent 和 Team。LLM 控制的是运行时——组什么团队、谁跟谁通信、什么时候解散。
运行流程
一个典型的团队协作流程是这样的:
// 1. 组建团队
const team = new Team("planner");
team.hire(
"planner",
"规划者",
"你是项目规划者。分析用户需求,拆解任务,分配给合适的成员。"
);
team.hire(
"implementer",
"实现者",
"你是代码实现者。根据规划者的方案写代码。有问题就通过 inbox 向规划者确认。"
);
team.hire(
"reviewer",
"审查者",
"你是代码审查者。审查实现者的代码,找出 bug 和设计问题。不要改代码,只指出问题。"
);
// 2. Planner 先分析需求,产出任务分配方案
const plan = await team.get("planner")!.chat(
"用户需要一个用户认证模块,包含登录、注册、密码重置。"
);
// Planner 通知实现者开始干活
team.send("planner", "implementer", `按这个方案实现:\n${plan}`);
// 3. 实现者收到消息后开始写代码
const code = await team.get("implementer")!.chat();
// 实现完成,通知审查者
team.broadcast("implementer", `代码已完成。请审查者检查。\n${code}`);
// 4. 审查者收到广播,开始审查
const review = await team.get("reviewer")!.chat();
// 审查发现问题,发回给实现者修改
team.send("reviewer", "implementer", `审查意见:\n${review}`);
// 5. 实现者根据意见修改
const revised = await team.get("implementer")!.chat();
// 修改完成,审查者复查
team.send("implementer", "reviewer", `已修改,请复查。\n${revised}`);
const finalReview = await team.get("reviewer")!.chat();
// 6. 解散团队
team.disband();流程里的每一步——谁先开始、谁给谁发消息、什么时候算完——都是 LLM 在做决策。Team 和 Agent 只提供基础设施:存储状态、路由消息、保证每个 Agent 能独立思考和通信。
回到面向对象
多 Agent 编排,说穿了就是把函数改写成类。
SubAgent 是静态方法——输入 task,输出 result。中间状态全是局部变量,函数返回就释放。没有身份,没有记忆,不能通信。
Agent 是对象——有自己的状态(messages),有身份(name、role),能接收消息(inbox)。
Team 是容器——管一组 Agent 对象的生命周期(hire、disband)和通信路由(send、broadcast)。
把整个系列的演进串起来看:
- Agent Loop — 一个 for 循环,最朴素的函数调用
- Memory — 把结果持久化,循环有了”过去”
- Plan — 任务拆分,循环套循环
- Rules / Skills / MCP — 给循环扩展输入
- SubAgent — 循环可以 spawn 子循环,但还是函数调函数
- Multi-Agent — 函数变成了对象。循环不再是一次性的执行过程,而是一个活的实体
至于 LLM 怎么用这些对象——组什么团队、谁跟谁通信、什么时候解散——它自己决定。代码只管把类写好。
下一篇:7. 上下文压缩