Agent 开发进阶(十五):Agent 团队系统,让多个智能体协同工作

21 阅读9分钟

Agent 开发进阶(十五):Agent 团队系统,让多个智能体协同工作

本文是「从零构建 Coding Agent」系列的第十五篇,适合想让多个 Agent 协同工作的开发者。

先问一个问题

当你的任务变得越来越复杂时,你是否希望:

  • 有专门的编码 Agent 负责写代码
  • 有专门的测试 Agent 负责测试
  • 有专门的文档 Agent 负责写文档
  • 这些 Agent 能够长期存在并协同工作

如果你的答案是肯定的,那么你需要一个 Agent 团队系统。

单 Agent 的「能力边界」问题

到了这一阶段,你的 Agent 已经具备了多种能力:

  • 核心循环运行
  • 工具使用与分发
  • 会话内规划(TodoWrite)
  • 子智能体机制(Subagent)
  • 技能加载
  • 上下文压缩
  • 权限系统
  • Hook 系统
  • Memory 系统
  • 系统提示词组装
  • 错误恢复
  • 任务系统
  • 后台任务系统
  • 定时调度系统

但当面对复杂任务时,单 Agent 会遇到明显限制:

  • 能力有限:一个 Agent 难以精通所有领域
  • 上下文冲突:不同类型的任务会污染上下文
  • 并行度低:只能串行处理任务
  • 可维护性差:代码和逻辑越来越复杂

虽然 s04 的 Subagent 可以帮助拆小任务,但它的生命周期是:

创建 -> 执行 -> 返回摘要 -> 消失

这很适合一次性的小委派,但不适合长期协作。

所以到了这个阶段,我们需要一个 Agent 团队系统:

一批有身份、能长期存在、能反复协作的队友。

Agent 团队系统的核心设计:名册、邮箱、独立循环

用一个图来表示 Agent 团队系统的工作流程:

lead
  |
  +-- spawn alice (coder)
  +-- spawn bob (tester)
  |
  +-- send message --> alice inbox
  +-- send message --> bob inbox

alice
  |
  +-- 自己的 messages
  +-- 自己的 inbox
  +-- 自己的 agent loop

bob
  |
  +-- 自己的 messages
  +-- 自己的 inbox
  +-- 自己的 agent loop

关键点只有三个:

  1. 名册:记录团队成员的身份和状态
  2. 邮箱:队友之间的通信渠道
  3. 独立循环:每个队友都有自己的 Agent Loop

几个必须搞懂的概念

队友(Teammate)

这里的 teammate 指的是:

一个拥有名字、角色、消息入口和生命周期的持久 agent。

名册(Roster)

名册就是团队成员列表。

它回答的是:

  • 现在队伍里有谁
  • 每个人是什么角色
  • 每个人现在是空闲、工作中还是已关闭

邮箱(Inbox)

邮箱就是每个队友的收件箱。

别人把消息发给它, 它在自己的下一轮工作前先去收消息。

消息信封(Message Envelope)

envelope 这个词本来是“信封”的意思。 程序里用它表示:

把消息正文和元信息一起包起来的一条记录。

最小实现

1. Team Manager

import os
import json
import threading
import time
import uuid
from pathlib import Path

