普通 Mailbox 不知道 shutdown_response 是否对应真实请求、响应双方是否匹配、请求是否 pending/过期、重试是否重复。协议把这些变成运行时状态机。
协议先登记 ProtocolRequest
→ 再发送 typed protocol message
→ 响应由确定性 router 处理
→ 状态只迁移一次
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,
}
新增独立 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
最新计划 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。
先写入 ProtocolStore 创建 pending 请求,再发送。
typed 请求进 history → 消费协议状态 → ack transport(顺序反了会出现状态已消费但 Lead 不知道);review_plan 发送响应。
shutdown 不调模型(模型调用 1→1);plan response 先迁移状态再用原 Runner 恢复(会多花一次模型调用)。
未批准时 effectful 工具在 handler 前被拒绝。