首页
/ learn-claude-code s10 团队协议:用 request_id 请求-响应握手实现关机协商与计划审批

learn-claude-code s10 团队协议:用 request_id 请求-响应握手实现关机协商与计划审批

2026-09-04 18:03:37作者:明树来

本文基于 learn-claude-code 课程仓库的 s10 团队协议文档 展开。s10 解决多 Agent 团队中的一个具体问题:当 Lead(领导)和多个 Teammate(队友,各自运行在独立线程中的 LLM 循环)已经可以通信之后,如何为「关机」和「计划审批」这类高风险控制操作建立结构化握手,而不是靠杀线程或口头约定。读完本文,你可以理解同一个 pending → approved | rejected 状态机如何以统一模式套用到两种协议上,并能直接运行 agents/s10_team_protocols.py 体验完整的请求-响应协商流程。

团队协议总览图

1. 问题背景:为什么「能通信」不等于「能协调」

s09(Agent Teams)已经实现了持久命名队友:每个队友在自己的线程里跑一个 agent loop,通过文件收件箱(JSONL inbox)互相发消息,团队成员状态记录在 .team/config.json 中。但 s09 缺少结构化协调,暴露出两个具体问题:

关机问题:直接杀线程会留下写了一半的文件和过期的 config.json。正确做法需要一次握手——领导请求关机,队友批准(收尾后退出)或拒绝(继续干活)。

计划审批问题:领导说「重构认证模块」,队友立刻开干。对高风险变更,应该先提交计划、经领导审查后再动手。

两者的结构完全一样:一方发一个带唯一 ID 的请求,另一方引用同一个 ID 响应。这就是 s10 的核心洞察:一个 FSM(有限状态机),两种用途,同一套 request_id 关联模式。

2. 解决方案总览:两种协议,一个状态机

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}}

注意两个协议方向相反:关机请求由 Lead 发起、Teammate 应答;计划审批由 Teammate 提交、Lead 审查。但消息结构一致:请求方生成 request_id 并跟踪状态,响应方引用同一 request_id 返回 approve/reject。

3. 消息底座:MessageBus 与五类消息

协议必须跑在 s09 的消息总线上。agents/s10_team_protocols.py#L87-L128 中的 MessageBus 为每个成员维护一个 JSONL 收件箱(.team/inbox/<name>.jsonl):

  • send() 以追加模式写入一行 JSON 消息,包含 typefromcontenttimestamp 以及可选的 extra 元数据(request_id 就放在这里);
  • read_inbox(clear=True) 读取并清空收件箱(drain 语义);
  • broadcast() 向除发送者外的所有成员逐一投递。

s10 允许的五种消息类型在 agents/s10_team_protocols.py#L73-L79 中声明,send() 会校验类型合法性:

消息类型 用途
message 普通文本消息
broadcast 广播给全体队友
shutdown_request 关机请求(s10 新增语义)
shutdown_response 关机批准/拒绝(s10 新增语义)
plan_approval_response 计划提交与审批响应(s10 新增语义)

请求状态的跟踪器是两个全局字典,外加一把线程锁(agents/s10_team_protocols.py#L81-L84):

# -- Request trackers: correlate by request_id --
shutdown_requests = {}
plan_requests = {}
_tracker_lock = threading.Lock()

_tracker_lock 的必要性来自运行时结构:队友循环运行在独立的 daemon 线程里(见下文 spawn()),而 Lead 的工具分发在主线程,两个线程会并发读写同一组 tracker 字典,因此所有对 tracker 的读写都包在 with _tracker_lock: 中。

4. 关机协议实现:从请求到线程退出

4.1 Lead 侧:发起关机请求

Lead 的 shutdown_request 工具调用 handle_shutdown_request()agents/s10_team_protocols.py#L352-L360):生成 8 位 request_id、登记 pending 状态、通过总线投递请求。

def handle_shutdown_request(teammate: str) -> str:
    req_id = str(uuid.uuid4())[:8]
    with _tracker_lock:
        shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
    BUS.send(
        "lead", teammate, "Please shut down gracefully.",
        "shutdown_request", {"request_id": req_id},
    )
    return f"Shutdown request {req_id} sent to '{teammate}' (status: pending)"

返回给 Lead 模型的字符串里同时带了 request_id 和 pending 状态,Lead 可以用它做后续追踪。

4.2 Teammate 侧:approve/reject 响应

队友的工具集里新增了 shutdown_response 工具(入参 request_idapprove、可选 reason),其执行分支在 agents/s10_team_protocols.py#L237-L247

if tool_name == "shutdown_response":
    req_id = args["request_id"]
    approve = args["approve"]
    with _tracker_lock:
        if req_id in shutdown_requests:
            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},
    )
    return f"Shutdown {'approved' if approve else 'rejected'}"

