多个 Agent 同时工作时,主 Agent 怎样接收队友结果

0 阅读9分钟

本文是「从零理解 Claude Code:20 个 Agent Harness 机制」系列的第 15 篇。
源码仓库:shareAI-lab/learn-claude-code
对应章节:s15:Agent Teams

设想一个前端项目准备迁移构建工具。需要先梳理现有插件配置,再检查路由构建产物,还要跑一遍测试确认改动范围。让一个 Agent 从头查到尾当然能做,只是它会陆续读进很多配置、日志和代码片段,等它回头处理最开始的问题时,上下文已经堆得像一座山那么高了。

s06 的子 Agent 适合处理一次性调查任务:给它一个问题,它在独立上下文里工作,最后交回总结。s15 继续往前走了一步,主 Agent 可以启动多个队友;队友各自处理任务,通过收件箱向主 Agent 汇报,也能在有限轮次内接收其他消息。

这章新增的重点不在于多开几个线程,而在于补上了队友之间传递结果的通道。

一、一次性子任务为什么无法覆盖协作场景

子 Agent 完成任务后,把一段总结交给主 Agent,生命周期基本结束。对于代码检索、定位问题这类任务,这种方式已经够用。

团队协作会遇到更长的过程。

例如主 Agent 让一个队友检查构建配置,让另一个队友验证测试脚本。检查构建配置的人发现某个别名规则可能影响测试,就需要把这个信息交给主 Agent。主 Agent拿到消息后,可能还要补充新的检查要求。这里需要的是一条可以异步投递和接收信息的通道,而不是只在任务结束时返回一次文本。

s15 把角色分成两类:

角色负责的工作
主 Agent拆分任务、启动队友、接收消息、继续决策
队友 Agent在自己的上下文中执行分配任务,并把结果写回收件箱

教学代码仍然只有一个主 Agent 的消息历史。每个队友启动后会创建自己的消息列表,主 Agent 不会自动看到队友读过哪些文件、执行过哪些命令。双方靠消息文件交换必要信息,避免把所有中间过程塞进同一段对话。

二、队友启动后拥有一段独立对话

主 Agent 调用 spawn_teammate 后,程序会创建一个守护线程,并为队友准备新的系统提示词、消息列表和工具列表。

下面这段代码位于队友线程启动阶段。需要关注的是,队友没有继承主 Agent 的消息历史:

messages = [{"role": "user", "content": prompt}]

sub_tools = [
    bash,
    read_file,
    write_file,
    send_message,
]

for _ in range(10):
    inbox = BUS.read_inbox(name)
    response = client.messages.create(
        model=MODEL,
        system=system,
        messages=messages[-20:],
        tools=sub_tools,
    )

队友最开始只知道主 Agent 交给它的任务。后续每一轮调用模型时,程序最多保留它最近的 20 条消息。这个限制让队友的上下文保持相对独立,也限制了它携带历史信息的长度。

工具列表同样被收窄了。队友可以执行命令、读取文件、写入文件和发送消息;它没有创建新队友、创建任务、定时调度等工具,这样可以避免队友继续拉起下一层队友。

不过,角色名称本身只是一段系统提示词。

例如传入 testerfrontend developer,程序会把它拼进队友的身份描述。代码没有为不同角色配置不同权限。两个队友都能读取和写入工作目录中的文件,也都能执行 Shell 命令。角色帮助模型聚焦工作内容,不能代替权限控制。

三、队友结果怎样写入收件箱

s15 的消息总线很朴素:每个 Agent 对应一个 JSONL 文件。发送消息时,程序向对方的文件追加一行 JSON;读取消息时,程序读取完整文件,再删除文件。

def send(self, from_agent, to_agent, content, msg_type="message"):
    inbox = MAILBOX_DIR / f"{to_agent}.jsonl"
    with open(inbox, "a") as f:
        f.write(json.dumps(msg) + "\n")

def read_inbox(self, agent):
    inbox = MAILBOX_DIR / f"{agent}.jsonl"
    msgs = [json.loads(line) for line in inbox.read_text().splitlines()]
    inbox.unlink()
    return msgs

例如名为 config-reviewer 的队友完成检查后,会向主 Agent 的收件箱写入一条结果。主 Agent 的收件箱文件位于 .mailboxes/lead.jsonl,其中每一行都带有发送者、接收者、文本内容、消息类型和时间戳。

这套设计有一个明确的消费动作:读取后删除。主 Agent 一旦读取了收件箱,原始文件就被移除,消息不会在下一轮重复出现。

下面可以帮助大家区分队友自己的上下文、共享工作目录和消息收件箱之间的关系。

ChatGPT Image 2026年7月30日 14_35_52.png

