面试10-Agent 团队协议的管理

三、第十季:给 Agent 团队沟通加"结构化协议"

核心目标

第九季的队友能通信,但沟通是 "无规则的" ------比如领导让队友关机,队友可能直接退出导致文件没写完;队友改代码,没有审批流程直接动手。第十季通过 "结构化协议" 构造一个 Team 来解决这些问题。

python 复制代码
"""
s10_team_protocols.py - 团队协议

关机协议和计划审批协议,均使用相同的request_id关联模式。基于s09的团队消息功能扩展。

    关机状态机(FSM):待处理(pending)→ 批准(approved) | 拒绝(rejected)

    领导                              队友
    +---------------------+          +---------------------+
    | 关机请求             |          |                     |
    | {                    | -------> | 接收请求            |
    |   request_id: abc    |          | 决策:是否批准?     |
    | }                    |          |                     |
    +---------------------+          +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | 关机响应             | <------- | 关机响应            |
    | {                    |          | {                   |
    |   request_id: abc    |          |   request_id: abc   |
    |   approve: true      |          |   approve: true     |
    | }                    |          | }                   |
    +---------------------+          +---------------------+
            |
            v
    状态 -> "关机完成",线程停止

    计划审批状态机(FSM):待处理(pending)→ 批准(approved) | 拒绝(rejected)

    队友                          领导
    +---------------------+          +---------------------+
    | 计划审批             |          |                     |
    | 提交:{plan:"..."}| -------> | 审核计划文本        |
    +---------------------+          | 批准/拒绝?         |
                                     +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | 计划审批响应         | <------- | 计划审批            |
    | {approve: true}      |          | 审核:{req_id,      |
    +---------------------+          |   approve: true}     |
                                     +---------------------+

    跟踪器:{request_id: {"目标|发起方": 名称, "状态": "待处理|已批准|已拒绝"}}

核心思路:"相同的request_id关联模式,适配两个业务场景。"
"""

核心概念:请求-响应协议 + FSM(有限状态机)

1. 核心思路

所有重要沟通都遵循"请求(带唯一ID)→ 响应(引用同一ID)→ 状态更新"的流程,用FSM(有限状态机)管理请求的状态(pending→approved/rejected)。

2. 协议1:关机协议(Shutdown Protocol)

解决"强制关机导致数据丢失"的问题,要求"领导发关机请求→队友确认(批准/拒绝)→再关机"。

步骤1:lead 发送关机请求(生成唯一request_id)

python 复制代码
shutdown_requests = {}  # 跟踪所有关机请求的状态

def handle_shutdown_request(teammate: str) -> str:
    # 生成8位唯一请求ID(避免冲突)
    req_id = str(uuid.uuid4())[:8]
    # 记录请求状态:待处理
    shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
    # 发消息给目标队友(消息类型是shutdown_request,带request_id)
    BUS.send(
        "lead",  # 发送者
        teammate,  # 接收者
        "Please shut down gracefully.",  # 内容
        "shutdown_request",  # 消息类型
        {"request_id": req_id}  # 附加请求ID
    )
    return f"Shutdown request {req_id} sent (status: pending)"
  • 发送内容: leadsub_agent 会发送任务内容;
  • 消息类型:任务分配 还是 消息回执
  • Request ID: 任务分配的 IDLead 发起;如果为消息回执,则 IDsub_agent发起;

步骤2:sub_agent 处理关机请求并响应

python 复制代码
# 队友收到shutdown_request后,调用shutdown_response工具
if tool_name == "shutdown_response":
    req_id = args["request_id"]  # 引用领导的请求ID
    approve = args["approve"]    # 批准/拒绝关机
    # 更新请求状态
    shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
    # 回复领导(同样带request_id,保证关联)
    BUS.send(
        sender,  # 队友自己
        "lead",  # 接收者是领导
        args.get("reason", ""),  # 拒绝理由(可选)
        "shutdown_response",  # 消息类型
        {"request_id": req_id, "approve": approve}
    )
  • 发送内容: 一般为空;
  • 消息类型: 是任务分配类型的消息,还是要求审批的消息;
  • Request ID: Lead 分配的任务 ID
  • Approve: sub_agent 是否同意任务的接收;

