learn-claude-code s09 实战用持久队友与文件邮箱构建 Agent Teams【免费下载链接】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一个从 0 到 1 构建 nano 版 Claude Code 风格 agent harness 的课程仓库中s09 章节回答了一个多智能体协作的核心问题当任务大到单个 Agent 扛不住时如何生成有身份、有生命周期、能互相通信的“队友”。本篇完整拆解 s09 的 TeammateManager 与 MessageBus 两套核心机制——基于 append-only JSONL 文件信箱、线程承载的独立 agent loop、以及 Lead 侧 9 个工具的调度设计并对照课程源码给出可直接运行的实操步骤帮助你掌握“文件即通信总线”这一轻量协作模式。1. 为什么需要持久队友s04 与 s08 留下的空白s09 的出发点是两个已有机制都不够用Subagents04/s06是一次性的spawn → 执行 → 返回摘要 → 销毁。没有身份两次调用之间不保留任何记忆后台任务s08只会跑 shell 命令它能执行长时间命令但无法做 LLM 主导的决策。真正的团队协作需要三样东西s09 逐一补齐跨提示词存活的持久 Agentoutlive a single prompt身份与生命周期管理名字、角色、状态Agent 之间的通信通道。文档给出的一句话定位很准确s09 是 harness 层的Team mailboxes —— 多个模型通过文件进行协调。2. 总体架构.team/目录即团队状态s09 的全部团队状态都落盘在.team/目录下目录结构如下原文档示意路径相对工作区根.team/ config.json - 团队名册 各成员状态 inbox/ alice.jsonl - append-only读后即清空drain-on-read bob.jsonl lead.jsonl通信模型是“点对点投递到收件箱文件”-------- send(alice,bob,...) -------- | alice | ----------------------------- | bob | | loop | bob.jsonl {json_line} | loop | -------- -------- ^ | | BUS.read_inbox(alice) | ---- alice.jsonl - read drain ---------生命周期则是一条状态链spawn - WORKING - IDLE - WORKING - ... - SHUTDOWN从源码看agents/s09_agent_teams.py模块级常量固定了这套布局TEAM_DIR WORKDIR / .team、INBOX_DIR TEAM_DIR / inbox且WORKDIR Path.cwd()——也就是说团队目录永远建在启动脚本时的工作目录下。这一点决定了 s09 是“每工作区一个团队”的轻量模型适合单进程内的演示与试验。3. TeammateManager名册持久化与生命周期管理TeammateManager负责维护config.json名册。初始化时会创建目录、加载或初始化配置class TeammateManager: def __init__(self, team_dir: Path): self.dir team_dir self.dir.mkdir(exist_okTrue) self.config_path self.dir / config.json self.config self._load_config() self.threads {}源码中的完整行为补充了两点细节_load_config()在config.json不存在时返回默认值{team_name: default, members: []}存在时直接反序列化——名册天然可跨进程重启恢复每次名册变动spawn、状态回写都通过_save_config()以indent2写回磁盘。一个成员在配置中的形状是{name: alice, role: coder, status: idle}s09用到的 status 取值有working、idle、shutdown文档与 s10 文档中的 SHUTDOWN 由自然结束或线程退出体现。list_all()会把名册渲染为Team: default 每行name (role): status的文本供/team命令直接打印。3.1spawn()注册名册 启动线程spawn()做两件事把成员写入config.json然后为该队友开一个 daemon 线程运行它自己的 agent loopdef spawn(self, name: str, role: str, prompt: str) - str: member {name: name, role: role, status: working} self.config[members].append(member) self._save_config() thread threading.Thread( targetself._teammate_loop, args(name, role, prompt), daemonTrue) thread.start() return fSpawned teammate {name} (role: {role})文档版本为了教学做了简化实际源码agents/s09_agent_teams.py在 append 之前多了一段同名复活逻辑member self._find_member(name) if member: if member[status] not in (idle, shutdown): return fError: {name} is currently {member[status]} member[status] working member[role] role else: member {name: name, role: role, status: working} self.config[members].append(member)这带来两个可验证的行为事实对一个working中的队友重名 spawn 会直接报错而不是开第二个线程对idle/shutdown队友重名 spawn 会把它原地复活为working并挂上一个全新线程、全新的 messages 历史注意名册只保留状态旧对话历史不会恢复线程统一登记在self.threads[name]标记daemonTrue主进程退出时不会阻塞。4. MessageBusappend-only JSONL 信箱s09 通信层的全部实现就是一个几十行的MessageBus类。核心不变式发送永远只追加读取永远读全量并清空。class MessageBus: def send(self, sender, to, content, msg_typemessage, extraNone): msg {type: msg_type, from: sender, content: content, timestamp: time.time()} if extra: msg.update(extra) with open(self.dir / f{to}.jsonl, a) as f: f.write(json.dumps(msg) \n) def read_inbox(self, name): path self.dir / f{name}.jsonl if not path.exists(): return [] msgs [json.loads(l) for l in path.read_text().strip().splitlines() if l] path.write_text() # drain return json.dumps(msgs, indent2)消息结构每行一个 JSON 对象为字段含义type消息类型默认messagefrom发送者名字Lead 恒为leadcontent正文timestamptime.time()时间戳extra可选会被平铺合并进消息体如{request_id: ...}供 s10 协议使用相对文档源码补充了三个值得注意的实现事实类型白名单send()先校验msg_type非法类型返回Error: Invalid type ...而不是抛异常——错误以字符串回流给模型符合课程一贯的“工具错误也是工具输出”风格返回值是给模型看的确认成功返回Sent {msg_type} to {to}broadcast()是独立方法对名册中除发送者外的每个成员各发一条broadcast类型消息返回Broadcast to {count} teammates。它复用的就是send()因此广播在磁盘上就是 N 次追加写。read_inbox()的 drain 语义是并发正确性的关键信箱文件天然是“单一消费者队列”谁读谁清空消息不会重复投递。代价是它没有锁保护——从源码结构看s09 假设每个信箱同一时刻只有一个读取者该队友自己的线程或 Lead 主循环这在教学场景下成立课程后续章节s13 的 s13_agent_teams/code.py正是用锁 Condition 的wait_for_messages()把这一点补成了生产级实现。5. 队友的 agent loop每次 LLM 调用前先看信箱队友线程执行的是标准 agent loop模型 → tool_use → 执行工具 → 回填 tool_result → 直到 stop_reason 非 tool_uses09 的关键改造是把信箱注入放在循环每一轮的最前面def _teammate_loop(self, name, role, prompt): messages [{role: user, content: prompt}] for _ in range(50): inbox BUS.read_inbox(name) if inbox ! []: messages.append({role: user, content: finbox{inbox}/inbox}) response client.messages.create(...) if response.stop_reason ! tool_use: break # execute tools, append results... self._find_member(name)[status] idle结合源码可以确认的具体行为agents/s09_agent_teams.py每轮开头BUS.read_inbox(name)读取并清空自己的信箱每条消息以一条user角色的 JSON 文本插入 messages——通信不靠轮询提示词而是自动进入上下文队友的系统提示词是独立身份You are {name}, role: {role}, at {WORKDIR}. Use send_message to communicate. Complete your task.与 Lead 的SYSTEM提示词分离max_tokens8000循环硬上限50 轮防止失控的 tool 循环模型调用抛异常时break而不是崩溃线程循环正常退出含 break后只要状态不是shutdown就把成员状态回写为idle并落盘——这就是文档表格中 “idle - working - idle” 生命周期的来源。队友侧可用的工具只有 6 个4 个基础工具bash、read_file、write_file、edit_file 2 个通信工具send_message、read_inbox。也就是说队友不能再 spawn团队拓扑固定为星型Lead 为中枢。5.1 队友工具的沙箱细节基础工具实现与 s02 一致源码注释也明确标注 “unchanged from s02”但有两层防护值得了解路径逃逸防护_safe_path()把相对路径解析后检查is_relative_to(WORKDIR)越界直接ValueError——队友的read_file/write_file/edit_file都被限制在工作区内危险命令拦截_run_bash()对rm -rf /、sudo、shutdown、reboot、 /dev/做子串黑名单命令cwdWORKDIR、120 秒超时输出截断到 50000 字符。6. Lead 侧9 个工具与信箱驱动的主循环Lead主线程的工具表是 s08 的 6 个基础/后台工具基础上新增 3 个团队工具后的 9 个通过TOOL_HANDLERS字典分发工具作用底层调用bash运行 shell 命令_run_bashread_file读文件可选 limit_run_readwrite_file写文件_run_writeedit_file精确文本替换_run_editspawn_teammate生成持久队友name/role/prompt 必填TEAM.spawn(...)list_teammates列出所有队友及状态TEAM.list_all()send_message向指定队友信箱发消息msg_type 枚举约束BUS.send(lead, ...)read_inbox读取并清空 Lead 自己的信箱BUS.read_inbox(lead)broadcast群发给所有队友BUS.broadcast(lead, ...)注意spawn_teammate是 Lead 专属它只在 Lead 的TOOLS表中声明队友工具表里没有。同时 Lead 的send_message固定以lead为from与队友侧以自身名字为发送者形成对称。Lead 主循环agent_loop与队友循环结构相同差别在信箱注入方式非空信箱被包成inbox{json}/inbox作为一条 user 消息插入历史工具执行打印 {tool}:前缀、截取 200 字符便于终端观察stop_reason ! tool_use时返回交还终端输入。6.1 五种消息类型一次声明逐步消费源码顶部定义了 5 种合法消息类型文档注释原话 “all declared, not all handled here”类型用途s09 是否处理message普通文本消息是broadcast群发所有队友是shutdown_request请求优雅关机声明s10 实现shutdown_response同意/拒绝关机声明s10 实现plan_approval_response同意/拒绝计划声明s10 实现这个设计很关键s09 的VALID_MSG_TYPES白名单和extra字段为 s10 的请求-响应协议request_id关联 pending - approved/rejected状态机预留了通道send()的签名一个字都不用改。参见 docs/en/s10-team-protocols.md。7. 从 s08 到 s09 的变化原文档的对比表完整继承ComponentBefore (s08)After (s09)Tools69 (spawn/send/read_inbox)AgentsSingleLead N teammatesPersistenceNoneconfig.json JSONL inboxesThreadsBackground cmdsFull agent loops per threadLifecycleFire-and-forgetidle - working - idleCommunicationNonemessage broadcast一句话概括s08 的线程里跑的是“命令”s09 的线程里跑的是“完整的 agent”。8. 动手运行依赖见 requirements.txtanthropic0.25.0、python-dotenv1.0.0、pyyaml6.0。运行前需要在.env中配置ANTHROPIC_API_KEY和MODEL_ID源码中MODEL os.environ[MODEL_ID]未设置会直接 KeyError如走兼容端点可另设ANTHROPIC_BASE_URL脚本会在这种情况下主动pop掉ANTHROPIC_AUTH_TOKEN避免双认证头冲突。cd learn-claude-code python agents/s09_agent_teams.py进入s09 提示符后按原文档给出的剧本依次执行Spawn alice (coder) and bob (tester). Have alice send bob a message.Broadcast status update: phase 1 complete to all teammatesCheck the lead inbox for any messages输入/team查看团队名册与各成员状态输入/inbox手动查看 Lead 信箱注意该命令用read_inbox(lead, False)只读不清空不会破坏待注入消息运行中可以直接打开工作区的.team/config.json观察 status 从working翻转为idle以及.team/inbox/*.jsonl文件的出现、追加与读后清空。退出方式输入q、exit或直接回车。9. 这套设计的演进与边界s09 是课程 12 步系列里“文件邮箱”的第一次亮相它的极简取舍值得明确列出均有源码依据无锁的 drain-on-read依赖“每信箱单读者”假设跨进程/多写者场景不安全50 轮硬上限与异常即 break意味着队友失败是静默的靠状态回写为idle兜底无身份重名防护源码允许任何名字包括lead被 spawnlead信箱仍按名册名解析。这些短板正是课程后话docs/en/s10-team-protocols.md 在此之上加了带request_id的关机和计划审批协议而重构后的 s13_agent_teams/code.py 把 MessageBus 升级为.mailboxes/name.jsonl 锁 Condition.wait()唤醒、把lead/agent设为保留名、把收件箱投递从“模型工具”改为“运行时事件注入”consume_lead_inbox()并叠加任务板原子认领与 worktree 绑定。相关回归测试如信箱路径逃逸拦截、保留名拒绝、shutdown 响应必须来自被请求方见 tests/test_agent_teams_runtime.py所有agents/*.py脚本的编译级冒烟测试见 tests/test_agents_smoke.py。小结s09 用约 400 行代码给出了多智能体协作的最小可行骨架——config.json管身份与状态JSONL 信箱管通信每个队友一个线程一个完整 agent loop每次 LLM 调用前自动 drain 信箱进上下文。它没有引入任何消息中间件只用了文件追加和清空却完整覆盖了“持久 Agent 生命周期 通信通道”三要素读懂它再去看 s10 的协议状态机与 s13 的运行时投递整条团队 harness 的演进路线就串起来了。【免费下载链接】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),仅供参考