AI in Harness(三)

33 阅读5分钟

多Agent 协同 - 需要一个团队

前面我们实现了 Subagent、Background Task,为什么还需要多 Agent 协同呢?

一句话区分

  • Background Task = "把这个工具调用派到后台跑,我等结果通知"
  • Subagent = "派一个新 agent 干一件具体事,跑完销毁,我等摘要"
  • Teammate = "派一个长期协作的队友,我们持续异步通信"

对比表

维度Background TaskSubagentTeammate
被派的是什么一个工具调用(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 那一层

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 双循环

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 协同的四个维度全部展开:

通信       → MessageBus       (能聊)
可靠协商   → Protocols        (聊得清)
自组织调度 → Autonomous Agents (自己领活)
工作目录隔离 → Worktree Isolation (各干各的)

未完待续。