图中最容易混淆的地方是工作目录。队友的对话上下文互相独立,但读写工具仍然指向同一个工作目录。消息通过收件箱传递,文件修改则直接发生在同一份项目代码里。

四、主 Agent 为什么能在没有用户输入时收到结果

收件箱里有新消息,并不会自动进入模型上下文。本章在主程序中增加了一个轮询线程,每秒检查一次主 Agent 的收件箱是否存在未读内容。

轮询线程只做检测,不会立即读取文件:

if BUS.peek("lead") or has_pending_background():
    events.put(("wake", None))

主程序把用户输入和异步通知都放进同一个事件队列。收到 wake 事件后,程序读取主 Agent 的收件箱,把消息拼成一段新的用户消息,再启动一轮 Agent Loop:

inbox = BUS.read_inbox("lead")

history.append({
    "role": "user",
    "content": "[Inbox]\n" + inbox_text,
})

agent_loop(history, context)

这样设计的好处是,主 Agent 不会同时运行两轮对话。用户输入、队友汇报和后台任务完成通知,都会排队进入同一个事件循环。

假设主 Agent 正在回答用户的问题,此时队友完成了构建配置检查。轮询线程会把唤醒事件放入队列,当前这轮回答结束后,主程序再读取队友结果,开始下一轮模型调用。主 Agent 不需要等用户继续输入,能及时根据队友消息调整后续工作。

当前代码对收件箱内容做了截断处理,每条消息只保留前 200 个字符后写入消息历史。队友发来一段较长的测试结论时,主 Agent 只能看到开头;原始收件箱文件又已经在读取后删除,后续没有读取完整消息的工具。这种处理适合演示异步唤醒流程,信息完整性仍然需要补强。

五、共享工作目录会带来哪些协作风险

队友的文件工具复用了主 Agent 的读写实现,工作目录也相同。消息隔离不等于文件隔离。

假设两个队友都在处理 vite.config.ts

  • 配置队友准备增加路径别名;
  • 测试队友为了临时验证构建结果,也修改了同一个配置文件;
  • 两个写入操作先后发生,后写入的内容可能覆盖前一次修改。

教学代码没有为文件写入建立锁,也没有给每个队友分配独立分支或工作目录。主 Agent 收到结果时,可能看到两个队友都说已经完成,但磁盘上只留下最后一次写入的版本。

任务系统在这一章仍然存在,不过队友拿到的简化工具列表里没有创建、认领和完成任务的能力。主 Agent可以管理任务文件,队友只能通过消息汇报进度。对于需要精确分工的场景,主 Agent 还要自行维护谁在做什么、哪项工作已经完成,以及哪项结果需要复查。

六、教学实现还缺少哪些团队协议

本章已经具备了多 Agent 协作的最小过程:主 Agent 启动队友,队友在独立上下文中执行工具,结果通过收件箱进入主 Agent 的下一轮对话。

但收件箱和线程生命周期仍然比较简化。

场景当前教学代码的处理可能产生的问题
队友运行轮数最多执行 10 轮长任务可能在完成前结束
队友结束条件模型不再请求工具时退出无法像常驻协作者一样等待后续指令
主 Agent 收件箱读取后删除文件长消息被截断后无法重新读取
并发读写收件箱文件追加、读取、删除,没有文件锁读写重叠时可能丢失消息
队友执行失败捕获异常后退出,仍会尝试发送总结主 Agent 可能只收到默认的 Done.
文件修改多个队友共享同一目录同一文件可能互相覆盖
主进程退出队友线程是守护线程未完成队友会被直接结束

仓库 README 中的源码分析提到了更完整的团队设计方向,例如带锁的收件箱、空闲等待机制、关闭请求和权限审批消息。它们没有出现在 s15 的教学代码中。

因此,这一章更适合用来理解团队协作最基本的三件事:独立上下文、异步消息和主 Agent 的结果回收。任务分配、权限审批、可靠关闭和文件隔离,还需要额外的协议和状态管理。

七、小结

s15 让主 Agent 可以把工作分给多个队友。每个队友从一段独立对话开始,使用有限的工具完成任务,再通过文件收件箱把结果交回主 Agent。主程序轮询主收件箱,有消息时自动开启新一轮对话,因此用户不必手动提醒主 Agent 去查看队友的进度。

这套实现把协作消息传递跑通了,但它还没有处理可靠交付。收件箱读取后会删除,长消息会被截断,线程退出后也没有恢复机制;多个队友同时改同一个文件时,代码同样没有协调手段。

下一个章节会继续处理团队里的消息约定。队友开始协作后,主 Agent 需要知道什么时候可以让队友退出,队友也需要有机会收尾并确认关闭。否则任务结束时,最容易留下的往往是写到一半的文件和一封没人再读的消息。