Agent Team 实现
多代理协作——共享任务列表、成员间消息通信与实时 Team Card UI
Agent Team 解决什么问题?
现有的 task
工具生成一次性 subagent:接收 prompt、隔离工作、交付产物、结束。没有协调,没有通信,没有共享状态。这对独立子任务有效,但当工作需要多个代理协作时就不够用了——共享发现、排序依赖任务、或根据其他成员的发现调整策略。
Agent Team 引入了持久化、协调的多代理会话:Lead Agent 生成专门化的成员,它们并行工作、通过消息通信、并在带有依赖解析的共享任务列表中推进。
何时使用
| 场景 | 方式 |
|---|---|
| 快速的一次性生成 | task 工具(subagent) |
| 独立的研究或分析 | task 工具(subagent) |
| 跨多个模块的代码审查 | Agent Team |
| 有顺序阶段的复杂项目 | Agent Team |
架构概览
Agent Team 在现有 TaskOrchestrator 流程内运行——没有独立的 orchestrator。Setup 阶段 setupTeamForLead()
将团队服务注册到进程级 team-registry(幂等),并把 Lead context 标记为
ctx.team = {};团队工具随后在标准 agent 循环内与 filesystem、execute 等一起工作,需要服务时通过
getTeamServices() 在使用点查找。若 "team" 不在agent 的工具列表中,setup 被跳过——零开销。
三个服务
TeamCoordinator——大脑。管理团队生命周期、解析任务依赖、路由成员间消息。底层由 TeamRepository
支撑(Server 用 PG,测试用内存实现)。当一个任务完成时,coordinator 自动解除下游任务的阻塞。
TeamExecutionService——肌肉。经 runAgentLoop 启动和管理并发的成员 agent stream。每个成员获得自己的
RuntimeContext,与 Lead 1:1 共享 sandbox 与 workspace(无按成员子目录)、TEAM_INSTRUCTIONS 提示词(kind: "team"),以及一个
prepareStep 钩子——在每次 LLM 调用前注入待处理消息。通过 AbortController 追踪每个成员以支持解散时优雅关闭。
team.tool.ts——接口。5 个 AI SDK 工具封装,Lead 和 Member 看到同一套,桥接 LLM 调用到 coordinator /
execution service。模型是成员中心的 DAG:不向 LLM 暴露任何 UUID(一律按成员名引用),任何工具也不接 teamId(框架从上下文解析当前唯一团队):
team_create——Lead 一次性声明整张 DAG:成员及每个成员的dependsOn。框架拓扑排序、拒绝环、启动成员;成员在其上游全部完成后自动运行team_dissolve——Lead 解散团队team_replan——Lead 的失败恢复:原子地cancel失败成员并/或add新成员(失败成员的下游保持 blocked——框架不自动解锁)team_status——团队状态快照(成员中心,无 UUID)。Lead long-poll 到事件为止;Member 拿到普通快照team_send_message——发给成员名、"Lead"或"broadcast"(共享)
成员通过通用 complete 工具交付结果(按 ctx.team?.memberId 分支),而非团队专用工具。工具集跨角色完全一致——模型的 tool
block 在团队生命周期内保持缓存稳定;角色限制在每个 execute 内部(Member 调 team_create 抛错,team_replan 仅 Lead,等等)。
端到端流程:一个具体场景
用户消息:“审查我们 auth 模块的安全性、性能和测试覆盖率。”
主 agent——就是处理每条用户消息的那个 agent——收到这条请求。如果 tier 启用了
"team",主 agent 的工具集中会包含 5 个团队工具,和 read_file、execute
等并列。主 agent 阅读请求后判断需要三个专家并行工作,自主决定调用 team_create。从这一刻起,主 agent 充当团队的
Lead 角色——不是新创建的实体,只是同一个 agent 多了一个角色。
① 创建——Lead 启动团队
Lead 在一次 team_create 调用里声明整张 DAG——每个成员携带自己的 prompt(任务)和可选的 dependsOn(上游成员名列表):
team_create({
name: "Auth 模块审查",
members: [
{ name: "SecurityReviewer", agentType: "code-reviewer", prompt: "审查 auth 模块的安全漏洞..." },
{ name: "PerfAnalyzer", agentType: "code-reviewer", prompt: "分析 auth 端点的性能瓶颈..." },
{ name: "TestReviewer", agentType: "code-reviewer", prompt: "审计 auth 模块的测试覆盖率..." },
{ name: "Reporter", agentType: "general", prompt: "综合三份审查产出汇总报告...",
dependsOn: ["SecurityReviewer", "PerfAnalyzer", "TestReviewer"] }
]
})
幕后:框架对 DAG 拓扑排序(拒绝环),coordinator 创建团队 + 成员记录,execution service 经 runAgentLoop
把所有成员作为并发 agent 流启动。每个成员与 Lead 1:1 共享 sandbox 与 workspace(协作靠文件名,而非隔离),运行
TEAM_INSTRUCTIONS 提示词。无 dependsOn 的成员(三个审查者)立即开工;有 dependsOn 的成员(Reporter)也 spawn 但保持
blocked,在其上游全部完成后自动运行。成员看到跟 Lead 完全相同的 5 个团队工具,但 team_create / team_dissolve /
team_replan 被 Member 调用时抛错;成员的 toolKeys 里也排除了 task / ask_user_question / confirm——不能嵌套 subagent,不能与用户交互。
此时,用户的聊天界面出现 Team Card,显示团队状态。
② 依赖解析——DAG 自行运转
没有单独的任务创建步骤:依赖图就是 team_create 的成员列表。coordinator 自动解析——三个审查者无 dependsOn,并行运行;Reporter 保持
blocked 直到三者全部完成,然后其流自动解锁运行。每个成员的状态走 blocked → in_progress → completed(或
failed),某成员完成会重扫下游、解锁依赖已满足者。
③ 工作——成员自主执行、经 complete 交付
成员不需要等待指令——各自跑自己的 agent 循环。每次 LLM 调用前,prepareStep 钩子检查 coordinator 的新消息(来自 Lead
或其他成员)并注入为 <system-reminder> 文本。
一个典型的成员工作流:
- SecurityReviewer 无
dependsOn,立即运行 → 阅读 auth 代码 → 把发现写入 workspace 文件 → 调用通用complete({ summary, paths })工具交付(与普通 agent 同一停止工具;按ctx.team.memberId分支) - SecurityReviewer 完成后重扫 DAG——三个审查者都完成后 Reporter 解锁运行
- PerfAnalyzer 与 TestReviewer 全程并行
- SecurityReviewer 发现 token 泄露 → 调用
team_send_message({ to: "broadcast", content: "在 /auth/callback 发现暴露的 refresh token" }) - 其他成员在下一次
prepareStep中收到并相应调整
若某成员失败,框架不自动解锁其下游——由 Lead 用 team_replan 恢复(取消失败成员,并/或添加带新 dependsOn 图的替补)。
④ 监控——Lead long-poll 等待进度
成员工作期间,Lead 调用 team_status()(不传 teamId——框架从上下文解析当前团队),该调用在 server
端阻塞直到团队任何状态变更(成员状态变更、完成、消息送达)——或到默认 long-poll 窗口为准。返回的结构化数据是成员中心的(用名字,无 UUID):
{
"members": [
{ "name": "SecurityReviewer", "status": "in_progress", "dependsOn": [] },
{ "name": "PerfAnalyzer", "status": "in_progress", "dependsOn": [] },
{ "name": "TestReviewer", "status": "completed", "dependsOn": [] },
{ "name": "Reporter", "status": "blocked", "dependsOn": ["SecurityReviewer", "PerfAnalyzer", "TestReviewer"] }
],
"pendingMessages": 1
}
如果某人卡住了,Lead 通过 team_send_message 发送指导。Team Card 通过 TOOL_STREAM
事件实时更新——用户无需等待 Lead 开口就能看到进展。
⑤ 综合——交付物
依赖三个审查者的 Reporter 成员在它们完成后自动运行,从共享 workspace 综合它们的交付物,经 complete 交付。Lead 从
team_status 读取终态,向用户写出最终回复:“以下是 auth 模块审查的发现:3 个安全问题、2 个性能瓶颈、87%
测试覆盖率……”(对于没有综合成员的简单团队,Lead 直接读取成员 artifact 自行综合。)
⑥ 解散——清理
Lead 调用 team_dissolve。所有成员 stream 被停止(AbortController.abort()),coordinator 标记团队为 "completed",Team
Card 显示终态。Zustand store 在 5 分钟后自动清理。
幕后实现
团队工具使用同一个 createTools()
函数。Lead 和 Member 拿到的返回对象 shape 完全相同——都是同一套 5 个工具,键名都一样。不同的是每个 execute
内部的运行时行为:
- Lead(
ctx.team已设,无memberId):team_create/team_dissolve/team_replan正常执行;team_statuslong-poll 到事件为止 - Member(
ctx.team.memberId已设):team_create/team_dissolve/team_replan抛错;team_status返回普通快照;交付经通用complete工具
服务实例(coordinator、execution)不挂在 ctx.team 上——它们是进程级单例,通过 team-registry 的
getTeamServices() 在使用点查找。Context 只携带 per-call 身份(memberId)。
Execution service 为每个成员构建 RuntimeContext(复用 Lead 的 sandbox),通过 runAgentLoop 以 kind: "team" 启动,选择
TEAM_INSTRUCTIONS 而非 MAIN_INSTRUCTIONS。详见架构图的完整组件关系。
核心机制
任务依赖解析
每个成员是 team_create 声明的 DAG 中的一个节点;coordinator 自动解析成员状态:
blocked → in_progress → completed(或 failed / cancelled)
- 有未完成
dependsOn的成员保持blocked——其流已 spawn 但被 park - 其上游全部完成后,自动解锁并运行——无手动认领步骤
- 完成后,coordinator 重扫下游成员并解锁已满足依赖者
- 某成员失败不会自动解锁其下游;由 Lead 的
team_replan恢复
成员通信
成员通过 prepareStep
钩子接收消息——不需要单独的通信通道。每次 LLM 调用前,钩子检查 coordinator 的邮箱、消费未读消息、并将其注入为
<system-reminder> 文本。每个成员只做自己被分配的任务(其 DAG 节点)——没有任务挑选。这复用了现有的 reminder 机制,零新增基础设施。
上下文隔离
| 属性 | Lead Agent | Team Member |
|---|---|---|
| Prompt | MAIN_INSTRUCTIONS + Lead 团队工具指导 | TEAM_INSTRUCTIONS + Member 团队工具指导 |
| 历史 | 完整对话 | 仅自己的 prompt(任务简报) |
| 沙箱 | 任务工作区 | 同一个 sandbox + workspace,1:1 |
| Tool block | 跟 Member 完全相同的 5 个团队工具 | 跟 Lead 完全相同的 5 个团队工具 |
| 仅 Lead 可调 | team_create / team_dissolve / team_replan | 被调用时抛错 |
| 交付 | 写出最终用户回复 | 经通用 complete 工具交付 |
team_status | long-poll 到事件为止 | 普通快照 |
| 用户交互 | 是(ask_user_question、confirm) | 否(不在成员的 toolKeys 里) |
| 嵌套代理 | 是(task) | 否(不在成员的 toolKeys 里) |
客户端
Team Card
核心 UX 洞察:团队在聊天中应表现为一个持续演变的实体,而非一系列重复的状态快照。team_create
渲染唯一的团队卡片,订阅 useTeamStore。所有其他工具渲染极简内联徽章。team_status
只渲染一个 ack(“状态已刷新”),同时静默刷新 store。
数据通道
数据通过两个通道从后端流向前端:
工具输出(同步)——LLM 调用团队工具时,输出作为标准 ToolUIPart 返回客户端。工具的 React 组件通过 useEffect 写入
useTeamStore。涵盖 team_create、team_replan、team_status、team_dissolve。
DataPartEvent(异步推送)——成员状态在后台变更时,TeamExecutionService 推送 TOOL_STREAM
事件,kind: "team"。客户端 use-task-events.ts 检测到 chunk.kind === "team" 后分发到 useTeamStore。Team
Card 即时重渲染。
| 数据源 | 触发时机 | Store Action |
|---|---|---|
team_create output | 团队 + 成员创建 | initTeam() |
team_replan output | 成员取消 / 添加 | updateTeam() |
team_status output | LLM 检查状态 | updateTeam()(全量刷新) |
team_dissolve output | 团队解散 | dissolveTeam() |
后端 TOOL_STREAM 事件 | 成员状态变更 | updateMember() |
数据库
各表从父 team 表级联删除。没有单独的任务表——每条成员行本身就是它的任务节点:
- team——每个团队会话一条,引用父对话 task
- team_member——每个生成的成员(包括自动注册的 Lead);携带该成员的
prompt、dependsOn、状态和结果 - team_message——成员间消息,带已读追踪
成员状态转换(blocked → in_progress → completed)使用条件更新确保原子性——成员绝不重复运行。
Tier 控制
Agent Team 通过 "team" 配置键控制,当前仅在 ultra tier 启用。"team" 键映射到
5 个工具名(team_create、team_dissolve、team_replan、team_status、team_send_message)。管理员可通过 Settings >
Tiers UI 为其他 tier 启用。