邮箱里有一句“同意”,就能动手了吗?
reader 给 Lead 发了一份计划:“读取 expected.txt,再运行 main.py,比较本次输出。”稍后收到“同意”。它准备执行时,又来了一条“请结束工作”。
现在有几个问题:这句同意对应哪个计划?如果它其实是在同意退出,能否拿来运行程序?同一封回复读了两次,要不要执行两遍?
上一课解决了消息怎样保存、何时进入独立上下文。消息可以到达以后,接下来要让宿主识别它的用途:谁发起哪一份请求,谁可以决定,哪一份回复能够改变它的状态。 这就是本课的团队协议(Team Protocols)。
reader准备先读expected.txt,再运行main.py比较结果。宿主分配req-1,明确发起人和接收者,计划正文随后不能改写。
- 队友提出这一次计划:reader准备先读expected.txt,再运行main.py比较结果。宿主分配req-1,明确发起人和接收者,计划正文随后不能改写。
- 请求保存后,Lead才能处理:请求进入Lead邮箱,同一ID登记为pending。收据表示成功保存,尚未有批准决定。
- Lead保存批准或拒绝的回复:回复携带plan_response、req-1、布尔决定和反馈。回复已入箱,请求方还没有匹配,所以请求仍pending。
- reader核对关联,才推进状态:宿主检查ID、类型、信封角色和已保存决定。全部一致时,这份请求才变为approved;拒绝则为rejected。
- 计划批准,运行仍要问宿主:协作门控允许继续这份计划;run_main还会进入原有执行权限检查。宿主拒绝时,程序仍不会运行。
- 匹配不会替你执行计划:模型后续提出工具调用,宿主才处理。重复消费同一回复只得到duplicate,不会因为再读一遍信就重跑程序。
- Lead请求结束reader的工作:宿主创建shutdown_request,关联新的请求ID。请求退出与成员已经退出,是两个不同事实。
- 忙碌时先留着请求:reader仍有活跃轮次时,确认返回deferred。没有关闭确认,也不强制终止当前线程。
- 空闲边界才给出确认:本轮工作结束后再处理。reader也可以拒绝并说明理由;同意关闭时先把回复保存到Lead邮箱。
- 确认保存后,本地成员标为closed:关闭发生在同一把状态锁内,之后新交付会被拒绝。Lead此刻尚未匹配回复,请求记录仍pending。
- Lead匹配同一个关闭请求:收到shutdown_response后核对ID、类型与角色,原请求才更新为approved。计划回复不能关闭另一份请求。
- 两边都有各自能证明的结果:成员closed证明本地不再接轮次;请求approved证明Lead已匹配确认。进程崩溃后的恢复需要另外设计。
先把“同意”放回它所属的请求
宿主为这份计划分配 req-1,保存类型 plan、发起人 reader、目标 lead、计划正文和 pending 状态。发出去的请求卡片带 plan_request、req-1 与正文;回复卡片带 plan_response、req-1、布尔决定与反馈。
request_id 回答“哪一次”,type 回答“哪一种”,外层信封的 sender 与 recipient 回答“谁给谁”。三个条件都要对上。模型在正文里声称自己是 Lead,并不会改变宿主绑定的发信角色。
本课的宿主还记住已经成功保存的回复内容。匹配时检查这份决定是否真的经过回复入口保存,避免另一段普通文本恰好拼出协议字段就推进状态。这是同进程中的一致性约束;成员身份仍由本地宿主管理,没有变成网络身份认证。
计划正文登记后不能改写。若把 req-1 的正文换成新方案,却保留旧批准,ID 看似匹配,批准的对象已经变了。需要修改计划时,旧决定保留,另开一份请求。
保存回复和完成协议之间,还有一步
Lead 保存了批准回复,此时 reader 的收件箱里多了一条记录,而原请求仍是 pending。等 reader 的宿主在下一次边界读取并核对它,才更新为 approved;拒绝则更新为 rejected。
| 观察位置 | 此刻能证明什么 |
|---|---|
| 请求收据 | 请求已成功保存并登记,正在等待决定 |
| 回复收据 | 决定已经保存到请求方邮箱 |
| 请求终态 | 请求方已经匹配同一份回复 |
这几项没有合并成一个“成功”标志。邮箱写入失败,不登记一个永远等不到对方的请求;回复保存失败,不提前写出批准。请求方重复消费相同记录时,只返回 duplicate,不再次推进状态。
对同一请求重复调用回复入口,也不该不断写信。本课相同决定返回 already_sent,试图把原来的同意改成拒绝则报错。重复投递和改变决定,是两件不同的事。
批准计划,为什么程序还是没运行?
计划审批解决的是“团队是否按这个方案继续”。程序执行解决的是“宿主是否允许这次调用”。
本课在 run_main 前增加一个协作检查:本次请求 ID 必须属于当前队友,类型是 plan,状态是 approved。通过后,调用还会进入之前的宿主权限检查。于是可以实际跑出三种结果:
- 计划批准,宿主也允许:真实运行 main.py,得到输出,再和读取的预期比较。
- 计划拒绝,宿主允许:在计划门控处停止,没有启动程序。
- 计划批准,宿主拒绝:通过计划检查,仍在执行许可处停止。
匹配函数本身不执行任何程序。即使它返回 approved,也只是让后续入口拥有一份可以检查的协作状态。
“请退出”也不能直接写成“已经退出”
关闭协议把方向反过来:Lead 发 shutdown_request,reader 回复 shutdown_response,携带另一份请求 ID。计划回复不能用来匹配关闭请求。
reader 正在工作时,本例返回 deferred,先不发送关闭确认。等活跃轮次结束,在空闲边界再处理。它可以拒绝,也可以同意;同意时先保存确认,再把本地成员标成 closed。旧的交付入口看到 closed,会拒绝新轮次。
成员本地 closed 与 Lead 的请求 approved 仍然是两个时刻。 Lead 只有读到并匹配确认后,才知道这次关闭协商已完成。我们没有强制终止线程,也没有因为发出请求就宣称已经完成收尾。
没有回复时,系统会一直等吗?
当前实现没有自动超时,未收到有效回复就保留 pending。同方向同类只能有一份未决请求,会话最多创建四份;它限制积压,不用不断请求模型来猜对方是否同意。
如果业务要求超时、取消或重新提案,应给它们单独的状态和关联规则。比如超时以后才到的批准,不能悄悄把新计划放行。本课先把一份请求从登记到终态的路径做清楚,再扩展生命周期。
实践篇将 open_request、reply_request、match_response 三个函数和各自的内部图放在一起,断点可以停在“已保存但未匹配”这个位置。
社区课程 Team Protocols以计划和关闭为例解释请求与响应的关联;Claude Code 的官方团队文档也区分计划批准与执行权限,并说明队友可以接受或拒绝关闭。本文的角色检查、有限请求表和协作门控是独立 Python 教学实现,不据此推断产品内部源码。