总结: 可以发现,Lead 和 sub_agent 在分配任务下的协议,一般都是通过 Request ID 来进行通信的,标识由分配方(Lead)确定;而消息的流动一般会走消息总线(MessageBus),每个 Agent 都会持久化一个自己的消息中心(.json文件)。sub_agent 拿到任务后可以选择 approve 的状态,并返回给 Lead 的消息中心;

3. 协议2:计划审批协议(Plan Approval Protocol)

解决"高风险任务无审批"的问题,要求"队友提交计划(带ID)→领导审批(引用ID)→队友执行/修改"。

python 复制代码
plan_requests = {}  # 跟踪所有计划请求的状态

# sub_agent 提交计划
def submit_plan(sender, plan_content):
    req_id = str(uuid.uuid4())[:8]
    plan_requests[req_id] = {
        "from": sender,
        "plan": plan_content,
        "status": "pending"
    }
    # 发消息给领导请求审批
    BUS.send(sender, "lead", plan_content, "plan_request", {"request_id": req_id})
    return f"Plan submitted (ID: {req_id}), waiting for approval."
  • 发送内容: 审批内容;
  • 消息类型: 要求 Lead 审批的消息;
  • Request ID: sub_agent 分配的任务 ID
  • status: 任务状态;
python 复制代码
# 领导审批计划
def handle_plan_review(request_id, approve, feedback=""):
    req = plan_requests[request_id]  # 通过ID找到对应的计划
    req["status"] = "approved" if approve else "rejected"
    # 回复队友审批结果
    BUS.send(
        "lead",
        req["from"],
        feedback,  # 审批意见(比如"代码需要加注释")
        "plan_approval_response",
        {"request_id": request_id, "approve": approve}
    )
  • 发送内容: Lead 的审批结果;
  • 消息类型: 要求 Lead 审批的消息;
  • Request ID: sub_agent 分配的任务 ID
  • approve: 是否同意审批;

4. 统一的FSM(有限状态机)

两个协议共享同一个状态机逻辑,简化代码:

复制代码
[pending(待处理)] 
    ↙      ↘
[approved(批准)]  [rejected(拒绝)]

不管是关机请求还是计划请求,都只需要维护这三种状态,逻辑统一,容易扩展。

5. sub_agent 的管理

