如果已经尝试过“单 Agent + 工具循环”编程模式,大概率会遇到一个尴尬场景:模型扫描代码仓库、读了一大段测试用例后,主对话直接被工具输出的信息塞满;想要一边规划,一边派人手查阅文档,却只能串行等待 subagent 完成;后台任务明明完成了,还得手动将结果粘贴回主对话——这种效率明显不够理想。
如果已经尝试过“单 Agent + 工具循环”编程模式,大概率会遇到一个尴尬场景:模型扫描代码仓库、读了一大段测试用例后,主对话直接被工具输出的信息塞满;想要一边规划,一边派人手查阅文档,却只能串行等待 subagent 完成;后台任务明明完成了,还得手动将结果粘贴回主对话——这种效率明显不够理想。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
Agent Teams 的设计思路很直接:一个 Lead 负责统筹调度,Teammate 在独立线程里运行自己的循环,彼此通过 MessageBus 互相传话;Lead 通过 inbox 注入,将队友的结果接入自己的主上下文。一句话总结:谁干活,谁负责沟通,各行其道。
先来看一个对比。
很多教学示例中,subagent 都是这样运行的:Lead 调用 task 工具 → 等待 subagent 完成(此处阻塞) → 整段历史作为 tool_result 塞回 Lead。处理简单任务尚可,任务一复杂就会明显感到“等得心焦”——Lead 无法一边思考下一步,一边让队友去查资料。
Agent Teams 换了一种思路:Lead 调用 spawn_teammate 时不会阻塞。后台线程直接启动 teammate_loop(不阻塞),Lead 可以继续对话或做其他事。Teammate 完成后通过 BUS.send 将结果发往 Lead 的 inbox,后台 queue_processor 检测到 inbox 有消息后,自动唤醒 Lead 的 agent_loop。Teammate 不再是工具返回的临时变量,而是真正在后台运行的工作者。
① 用户输入 → history → agent_loop
② Cron 触发 → _queue → inject → agent_loop
③ Teammate 回复 → inbox → inject → agent_loop
Cron 和 Teammate 都不会直接调用模型“乱入”,而是被打包成外部事件,由后台 queue_processor 抢锁后统一交给 Lead 的 agent_loop 处理。这种机制是成熟 Agent Runtime 的常见形态,对教学理解很有帮助。
Lead agent loop MessageBus Teammate loop
+----------------+ +-----------+ +-------------+
| prompt + tools | --spawn----->| .jsonl | <---send---- | own history |
| inject inbox | <---send------ | 邮箱 | ---inbox---> | own tools |
+----------------+ +-----------+ +-------------+
两个关键点值得留意:
换言之,队友完成工作后只向 Lead 提交一份最终“报告”,不会将中间的大量调试信息一并塞入。
生产环境中,MessageBus 通常对应 Redis、Kafka 或数据库消息队列。但教学代码为了清晰展示机制,直接为每个 Agent 使用一个 JSONL 文件模拟邮箱,效果毫不含糊。
目录结构如下:
.mailboxes/
lead.jsonl
reviewer.jsonl
send() 操作是向目标文件追加一行 JSON:
{"from": "reviewer","to": "lead","content": "Parser looks good","type": "result","ts": 1779780000.0}
read_inbox() 则直接读取并删除文件——消费式操作,确保同一行消息不会重复注入。简单直接,但足以说明核心逻辑。
| 工具 | 作用 |
|---|---|
| spawn_teammate | 按 name / role / prompt 启动后台队友 |
| send_message | 向指定 agent 发送消息(队友之间也可互通) |
| check_inbox | 主动读取 Lead 邮箱(自动注入时可不调用) |
spawn_teammate 底层是一个守护线程:
threading.Thread(
target=teammate_loop,
args=(client, model_name, name, role, prompt),
daemon=True,
).start()
Lead 立即获得“已启动”信息,不会被卡在队友的多轮工具循环中。
Teammate 拥有自己的 system prompt 和 messages,入口任务来自 Lead 的 prompt。教学版工具白名单如下:
轮数上限锁定为 10 轮工具循环,防止后台线程无限消耗 token。任务结束(无论成功或失败)都会通过 BUS.send 将结果发回 Lead,然后从 active_teammates 中注销。相当于一个人默默完成工作,提交报告后下线。
Lead 每轮调用模型前执行 inject_inbox_messages(messages),即读取 lead.jsonl → 删除文件 → 追加一条 user 消息。注入内容大致如下:
[reviewer:result] queue_processor 会监听外部事件并安全唤醒 Lead……
有人可能会问:为什么一定要使用 role: user?
原因很简单:对于 Agent Loop 而言,inbox、cron 触发、用户输入都是“外部世界发来的新事件”。模型下一轮统一按“用户侧输入”处理,无需为每种事件单独设计 role。Cron 触发同样适用,只是用 包裹。这种做法使系统统一、实现简单,且后续扩展事件类型时无需修改核心 loop。
上一篇介绍 cron 时,后台线程只监控定时队列。本版扩大了条件:判断 SCHEDULER 是否有待执行任务,或者 BUS 的 lead 邮箱是否有新消息。两者都没有则继续休眠;存在任一条件时,尝试非阻塞获取 agent_lock,获取成功则运行一轮 agent_loop(history)。
agent_lock 解决了一个现实问题:用户正在终端输入时,后台不能同时修改同一份 history。若拿不到锁则 0.2 秒后重试,避免数据冲突。
这里并未替换掉 cron,而是将两者合并到同一套“外部事件”模型中:
cron 触发 → tick 入队 → inject_cron_jobs →
队友完成 → send 到 lead → inject_inbox_messages →
↓
queue_processor → agent_loop → 模型 + 工具
持久化方面,cron 的 durable=True 将任务写入 .scheduled_tasks.json,重启后通过 load() 恢复;已到点但未消费的队列仍在内存中,进程终止后丢失——这是教学版本的边界。
环境变量只需一个 .env 文件:
OPENAI_BASE_URL
OPENAI_API_KEY
MODEL_NAME
在仓库根目录执行:
python3 harness_agent/09_agent_teams.py
推荐直接复制以下指令到终端:
启动一个 teammate,名字叫 reviewer,角色是 code reviewer。让它阅读 harness_agent/09_agent_teams.py,用几句话说明 MessageBus 怎么工作。
预期效果:
全程无需手动拷贝结果,只需编写任务 prompt,系统自动流转。
| 简化点 | 真实系统常见补充 |
|---|---|
| 文件 JSONL 邮箱 | MQ / DB / gRPC |
| 单进程锁 | 分布式锁、ack、重试 |
| Teammate 最多 10 轮 | 长期 worker、idle 唤醒 |
| 共享工作目录 | 沙箱、per-agent 权限 |
| 无取消/超时/成本上限 | lifecycle、budget、审计 |
了解这些边界,才能清晰规划下一版本的发展方向。
从单人工作到团队分工,是 Coding Agent 走向工程化协作的第一步。后续可以叠加权限管理、结构化 schema、队友生命周期管理——但邮箱 + 注入 + 后台唤醒这条骨架,已足以支撑第一版运行。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述