多Agent 协同 - 需要一个团队
前面我们实现了 Subagent、Background Task,为什么还需要多 Agent 协同呢?
一句话区分
- Background Task = "把这个工具调用派到后台跑,我等结果通知"
- Subagent = "派一个新 agent 干一件具体事,跑完销毁,我等摘要"
- Teammate = "派一个长期协作的队友,我们持续异步通信"
对比表
| 维度 | Background Task | Subagent | Teammate |
|---|---|---|---|
| 被派的是什么 | 一个工具调用(bash) | 一个全新 agent(独立 LLM + messages) | 一个全新 agent |
| 谁在执行 | 工具 executor | 新一轮 LLM 调用 | 新一轮 LLM 调用 |
| 生命周期 | 短(工具一次调用结束) | 短(派一次跑完销毁) | 长(教学版限 10 轮) |
| 同步 / 异步 | 异步(daemon thread) | 同步(父等子返回) | 异步(daemon thread) |
| 父是否阻塞 | 不阻塞,立即拿 placeholder | 阻塞,等子 agent 结果 | 不阻塞,立即拿"已派出" |
| 通信方式 | 单向 --- 后台→父 (<task_notification>) |
单向 --- 子→父(只回 last text) | 双向 --- MessageBus 文件邮箱 |
| 能否多个并行 | 是 | 否(父 spawn 时阻塞) | 是 |
| 能否互相通信 | 不能 | 不能 | 能(teammate 之间能 send) |
| 典型用途 | 慢命令(./mvnw test) |
"分析 X 模块,做完告诉我" | "重构后端 --- 多 agent 长期协作" |
| 能调用工具 | 不能(它就是工具) | 能(白名单子集) | 能(白名单 + send_message) |
人多力量大。遇到一个很大的任务时,从 Java 程序员的思路来看,可以多来几个分布式 Agent 来协同处理问题。
还有一个核心问题,多个Agent 之间怎么通信呢?
Background task 和 Subagent 都是单向通信:父向子安排工作后,子完成后单向地向父汇报结果,中间没有交互。
Teammate 之间是同事关系。双方之间通过 MessageBus 进行双向通信。
MessageBus 基于文件形式,Agent A 告诉 Agent B 应该干什么,Agent B 告诉 Agent A 我完成了。
Protocols - 从"能聊"到"聊得清"
MessageBus 让 Agent "能聊",但只有字节流。Protocols 在 MessageBus 之上加一层结构化握手 + 状态机 + 类型校验,把"扔字符串靠 LLM 意会"升级为"业务级应答"。
Protocols 强制的三件套
| 给了什么 | 没它会怎样 |
|---|---|
| 关联键(request_id) | 多个并行请求的响应分不清谁回谁 |
| 状态机(pending → approved/rejected) | 不知道某请求"现在到哪一步" |
| 类型校验(shutdown 只能被 shutdown_response 应) | 响应误处理别的请求,状态错乱 |
这三个是任何可靠请求-响应通信的最小公因子(HTTP、RPC、分布式系统都有),Protocols 只是把它搬到 LLM Agent 之间。
相关 API
| API | 作用 | 关键点 |
|---|---|---|
request_shutdown(teammate) |
请队友体面退出 | 自动分配 req_id,注册 pending 状态 |
submit_plan(plan) |
队友提交计划待审批 | 拿到 req_id 后进入等待 |
review_plan(req_id, approve, feedback) |
Lead 审批 / 拒绝并附反馈 | 只改自己那一条 state |
protocols.list() |
查看所有请求当前状态 | pending / approved / rejected |
check_inbox() |
LLM 主动收信;自动路由 *_response |
协议消息不给 LLM 看,普通消息才展示 |
设计要点:协议消息自动路由 ,LLM 只看到"这个请求现在是什么状态",不用自己去 parse _response 消息。
定位:Protocols 是 RPC 那一层,不是 TCP 那一层
makefile
MessageBus: 带外、双向、多次的通信通道 --- 能聊(可靠传输,文件落盘)
Protocols: 通道上的握手 / 关联 / 状态机 --- 聊得清(业务级同意/拒绝)
Protocols 的 ack 不是 TCP 那种"字节收到了",而是 LLM 主动决定的"我同意/拒绝"------语义级 ack,内容是 request_id + approve + feedback。
Protocols 不解决的问题
教学版严格对齐 happy path,以下都留给生产化再补:
| 问题 | 当前表现 | 后续补什么 |
|---|---|---|
| 响应丢了 / 对方挂了 | pending 状态永远卡着 | timeout + 由 LLM 自己决定放弃 |
| 消息因故未送达 | Lead 不知道也不会重发 | 重试 + 幂等 |
| 对方是否还活着 | 只能等下次 LLM 主动 check | 心跳 / 探活 |
一句话总结
MessageBus 让 Agent "能聊",Protocols 让 Agent "聊得清"------从 chat 升级到 RPC。
Autonomous Agents - 从"被派活"到"自己领活"
Teammate 已经能通信、能协商,但任务分配还是靠 Lead 手动 spawn 时指定。Autonomous Agents 让 teammate 从 TaskBoard 自己扫未认领任务、自动 claim、做完再找下一个 ------ 从"被动接令"升级到"自组织 worker"。
核心机制:WORK ↔ IDLE 双循环
sql
outer loop (总 turn 上限 30):
├── WORK 阶段 (inner 最多 10 LLM call)
│ 消费 inbox → LLM 调用 → 执行工具 → 无 tool_use 就进 IDLE
└── IDLE 阶段 (idlePoll, 默认 60s 超时)
每 5s 轮询:
1. 优先看 inbox(可能有 shutdown_request)
2. inbox 空 → 扫看板 tryClaim
任一命中 → 回 WORK
超时 → shutdown
双层 turn 上限的意义:
- inner 10:一次 WORK 最多 10 LLM call,防止闷头干太久,定期"喘气"处理 inbox
- outer 30:daemon thread 总 LLM 预算 ceiling,防永生
关键实现点
| 点 | 做法 |
|---|---|
| 不造新轮子 | scan/claim 复用 TaskService 已有的 list / canStart / claim |
| owner 强制注入 | claim_task 调用时代码层自动填 owner=teammateName,不给 LLM 传错的机会 |
| 工具白名单 | 只开 list_tasks / claim_task / complete_task;不给 create_task(task 创建是 Lead 职责) |
| 身份重注入 | messages.size() <= 3 时补 <identity>,防 compact 后失忆 |
| 抢任务竞态 | tryClaim 遇到 claim 失败自动跳下一个 task;同进程内 ConcurrentHashMap 语义安全,跨进程需要文件锁(未做) |
Worktree Isolation - 从"共享目录"到"各干各的"
多个 worker 自助领任务后,都在同一个 user.dir 干活 ------ 两人都 write_file("config.py") 就互相覆盖。Worktree Isolation 让每个 task 绑定独立 git worktree + 独立分支,改同名文件互不冲突。
一句话总结
Worktree Isolation 改的是「在哪执行」这一格 ------ 把工作目录变成显式的调用参数,让 teammate 在各自 git worktree 里独立干活,改同名文件互不冲突。
至此,多 Agent 协同的四个维度全部展开:
scss
通信 → MessageBus (能聊)
可靠协商 → Protocols (聊得清)
自组织调度 → Autonomous Agents (自己领活)
工作目录隔离 → Worktree Isolation (各干各的)
未完待续。