FEATURED · 精选文章

learn-claude-code 团队协议(Team Protocols):用同一个 request_id 关联模式实现 Agent 间的关停与计划审批

发布时间 / 2026/9/6 16:49:40
来源 / 创域科博编辑部
栏目 / 资讯中心
learn-claude-code 团队协议(Team Protocols):用同一个 request_id 关联模式实现 Agent 间的关停与计划审批 learn-claude-code 团队协议Team Protocols用同一个 request_id 关联模式实现 Agent 间的关停与计划审批【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code本文围绕 learn-claude-code 仓库中旧版 12 课教程的 s10 团队协议文档 展开在 s09 已经为 Agent 团队提供持久队友和异步邮箱之后s10 用一套请求-响应结构化握手协议解决队友关停shutdown与计划审批plan approval两个协调问题。读完本文你将掌握如何用request_id关联请求与响应、如何设计pending → approved | rejected的共享状态机FSM并能在 agents/s10_team_protocols.py 中运行验证同时本文会结合当前课程主线 s13_agent_teams 的ProtocolState实现和 tests/test_agent_teams_runtime.py 测试用例展示这套协议在更完整团队运行时中的演进形态。为什么需要结构化协议s09 留下的两个协调缺口s10 处于旧版课程链路的中间位置s01 s02 s03 s04 s05 s06 | s07 s08 s09 [ s10 ] s11 s12其上一节 s09 Agent Teams 已经实现了三样东西持久队友TeammateManager.spawn()用守护线程为每个队友跑一个完整的 agent loop身份与生命周期.team/config.json记录成员名、角色和状态working / idle / shutdown通信信道MessageBus为每个成员维护一个只追加的 JSONL 收件箱.team/inbox/name.jsonlsend()追加一行 JSONread_inbox()读取并清空。但文档指出能通信不等于能协调。s09 存在两个具体问题关停问题Shutdown直接杀掉一个队友线程会留下写到一半的文件config.json里的状态也不会更新。团队需要一个握手lead 发起请求队友审批干完手头的事再退出或拒绝继续工作。计划审批问题Plan approvallead 说一句重构 auth 模块队友会立刻动手。对高风险变更应该让队友先提交计划lead 审阅通过后再执行。文档给出的关键洞察是这两个问题共享同一种结构——一方发送带唯一 ID 的请求另一方引用该 ID 回复。这就是 s10 的全部内容一个 FSM两个应用One FSM, two applications。协议设计共享 FSM 与双追踪表s10 的协议全景如下直接继承自原文档Shutdown Protocol Plan Approval Protocol Lead Teammate Teammate Lead | | | | |--shutdown_req--| |--plan_req------| | {req_id:abc} | | {req_id:xyz} | | | | | |--shutdown_resp-| |--plan_resp-----| | {req_id:abc, | | {req_id:xyz, | | approve:true} | | approve:true} | Shared FSM: [pending] --approve-- [approved] [pending] --reject--- [rejected] Trackers: shutdown_requests {req_id: {target, status}} plan_requests {req_id: {from, plan, status}}三个要点方向相反shutdown 是 lead → 队友lead 请求队友决定plan approval 是队友 → lead队友提交lead 决定。但消息形态完全对称*_request/*_response都携带request_id与approve布尔值。共享状态机无论哪条协议请求创建时状态都是pending收到响应后根据approve落到approved或rejected。状态机不感知协议类型因此可以复用到任何请求-响应协商上。双追踪表shutdown_requests记录{req_id: {target, status}}plan_requests记录{req_id: {from, plan, status}}。追踪表是内存中的事实来源让 lead 不必解析自然语言回复就能查询进度。在 agents/s10_team_protocols.py 中可以看到这两个追踪表的全局定义# -- Request trackers: correlate by request_id -- shutdown_requests {} plan_requests {} _tracker_lock threading.Lock()注意_tracker_lock因为队友在独立线程里运行队友处理shutdown_response时会写shutdown_requests而 lead 主线程可能在同时读它。实现里所有对追踪表的读写都包在with _tracker_lock:中见 agents/s10_team_protocols.py 和 L352-L360这是多线程环境下协议状态一致性的最小保证。关停协议Shutdown Protocol的完整链路第一步lead 发起关停请求文档中的实现与源码一致。lead 生成一个 8 位 UUID 前缀作为request_id在追踪表中登记pending然后通过MessageBus把请求写进队友的收件箱shutdown_requests {} def handle_shutdown_request(teammate: str) - str: req_id str(uuid.uuid4())[:8] shutdown_requests[req_id] {target: teammate, status: pending} BUS.send(lead, teammate, Please shut down gracefully., shutdown_request, {request_id: req_id}) return fShutdown request {req_id} sent (status: pending)对应 agents/s10_team_protocols.py 的handle_shutdown_request。注意两点实现细节request_id是请求方生成的保证全局唯一且回复可回指BUS.send的第四个参数是消息类型shutdown_request。MessageBus.send()会校验类型必须属于白名单VALID_MSG_TYPES {message, broadcast, shutdown_request, shutdown_response, plan_approval_response}见 agents/s10_team_protocols.py非法类型直接返回错误。这个白名单让控制消息和普通聊天消息在协议层可区分。写入邮箱的消息是一条 JSONL 行形如{type: shutdown_request, from: lead, content: Please shut down gracefully., timestamp: 1756953600.0, request_id: a1b2c3d4}第二步队友收到请求回复 approve / reject队友在自己的 agent loop 里每个循环先BUS.read_inbox(name)把收到的消息 JSON 注入上下文见 agents/s10_team_protocols.py。队友判断后调用shutdown_response工具if tool_name shutdown_response: req_id args[request_id] approve args[approve] shutdown_requests[req_id][status] approved if approve else rejected BUS.send(sender, lead, args.get(reason, ), shutdown_response, {request_id: req_id, approve: approve})对应 agents/s10_team_protocols.py 的_exec分支。这里有两个值得注意的行为更新追踪表 回发消息是成对出现的。追踪表让 lead 用shutdown_response工具在 lead 侧它是查询状态的 handler见 L394 的_check_shutdown_status随时轮询进度回发消息则让 lead 的收件箱收到正式应答。approve 触发真正的退出。在_teammate_loop中工具执行后有一段关键判断agents/s10_team_protocols.pyif block.name shutdown_response and block.input.get(approve): should_exit True循环结束后成员状态按退出原因落盘L218-L221member[status] shutdown if should_exit else idle self._save_config()这就解决了 s09 的问题线程不是被杀死的而是自己走到一个安全的退出点——当前 LLM 回合的工具执行完成后才 break文件写完、状态落盘。这正是优雅关停graceful shutdown的含义。拒绝路径同样成立队友回复approvefalse加一段reason比如正在写半个文件追踪表落到rejected线程继续工作lead 可以在稍后重试或换人。计划审批协议Plan Approval同一 FSM 的第二个应用计划审批方向相反队友先提交计划lead 后审查。队友侧提交计划plan_requests {} def handle_plan_review(request_id, approve, feedback): req plan_requests[request_id] req[status] approved if approve else rejected BUS.send(lead, req[from], feedback, plan_approval_response, {request_id: request_id, approve: approve})以上是文档中 lead 侧的审查实现对应 agents/s10_team_protocols.py。队友侧的提交在_exec的plan_approval分支agents/s10_team_protocols.pyif tool_name plan_approval: plan_text args.get(plan, ) req_id str(uuid.uuid4())[:8] with _tracker_lock: plan_requests[req_id] {from: sender, plan: plan_text, status: pending} BUS.send( sender, lead, plan_text, plan_approval_response, {request_id: req_id, plan: plan_text}, ) return fPlan submitted (request_id{req_id}). Waiting for lead approval.从源码结构看s10 的实现刻意保持最小提交计划后队友线程并不会阻塞模型只是被告知在等待返回字符串提示等待 lead 审批。是否真正阻止执行依赖队友的系统提示词Submit plans via plan_approval before major work见 L177-L182和模型的自觉——这是一个协议层的约束而非执行层的强制。这一点在下一节的 s13 演进中会得到硬化的对照。lead 收到request_id后通过收件箱中的消息用plan_approval工具审批handle_plan_review校验request_id存在不存在时返回Error: Unknown plan request_id ...更新状态并把带feedback的响应发回队友。工具面从 9 个工具到 12 个工具文档的What Changed From s09表格如下组件s09s10工具数912shutdown_req/resp plan关停仅自然退出请求-响应握手计划门禁无提交/审查需审批关联无每个请求一个 request_idFSM无pending → approved/rejected对照 agents/s10_team_protocols.py 的 lead 侧TOOL_HANDLERS分发表可以精确核对这 12 个工具TOOL_HANDLERS { bash: lambda **kw: _run_bash(kw[command]), read_file: lambda **kw: _run_read(kw[path], kw.get(limit)), write_file: lambda **kw: _run_write(kw[path], kw[content]), edit_file: lambda **kw: _run_edit(kw[path], kw[old_text], kw[new_text]), spawn_teammate: lambda **kw: TEAM.spawn(kw[name], kw[role], kw[prompt]), list_teammates: lambda **kw: TEAM.list_all(), send_message: lambda **kw: BUS.send(lead, kw[to], kw[content], kw.get(msg_type, message)), read_inbox: lambda **kw: json.dumps(BUS.read_inbox(lead), indent2), broadcast: lambda **kw: BUS.broadcast(lead, kw[content], TEAM.member_names()), shutdown_request: lambda **kw: handle_shutdown_request(kw[teammate]), shutdown_response: lambda **kw: _check_shutdown_status(kw.get(request_id, )), plan_approval: lambda **kw: handle_plan_review(kw[request_id], kw[approve], kw.get(feedback, )), }前 9 个继承自 s09基础文件工具 4 个 团队工具 5 个新增 3 个即协议工具。注意一个非对称设计同名工具在 lead 侧和队友侧语义不同——shutdown_response对队友是回复关停请求L237-L247对 lead 是查询关停请求状态_check_shutdown_statusL377-L379plan_approval对队友是提交计划对 lead 是审批计划。工具集按角色裁剪是团队运行时里避免越权的重要手法。纵深对照当前主线 s13 中的协议硬化仓库 README 明确了双轨结构agents/与docs/是旧版 12 课的遗留轨根级s01_*~s17_*是当前主线旧 s10Team Protocols的内容并入新 s13 Agent Teams。当前主线 s13_agent_teams/code.py 把 s10 的最小协议升级成了一套带校验的团队运行时值得作为对照阅读s13_agent_teams/README.md 的第 11、12 节专门讲这部分。统一的 ProtocolState 取代两个字典s13 用一个 dataclass 统一两种协议状态s13_agent_teams/code.pydataclass class ProtocolState: request_id: str type: str # shutdown | plan_approval sender: str target: str status: str # pending - approved | rejected payload: str work_version: int | None None task_id: str | None None created_at: float field(default_factorytime.time) pending_requests: dict[str, ProtocolState] {}type字段让一个追踪表同时承载两种协议而work_version/task_id额外记录了计划提交时队友的工作上下文——这是 s10 没有的s13 要求审批响应必须与当时那份计划所属的任务和工作版本匹配。match_response四重校验替代盲信s10 中 lead 收到响应即更新状态s13 的match_responses13_agent_teams/code.py在更新前做四重校验request_id必须存在于pending_requests响应类型必须与请求类型匹配shutdown 请求只接受shutdown_response计划请求只接受plan_approval_response响应方向必须匹配from_agent是原请求的 targetto_agent是原请求的 sender——防止 A 代 B 回复状态必须还是pending——防止重复响应被应用两次。任意一条不满足就打印[protocol] ...日志并拒绝。consume_lead_inbox()L901-L911在把 Lead 邮箱内容注入模型上下文之前先跑一遍match_response即运行时负责状态机模型只负责决策。计划门禁从提示词约束变为工具分发层拦截这是 s10 与 s13 最实质的差异。s10 里先有计划再动手写在队友系统提示词里靠模型自觉s13 把它做到了工具执行路径上s13_agent_teams/code.pydef _run_teammate_tool(name: str, block, handlers: dict) - str: gate plan_gates.get(name, not_required) if block.name in {bash, write_file, edit_file}: if gate ! approved: if gate ! not_required: return (fBlocked: plan status is {gate}. Submit or revise the plan and wait for approval before changing the workspace.) ...plan_gates的取值是not_required / required / pending / approved / rejected只要不在not_required和approved两态队友的bash、write_file、edit_file一律返回Blocked。读文件和重新提交计划不受阻所以被拒的队友可以改完计划再交。此外run_review_planL1408-L1426还会检查work_version与task_id是否变化任务切换会使旧审批失效需要重新提交计划。测试用例如何验证这些协议tests/test_agent_teams_runtime.py 用假anthropic模块加载 s13 课程代码不真正调用 API对协议行为做了可复现的断言其中与本文主题直接相关的几条test_plan_rejection_requires_a_new_submissiontests/test_agent_teams_runtime.py完整走一遍lead 要求计划 → 队友提交pending→ lead 驳回rejected→ 队友应用响应 → 重新提交拿到新的 request_id验证被拒后必须重新提交而不是复用旧请求test_mismatched_plan_response_cannot_release_gateL895-L924构造一条request_id不匹配的伪造审批消息断言apply_plan_response返回[Ignored plan response: request mismatch]且门禁保持pending——即使request_id匹配如果 lead 尚未在追踪表中把状态置为已审查同样不会放行test_shutdown_response_must_come_from_requested_teammateL926-L942bob 冒名替 alice 回复shutdown_response断言请求状态保持pendingtest_shutdown_request_must_match_active_protocolL944-L972未知request_id的关停请求被忽略合法请求使队友状态变为stopping同一请求重放第二次不再生效幂等test_plan_gate_blocks_mutating_tools_until_approvalL672-L703门禁为pending时write_file/edit_file被拦截且 handler 未被调用approved后正常执行。这些测试覆盖了协议设计的三条不变量身份校验响应方必须是请求的目标、类型校验响应类型必须匹配请求类型、幂等性已决请求不可二次变更。动手运行 s10运行方式直接继承原文档的 Try It 部分旧版轨脚本 agents/s10_team_protocols.py 独立可运行需先安装 requirements.txt 并配置ANTHROPIC_API_KEY/MODEL_ID环境变量脚本通过load_dotenv读取.env并支持ANTHROPIC_BASE_URL覆盖 API 端点见 agents/s10_team_protocols.pycd learn-claude-code python agents/s10_team_protocols.py按文档给出的步骤操作Spawn alice as a coder. Then request her shutdown.——观察 lead 调用shutdown_request返回request_id随后 alice 的线程打印[alice] shutdown_response: ...并优雅退出List teammates to see alices status after shutdown approval——config.json中 alice 的状态应为shutdownSpawn bob with a risky refactoring task. Review and reject his plan.——观察 bob 提交计划后lead 用plan_approvalapprovefalse feedback驳回Spawn charlie, have him submit a plan, then approve it.——完整走通 approve 路径输入/team随时查看团队名册与状态另有/inbox命令可查看 lead 收件箱agents/s10_team_protocols.py。运行产物会落在工作目录的.team/下config.json名册与inbox/*.jsonl邮箱。建议运行结束后直接查看这些文件能直观理解文件即协议介质的设计——协议状态不依赖内存共享任何进程都能通过邮箱行 JSON 追溯一次握手的全过程。小结一个模式两个领域s10 的核心贡献可以压缩成一句话也即 agents/s10_team_protocols.py 文档字符串里的 Key insightSamerequest_idcorrelation pattern, two domains.请求方生成唯一request_id把pending状态登记进追踪表经文件邮箱投递结构化请求响应方引用同一request_id回复approve布尔值状态机落到approved/rejectedshutdown用它实现优雅关停线程自己走到安全退出点名册落盘plan approval用它实现高风险变更的先审后做。从当前主线 s13_agent_teams/code.py 的ProtocolStatematch_responseplan_gates可以看到这套模式的工程化方向追踪表统一化、响应四重校验、把计划门禁从提示词层下沉到工具分发层。若要继续深入建议按顺序阅读 docs/en/s10-team-protocols.md 的上一节 s09 Agent Teams 理解邮箱基础再读 s13_agent_teams/README.md 第 11、12 节及其code.py对应实现最后用 tests/test_agent_teams_runtime.py 中的协议测试自证理解。【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