Agent 开发进阶(十六):团队协议系统,让智能体协作更加结构化
本文是「从零构建 Coding Agent」系列的第十六篇,适合想让多个 Agent 进行结构化协作的开发者。
先问一个问题
当你的多个 Agent 开始协同工作时,你是否遇到过这样的问题:
- 队友要关机,如何确保它优雅退出而不是突然消失?
- 某个高风险操作需要审批,如何确保请求和回复能够准确对应?
- 多个请求同时存在时,如何知道某条回复对应哪件事?
如果你的答案是肯定的,那么你需要一个团队协议系统。
自由聊天的「协作困境」问题
到了这一阶段,你的 Agent 已经具备了多种能力:
- 核心循环运行
- 工具使用与分发
- 会话内规划(TodoWrite)
- 子智能体机制(Subagent)
- 技能加载
- 上下文压缩
- 权限系统
- Hook 系统
- Memory 系统
- 系统提示词组装
- 错误恢复
- 任务系统
- 后台任务系统
- 定时调度系统
- Agent 团队系统
但当团队成员需要协作时,仅靠自由文本会遇到明显问题:
- 无法追踪:多个请求同时存在时,很难知道回复对应哪件事
- 无法审批:某些动作必须明确批准或拒绝,不能只靠模糊回复
- 无法确认:无法确认对方是否真的收到了请求
- 无法恢复:系统重启后,协作状态会丢失
虽然 s15 的邮箱系统让队友可以互相发消息,但如果所有事情都只靠自由文本,协作会变得不稳定。
所以到了这个阶段,我们需要一个团队协议系统:
一层结构化协议,让团队成员能够按规则协作。
团队协议系统的核心设计:请求-响应模式与状态追踪
用一个图来表示团队协议系统的工作流程:
某个队友 / lead 发起请求
->
写入 RequestRecord
->
把 ProtocolEnvelope 投递进对方 inbox
->
对方下一轮 drain inbox
->
按 request_id 更新请求状态
->
必要时再回一条 response
->
请求方根据 approved / rejected 继续后续动作
关键点只有三个:
- 请求-响应模式:明确的请求和响应
- request_id:唯一标识每个请求
- 状态追踪:记录请求的状态变化
几个必须搞懂的概念
协议(Protocol)
协议可以简单理解成:
双方提前约定好「消息长什么样、收到以后怎么处理」。
request_id
request_id 就是请求编号。
它的作用是:
- 某个请求发出去以后有一个唯一身份
- 之后的批准、拒绝、超时都能准确指向这一个请求
请求-响应模式(Request-Response Pattern)
这个词听起来像高级概念,其实很简单:
请求方:我发起一件事
响应方:我明确回答同意还是不同意
协议消息 vs 普通消息
- 普通消息:适合讨论、提醒、补充说明
- 协议消息:适合审批、关机、交接、签收
最小实现
1. Protocol Manager
import os
import json
import threading
import time
import uuid
from pathlib import Path
class ProtocolManager:
"""协议管理器"""
def __init__(self, team_dir=".team"):
self.team_dir = Path(team_dir)
self.requests_dir = self.team_dir / "requests"
self.requests_dir.mkdir(exist_ok=True)
# 请求记录
self.requests = {}
self.lock = threading.Lock()
# 加载已存在的请求
self._load_existing_requests()
def _load_existing_requests(self):
"""加载已存在的请求"""
for request_file in self.requests_dir.glob("*.json"):
try:
request = json.loads(request_file.read_text(encoding="utf-8"))
self.requests[request["request_id"]] = request
except Exception as e:
print(f"加载请求失败 {request_file}: {e}")
def _generate_request_id(self):
"""生成请求 ID"""
return f"req_{str(uuid.uuid4())[:8]}"
def _save_request(self, request):
"""保存请求"""
request_file = self.requests_dir / f"{request['request_id']}.json"
request_file.write_text(
json.dumps(request, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def create_request(self, kind, from_name, to_name, payload=None):
"""创建请求"""
request_id = self._generate_request_id()
request = {
"request_id": request_id,
"kind": kind,
"from": from_name,
"to": to_name,
"status": "pending",
"payload": payload or {},
"created_at": time.time(),
}
with self.lock:
self.requests[request_id] = request
self._save_request(request)
return request
def get_request(self, request_id):
"""获取请求"""
with self.lock:
return self.requests.get(request_id)
def update_request(self, request_id, status, response_payload=None):
"""更新请求状态"""
with self.lock:
request = self.requests.get(request_id)
if not request:
return None
request["status"] = status
if response_payload:
request["response"] = response_payload
request["updated_at"] = time.time()
self._save_request(request)
return request
def list_requests(self, status=None):
"""列出请求"""
with self.lock:
requests = list(self.requests.values())
if status:
requests = [r for r in requests if r["status"] == status]
return requests
2. 协议工具
def create_protocol_tools(protocol_manager, team_manager):
"""创建协议相关的工具"""
def request_shutdown(target):
"""请求优雅关机"""
request = protocol_manager.create_request(
kind="shutdown",
from_name="lead",
to_name=target,
payload={"reason": "System shutdown"}
)
# 发送协议消息
message = {
"type": "shutdown_request",
"from": "lead",
"to": target,
"request_id": request["request_id"],
"payload": request["payload"],
"timestamp": time.time(),
}
# 这里应该通过 team_manager 发送消息
# 简化版本直接返回
return f"关机请求已发送给 {target},请求 ID: {request['request_id']}"
def respond_shutdown(request_id, approve):
"""响应关机请求"""
request = protocol_manager.update_request(
request_id,
status="approved" if approve else "rejected",
response_payload={"approve": approve}
)
if not request:
return f"请求 {request_id} 不存在"
return f"已{'批准' if approve else '拒绝'}关机请求 {request_id}"
def submit_plan(plan_text):
"""提交计划审批"""
# 假设当前是某个队友
from_name = "alice" # 应该从上下文获取
request = protocol_manager.create_request(
kind="plan_approval",
from_name=from_name,
to_name="lead",
payload={"plan": plan_text}
)
return f"计划已提交审批,请求 ID: {request['request_id']}"
def review_plan(request_id, approve, feedback=""):
"""审批计划"""
request = protocol_manager.update_request(
request_id,
status="approved" if approve else "rejected",
response_payload={"approve": approve, "feedback": feedback}
)
if not request:
return f"请求 {request_id} 不存在"
return f"计划已{'批准' if approve else '拒绝'}: {feedback}"
def list_requests(status=None):
"""列出所有请求"""
requests = protocol_manager.list_requests(status)
if not requests:
return "暂无请求"
lines = ["# 协议请求\n"]
for req in requests:
lines.append(f"- **#{req['request_id']}** {req['kind']} [{req['status']}]")
lines.append(f" 从 {req['from']} 到 {req['to']}")
if req.get('payload'):
lines.append(f" 内容: {req['payload']}")
return "\n".join(lines)
return {
"request_shutdown": request_shutdown,
"respond_shutdown": respond_shutdown,
"submit_plan": submit_plan,
"review_plan": review_plan,
"list_requests": list_requests,
}
3. 集成到团队系统
class EnhancedTeamManager:
"""增强的团队管理器,支持协议"""
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.protocol_manager = ProtocolManager(team_dir)
# 加载配置
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 send_protocol_message(self, sender, to, message):
"""发送协议消息"""
inbox_file = self.inbox_dir / f"{to}.jsonl"
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:
# 处理协议消息
if msg.get("type") == "shutdown_request":
request_id = msg.get("request_id")
print(f"队友 {name} 收到关机请求 {request_id}")
# 这里应该自动批准或询问用户
# 简化版本直接批准
self.protocol_manager.update_request(
request_id,
status="approved",
response_payload={"approve": True}
)
# 退出循环
print(f"队友 {name} 正在关机...")
return
else:
# 普通消息
messages.append({
"role": "user",
"content": f"来自 {msg['from']} 的消息: {msg['content']}"
})
# 处理任务
if messages:
print(f"队友 {name} 正在处理任务...")
time.sleep(2)
# 等待一段时间再检查邮箱
time.sleep(5)
核心功能说明
1. 优雅关机协议
请求关机:
request = protocol_manager.create_request(
kind="shutdown",
from_name="lead",
to_name="alice",
payload={"reason": "System shutdown"}
)
响应关机:
protocol_manager.update_request(
request_id,
status="approved",
response_payload={"approve": True}
)
2. 计划审批协议
提交计划:
request = protocol_manager.create_request(
kind="plan_approval",
from_name="alice",
to_name="lead",
payload={"plan": "Refactor authentication module"}
)
审批计划:
protocol_manager.update_request(
request_id,
status="approved",
response_payload={"approve": True, "feedback": "Good plan, proceed"}
)
3. 请求状态管理
查看请求状态:
request = protocol_manager.get_request("req_12345678")
# 返回请求的完整信息,包括状态
列出所有请求:
requests = protocol_manager.list_requests(status="pending")
# 返回所有待处理的请求
4. 持久化
请求记录会被保存到 .team/requests/ 目录,每个请求一个 JSON 文件:
.team/requests/
req_12345678.json
req_87654321.json
这样即使程序重启,请求状态也不会丢失。
协议消息 vs 普通消息
| 特性 | 普通消息 | 协议消息 |
|---|---|---|
| 用途 | 讨论、提醒、补充说明 | 审批、关机、交接、签收 |
| 结构 | 自由文本 | 结构化数据 |
| 标识 | 无 | request_id |
| 状态 | 无 | pending / approved / rejected / expired |
| 追踪 | 无 | 完整的状态追踪 |
使用建议:
- 对于日常交流:使用普通消息
- 对于需要明确批准或拒绝的操作:使用协议消息
- 对于需要追踪状态的协作:使用协议消息
各种数据结构的边界
| 对象 | 回答的问题 | 典型字段 |
|---|---|---|
MessageEnvelope | 谁跟谁说了什么 | from / to / content |
ProtocolEnvelope | 这是不是一条结构化请求或响应 | type / request_id / payload |
RequestRecord | 这件协作流程现在走到哪一步 | kind / status / from / to |
TaskRecord | 真正的工作项是什么、谁在做、还卡着谁 | subject / status / blockedBy / owner |
一定要牢牢记住:
- 协议请求不是任务本身
- 请求状态表也不是任务板
- 协议只负责「协作流程」
- 任务系统才负责「真正的工作推进」
新手最容易犯的 4 个错
1. 没有 request_id
# ❌ 错误
# 没有请求编号,多个请求会混淆
message = {
"type": "shutdown_request",
"from": "lead",
"to": "alice",
"content": "Please shut down"
}
# ✅ 正确
# 每个请求都有唯一编号
message = {
"type": "shutdown_request",
"from": "lead",
"to": "alice",
"request_id": "req_12345678",
"payload": {"reason": "System shutdown"}
}
2. 收到请求以后只回一句自然语言
# ❌ 错误
# 只回自然语言,系统无法处理
response = "好的,我知道了"
# ✅ 正确
# 回复结构化的批准或拒绝
response = {
"type": "shutdown_response",
"request_id": "req_12345678",
"approve": True
}
3. 没有请求状态表
# ❌ 错误
# 没有状态追踪
def handle_request(request_id):
# 直接处理,不记录状态
pass
# ✅ 正确
# 记录请求状态
def handle_request(request_id):
request = protocol_manager.get_request(request_id)
request["status"] = "approved"
protocol_manager._save_request(request)
4. 把协议消息和普通消息混成一种结构
# ❌ 错误
# 没有区分消息类型
message = {
"content": "Please shut down"
}
# ✅ 正确
# 明确区分消息类型
protocol_message = {
"type": "shutdown_request",
"request_id": "req_12345678",
"payload": {}
}
normal_message = {
"content": "Hi, how are you?"
}
为什么这很重要
因为一个真正可靠的协作系统,需要明确的协议和状态追踪。
团队协议系统让你能够:
- 结构化协作:明确的请求和响应,而不是模糊的自由文本
- 状态追踪:知道每个请求当前的状态
- 可恢复性:系统重启后,协作状态可以恢复
- 可扩展性:基于统一的协议模板,可以轻松添加新的协议类型
- 可靠性:避免请求和回复的混淆
推荐的实现步骤
- 第一步:实现 ProtocolManager 类,管理请求记录和状态
- 第二步:实现基本的协议类型(关机、计划审批)
- 第三步:实现协议消息的发送和接收
- 第四步:在队友循环中处理协议消息
- 第五步:创建协议相关的工具,暴露给模型
- 第六步:集成到团队系统,支持协议化协作
团队协议系统与后续章节的关系
- s16 团队协议:解决团队如何按规则协作的问题
- s17 自主智能体:会利用协议系统来支持自主认领任务
- s18 Worktree:会利用协议系统来协调工作区操作
所以团队协议系统是构建高级协作系统的基础组件。
下一章预告
有了团队协议系统,你的多个 Agent 已经能够进行结构化协作。下一章我们将探讨自主智能体系统,让 Agent 能够自主工作、主动认领任务,实现更高的自主性。
一句话总结:普通消息解决「说了什么」,协议消息解决「这件事走到哪一步了」。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。