python 复制代码
# -- TeammateManager with shutdown + plan approval --
class TeammateManager:
    """团队成员管理器:负责创建、管理、销毁团队成员,处理成员的核心逻辑"""
    def __init__(self, team_dir: Path):
        """初始化管理器
        Args:
            team_dir: 团队数据目录
        """
        self.dir = team_dir
        self.dir.mkdir(exist_ok=True)  # 创建团队目录
        self.config_path = self.dir / "config.json"  # 团队配置文件路径
        self.config = self._load_config()  # 加载配置(成员列表、团队名称)
        self.threads = {}  # 存储成员的线程:{成员名: 线程对象}

    def _load_config(self) -> dict:
        """加载团队配置文件(JSON格式)"""
        if self.config_path.exists():
            return json.loads(self.config_path.read_text())
        # 配置文件不存在则返回默认配置
        return {"team_name": "default", "members": []}

    def _save_config(self):
        """保存团队配置到文件(格式化输出,便于阅读)"""
        self.config_path.write_text(json.dumps(self.config, indent=2))

    def _find_member(self, name: str) -> dict:
        """查找指定名称的成员
        Returns:
            成员字典(name/role/status)或None
        """
        for m in self.config["members"]:
            if m["name"] == name:
                return m
        return None

    def spawn(self, name: str, role: str, prompt: str) -> str:
        """创建并启动一个团队成员(以独立线程运行)
        Args:
            name: 成员名称
            role: 成员角色(如"开发工程师")
            prompt: 给成员的初始提示词
        Returns:
            创建结果提示
        """
        # 查找成员是否已存在
        member = self._find_member(name)
        if member:
            # 成员已存在且非空闲/关机状态,返回错误
            if member["status"] not in ("idle", "shutdown"):
                return f"Error: '{name}' is currently {member['status']}"
            # 更新成员状态和角色
            member["status"] = "working"
            member["role"] = role
        else:
            # 成员不存在则创建新成员
            member = {"name": name, "role": role, "status": "working"}
            self.config["members"].append(member)
        # 保存配置
        self._save_config()
        
        # 创建并启动线程(daemon=True:主线程退出时子线程也退出)
        thread = threading.Thread(
            target=self._teammate_loop,  # 成员的核心循环函数
            args=(name, role, prompt),   # 传递给循环函数的参数
            daemon=True,
        )
        self.threads[name] = thread
        thread.start()
        return f"Spawned '{name}' (role: {role})"

    def _teammate_loop(self, name: str, role: str, prompt: str):
        """团队成员的核心循环:持续读取消息、调用AI、执行工具
        Args:
            name: 成员名称
            role: 成员角色
            prompt: 初始提示词
        """
        # 构造成员的系统提示词(定义成员的行为准则)
        sys_prompt = (
            f"You are '{name}', role: {role}, at {WORKDIR}. "
            f"Submit plans via plan_approval before major work. "
            f"Respond to shutdown_request with shutdown_response."
        )
        # 初始化AI对话历史
        messages = [{"role": "user", "content": prompt}]
        # 获取成员可用的工具列表
        tools = self._teammate_tools()
        should_exit = False  # 是否退出循环(关机标志)
        # 循环次数限制(防止无限循环)
        for _ in range(50):
            # 读取并清空收件箱
            inbox = BUS.read_inbox(name)
            # 将新消息加入对话历史
            for msg in inbox:
                messages.append({"role": "user", "content": json.dumps(msg)})
            # 收到关机批准则退出循环
            if should_exit:
                break
            try:
                # 调用Anthropic AI模型获取响应
                response = client.messages.create(
                    model=MODEL,
                    system=sys_prompt,
                    messages=messages,
                    tools=tools,
                    max_tokens=8000,
                )
            except Exception:
                # 调用失败则退出循环
                break
            # 将AI响应加入对话历史
            messages.append({"role": "assistant", "content": response.content})
            # 如果AI没有调用工具,退出循环
            if response.stop_reason != "tool_use":
                break
            # 处理工具调用结果
            results = []
            for block in response.content:
                if block.type == "tool_use":
                    # 执行工具并获取输出
                    output = self._exec(name, block.name, block.input)
                    # 打印工具执行日志(截断过长内容)
                    print(f"  [{name}] {block.name}: {str(output)[:120]}")
                    # 构造工具结果(供AI后续处理)
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": str(output),
                    })
                    # 如果是关机响应且批准关机,设置退出标志
                    if block.name == "shutdown_response" and block.input.get("approve"):
                        should_exit = True
            # 将工具结果加入对话历史
            messages.append({"role": "user", "content": results})
        # 更新成员状态(关机/空闲)
        member = self._find_member(name)
        if member:
            member["status"] = "shutdown" if should_exit else "idle"
            self._save_config()

    def _exec(self, sender: str, tool_name: str, args: dict) -> str:
        """执行成员调用的工具
        Args:
            sender: 工具调用者(成员名称)
            tool_name: 工具名称
            args: 工具参数
        Returns:
            工具执行结果
        """
        # 基础工具(与s02版本一致)
        if tool_name == "bash":
            return _run_bash(args["command"])
        if tool_name == "read_file":
            return _run_read(args["path"])
        if tool_name == "write_file":
            return _run_write(args["path"], args["content"])
        if tool_name == "edit_file":
            return _run_edit(args["path"], args["old_text"], args["new_text"])
        if tool_name == "send_message":
            return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
        if tool_name == "read_inbox":
            return json.dumps(BUS.read_inbox(sender), indent=2)
        # 关机响应工具(核心协议)
        if tool_name == "shutdown_response":
            req_id = args["request_id"]
            approve = args["approve"]
            # 加锁更新关机请求状态(线程安全)
            with _tracker_lock:
                if req_id in shutdown_requests:
                    shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
            # 发送关机响应给负责人
            BUS.send(
                sender, "lead", args.get("reason", ""),
                "shutdown_response", {"request_id": req_id, "approve": approve},
            )
            return f"Shutdown {'approved' if approve else 'rejected'}"
        # 计划审批工具(核心协议)
        if tool_name == "plan_approval":
            plan_text = args.get("plan", "")
            # 生成8位唯一request_id
            req_id = str(uuid.uuid4())[:8]
            # 加锁记录计划请求(线程安全)
            with _tracker_lock:
                plan_requests[req_id] = {"from": sender, "plan": plan_text, "status": "pending"}
            # 发送计划审批请求给负责人
            BUS.send(
                sender, "lead", plan_text, "plan_approval_response",
                {"request_id": req_id, "plan": plan_text},
            )
            return f"Plan submitted (request_id={req_id}). Waiting for lead approval."
        # 未知工具
        return f"Unknown tool: {tool_name}"

    def _teammate_tools(self) -> list:
        """返回成员可用的工具列表(包含工具描述和参数schema)"""
        return [
            # 基础工具(与s02版本一致)
            {"name": "bash", "description": "Run a shell command.",
             "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
            {"name": "read_file", "description": "Read file contents.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
            {"name": "write_file", "description": "Write content to file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
            {"name": "edit_file", "description": "Replace exact text in file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
            {"name": "send_message", "description": "Send message to a teammate.",
             "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
            {"name": "read_inbox", "description": "Read and drain your inbox.",
             "input_schema": {"type": "object", "properties": {}}},
            # 关机响应工具
            {"name": "shutdown_response", "description": "Respond to a shutdown request. Approve to shut down, reject to keep working.",
             "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "reason": {"type": "string"}}, "required": ["request_id", "approve"]}},
            # 计划审批工具
            {"name": "plan_approval", "description": "Submit a plan for lead approval. Provide plan text.",
             "input_schema": {"type": "object", "properties": {"plan": {"type": "string"}}, "required": ["plan"]}},
        ]

    def list_all(self) -> str:
        """列出所有团队成员及其状态"""
        if not self.config["members"]:
            return "No teammates."
        lines = [f"Team: {self.config['team_name']}"]
        for m in self.config["members"]:
            lines.append(f"  {m['name']} ({m['role']}): {m['status']}")
        return "\n".join(lines)

    def member_names(self) -> list:
        """返回所有成员名称列表"""
        return [m["name"] for m in self.config["members"]]

# 初始化全局团队管理器实例
TEAM = TeammateManager(TEAM_DIR)
  • sub_agent 的初始化: 通过 .json 文件进行持久化,并创建对应线程,后进行管理,防止重复创建并监控线程状态;
  • sub_agent 的提示词: 每个 sub_agent 都有统一的 Prompt,和 Lead 的不一样;
  • sub_agent 的工作逻辑: 如下所示,通过 _teamate_loop执行,由于是 sub_agent,所以首先会从自己的邮箱冲取得消息,然后封装到 message 列表中,然后执行得到 response;其次,判断用什么工具,然后同样将 tool_result 封装到 message 列表中;最后流程和 agent_loop 是一样的没什么区别;
python 复制代码
thread = threading.Thread(
            target=self._teammate_loop,  # 成员的核心循环函数
            args=(name, role, prompt),   # 传递给循环函数的参数
            daemon=True,
        )
  • sub_agent 工作逻辑 _teamate_loop 和 agent_loop 不同之处: (1)执行工具那部分包含了很多协议工具,比如申请复查等等;(2)跳出 loop 的条件不再是单单 "有无使用工具了",如果使用了协议工具也需要跳出 loop,目的是告诉 Lead "我不同此次任务的分配" 或者 "我想要提出这个解决方案";

6. 主 Agent 的管理

python 复制代码
def agent_loop(messages: list):
    """负责人的核心循环:读取消息、调用AI、执行工具
    Args:
        messages: 对话历史列表
    """
    while True:
        # 读取并处理负责人的收件箱
        inbox = BUS.read_inbox("lead")
        if inbox:
            messages.append({
                "role": "user",
                "content": f"<inbox>{json.dumps(inbox, indent=2)}</inbox>",
            })
            messages.append({
                "role": "assistant",
                "content": "Noted inbox messages.",
            })
        # 调用AI模型获取响应
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )
        # 将AI响应加入对话历史
        messages.append({"role": "assistant", "content": response.content})
        # 如果AI没有调用工具,退出循环
        if response.stop_reason != "tool_use":
            return
        # 处理工具调用
        results = []
        for block in response.content:
            if block.type == "tool_use":
                # 获取工具处理函数
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    # 执行工具并获取结果
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                # 打印工具执行日志
                print(f"> {block.name}: {str(output)[:200]}")
                # 构造工具结果
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output),
                })
        # 将工具结果加入对话历史
        messages.append({"role": "user", "content": results})

主 Agent Loop: (1)工具变多了,因为工具和 Query 的关系,主 Agent 能够让 Sub_Agent 接任务;(2)先查看邮箱再继续生成;

第十季核心亮点

特性 解决的问题 实现方式
结构化协议 沟通无规则,易出错 请求-响应模式+唯一request_id
状态跟踪 不知道请求的处理结果 shutdown_requests/plan_requests字典
优雅关机 强制关机导致数据丢失 关机请求-响应握手
计划审批 高风险任务无审核直接执行 计划提交-审批流程

四、两季核心对比(学生视角)

维度 第九季(基础版) 第十季(进阶版)
核心能力 队友能持久运行、异步发消息 在第九季基础上,沟通有规则、可追溯
消息特点 无结构化(普通文本) 结构化(带类型、request_id)
状态管理 仅队友的工作状态(working/idle) 新增请求状态(pending/approved/rejected)
工具数量 9个(基础通信、执行) 12个(+关机请求/响应、计划审批)
协作规范 无明确规范 有请求-响应的协议规范

五、代码运行与验证(实操步骤)

作为学生,你可以按以下步骤跑通代码,理解实际效果:

1. 第九季运行

bash 复制代码
# 进入代码目录
cd learn-claude-code
# 运行第九季代码
python agents/s09_agent_teams.py

# 交互操作(示例):
# 1. 创建alice(编码员)和bob(测试员)
# 2. 让alice给bob发消息
# 3. 广播"status update: phase 1 complete"给所有队友
# 4. 查看领导的收件箱
# 5. 查看团队名单(/team)和收件箱(/inbox)

2. 第十季运行

bash 复制代码
cd learn-claude-code
python agents/s10_team_protocols.py

# 交互操作(示例):
# 1. 创建alice(编码员),发送关机请求,查看她的状态
# 2. 创建bob(高风险重构任务),提交计划,领导拒绝
# 3. 创建charlie,提交计划,领导批准
# 4. 查看团队状态(/team)

总结

核心知识点回顾

  1. 第九季核心 :用TeammateManager实现队友持久化(线程+配置文件),用MessageBus(JSONL文件)实现异步通信,解决"代理能组队、能发消息"的基础问题;
  2. 第十季核心 :在通信基础上增加"请求-响应协议",通过request_id关联请求和响应,用状态机管理请求状态,解决"沟通有规则、可追溯、不混乱"的进阶问题;
  3. 关键设计思想:从"无结构"到"结构化"是编程中常见的升级思路,比如先做功能,再做规范,这和真实团队协作的逻辑一致。

这两季的代码核心是"模拟人类团队的协作模式",把编程中的"线程、文件IO、状态管理、协议设计"等知识点结合起来,是非常好的实战案例。

相关推荐
袁庭新2 小时前
M系列芯片Mac上通过Homebrew一键安装/卸载Nginx并上线项目全指南
运维·nginx·macos·袁庭新·袁庭新ai
偷懒下载原神2 小时前
【linux操作系统】信号
linux·运维·服务器·开发语言·c++·git·后端
skd89992 小时前
MicroSIP助手,智慧语音V3.2.3版本,MicroSIP自动拨号助手
服务器
源远流长jerry2 小时前
RDMA 传输服务详解:可靠性与连接模式的深度剖析
linux·运维·网络·tcp/ip·架构
南梦浅2 小时前
《企业网络实战(二):NAT 实现内网 Web 服务对外发布》
网络
存储服务专家StorageExpert2 小时前
NetApp NVME SSD 盘的学习笔记
运维·服务器·笔记·学习·存储维护·emc存储·netapp
Du_chong_huan2 小时前
《计算机网络:自顶向下方法》第 1 章 核心知识梳理 + 原版习题解析
网络·智能路由器
小璐资源网2 小时前
新服务器上线:标准化初始化流程
运维·服务器
2501_918126912 小时前
学习所有python写服务器的语句
服务器·人工智能·python·学习·个人开发