learn-claude-code s13 精讲:Agent Teams 团队运行时与协调协议——持久队友、原子 Claim、worktree 与计划门禁

learn-claude-code s13 精讲:Agent Teams 团队运行时与协调协议——持久队友、原子 Claim、worktree 与计划门禁
learn-claude-code s13 精讲Agent Teams 团队运行时与协调协议——持久队友、原子 Claim、worktree 与计划门禁【免费下载链接】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课程第 13 章文档 s13_agent_teams/README.ja.md 展开讲解当单个 Agent 无法承载整份工作时的团队化方案Lead 与持久 teammates 如何分工、如何通过文件信箱通信、如何对共享任务板做原子 Claim、如何用可选 worktree 隔离并行编辑以及 shutdown 与计划批准如何成为可追踪、可强制执行的类型化协议。读完本文你将理解code.py中团队运行时的完整设计并能实际运行该课程验证每个机制。1. 问题单 Agent 无法承载的整块工作假设让 Agent 重构整个后端工作横跨配置加载、认证、测试等多个领域。单个 Agent 也能顺序完成但耗时长且早期细节会逐渐滑出上下文。这类工作天然适合并行化但用户通常只描述目标而非设计团队Refactor this sample backend. Clean up configuration loading, authentication, and tests, preserve the existing interfaces, and make sure the tests pass.因此 Harness 必须回答一组相互关联的问题谁来判断并行工作有价值、谁来批准追加 Agent每个 teammate 如何在多次任务分配之间保持身份与上下文结果如何回到 Lead而不要求模型轮询信箱IDLE 的 teammate 能否不等新分配就领取就绪任务并行编辑可能冲突时任务应该使用哪个工作目录shutdown 与计划批准如何成为可追踪、可强制的协议。2. 解决方案总览在 s10 基础上叠加 Lead 管理团队运行时s13 复用 s10 的基础工具、Hooks、Permission 检查与 Task System再叠加一层 Lead 管理团队运行时Lead拥有用户会话提出分工方案并等待确认Teammates各自运行独立的 agent loop在 WORK 与 IDLE 之间交替MessageBus通过文件信箱.mailboxes/name.jsonl承载普通消息、结果与控制事件运行时投递Runtime delivery消费 Lead 的信箱把团队事件注入下一轮对话共享任务板让 IDLE 的 teammate 发现就绪工作并在锁下 Claim可选 worktree需要时为任务绑定另一个工作目录未绑定的任务使用常规仓库目录类型化协议与计划门禁让 shutdown 与批准状态显式化并在必需计划被批准前阻止变更类工具。需要强调的两个设计边界s11 的 background task 与 s12 的 scheduled task没有被带入本章——两者对 teammate 通信、任务 Claim、计划批准都不是必需的并且这些机制都是 Team Harness 层的一部分teammate 不需要为任务发现另建循环worktree 也不会创造一种新的 Agent。这一取舍在测试 tests/test_agent_teams_runtime.py 的test_agent_teams_builds_on_tasks_not_background_or_cron中也有验证。3. 机制详解3.1 Lead 提出团队方案并等待用户确认启动 teammate 会改变成本、并发度以及“谁可以编辑工作区”这一主体集合。Lead 的系统提示词把这条边界写得很明确见 code.py 的 PROMPT 部分When parallel work would help, first propose a small team with clear responsibilities and wait for the users confirmation. Do not call spawn_teammate before the user confirms.面对第一个请求Lead 只给出分工草案I suggest three parallel areas: - config: clean up configuration loading - auth: refactor authentication - tests: add regression coverage I will start the teammates after you confirm.用户回复“Go ahead”之后Lead 才能调用spawn_teammate。Lead 先创建 Task再把初始task_id传给 teammate——用户陈述目标、Lead 设计团队、用户确认执行边界三者职责分明。3.2 每个 teammate 拥有独立的持久循环s06 的 subagent 是一次性调用teammate 则是持久执行单元s06 Subagents13 Teammate生命周期一次调用后结束直到 shutdown 前持续WORK → IDLE → WORK上下文只存在于一个任务中跨多次分配保持通信返回一次结果接收消息并发出事件协调单向委派与 Lead 双向协作TeammateRuntimecode.py#L1085为每个 teammate 维护专属的 system prompt、messages、handlers 与当前 Task并在 daemon thread 中运行 WORK / IDLE 循环teammate 工作时 Lead 可以继续协调。lead与agent作为运行时身份被保留MessageBus仍接受lead作为协调者信箱保留名校验对应测试test_reserved_teammate_names_do_not_shadow_runtime_identities。关键细节spawn_teammate在启动线程之前先 Claim 初始 TaskClaim 失败则 teammate 根本不启动code.py#L1314-L1353测试见test_spawn_claims_the_initial_task_before_starting_the_thread。没有 Task 时workspace 与 Shell 工具会要求先 Claim而不是回退到仓库目录。3.3 MessageBus把通信移出模型上下文Lead 与 teammates 不能共享同一个 messages 数组否则某个 teammate 的工具结果会污染另一个 teammate 的推理。MessageBus为每个 Agent 准备.mailboxes/name.jsonl信箱code.py#L783-L850class MessageBus: def send(self, from_agent, to_agent, content, msg_typemessage, metadataNone): msg { from: from_agent, to: to_agent, content: content, type: msg_type, metadata: metadata or {}, } with self._changed: MAILBOX_DIR.mkdir(parentsTrue, exist_okTrue) with self._path(to_agent).open(a, encodingutf-8) as handle: handle.write(json.dumps(msg, ensure_asciiTrue) \n) self._changed.notify_all() def wait_for_messages(self, agent, timeoutNone): deadline None if timeout is None else time.monotonic() timeout with self._changed: while not self.peek(agent): remaining (None if deadline is None else deadline - time.monotonic()) if remaining is not None and remaining 0: return [] self._changed.wait(remaining) return self._read_unlocked(agent)锁保护信箱文件的并发访问Condition既能在消息到达时唤醒 teammate也支持 IDLE 期间的短超时扫描。消息体是 JSONLfrom、to、content、typemessage/result/idle_notification/shutdown_request/plan_approval_request等、metadata携带request_id、approve。3.4 运行时投递信箱事件模型不轮询read_inbox()通过读取并删除信箱文件来消费消息因此 Lead 侧只保留单一消费者consume_lead_inbox()code.py#L901-L911def consume_lead_inbox(): messages BUS.read_inbox(lead) for message in messages: if message[type].endswith(_response): match_response(...) return messagesCLI 主循环用select同时等待终端输入与 Lead 信箱wait_for_cli_eventcode.py#L1743-L1758输入轮询间隔 0.25 秒。消息到达后的投递链路MessageBus → consume_lead_inbox → 更新协议状态 → 将 [Team events] 注入 history → 开启 Lead 的下一轮Lead 在 spawn teammate 之后会直接结束当前 turn而不是反复调用list_teammates、get_task等待团队事件到达时由运行时开启下一轮。check_inbox不是模型工具——消息到达属于运行时职责模型只处理已被投递进上下文的事件。这一职责划分有专门测试test_inbox_delivery_is_runtime_owned保证。3.5 result 与 IDLE 是两个独立事件teammate 完成一次分配后运行时按顺序发送两个事件code.py#L1237-L1250result: Authentication refactored; related tests pass. idle_notification: Waiting for more work.result回答“这个分配产出了什么”idle_notification回答“这个 teammate 现在能否接受新工作”。一个含糊的“完成”无法同时表达两种状态。IDLE 的 teammate 并不退出收到直接消息或 ready task 会回到 WORK收到shutdown_request则开始优雅关闭握手。3.6 IDLE 优先查信箱再找 ready taskIDLE 循环code.py#L1252-L1276IDLE_SCAN_INTERVAL 2.0秒先等消息、后查任务板while True: inbox BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL) if inbox: should_stop handle_messages(inbox) if should_stop or messages[-1][role] user: break continue task claim_next_task(name) if task: messages.append({ role: user, content: f[Auto-claimed task {task.id}] {task.subject}, }) breakshutdown、计划批准、Lead 的直达指令都应当先于“顺手捡活”被处理。既无消息又无就绪任务时teammate 保持 IDLE被阻塞的任务也可能因另一 teammate 完成前置任务而转为 ready依赖检查由can_start(task_id)基于blockedBy完成。3.7 发现与 Claim 分离Claim 是原子操作扫描只产生候选、不改变状态code.py#L1056-L1067def scan_unclaimed_tasks() - list[Task]: return [ task for task in list_tasks() if task.status pending and task.owner is None and can_start(task.id) ]候选列表只是某时刻的快照另一个 teammate、甚至另一个使用同一任务目录的 Harness 进程都可能看到同一个任务。因此所有权变更发生在claim_task()内由task_store_lock()串行化——它组合了进程内threading.RLock与fcntl.flock文件锁code.py#L70-L89def claim_task(task_id: str, owner: str) - str: with task_store_lock(): task load_task(task_id) if task.status ! pending or task.owner is not None: return Task is no longer available if _owner_in_progress(owner): return Owner must complete its current task first if not can_start(task_id): return Task is blocked cwd, error task_worktree_cwd(task) if error: return fCannot claim {task_id}: {error} task.owner owner task.status in_progress save_task(task) teammate_assignments[owner] {task_id: task.id, cwd: cwd} return fClaimed {task.id}多个 teammate 可能发现同一候选但只有一个Claim 能把它推进到in_progress。任务文件通过临时文件写入并在同一把 store lock 持有期间原子替换。teammate 必须完成当前任务才能领取下一个任务worktree 绑定损坏时 Claim失败关闭fail closed而不是悄悄回退到仓库目录。3.8 被 Claim 的工作复用同一个 WORK 循环Claim 成功后运行时把任务 ID、主题、描述注入 teammate 的 messages[Auto-claimed task ...]随后状态流转为ready task appears → IDLE teammate discovers it → claim_task writes owner and in_progress → task enters teammate messages → WORK → complete_task → result idle_notification → IDLEteammate 在此复用与 Lead 直接分配完全相同的模型调用、文件工具、Shell、计划门禁、结果上报与 shutdown 协议。任务发现只是进入既有 WORK 循环的另一条入口——这正是“不需要为任务发现单独建循环”的含义。3.9 由任务选择工具的工作目录Task的worktree是可选字段code.py#L112-L120dataclass class Task: id: str subject: str description: str status: str owner: str | None blockedBy: list[str] worktree: str | None None需要把并行编辑分到其他目录时Lead 可以创建并绑定 worktreecreate_worktree(nameauth-refactor, task_idtask_1a2b3c4d)create_worktree是Lead 专属工具code.py#L445-L522只接受 pending、无 owner、未绑定 worktree 的任务依次校验名称^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$、禁止..、路径、分支wt/name与 Git 注册表全部通过后才执行git worktree addcheckout 创建成功后才写入任务绑定。如果 Git 失败但残留了分支或已注册 checkout运行时报告 partial operation、保持任务未绑定并把残留物保留供人工恢复。teammate 只拥有任务工具与文件工具。Claim 时解析出的目录存入teammate_assignmentscode.py#L64-L67该 teammate 的bash、read_file、write_file、edit_file、globwrapper 全部从 assignment 读取 cwdcode.py#L1129-L1155无 worktree 的任务解析到WORKDIR没有 Claim 过 Task 的 teammate 无法使用这些 workspace 工具测试test_teammate_workspace_tools_require_a_claimed_taskcwd, error task_worktree_cwd(task) if not error: teammate_assignments[owner] { task_id: task.id, cwd: cwd, }complete_task(task_id, owner)会校验调用者确实是该 in-progress 任务的 owner。成功时记录结果但不立即解除 assignment 目录——让同一模型轮次的后续 tool call 仍停留在该任务的目录中teammate 回到 IDLE 时由运行时解除。失败则保留目录便于修复后重试。进程重启后assignment_cwd()code.py#L390-L410能基于持久化的 task owner 与 worktree 绑定重建 in-progress 的 assignmentbinding 缺失或无效时同样失败关闭不会悄悄把工作路由到仓库目录。注意worktree 隔离的是 Git 工作目录与分支不是 sandbox——Shell 命令仍可访问父进程被允许的路径与资源。3.10 worktree 删除权属于 host模型可以创建 task-bound worktree但不能删除它。清理保留为 host helperremove_worktree(name, discard_changes)code.py#L524-L606以便 user 或 host 先检查任务所有权、assignment lease 与 Git 状态。helper 拒绝 pending / in-progress 的任务绑定与当前轮次的 lease除非显式选择破坏性删除tracked、untracked、ignored 文件都会阻止删除测试test_worktree_removal_is_host_only。remove_worktree(name, discard_changesTrue)只能由已取得用户明确确认的 host 代码调用。两种删除路径都保留wt/name分支包括无 upstream 的 clean 本地提交删除成功后解除任务绑定因为 checkout 已不存在clean worktree → host may remove directory and retain wt/name branch changed worktree → user decides how to preserve or discard it pending/running task → refuse removal任务完成与 worktree 清理也是解耦的complete_task只记录任务结果teammate 回到 IDLE 后user 或 host 再决定检查、合并、保留还是删除 worktree。3.11 控制消息使用类型与 request_id普通协作可以用自由文本但 shutdown 与批准不应依赖“猜意图”。它们使用结构化消息与显式状态机code.py#L853-L898dataclass class ProtocolState: request_id: str type: str sender: str target: str status: str payload: str work_version: int | None None task_id: str | None None pending_requests: dict[str, ProtocolState] {}shutdown 的完整路径Lead creates a pending shutdown request → shutdown_request(request_id) enters the teammate inbox → the teammate finishes its current step → shutdown_response(request_id) returns to Lead → request_id locates the original request → pending becomes approved and the teammate loop exitsmatch_response()做三重校验request_id把一个响应关联到一个请求类型防止不匹配的响应改变状态shutdown只接受shutdown_responseplan_approval只接受plan_approval_responsestatus防止同一响应被重复应用非pending直接拒绝发送方/接收方与sender/target不一致也会被记录并丢弃。3.12 计划批准不仅追踪还约束执行计划协议方向相反Lead → plan_request teammate → plan_approval_request(request_id, plan) Lead → plan_approval_response(request_id, approve, feedback)若 Lead 一开始就知道某 teammate 必须先出计划使用spawn_teammate(..., task_idtask.id, require_planTrue)——运行时先 Claim Task、激活 gate再启动 teammate 线程对已在运行的 teammate 则可用request_plan补提要求。工具 dispatch 在门禁处强制code.py#L969-L982。源码中 gate 的实际取值是not_required / required / pending / rejected / approvedbash、write_file、edit_file只有在前两者之外被放行def _run_teammate_tool(name, block, handlers): gate plan_gates.get(name, not_required) if block.name in {bash, write_file, edit_file}: if gate ! approved and gate ! not_required: return fBlocked: plan status is {gate}. # 之后还会走 check_permission 的 deny list 与路径检查 ...状态处于required、pending、rejected期间teammate 可以读文件、可以提交或修订计划但不能运行 Shell 命令、写文件、编辑文件测试test_plan_gate_blocks_mutating_tools_until_approval。计划提交时记录 teammate 当前的 task 与 work versionassignment_versionsClaim 或 release Task 会推进 version从而使旧批准失效普通 message 既不改变任务身份也不改变批准状态测试test_plain_message_does_not_change_assignment_or_plan_version。Lead 端run_review_plan在批准前还会复核work_version与task_id是否仍与提交时一致不一致会返回 “belongs to an earlier assignment”。另外teammate 不从后台线程读取用户输入危险命令或 workspace 外的路径直接返回 permission error交由 Lead 与用户决策。4. 一次完整运行示例s13 Put the backend refactor on a shared task board. Clean up configuration, authentication, and tests in parallel where possible. Use a worktree for authentication, preserve existing interfaces, and make sure the tests pass. Lead: I suggest config, auth, and tests as three areas. Shall I start the team? s13 Go ahead. [task] config created [task] auth created → worktree auth-refactor [task] tests created [claim] alice → config (cwd: repository) [claim] bob → auth (cwd: .worktrees/auth-refactor) [teammate] alice spawned [teammate] bob spawned [complete] auth [bus] bob → lead (result) ... [bus] bob → lead (idle_notification) ... [wake: 2 team events → new turn] Lead: I received the authentication result and will coordinate the rest.终端完整暴露了用户请求、Lead 提案、任务状态、Claim、所选目录、结果、IDLE 迁移与控制事件。用户不需要指名 Lead也不需要请求它“检查一下信箱”——投递全部由运行时完成。5. 与 s10 的差异对照组件s10s13Agent单个 Agent1 个 Lead 持久 teammates用户流程直接执行请求先提出团队方案再确认启动通信无文件信箱 运行时投递生命周期一个循环teammate 的WORK / IDLE / shutdown共享工作一个 Agent 使用任务工具IDLE 扫描 teammate 原子 Claim工作目录仓库WORKDIR已 Claim 的 Task可选 worktree结果通知当前 Agent 的输出result与idle_notification分离控制无类型化 shutdown 与计划批准协议强制无团队约束必需计划会门控变更类工具工具面上Lead 可用spawn_teammatename 限^[A-Za-z0-9_-]{1,64}$、task_id 限^task_[0-9a-f]{8}$、list_teammates、send_message、request_shutdown、request_plan、review_plan、create_worktree外加全部 TASK_TOOLSteammate 的TEAMMATE_TOOLS则是基础文件/Shell 工具加send_message、submit_plan、list_tasks、claim_task、complete_taskcode.py#L1493-L1562。6. 动手运行运行方式需要pip install anthropic python-dotenv并在.env中配置ANTHROPIC_API_KEY程序读取环境变量MODEL_ID且要求当前目录是 Git 仓库根create_worktree依赖git worktreecd learn-claude-code python s13_agent_teams/code.py输入一条普通请求Put the backend refactor on a shared task board. Complete configuration, authentication, and tests in parallel where dependencies allow. Use a worktree for authentication, preserve existing interfaces, and summarize the result.Lead 提出团队方案后回复Go ahead.观察点.tasks/中状态从pending到in_progress再到completed的流转.mailboxes/中result与idle_notification的投递.worktrees/只为绑定了 worktree 的任务生成。还可以验证直接消息优先于任务板扫描complete_task失败后 teammate 的工作目录不会被重置。7. 本章定位与后续code.py中 Lead 与 teammate 能调用的只有直接定义在文件里的工具TOOL_HANDLERS注册表。要连接 Jira、部署平台或知识库仍需要为每个外部系统单独编写 tool schema 与 handler外部工具的增删改也要同步修改课程代码。这正是下一章的动机s14 MCP Tools 将通过统一的发现与调用协议在运行时接入外部服务并将其工具加入工具池参见 s14_mcp_plugin/README.md。相关资源s13 英文版文档、s13 实现代码、团队运行时测试、s10 任务系统文档。【免费下载链接】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),仅供参考

最新新闻

日新闻

周新闻

月新闻