从「单兵作战」到「自组织团队」
🎯 学习目标
- 消息送达等于协议完成吗?普通 Mailbox 不知道哪些事实?
- 第一条硬规则:先登记还是先发送?为什么顺序不能反?
- ProtocolRequest 有哪些不变量?响应窗口默认多久?
- plan_gate 如何门控?pending/rejected 时写入工具会怎样?
- shutdown 的正确顺序是什么?能直接取消正在写文件的队友吗?
- 为什么不把协议塞进普通 content 字符串?
🧠 核心概念
1 · 消息送达 ≠ 协议完成 ▸
普通 Mailbox 已解决持久投递(每条消息有稳定 UUID,经 ready→processing→done/quarantine)。但它不知道:
- shutdown_response 是否对应真实存在的 shutdown 请求;
- 响应双方是否正好是原请求的 target 和 sender;
- 请求是否仍 pending、是否已过期;
- 同一响应因 Mailbox ack 故障重试时是否已消费过;
- 不同 transport message 携带相同 request ID 时是否重复响应。
因此 P16 没把协议塞进普通 content 字符串,也没让模型解析自由文本 JSON,而是增加确定性运行时。
2 · 第一条硬规则:先登记,再发送 ▸
const request = await protocol.requestShutdown("alice");
// request 已先写入 JsonProtocolStore,再由 TeammateRuntime 投递 typed Mailbox
3 · ProtocolRequest 不变量 ▸
使用规范 UUID、明确参与方、aware UTC、封闭状态(pending/approved/rejected)。
- sender 和 target 不能相同,名字必须是安全小写 Agent slug;
- ID 必须是规范 UUID,不接受六位随机短 ID/大小写变体/别名;
- pending 请求不能带 resolution;approved/rejected 必须带匹配 resolution;
- resolution 时间不能早于创建,不能落在或超过过期时刻;
- 默认响应窗口 5 分钟,区间 [created_at_utc, expires_at_utc)。
JsonProtocolStore 把全部请求保存到 .agent_tutorial/protocol/state.json(带 version 单一快照)。进程内 mutex 串行化 + 跨进程 proper-lockfile 锁,临时文件+fsync+原子 rename,持久化失败旧字节旧状态不变。
4 · plan_gate 门控 ▸
Bob 要重构认证模块,先 submit_plan 提交计划,Lead 用 review_plan 批准或驳回。只要计划仍是 pending 或 rejected,写入和执行类工具就必须在 handler 之前被拒绝,不能只靠 Prompt 提醒模型「先等批准」。
5 · shutdown 正确顺序 ▸
Lead 想让 Alice 停下来,但不能在她写文件时直接取消。正确顺序:
- 先让当前消息完成(不打断正在处理的 Mailbox 消息);
- 再处理结构化 shutdown 请求(协议消息先走确定性 router);
- 发回响应;
- 然后结束 worker;
- Runner 资源仍由组合根统一关闭。
协议消息先走确定性 router,普通消息才进入模型。这保证 shutdown/plan 这类控制信号不被普通消息队列阻塞。shutdown 不进模型:先干完当前消息,然后在模型调用前确定性路由回结构化响应——关闭全程模型调用次数 1→1,不多花一次;plan 响应则状态先变、再用原 Runner 恢复(会多花一次模型调用)。Lead 侧顺序:先写 history,再消费协议状态,最后 ack transport;协议事件不享受 mailbox 的暂存和半状态。
6 · typed Mailbox:不降级成 content JSON ▸
P15 的 MailboxMessageKind 只有 task/message/result。P16 新增独立 ProtocolMessageKind:shutdown_request、shutdown_response、plan_approval_request、plan_approval_response。ProtocolMailboxMessage 显式包含 request_id 和 approved。
协议没创建第二套消息目录、第二个 EventInbox 或第二个 Agent Loop。它复用 P15 的 FileMailboxStore,只是消息 kind 扩展。请求 approved 为 null,响应必须是 boolean。
7 · 失败分三类 ▸
| 失败类型 | 处置 |
|---|---|
| 内容错(校验不过/未知类型) | quarantine 隔离,队友活着继续 |
| 投递/存储故障 | release 退回 ready + 队友进 failed |
| 取消/关闭 | release,不伪装成失败 |
本章承诺的是至少一次:响应只迁移一次是状态机层面的,不等于全局 exactly-once;重试仍可能重复,靠稳定 ID 与工具幂等键兑底。idle 不等于轮询:直接往 store 写消息不会唤醒队友,唤醒必须走 wakeup 回调跑 runEvents。