在上一篇中,我们构建了基于磁盘 Mailbox 的文件消息总线,使 Agent 团队成员之间能够互发消息。
然而,如果消息只是任意的自由自然语言文本,团队很快会在高风险场景中陷入混乱与失控:
- 强制关机导致数据损坏:Leader 突然发一句“请停止工作退出”,而 Coder 正在写入关键文件,强行退出会导致磁盘留下半截残缺代码;
- 高危行为缺乏前置审批:Worker 智能体在处理疑难依赖时,擅自调用
rm -rf 清空目录或重置 Git 分支;
- 分布式通信死锁:Agent A 等待 Agent B 确认接口定义,而 Agent B 也在等待 Agent A 产出模型结构,两个智能体在消息队列前无限期相互挂起。
为了解决这些现实工程痛点,Claude Code 在自由通信之上建立了一套严格的强类型协议层(Team Protocols)。通过唯一的 request_id 追踪状态,实现“请求 $\rightarrow$ 审批/拒绝 $\rightarrow$ 执行反馈”的可靠闭环。
本文深入剖析其结构化协议的设计细节与死锁防范策略。
在 Claude Code 源码 src/tools/SendMessageTool/SendMessageTool.ts 中,message 字段并不是普通的字符串,而是一个复合联合体:支持普通纯文本,或者严格受控的 StructuredMessage。
结构化协议的类型声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const StructuredMessage = z.discriminatedUnion('type', [ z.object({ type: z.literal('shutdown_request'), reason: z.string().optional() }), z.object({ type: z.literal('shutdown_response'), request_id: z.string(), approve: semanticBoolean(), reason: z.string().optional() }), z.object({ type: z.literal('plan_approval_response'), request_id: z.string(), approve: semanticBoolean(), feedback: z.string().optional() }) ])
|
源码在执行消息发送前,强制实施了三条铁律:
1 2 3 4 5 6 7 8 9 10 11 12 13
| if (input.message.type === 'shutdown_response' && input.to !== TEAM_LEAD_NAME) { return { result: false, message: '下线确认协议 (shutdown_response) 必须且只能发送给 "team-lead"!' } }
if (input.message.type === 'shutdown_response' && !input.message.approve && (!input.message.reason || input.message.reason.trim().length === 0)) { return { result: false, message: '拒绝下线请求时,必须在 reason 字段中明确说明当前阻塞原因!' } }
if (input.to === '*') { return { result: false, message: '结构化握手协议禁止执行广播 (to: "*"),必须点对点通信!' } }
|
这三条规则直接从物理层面掐死了常见的协作漏洞:
- 成员下线必须向 Leader 报备,防止越权指挥;
- 拒绝不能沉默:成员如果拒绝 Leader 的关机命令,必须附带理由(例如:“当前正在执行单测写入,预计 10 秒后就绪”);
- 握手协议禁止广播:握手涉及状态机单向转移,广播会导致确认状态混乱(Split-Brain)。
请求-响应(Request-Response)握手状态机
sequenceDiagram
autonumber
participant L as Leader 协调者
participant W as Worker 执行者
L->>W: 发起请求 (type: shutdown_request, request_id: req_001)
Note over W: Worker 检查本地状态: 是否有未写入的脏文件?
alt 存在未保存改动
W-->>L: 拒绝握手 (approve: false, reason: "正在重写 login.ts")
Note over L: Leader 收到驳回,暂缓下线调度
else 工作区纯净
W-->>L: 同意握手 (approve: true, request_id: req_001)
Note over L: Leader 确认安全,执行回收操作
W->>W: 优雅关闭当前子进程
end
每一次协议交互通过递增或随机生成的 request_id 强关联。接收方回填消息时必须携带原请求的 ID,任何没有匹配上下文的孤儿回执会被丢弃。
极简 Python 实现:支持握手的协议引擎
我们在消息总线之上封装一层强类型协议调度器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import time import uuid from typing import Dict, Optional
class ProtocolEngine: def __init__(self, message_bus, current_agent_id: str): self.bus = message_bus self.agent_id = current_agent_id self.pending_requests: Dict[str, dict] = {}
def send_shutdown_request(self, target_agent: str, reason: str = "") -> str: """向目标 Agent 发送文明关机请求""" req_id = f"req_{uuid.uuid4().hex[:6]}" payload = { "type": "shutdown_request", "request_id": req_id, "reason": reason } self.pending_requests[req_id] = { "target": target_agent, "type": "shutdown", "send_time": time.time(), "status": "waiting" } self.bus.send_message( sender_id=self.agent_id, recipient_id=target_agent, content=json.dumps(payload), msg_type="protocol" ) return req_id
def handle_protocol_message(self, raw_msg: dict) -> Optional[str]: """解析并响应收到的协议消息""" data = json.loads(raw_msg["content"]) p_type = data.get("type") sender = raw_msg["sender"]
if p_type == "shutdown_request": req_id = data["request_id"] has_unsaved_work = False if has_unsaved_work: reply = { "type": "shutdown_response", "request_id": req_id, "approve": False, "reason": "单测正在运行,预计 15 秒后完成" } else: reply = { "type": "shutdown_response", "request_id": req_id, "approve": True } self.bus.send_message( sender_id=self.agent_id, recipient_id=sender, content=json.dumps(reply), msg_type="protocol" ) return f"已响应来自 {sender} 的关机请求 [{req_id}]"
elif p_type == "shutdown_response": req_id = data["request_id"] if req_id in self.pending_requests: approve = data["approve"] self.pending_requests[req_id]["status"] = "approved" if approve else "rejected" return f"收到 {sender} 的关机审批结果: approve={approve}, reason={data.get('reason', '')}"
return None
|
防死锁与超时熔断保护
在分布式 Agent 系统中,通信死锁是最难定位的 Bug。Claude Code 制定了严格的层级与超时保护规程:
1. 严格层级命令流向(单向约束)
shutdown_request 只能由 Leader 下发给 Worker,Worker 严禁向 Leader 反向发关机申请;
- 同级平级 Worker 之间仅允许共享只读的分析数据,严禁相互施加阻塞式审批指令。
2. 超时快速熔断(Circuit Breaker)
挂起表中的请求设置默认超时窗口(如 60 秒)。若目标 Worker 由于死循环未能在规定时间内返回结构化确认:
- 系统自动判定为
TIMEOUT_REJECTED;
- 解除 Leader 的等待阻塞状态;
- 将故障子 Agent 标记为失联,触发强行回收与告警,防止整个团队长期卡死。
总结
Team Protocols 让多智能体协作从“随意的聊天群”蜕变为“严谨的生产流水线”:
- 强类型消息契约:区分普通文本与协议指令,杜绝非结构化文本的理解歧义;
- 状态显式握手:用唯一的
request_id 追踪关键决策流,确保下线与高危操作绝对可控;
- 超时熔断防护:规避分布式协作中的相互挂起与死锁风险。
但到目前为止,团队的所有任务依然依赖 Leader 手动拆解并点名派发给具体的 Worker。如果团队有 10 个智能体,Leader 就会成为吞吐瓶颈。
下一篇我们将探讨去中心化协同范式:Autonomous Agents 自组织团队与动态抢单机制。