class TeamManager:
    """团队管理器"""
    
    def __init__(self, team_dir=".team"):
        self.team_dir = Path(team_dir)
        self.team_dir.mkdir(exist_ok=True)
        
        # 创建必要的目录
        self.config_dir = self.team_dir
        self.inbox_dir = self.team_dir / "inbox"
        self.inbox_dir.mkdir(exist_ok=True)
        
        # 加载配置
        self.config_path = self.config_dir / "config.json"
        self.config = self._load_config()
        
        # 存储队友线程
        self.teammate_threads = {}
    
    def _load_config(self):
        """加载配置"""
        if self.config_path.exists():
            try:
                return json.loads(self.config_path.read_text(encoding="utf-8"))
            except Exception as e:
                print(f"加载配置失败: {e}")
        return {"team_name": "default", "members": []}
    
    def _save_config(self):
        """保存配置"""
        self.config_path.write_text(
            json.dumps(self.config, indent=2, ensure_ascii=False),
            encoding="utf-8"
        )
    
    def spawn(self, name, role, prompt):
        """创建队友"""
        # 检查是否已存在
        for member in self.config["members"]:
            if member["name"] == name:
                return f"队友 {name} 已存在"
        
        # 添加到配置
        member = {
            "name": name,
            "role": role,
            "status": "working"
        }
        self.config["members"].append(member)
        self._save_config()
        
        # 启动队友线程
        thread = threading.Thread(
            target=self._teammate_loop,
            args=(name, role, prompt),
            daemon=True,
        )
        thread.start()
        self.teammate_threads[name] = thread
        
        return f"队友 {name} ({role}) 已创建"
    
    def list(self):
        """列出所有队友"""
        if not self.config["members"]:
            return "团队暂无成员"
        
        lines = ["# 团队成员\n"]
        for member in self.config["members"]:
            lines.append(f"- **{member['name']}** ({member['role']}) [{member['status']}]")
        
        return "\n".join(lines)
    
    def send(self, sender, to, content):
        """发送消息"""
        # 检查接收者是否存在
        recipient_exists = False
        for member in self.config["members"]:
            if member["name"] == to:
                recipient_exists = True
                break
        
        if not recipient_exists:
            return f"队友 {to} 不存在"
        
        # 发送消息到邮箱
        inbox_file = self.inbox_dir / f"{to}.jsonl"
        message = {
            "type": "message",
            "from": sender,
            "content": content,
            "timestamp": time.time(),
        }
        
        with open(inbox_file, "a", encoding="utf-8") as f:
            f.write(json.dumps(message, ensure_ascii=False) + "\n")
        
        return f"消息已发送给 {to}"
    
    def _read_inbox(self, name):
        """读取邮箱"""
        inbox_file = self.inbox_dir / f"{name}.jsonl"
        if not inbox_file.exists():
            return []
        
        messages = []
        try:
            with open(inbox_file, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if line:
                        messages.append(json.loads(line))
            
            # 清空邮箱
            inbox_file.write_text("", encoding="utf-8")
        except Exception as e:
            print(f"读取邮箱失败 {name}: {e}")
        
        return messages
    
    def _teammate_loop(self, name, role, prompt):
        """队友循环"""
        print(f"队友 {name} ({role}) 已启动")
        
        # 初始化消息
        messages = [{
            "role": "system",
            "content": f"你是 {name},一个 {role}。请专注于你的职责,完成任务后等待新的指示。"
        }, {
            "role": "user",
            "content": prompt
        }]
        
        while True:
            # 读取邮箱
            inbox = self._read_inbox(name)
            if inbox:
                print(f"队友 {name} 收到 {len(inbox)} 条消息")
                for msg in inbox:
                    messages.append({
                        "role": "user",
                        "content": f"来自 {msg['from']} 的消息: {msg['content']}"
                    })
            
            # 这里应该调用模型,但为了简化,我们模拟一下
            if messages:
                print(f"队友 {name} 正在处理任务...")
                # 模拟处理时间
                time.sleep(2)
                
                # 模拟回复
                response = f"{name} 已收到并处理了消息"
                messages.append({"role": "assistant", "content": response})
            
            # 等待一段时间再检查邮箱
            time.sleep(5)
    
    def shutdown(self):
        """关闭团队"""
        # 这里可以添加清理逻辑
        pass

2. 团队工具

def create_team_tools(team_manager):
    """创建团队相关的工具"""
    
    def team_spawn(name, role, prompt):
        """创建队友"""
        return team_manager.spawn(name, role, prompt)
    
    def team_list():
        """列出所有队友"""
        return team_manager.list()
    
    def team_send(to, content):
        """发送消息"""
        return team_manager.send("lead", to, content)
    
    return {
        "team_spawn": team_spawn,
        "team_list": team_list,
        "team_send": team_send,
    }

3. 集成到 Agent Loop

def agent_loop_with_team(state):
    """带团队系统的 Agent Loop"""
    # 初始化团队管理器
    team_manager = TeamManager()
    
    # 创建团队工具
    team_tools = create_team_tools(team_manager)
    state["tools"] = state.get("tools", []) + [
        {
            "name": "team_spawn",
            "description": "创建队友",
            "parameters": {
                "name": {"type": "string", "description": "队友名字"},
                "role": {"type": "string", "description": "队友角色"},
                "prompt": {"type": "string", "description": "队友提示词"}
            }
        },
        {
            "name": "team_list",
            "description": "列出所有队友",
            "parameters": {}
        },
        {
            "name": "team_send",
            "description": "发送消息给队友",
            "parameters": {
                "to": {"type": "string", "description": "接收者名字"},
                "content": {"type": "string", "description": "消息内容"}
            }
        }
    ]
    
    # 主循环
    while True:
        # 调用模型
        response = call_model(state["messages"])
        
        if response.stop_reason != "tool_use":
            return response.content
        
        results = []
        for block in response.content:
            if hasattr(block, "type") and block.type == "tool_use":
                tool_name = block.name
                tool_input = block.input
                
                # 执行团队工具
                if tool_name in team_tools:
                    output = team_tools[tool_name](**tool_input)
                else:
                    # 执行其他工具
                    output = run_tool(tool_name, tool_input)
                
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output
                })
        
        if results:
            state["messages"].append({"role": "user", "content": results})

核心功能说明

1. 创建队友

创建编码队友

team_manager.spawn("alice", "coder", "你是 Alice,一名专业的 Python 开发者。请负责编写高质量的代码。")

创建测试队友

team_manager.spawn("bob", "tester", "你是 Bob,一名专业的测试工程师。请负责测试代码的质量和功能。")

2. 管理团队

列出所有队友

team_manager.list()
# 查看团队成员列表

发送消息

team_manager.send("lead", "alice", "请编写一个 JSON 解析器")
# 向 Alice 发送任务

3. 队友循环

