上一篇的基础
上一篇讲了消息 ACK 三态:消息从 pending 到 delivered 到 processed,每个状态都有明确的触发时机,协调者随时可以查询。
现在我们往上走一层,看任务是怎么被批量分发出去(TeamFanout),结果又是怎么被汇总回来的(TeamCollect)。
这两个操作合在一起,就是 AgentTeams 的核心能力:把一个大任务拆成多个小任务,并行执行,再把结果拼回来。
结论先说
| 工具 | 职责 | 关键设计 |
|---|---|---|
| TeamFanout | 把任务分发给多个成员 agent | 每个成员拿到独立的上下文片段,互不干扰 |
| TeamCollect | 等待所有成员完成,汇总结果 | 支持超时、部分结果、冲突检测 |
两者配合的核心思想:分而治之(divide and conquer)------拆分时上下文隔离,汇总时冲突显式处理。
一、TeamFanout:分发不是复制
最直觉的多 agent 分发方式:把整个任务描述复制一份,发给每个成员,让它们各自做。
这个方式有一个根本问题:每个成员的上下文里都有完整信息,但它们实际上只需要处理其中的一部分。
比如有 50 个文件需要重构,你发给 5 个 agent 的消息里都包含了全部 50 个文件的路径和内容------每个 agent 的上下文窗口都被撑得很满,而且每个 agent 做决策时都要在 50 个文件里搜索,浪费大量 token。
TeamFanout 的设计不是这样的。
ini
协调者调用 TeamFanout:
tasks = [
{"member": "agent_1", "scope": ["file_1.py", ..., "file_10.py"], "instruction": "重构为 async"},
{"member": "agent_2", "scope": ["file_11.py", ..., "file_20.py"], "instruction": "重构为 async"},
...
]
每个成员 agent 只收到属于自己的那一份任务,包含:
- 自己负责的文件列表(scope)
- 这批文件的处理指令(instruction)
- 必要的上下文(比如共享的接口定义)
成员 agent 启动后,只看到这些信息,不知道也不需要知道其他成员在做什么。
这就是上下文隔离:每个 agent 的认知边界被人为划定,专注在自己的子任务上。
二、成员 agent 怎么"真正并行"运行
上一篇提到了三种执行模式:in-process、tmux、auto。
要真正理解并行,需要了解这三种模式的本质区别。
in-process 模式
arduino
主进程
├── 主 agent loop (thread A)
├── 成员 agent 1 loop (thread B)
├── 成员 agent 2 loop (thread C)
└── 成员 agent 3 loop (thread D)
所有 agent 都在同一个 Python 进程里,用不同线程运行。
优点 :启动快,内存共享,通信开销小。
缺点:受 Python GIL(全局解释器锁)限制,CPU 密集型操作不能真正并行。但因为 LLM 调用是 IO 密集型操作(大部分时间在等网络返回),GIL 的影响没有想象中大------等待期间 GIL 会释放,其他线程可以运行。
tmux 模式
主进程 tmux session
│ │
│ ┌─────┴─────┐
│ 成员1终端 成员2终端 成员3终端
│ │ │
│ 独立进程 独立进程
│ │
└────── 通过消息队列通信 ──────┘
每个成员 agent 是一个独立的操作系统进程,运行在独立的 tmux 窗格里。
优点 :真正的操作系统级并行,互不干扰,一个成员崩溃不影响其他成员。
缺点:启动一个 tmux 进程需要几百毫秒,如果任务很多,启动开销会很显著。
auto 模式
框架根据当前环境自动选择:有 tmux 就用 tmux,否则用 in-process。
三、TeamFanout 的分发过程
现在把 TeamFanout 的完整执行过程走一遍:
css
协调者调用 TeamFanout(tasks=[task1, task2, task3])
│
▼
1. 为每个 task 生成一条消息,写入消息队列
(task1 → message_id_1, task2 → message_id_2, task3 → message_id_3)
所有消息初始状态:pending
│
▼
2. 根据执行模式启动成员 agent
(in-process: 创建新线程; tmux: fork新进程)
│
▼
3. 每个成员 agent 启动后:
a. 检查自己的"收件箱",取到 pending 消息
b. 消息状态变为 delivered
c. 开始执行任务
│
▼
4. TeamFanout 立刻返回(不等成员完成!)
返回: {message_ids: [message_id_1, message_id_2, message_id_3]}
│
▼
协调者拿到 message_ids,可以继续做其他事情
(比如自己处理第一批,或者准备合并逻辑)
这里有个关键点:TeamFanout 是非阻塞的。它发完任务就返回,协调者不需要在这里等待。
这和我们通常理解的"函数调用"不同------调用函数,等结果,函数返回。TeamFanout 是"调用函数,函数立刻返回,结果以后再取"。
四、TeamCollect:把结果拼回来
成员 agent 各自完成任务后,协调者需要收集结果。这就是 TeamCollect 的职责。
python
results = TeamCollect(
message_ids=[message_id_1, message_id_2, message_id_3],
timeout=300, # 最多等待5分钟
require_all=False, # 是否要求所有成员都完成
)
TeamCollect 会:
- 等待 :持续查询消息状态,直到所有指定消息变为
processed,或者超时 - 收集:把每条消息对应的处理结果取出来
- 返回:把所有结果打包返回给协调者
返回的结果大概是这样的结构:
json
{
"completed": [
{"message_id": "id_1", "member": "agent_1", "result": "file_1.py 已重构", "files_modified": ["file_1.py"]},
{"message_id": "id_2", "member": "agent_2", "result": "file_11.py 已重构", "files_modified": ["file_11.py"]},
],
"failed": [
{"message_id": "id_3", "member": "agent_3", "error": "超时", "status": "delivered"}
],
"pending": []
}
注意 require_all=False 这个参数:允许部分完成就返回。这很重要------如果一个成员崩溃了,你不希望整个任务一直卡在那里等,而是先拿到已完成的部分,把失败的部分单独处理。
五、结果有冲突怎么办
多个成员 agent 并行工作,最棘手的问题就是冲突:两个成员都修改了同一个文件,谁的算数?
AgentTeams 对这个问题的处理是:冲突检测,但不自动解决。
TeamCollect 在汇总结果时,会检查各成员修改的文件列表,如果发现重叠,会在返回结果里标记出来:
json
{
"conflicts": [
{
"file": "config.py",
"modified_by": ["agent_1", "agent_3"],
"message": "该文件被多个成员修改,需要人工或协调者介入合并"
}
]
}
然后由协调者(或者人类)来决定怎么解决。
这个设计选择很务实:自动合并两个 LLM 的代码修改,几乎不可能做得对。与其搞一个很复杂的自动合并算法,不如把冲突明确标出来,让有判断力的一方来处理。
六、一个完整的流程示例
把 16-18 篇的内容串起来,一个完整的 AgentTeams 协作流程是这样的:
ini
1. 主 agent 分析任务,决定怎么切分
↓
2. 调用 TeamCreate,创建一个团队
返回: team_id = "team_abc"
↓
3. 准备任务列表,每个成员一份独立的 scope
tasks = [
{member: "agent_1", scope: ["file_1..10.py"]},
{member: "agent_2", scope: ["file_11..20.py"]},
...
]
↓
4. 调用 TeamFanout,把任务分发出去(非阻塞,立刻返回)
返回: message_ids = ["msg_1", "msg_2", ...]
↓
5. 主 agent 继续做自己的事(比如写框架代码、准备测试)
同时,成员 agent 并行工作...
↓
6. 主 agent 自己的事做完了,调用 TeamCollect 等待成员结果
results = TeamCollect(message_ids, timeout=300)
↓
7. 检查结果:哪些成功了,哪些失败了,有没有冲突
↓
8. 处理失败和冲突,合并所有结果
↓
9. 调用 TeamDelete,清理团队,释放资源
这个流程里,步骤 5 是关键------主 agent 在等待成员的同时没有闲着,而是在并行处理其他任务。这才是多 agent 系统的真正价值。
设计亮点
1. 上下文裁剪到成员粒度
每个成员只收到自己需要的信息。这不只是节省 token,更重要的是让成员 agent 的决策更聚焦------它看到的上下文越干净,做出来的结果越准确。
2. 非阻塞的 Fanout
TeamFanout 立刻返回,允许协调者在等待期间继续工作。这是多 agent 系统效率提升的核心:协调者和成员是真正并行的,而不是协调者等着看成员干活。
3. 显式冲突而不是自动合并
把冲突标记出来,交给有判断力的一方处理。这在工程上比自动合并更稳健------你永远不知道两个 LLM 对同一个文件的修改在语义上是否兼容。
4. 部分结果也可用(require_all=False)
允许在部分成员失败的情况下仍然返回已完成的结果。这让系统在面对单个成员故障时,不是全部重来,而是保留已完成的工作,只重做失败的部分。
小结
| 设计选择 | 方案 | 工程价值 |
|---|---|---|
| 任务分发 | TeamFanout 非阻塞,每成员独立 scope | 协调者和成员真正并行,上下文隔离 |
| 并行执行 | in-process / tmux 二选一 | 平衡启动开销与真实并行度 |
| 结果收集 | TeamCollect 等待 + 超时保护 | 可容忍部分失败,不死等 |
| 冲突处理 | 检测但不自动解决 | 比自动合并更稳健,把判断交给更有能力的一方 |
下一篇是这个系列的最后一篇,也是最有意思的一篇:AgentTeams 最终为什么被移出了稳定版本?它的失败教训告诉了我们什么?
关于本系列的源码
本系列所有分析均基于开源项目 MyCodeAgent。
AgentTeams 的实现已从稳定版本移除,具体设计记录见 docs/archives/legacy-harness/HARNESS_ROADMAP.md Phase 7 章节,移除计划见 docs/plans/2026-07-12-lean-runtime/tasks/M2-03-remove-agent-teams.md。
bash
git clone https://github.com/chendongqi/MyCodeAgent
cd MyCodeAgent
cp .env.example .env
uv sync
uv run python main.py
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页