第 十六 章 Agent 架构实操 深入学习 · 交互式 含 QA 测试

从“单兵作战”到“自组织团队”,多 Agent 协同的必经之路

💡 protocol + plan_gate:先登记请求、响应严格匹配、计划硬门控。
普通 message 只表达内容,不表达『这条回复在解决哪个请求』。本章加确定性运行时协议:请求先持久登记,消息带稳定 ID,响应严格匹配,状态只迁移一次;计划未批准时 effectful 工具无法执行。
本章进度
0%
1 本章要掌握的目标
  • 公开工具:Lead 增加 request_shutdown / review_plan(共 20 个);队友增加 submit_plan(共 5 个)。
  • ProtocolRequest 使用规范 UUID、aware UTC、封闭状态 pending/approved/rejected,持久在单一 state.json 快照。
  • 先登记再发送(发送失败不回滚状态);响应六条件完整匹配,只迁移一次。
  • Lead 侧顺序:先写 history,再消费协议状态,最后 ack transport;协议事件不享受 mailbox 的暂存/半状态。
  • shutdown 完成当前消息后走确定性路由,不进模型(模型调用 1→1);plan_gate 在权限硬边界生效。
2 核心知识点
消息送达不等于协议完成

普通 Mailbox 不知道 shutdown_response 是否对应真实请求、响应双方是否匹配、请求是否 pending/过期、重试是否重复。协议把这些变成运行时状态机。

协议先登记 ProtocolRequest
  → 再发送 typed protocol message
  → 响应由确定性 router 处理
  → 状态只迁移一次
ProtocolRequest 持久请求

sender/target 不能相同;pending 不能带 resolution,approved/rejected 必须带匹配 resolution;默认响应窗口 5 分钟 [created, expires)。

interface ProtocolRequest {
  id, kind: "shutdown"|"plan_approval",
  sender, target,
  status: "pending"|"approved"|"rejected",
  content, createdAtUtc, expiresAtUtc,
  resolution,
}
typed Mailbox 不把协议降级成 content JSON

新增独立 ProtocolMessageKind(shutdown_request/response、plan_approval_request/response)。ProtocolMailboxMessage 显式包含 request_id 和 approved;router 在模型之前用类型分流。

普通消息:  task / message / result
协议消息:  shutdown_request / shutdown_response
          plan_approval_request / plan_approval_response
字段:      request_id(对应持久请求) + approved(response 必填布尔)
响应必须完整匹配

同时满足:request_id 指向真实请求;kind 配对;sender 等于原 target;recipient 等于原 sender;仍 pending;早于 expires。错任一项原状态不变。

1. request_id 指向真实请求
2. response kind 与 request kind 配对
3. sender == 原 target
4. recipient == 原 sender
5. 仍为 pending
6. 当前时间 < expires_at_utc
plan_gate:Prompt 不能授予执行权限

最新计划 pending/rejected 时,写入、执行等 effectful 工具被 deny;read 工具保持可用;send_message/submit_plan 保持可用;approved 后仍按原权限策略执行。

planGateRule(PermissionRule):
  pending/rejected → 拒绝 effectful 工具
  read 工具可用(收集信息)
  send_message / submit_plan 可用(沟通/重提交)
  approved → 按原权限执行(不影响原审批/审计)
失败分三类;这是至少一次,不是恰好一次

内容错(校验不过/未知类型)→ quarantine 隔离,队友活着继续;投递/存储故障 → release 退回 ready + 队友进 failed;取消/关闭 → release。承诺的是 at-least-once:「响应只消费一次」是状态机层面的,不等于全局 exactly-once——重试仍可能重复执行,靠稳定 ID 与幂等边界兜底。另外 idle 不等于轮询:直接往 store 写一条消息不会唤醒队友,必须经 wakeup 回调跑 runEvents。

3 机制流程
1
submit_plan / request_shutdown

先写入 ProtocolStore 创建 pending 请求,再发送。

2
Lead event turn

typed 请求进 history → 消费协议状态 → ack transport(顺序反了会出现状态已消费但 Lead 不知道);review_plan 发送响应。

3
确定性 router

shutdown 不调模型(模型调用 1→1);plan response 先迁移状态再用原 Runner 恢复(会多花一次模型调用)。

4
plan gate 生效

未批准时 effectful 工具在 handler 前被拒绝。

4 术语表
ProtocolRequest持久请求状态真相(shutdown / plan_approval);单一 state.json 快照 + 两把锁。
先登记再发送请求先写入 store 再投递;发送失败不回滚状态,保留 pending 等重投。
ProtocolMailboxMessage独立 typed envelope,kind=protocol 物理隔离;请求 approved:null,响应必须 boolean。
plan gate把计划状态接入 PermissionRule 的硬门控;没提交过计划放行,被拒过则拦住。
幂等 vs 重复同 transport message 重试幂等;另一 message 携带同 request_id 拒绝。
失败三类内容错→quarantine;投递/存储故障→release+failed;取消/关闭→release。
idle ≠ 轮询直接写 store 不唤醒队友,唤醒必须走 wakeup → runEvents。
5 QA 测试环节(自测题)
已完成 0 / 6 · 答对 0
Q1. 协议请求必须先做什么再发送?
Q2. 响应要改变请求状态需满足?
Q3. shutdown 请求对队友会?
Q4. plan_gate 对 pending/rejected 计划的作用?
Q5. 已批准计划之后又提交新计划会?
Q6. 另一条 message 携带相同 request_id 会?
6 验证与实验
  • npm run test:ch16:49 个测试文件 / 349 个用例(含前 15 章累积,P15 枚举与序列化 schema 完全不变是本章硬要求);Lead 工具面 20 个,队友 5 个,P15+protocol+plan_gate 两个能力位。
  • 验证先登记后发送(发送失败保留 pending)、响应六条件只消费一次、过期/错参与方拒绝、shutdown 不进模型、协议状态单快照恢复。
  • npm run ch16 -- --prompt "创建 writer 队友,让她先提交修改 README 的计划,批准后执行并优雅关闭"