每个队友都有自己的循环:

  • 读取邮箱中的消息
  • 处理任务
  • 等待新的消息

这样队友可以长期存在,反复接活。

4. 持久化

团队配置会被保存到 .team/config.json 文件:

{
  "team_name": "default",
  "members": [
    {
      "name": "alice",
      "role": "coder",
      "status": "working"
    },
    {
      "name": "bob",
      "role": "tester",
      "status": "working"
    }
  ]
}

这样即使程序重启,团队成员信息也不会丢失。

Teammate vs Subagent vs Runtime Task 的边界

特性Teammate (队友)Subagent (子智能体)Runtime Task (运行时任务)
生命周期长期存在一次性短期(任务完成后结束)
身份有名字、角色、邮箱临时身份无独立身份
通信邮箱系统直接返回结果通知队列
上下文独立上下文临时上下文无独立上下文
用途长期协作一次性委派后台执行慢命令

使用建议

  • 对于长期需要的角色:使用 Teammate
  • 对于一次性的小任务:使用 Subagent
  • 对于耗时较长的命令:使用 Runtime Task

新手最容易犯的 4 个错

1. 把队友当成「名字不同的 subagent」

# ❌ 错误
# 生命周期还是执行完就销毁
def spawn_subagent(name, task):
    # 执行任务
    result = execute_task(task)
    # 返回结果后销毁
    return result

# ✅ 正确
# 队友长期存在
def spawn_teammate(name, role, prompt):
    # 创建队友
    member = {"name": name, "role": role, "status": "working"}
    # 启动独立循环
    thread = threading.Thread(target=teammate_loop, args=(name, role, prompt))
    thread.start()
    # 队友继续存在
    return f"队友 {name} 已创建"

2. 队友之间共用同一份 messages

# ❌ 错误
# 所有队友共用一个消息列表
class TeamManager:
    def __init__(self):
        self.messages = []
    
    def spawn(self, name):
        # 所有队友都用 self.messages
        pass

# ✅ 正确
# 每个队友有自己的消息列表
def teammate_loop(name, role, prompt):
    # 每个队友有自己的 messages
    messages = [{
        "role": "system",
        "content": f"你是 {name},一个 {role}。"
    }]
    # 处理自己的消息
    while True:
        pass

3. 没有持久名册

# ❌ 错误
# 名册只存在内存中
class TeamManager:
    def __init__(self):
        self.members = []

# ✅ 正确
# 名册持久化到磁盘
class TeamManager:
    def __init__(self, team_dir=".team"):
        self.team_dir = Path(team_dir)
        self.config_path = self.team_dir / "config.json"
        self.config = self._load_config()
    
    def _load_config(self):
        if self.config_path.exists():
            return json.loads(self.config_path.read_text(encoding="utf-8"))
        return {"team_name": "default", "members": []}

4. 没有邮箱,靠共享变量直接喊话

# ❌ 错误
# 直接使用共享变量通信
shared_messages = {}

def send_message(to, content):
    if to not in shared_messages:
        shared_messages[to] = []
    shared_messages[to].append(content)

def get_messages(to):
    return shared_messages.get(to, [])

# ✅ 正确
# 使用邮箱系统通信
def send(self, sender, to, content):
    inbox_file = self.inbox_dir / f"{to}.jsonl"
    message = {
        "type": "message",
        "from": sender,
        "content": content,
        "timestamp": time.time(),
    }
    with open(inbox_file, "a", encoding="utf-8") as f:
        f.write(json.dumps(message, ensure_ascii=False) + "\n")

为什么这很重要

因为一个真正强大的系统,往往需要多个专业角色的协同工作。

Agent 团队系统让你能够:

  1. 分工协作:不同角色专注于自己的领域
  2. 并行处理:多个任务同时进行,提高效率
  3. 上下文隔离:不同任务的上下文不会互相污染
  4. 长期存在:队友可以持续接活,不需要每次重新创建
  5. 可扩展性:可以根据需要添加新的角色

推荐的实现步骤

  1. 第一步:实现 TeamManager 类,管理团队成员和配置
  2. 第二步:实现队友创建和管理功能
  3. 第三步:实现邮箱系统,支持队友之间的通信
  4. 第四步:实现队友的独立循环,处理任务
  5. 第五步:创建团队相关的工具,暴露给模型
  6. 第六步:集成到 Agent Loop,支持团队操作

Agent 团队系统与后续章节的关系

  • s15 Agent 团队:解决团队成员如何长期存在、互相发消息的问题
  • s16 团队协议:解决团队成员之间如何进行结构化协作的问题
  • s17 自主智能体:解决智能体如何自主工作、主动认领任务的问题

所以 Agent 团队系统是构建复杂智能体系统的基础组件。

下一章预告

有了 Agent 团队系统,你的多个 Agent 已经能够长期存在并互相通信。下一章我们将探讨团队协议系统,让团队成员之间的协作更加结构化、可追踪。


一句话总结:Subagent 是一次性外包助手,Teammate 是长期在线队友。


如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。