Agent 团队协作:从 "异步通信" 到 "结构化协议"
作为编程学习的视角,我们先把这两季的核心逻辑串起来:第九季解决了 "团队能沟通" 的问题(基础通信层),第十季解决了 "团队沟通有规则" 的问题(协议层) ,就像先给团队装了邮箱,再制定了 "请假审批" "方案评审" 的 公司制度 。
一、核心背景:为什么要做这两季的升级?
在第九季之前(第八季及更早),AI代理存在两个致命问题:
- 一次性死亡:子 Agent 用完就销毁,没有 "身份" 和 "记忆" ,没法持续协作;
- 无规则沟通:就算能通信,也只是零散消息,没有 "请求-响应" 的规范,比如关线程时容易数据丢失、改代码时没有审批流程。
第九季先搭建 "能持续沟通的团队",第十季再给沟通加"规则和流程",一步步接近真实的人类团队协作。
二、第九季:搭建 "有身份、能异步通信的持久 Agent 团队"
核心目标
让 AI Agent 从 "一次性工具" 变成 "持久存在的队友" ,支持 跨 Agent 的异步消息传递(比如A发消息给B,B忙完再看)。
关键组件拆解(带代码解释)
1. 团队管理核心:TeammateManager(队友生命周期管理)
作用: 创建、跟踪队友(Agent),给每个队友(Agent)分配独立线程运行(保证同时工作)。
python
class TeammateManager:
def __init__(self, team_dir: Path):
self.dir = team_dir # 团队数据目录(存配置、邮箱)
self.dir.mkdir(exist_ok=True) # 目录不存在则创建
self.config_path = self.dir / "config.json" # 团队配置文件(存队友名单、状态)
self.config = self._load_config() # 加载配置(比如{"members": [{"name":"alice","role":"coder","status":"working"}]})
self.threads = {} # 存储每个队友(Agent)的运行线程
# 查找对应 Agent 的信息
def _find_member(self, name: str) -> dict:
for m in self.config["members"]:
if m["name"] == name:
return m
return None
def _save_config(self, config=None):
"""保存团队配置到文件"""
config = config or self.config
self.config_path.write_text(json.dumps(config, indent=2))
def spawn(self, name: str, role: str, prompt: str) -> str:
# 1. 记录队友信息存储到配置文件(持久化,不会丢)
member = {"name": name, "role": role, "status": "working"}
self.config["members"].append(member)
self._save_config() # 写入config.json,就算程序重启也能找到队友
# 2. 启动独立线程运行队友(持久存在,不是用完就扔)
thread = threading.Thread(
target=self._teammate_loop, # 队友的核心工作循环
args=(name, role, prompt),
daemon=True # 守护线程:主程序退出时自动结束
)
thread.start()
self.threads[name] = thread # 跟踪线程状态
return f"Spawned teammate '{name}' (role: {role})"
问题一:从 Agent 的创建上,是如何实现异步通信的? :
这和 Agent 的生命周期相关。具体可分为,(1)Agent 的持久化、(2)线程创建(跟管理 Agent 工作逻辑相关)、(3)线程的管理;
- 第一部分 - 持久化: 通过对每个
Agent创建json文件的方式,持久化Agent的信息(包含Agent的名称、角色、状态); - 第二部分 - 创建线程: 给每个
Agent开独立线程,让他们一直活着;为什么要开独立线程呢?,是为了让每个Agent的工作循环互不干扰(比如 A 队友工作时,B 队友可以摸鱼),实现异步通信功能; - 第三部分 - 管理线程: 通过字典的形式存储所有的
Agent线程,方便管理(比如,(1)查询线程是否存活;(2)线程状态是否空闲;(3)防止重复创建线程;
问题二:Agent 的工作逻辑是如何实现异步通信的呢?
如以下代码所示:
python
def _teammate_loop(self, name: str, role: str, prompt: str):
"""
单个队友的核心工作循环(独立线程中运行)
核心功能:
1. 初始化队友的系统提示和对话上下文
2. 循环读取消息队列(inbox)接收其他队友/管理器的消息
3. 调用大模型(LLM)生成响应,支持工具调用
4. 执行工具调用并返回结果给大模型
5. 循环最多50次或直到停止(非工具调用)
6. 循环结束后更新队友状态为idle并保存配置
参数:
name: str - 队友名称(唯一标识)
role: str - 队友角色(如"后端开发"、"测试工程师")
prompt: str - 初始任务提示(队友的核心任务)
"""
# 1. 构建系统提示词:定义队友的身份、工作目录、行为规则
# WORKDIR是全局变量,代表队友的工作目录;send_message是预设的通信方式
sys_prompt = (
f"You are '{name}', role: {role}, at {WORKDIR}. " # 身份与环境
f"Use send_message to communicate. Complete your task." # 行为规则:用send_message通信,完成任务
)
# 2. 初始化对话消息列表:初始只有用户(管理器)下发的任务提示
# messages是大模型的对话上下文,格式遵循OpenAI API规范
messages = [{"role": "user", "content": prompt}]
# 3. 获取该队友可使用的工具列表(如文件操作、命令执行、消息发送等)
# _teammate_tools()是管理器的私有方法,返回该队友支持的工具定义(符合OpenAI工具调用规范)
tools = self._teammate_tools()
# 4. 核心工作循环:最多执行50轮(避免无限循环)
for _ in range(50):
# 4.1 读取当前队友的收件箱消息(从全局消息总线BUS中读取)
# BUS是全局消息总线,read_inbox(name)获取发给该队友的所有未读消息
inbox = BUS.read_inbox(name)
# 4.2 将收件箱的新消息添加到对话上下文(转JSON保证格式统一)
for msg in inbox:
messages.append({"role": "user", "content": json.dumps(msg)})
try:
# 4.3 调用大模型API生成响应
# model=MODEL:全局变量,指定使用的大模型(如gpt-4o)
# system=sys_prompt:设置大模型的系统角色
# tools=tools:传入该队友可调用的工具列表
# max_tokens=8000:限制单次响应的最大token数
response = client.messages.create(
model=MODEL,
system=sys_prompt,
messages=messages,
tools=tools,
max_tokens=8000,
)
except Exception:
# 捕获API调用异常(如网络、额度、模型服务问题),直接终止循环
break
# 4.4 将大模型的响应添加到对话上下文(维护多轮对话状态)
messages.append({"role": "assistant", "content": response.content})
# 4.5 判断是否停止循环:如果响应不是因为工具调用停止,则结束工作
# stop_reason="tool_use"表示大模型要求调用工具,需要继续循环;否则任务完成
if response.stop_reason != "tool_use":
break
# 4.6 处理工具调用:遍历响应中的所有工具调用块
results = []
for block in response.content:
# 筛选出类型为"tool_use"的响应块(大模型要求执行的工具)
if block.type == "tool_use":
# 执行工具调用:_exec是管理器的私有方法,封装工具执行逻辑
# 参数:调用者名称、工具名称、工具入参
output = self._exec(name, block.name, block.input)
# 打印工具执行结果(截断到120字符,避免输出过长)
print(f" [{name}] {block.name}: {str(output)[:120]}")
# 整理工具执行结果:符合OpenAI工具结果格式,用于返回给大模型
results.append({
"type": "tool_result",
"tool_use_id": block.id, # 关联对应的工具调用ID
"content": str(output), # 工具执行的结果内容
})
# 4.7 将工具执行结果作为用户消息添加到上下文,供下一轮大模型处理
messages.append({"role": "user", "content": results})
# 5. 循环结束后:更新队友状态并保存配置
# 5.1 查找该队友的信息(从管理器的成员列表中)
member = self._find_member(name)
# 5.2 如果队友存在且未被关闭(shutdown),将状态改为idle(闲置)
if member and member["status"] != "shutdown":
member["status"] = "idle"
# 5.3 保存配置到文件(如config.json),持久化状态变更
self._save_config()
异步功能实现的逻辑如下:
读取消息队列中的内容:读取类似异步邮箱中的内容,并将其封装到messages列表中;第一次回答:大模型回答,解决邮箱中的问题;(两种情况:(1)无需调用工具,说明一次QA就解决了;(2)需要调用工具,则和原来的Agent Loop一样的)调用所需工具:调用工具,得到 tool response;然后封装到messages列表中;Agent Loop:循环直至,无需使用工具,说明任务解决;更新线程状态:更新线程状态;
2. 通信核心:MessageBus(基于JSONL的异步邮箱)
作用: 实现队友间的异步消息传递,就像公司的邮箱系统------A发消息给B,B忙完再看,消息存在文件里不会丢。
python
class MessageBus:
def send(self, sender, to, content, msg_type="message", extra=None):
# 构造标准化消息(谁发的、发给谁、内容、时间、类型)
msg = {
"type": msg_type, # 消息类型(普通消息/关机请求/计划审批)
"from": sender,
"content": content,
"timestamp": time.time() # 时间戳,保证顺序
}
if extra:
msg.update(extra) # 附加字段(比如request_id)
# 追加写入收件人的JSONL文件(append-only,只加不删,保证不丢消息)
with open(self.dir / f"{to}.jsonl", "a") as f:
f.write(json.dumps(msg) + "\n")
def read_inbox(self, name):
path = self.dir / f"{name}.jsonl"
if not path.exists(): return "[]"
# 读取所有消息并解析
msgs = [json.loads(l) for l in path.read_text().strip().splitlines() if l]
path.write_text("") # 读完清空(drain),避免重复处理
return json.dumps(msgs, indent=2)
MessageBus 的目的:
- 持久化 Agent 的异步邮箱: 每个
Agent有自己的专属邮箱文件(比如 alice.jsonl、bob.jsonl); - 异步操作: 实现每个
Agent发消息、读消息的功能,消息存储到json文件中;
3. 队友的工作循环:_teammate_loop
作用:让队友持续运行,定期查邮箱,和LLM交互,实现"持久工作"。
python
def _teammate_loop(self, name, role, prompt):
# 初始化对话上下文
messages = [{"role": "user", "content": prompt}]
# 循环运行(最多50轮,避免无限循环)
for _ in range(50):
# 第一步:查邮箱,有消息就注入上下文
inbox = BUS.read_inbox(name)
if inbox != "[]":
messages.append({"role": "user", "content": f"<inbox>{inbox}</inbox>"})
messages.append({"role": "assistant", "content": "Noted inbox messages."})
# 第二步:调用LLM,获取响应
response = client.messages.create(...)
# 第三步:如果LLM不调用工具了,就停止本轮工作
if response.stop_reason != "tool_use":
break
# 第四步:执行工具(比如发消息、写代码),把结果追加到上下文
# ...(工具执行逻辑)
# 第五步:标记队友状态为"空闲"
self._find_member(name)["status"] = "idle"
关键理解:
- 队友不是"一次性的",而是在循环里持续运行,直到完成任务或达到循环上限;
- 每次和LLM对话前先查邮箱,保证队友能及时收到其他成员的消息;
- 状态从"working"变为"idle",实现生命周期管理。
第九季核心亮点
| 特性 | 解决的问题 | 实现方式 |
|---|---|---|
| 持久化队友 | Agent 用完就丢,无身份 | 线程+config.json持久化队友信息 |
| 异步通信 | Agent 间无法传递消息 | JSONL邮箱(send/read_inbox) |
| 生命周期管理 | Agent 只有"运行/死亡"两种状态 | working/idle状态切换 |
三、第十季:给 Agent 团队沟通加"结构化协议"
核心目标
第九季的队友能通信,但沟通是"无规则的"------比如领导让队友关机,队友可能直接退出导致文件没写完;队友改代码,没有审批流程直接动手。第十季通过"结构化协议"解决这些问题。
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:领导发送关机请求(生成唯一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)"
步骤2:队友处理关机请求并响应
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}
)
3. 协议2:计划审批协议(Plan Approval Protocol)
解决"高风险任务无审批"的问题,要求"队友提交计划(带ID)→领导审批(引用ID)→队友执行/修改"。
python
plan_requests = {} # 跟踪所有计划请求的状态
# 队友提交计划
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."
# 领导审批计划
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}
)
4. 统一的FSM(有限状态机)
两个协议共享同一个状态机逻辑,简化代码:
[pending(待处理)]
↙ ↘
[approved(批准)] [rejected(拒绝)]
不管是关机请求还是计划请求,都只需要维护这三种状态,逻辑统一,容易扩展。
第十季核心亮点
| 特性 | 解决的问题 | 实现方式 |
|---|---|---|
| 结构化协议 | 沟通无规则,易出错 | 请求-响应模式+唯一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)
总结
核心知识点回顾
- 第九季核心 :用
TeammateManager实现队友持久化(线程+配置文件),用MessageBus(JSONL文件)实现异步通信,解决"代理能组队、能发消息"的基础问题; - 第十季核心 :在通信基础上增加"请求-响应协议",通过
request_id关联请求和响应,用状态机管理请求状态,解决"沟通有规则、可追溯、不混乱"的进阶问题; - 关键设计思想:从"无结构"到"结构化"是编程中常见的升级思路,比如先做功能,再做规范,这和真实团队协作的逻辑一致。
这两季的代码核心是"模拟人类团队的协作模式",把编程中的"线程、文件IO、状态管理、协议设计"等知识点结合起来,是非常好的实战案例。