这里有一个值得注意的细节:队友自己也会更新 tracker(req_id in shutdown_requests 检查),因为队友线程和 Lead 共享同一进程内存中的 tracker;随后它把带同一 request_id 的响应发回 Lead 的收件箱。队友的系统提示词里也明确写了协议义务(agents/s10_team_protocols.py#L178-L182):

sys_prompt = (
    f"You are '{name}', role: {role}, at {WORKDIR}. "
    f"Submit plans via plan_approval before major work. "
    f"Respond to shutdown_request with shutdown_response."
)

4.3 审批后的收尾:状态落盘与线程退出

队友循环在每轮模型调用前 drain 自己的收件箱,把收到的消息(含 shutdown_request)注入对话。关键在于 agents/s10_team_protocols.py#L215-L221:当队友执行 shutdown_response(approve=True) 时,循环置 should_exit = True,退出循环后把成员状态写成 shutdown 并保存 config.json

if block.name == "shutdown_response" and block.input.get("approve"):
    should_exit = True
# 循环结束后:
member = self._find_member(name)
if member:
    member["status"] = "shutdown" if should_exit else "idle"
    self._save_config()

这正对应文档中「关机握手」的收益:不是直接杀线程,而是让队友在批准的时机自行收尾,最终状态在 config.json 中从 working 变为 shutdown(而非自然结束时的 idle),可通过 list_teammates 区分。

4.4 Lead 侧:查询请求状态

Lead 侧还注册了同名但语义不同的 shutdown_response 工具,它不是「响应」,而是查询——_check_shutdown_status()agents/s10_team_protocols.py#L377-L379)按 request_id 返回 tracker 中该请求的当前状态。也就是说,在 s10 的 Lead 工具表里 shutdown_response 是「查状态」,在队友工具表里才是「回复审批」,阅读源码时不要混淆。

5. 计划审批实现:方向相反、结构相同

5.1 Teammate 侧:提交计划

队友的 plan_approval 工具(入参只有一个 plan 文本)生成 request_id、登记计划、通知 Lead(agents/s10_team_protocols.py#L248-L257):

if 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 f"Plan submitted (request_id={req_id}). Waiting for lead approval."

工具返回提示「等待领导审批」,引导模型在拿到批复前不要动手。从源码结构看还有一个实现细节:计划提交消息和审批回复消息使用的消息类型都是 plan_approval_response,靠 extra 中的 request_idapprove 字段区分方向——类型校验(VALID_MSG_TYPES)管的是「是不是协议消息」,而方向与状态由 request_id 关联保证。

5.2 Lead 侧:审查与批复

Lead 的 plan_approval 工具(入参 request_idapprove、可选 feedback)调用 handle_plan_review()agents/s10_team_protocols.py#L363-L374):

def handle_plan_review(request_id: str, approve: bool, feedback: str = "") -> str:
    with _tracker_lock:
        req = plan_requests.get(request_id)
    if not req:
        return f"Error: Unknown plan request_id '{request_id}'"
    with _tracker_lock:
        req["status"] = "approved" if approve else "rejected"
    BUS.send(
        "lead", req["from"], feedback, "plan_approval_response",
        {"request_id": request_id, "approve": approve, "feedback": feedback},
    )
    return f"Plan {req['status']} for '{req['from']}'"

注意两个健壮性处理:未知 request_id 直接返回错误(而不是抛异常中断 Lead 循环),以及把 req["from"] 作为回复目的地——Lead 不需要记住谁提交了计划,tracker 里记着。

6. 工具面变化:相对 s09 的 diff

文档给出的变更对比表如下,可与源码逐一核对:

组件 之前 (s09) 之后 (s10)
Tools 9 12 (+shutdown_req/resp +plan)
关机 仅自然退出 请求-响应握手
计划门控 提交/审查与审批
关联 每个请求一个 request_id
FSM pending -> approved/rejected

源码印证:

7. 运行与实操

7.1 环境准备

依赖见 requirements.txtanthropic>=0.25.0python-dotenv>=1.0.0pyyaml>=6.0。程序入口会读取环境变量(agents/s10_team_protocols.py#L61-L68):

  • MODEL_ID必填,模型 ID,未设置会直接抛 KeyError;
  • ANTHROPIC_BASE_URL:可选,若设置则通过 base_url 构造 client,并自动 popANTHROPIC_AUTH_TOKEN(适配兼容网关的常见做法);
  • API Key 由 anthropic SDK 从环境(如 ANTHROPIC_API_KEY)读取,load_dotenv() 会优先加载本地 .envoverride=True)。

7.2 启动

cd learn-claude-code
python agents/s10_team_protocols.py

启动后进入交互式 CLI(提示符 s10 >>),支持两个监控命令(agents/s10_team_protocols.py#L463-L477):

  • /team:打印 .team/config.json 中所有成员的 name、role、status;
  • /inbox:只读打印 Lead 收件箱(read_inbox("lead", clear=False),不清空);
  • 输入 q / exit 或空行退出。

7.3 推荐体验路径(英文 prompt 对 LLM 效果通常更好)

  1. Spawn alice as a coder. Then request her shutdown. —— 验证关机请求-响应握手;
  2. List teammates to see alice's status after shutdown approval —— 验证 alice 的状态从 working 变为 shutdown
  3. Spawn bob with a risky refactoring task. Review and reject his plan. —— 验证计划提交与拒绝路径;
  4. Spawn charlie, have him submit a plan, then approve it. —— 验证批准路径;
  5. 随时输入 /team 监控成员状态,输入 /inbox 查看协议消息原文。

每次关机/审批请求都携带一个 8 位 request_id,你可以在 /inbox 输出里核对请求与响应是否引用同一 ID,这正是协议关联性的直接证据。

8. 设计要点与延伸

从 s10 的实现可以提炼出三点可复用的模式:

  1. 同一 request_id 关联模式覆盖多个协议域。tracker 只是 {req_id: {…, "status": "pending|approved|rejected"}},新增一种「需要对方点头」的操作(如危险命令审批、资源占用审批)时,只需新登记一个 tracker 和一对消息类型,状态机逻辑可以整体复用。
  2. 协议消息必须结构化,自由文本只做载荷。所有协商消息都经过 VALID_MSG_TYPES 校验,request_id 放在 extra 元数据里而非混入 content,避免靠「猜模型意图」来解析协商结果。
  3. 状态必须落盘,且退出要可区分config.jsonshutdownidle 是不同终态;队友线程自行收尾并保存状态,而不是被外力杀死,保证「写了一半的文件」问题在协议层面被显式处理。

需要说明的是,s10 是教学阶段的轻量实现:tracker 存活于单进程内存中,计划审批是「模型自觉」级别的门控(提示词要求队友先提交计划)。课程在后续章节 s13 Agent Teams 中把协议升级为可强制执行的运行时机制——ProtocolState 数据结构统一管理 pending_requests,工具分发层会真正拦截未获批计划期间的 bash/write_file/edit_file 调用,并对关机请求做了「必须匹配活跃协议」和「防重复响应」的校验,相关测试可见 tests/test_agent_teams_runtime.pytest_shutdown_request_must_match_active_protocoltest_plan_approval_cannot_cross_assignment_boundary 等用例。若你要在自己的 harness 中落地团队协议,建议把 s10 的 request_id 模式作为协议骨架,再参考 s13 的思路把「门控」从事后约定变为工具分发层的硬性拦截。

附:关键文件索引

内容 路径
s10 协议文档(本文主体依据) docs/zh/s10-team-protocols.md
s10 可运行实现(12 个 Lead 工具 + 8 个队友工具) agents/s10_team_protocols.py
s09 对照实现(9 个 Lead 工具) agents/s09_agent_teams.py
协议运行时演进(s13,Plan 门控强制执行) s13_agent_teams/README.md
团队运行时测试(含协议匹配/边界用例) tests/test_agent_teams_runtime.py
依赖清单 requirements.txt
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384