多 Agent 协作:从指派到受限执行

你让 Agent 检查一个复杂项目的测试失败。
它要读实现代码,要看测试用例,还要对照文档判断行为是否变更。单个 Agent 当然也能顺着做,但上下文会越来越重,探索路径也会互相污染:刚读完日志,又被长文档里的旧设计带偏;刚定位到实现,又忘了测试断言的细节。
这时很容易想到一个方案:多启动几个 Agent,让它们分头干活。
但工程上真正难的不是“多几个模型实例”,而是:谁来拆任务,worker 能看到什么,能调用什么工具,失败后谁负责,以及整条链路如何审计。
问题入口
多 Agent 协作经常被讲成“几个智能体互相讨论”。这个说法有演示效果,但离生产系统还很远。
如果每个 worker 都拿到完整上下文、完整工具集和继续委派的能力,系统不会更可靠,只会更难控制。一个 worker 可以再创建 worker,多个 worker 可以同时写同一个文件,某个 worker 可以直接通知用户,最后主 Agent 只收到几段看似完整的总结,却不知道中间发生了什么。
这类系统的失败方式很典型:递归扩散、权限扩散、上下文污染、结果冲突、成本失控,以及责任不清。
所以,多 Agent 的第一条工程判断不是“能不能拆”,而是“拆出去以后还能不能收回来”。
适合委派的任务通常有几个特征:子问题相对独立,输入输出边界清楚,执行可以并行或隔离,结果可以被验证,失败不会破坏主流程。
不适合委派的任务也很明确:简单问答、强依赖主上下文的连续判断、需要频繁向用户澄清的任务,以及高风险副作用操作。除非工具集合和审批边界非常清楚,否则高风险动作不应该轻易交给 worker。
概念边界
为了不停留在抽象层面,下面以 echo-agent 的实现为例。
echo-agent 当前采用的是 orchestrator-worker 模型。主 Agent 是 orchestrator:它理解用户目标,决定是否委派,限定 worker 权限,汇总结果,并最终对用户负责。
worker 不是主 Agent 的同级自治体。它更像一个受控的子执行循环:接收一个明确 goal,使用一组被允许的工具,在有限迭代次数内完成子任务,最后返回结构化结果。
这个边界很重要。worker 没有长期会话地位,不直接与用户沟通,也不应继续把任务转给其他 worker。委派不是责任转移,而是主 Agent 在自己控制范围内创建临时执行单元。
可以把几类机制放在一起比较:
| 机制 | 解决的问题 | 是否等待结果 | 是否是多 worker 主机制 |
|---|---|---|---|
delegate_task | 当前推理中的并行委派 | 等待 worker 返回后汇总 | 是 |
spawn_task | 后台轻量异步文本任务 | 立即返回 background task id | 否 |
workflow | 持久化步骤依赖与状态推进 | 由任务状态驱动 | 否 |
delegate_task 是多 worker 协作的入口。spawn_task 容易混淆,但它没有 worker 工具循环,只是在后台用简短 system prompt 完成文本任务,并通过消息总线通知原会话。它适合轻量异步工作,不适合需要受限工具执行、并行研究和结构化结果汇总的场景。
workflow 也不是 worker。工作流系统管状态、依赖和恢复;多 Agent 系统管执行拆分、上下文隔离和结果汇总。一个 workflow 步骤可以调用 delegate_task 做并行研究,但 worker 本身不是长期任务实体。
Worker 模板
echo-agent 用 WorkerProfile 描述 worker 模板。
它不是完整 Agent 实例,而是运行 worker 时套用的配置:id 用于工具参数引用,name 和 description 用于角色提示,instructions 补充执行规则,default_tools 给出默认工具集合,模型、最大迭代次数、token 和温度约束推理行为。
这里最容易误解的是 default_tools。它只是默认值,不是最终权限。最终可用工具必须同时满足几个条件:存在于当前工具注册表,处于 ready 状态,没有出现在 worker 禁用列表里,并且通过 DelegateTool 的过滤。
WorkerRegistry 则保持得很轻。它只把配置中的 profile 转成可按 ID 查询的模板,不执行 worker,不决定任务拆分,也不持久化状态。
这种简单性是有意的。多 Agent 系统里最危险的设计,是把模板、实例、任务、会话、权限揉成一个对象。echo-agent 把模板发现放在 WorkerRegistry,把执行循环放在 WorkerExecutor,把工具入口放在 DelegateTool,把权限收束放在工具上下文与 ApprovalGate。
最小化理解可以写成这样:
复制代码@dataclass(frozen=True)
class WorkerProfile:
id: str
name: str
instructions: str = ""
default_tools: tuple[str, ...] = ()
model: str = ""
max_iterations: int = 12
max_tokens: int = 4096
temperature: float = 0.4
class WorkerRegistry:
def __init__(self, profiles):
self._profiles = {p.id: p for p in profiles if p.id}
def get(self, profile_id):
return self._profiles.get(profile_id)
模板层只负责“有哪些 worker 类型”。真正的安全边界不在模板名里,而在运行时的工具过滤、审批和审计里。
工具收束
多 Agent 系统最容易放大的风险,就是权限。
如果主 Agent 能访问十个工具,不能默认让每个 worker 也访问这十个工具。worker 越多,越容易出现副作用扩散:重复创建任务、绕过主 Agent 通知用户、设置未来定时任务,甚至继续委派形成递归树。
echo-agent 的做法是先从主 Agent 当前 ready tools 中取集合,再排除 worker 禁用工具。
复制代码WORKER_BLOCKED_TOOLS = frozenset({
"delegate_task",
"spawn_task",
"clarify",
"message",
"notify",
"cronjob",
})
这些禁用项都有明确理由。delegate_task 防止 worker 继续委派造成递归扩散;spawn_task 防止 worker 创建归属不清的后台任务;clarify、message、notify 防止 worker 直接打扰用户或绕过主 Agent 汇总;cronjob 会制造未来自动执行,worker 不应拥有这种能力。
最终工具集合的计算逻辑可以概括为:
复制代码available = ready_tools - WORKER_BLOCKED_TOOLS
if requested_tools:
allowed = requested_tools & available
elif profile.default_tools:
allowed = set(profile.default_tools) & available
else:
allowed = available
工具执行时,ToolExecutionContext.allowed_tools 会记录 worker 实际允许的工具集合。工具注册表在执行前再次检查这个字段,防止 worker 调用未授权工具。
worker 工具调用还要经过 ApprovalGate。DelegateTool 会生成一个闭包 _execute():先检查工具名是否在 allowed tools 中;如果不在,直接返回错误;如果在,再调用审批检查。只有通过审批后,系统才构造 ToolExecutionContext 并执行工具。
这说明 worker 没有独立身份去绕过用户会话。它沿用父上下文的 session_key、user_id 和 trace_id,同时标记自己的 agent_id 与 parent_execution_id。权限归属仍在原会话,日志里也能区分 worker 调用。
执行循环
WorkerExecutor 负责运行单个 worker。它没有复用完整主 Agent Loop,而是实现一个更窄的循环:
- 构建 worker system prompt。
- 将 goal 作为 user message。
- 调用模型。
- 如果没有工具调用,返回文本结果。
- 如果有工具调用,执行允许的工具并追加 tool message。
- 重复直到完成、出错、达到最大迭代次数或超时。
这个循环看起来和普通 Agent Loop 相似,但边界更窄。worker 的目标是完成分配任务,不负责理解用户全部意图,也不负责最终对话输出。
模型和参数也有层级。有效模型通常来自调用方传入值、profile 默认值和系统默认值。迭代次数则取请求值与 profile 限制的较小值,防止调用方突破模板边界。
复制代码effective_model = model or profile.model or default_model
effective_temp = profile.temperature if profile else temperature
effective_max_tokens = profile.max_tokens if profile else max_tokens
effective_max_iter = min(requested_max_iterations, profile.max_iterations)
此外,多 Agent 还必须限制深度与并行数。max_depth 控制委派深度,达到限制后 delegate_task 直接失败并提示主 Agent 自行处理。max_parallel_workers 控制一次最多并行多少 worker,请求任务数超过上限时会截断并记录警告。
这些限制不是性能优化,而是安全机制。没有深度限制,worker 会形成树状爆炸;没有并行限制,一次模型调用可能制造大量外部工具调用;没有迭代限制,worker 可能长期卡在工具循环里。
结果合并
多 Agent 协作不能以“拼接几个 worker 的回答”为终点。
worker 结果可能重复、冲突、粒度不一致,也可能只有部分成功。主 Agent 需要知道每个 worker 的状态、输出、错误、迭代次数、工具调用次数、耗时和模型信息,才能决定是继续、重试、缩小范围,还是向用户说明局部失败。
echo-agent 的 WorkerResult 就是为这个目的存在的:
复制代码@dataclass
class WorkerResult:
task_index: int
status: str = "completed"
output: str = ""
error: str = ""
iterations: int = 0
tool_calls: int = 0
duration_seconds: float = 0.0
model: str = ""
DelegateTool 使用 asyncio.gather(..., return_exceptions=True) 并行执行 workers。这意味着单个 worker 抛异常,不会让整个委派工具直接崩溃。异常会被转换成对应的 WorkerResult,状态为 failed。
最终输出按 worker index 排序。工具结果的 success 取决于所有 worker 是否都 completed;如果有 worker failed 或 timeout,主 Agent 会看到工具调用失败,但仍能读取成功 worker 的部分结果。
这种“部分失败可见”非常关键。某个 worker 失败,不等于所有信息都无用。比如一个 worker 成功读完实现,另一个 worker 因权限不足没能运行测试,主 Agent 仍可以用已获得的实现结论继续分析,并明确告诉用户测试验证没有完成。
结果契约还应尽量结构化。至少包含状态、摘要、关键证据、使用过的工具、产生的文件或修改、失败原因和后续建议。代码任务应包含测试结果或未测试原因;检索任务应包含来源;审查任务应包含问题位置和严重程度。
没有结果契约,主 Agent 只能“相信”worker。有结果契约,主 Agent 才能复核、合并、追问或拒绝结果。
审计与黑板
多 Agent 让执行链路变长,审计日志就不是可选项。
echo-agent 提供 DispatchAuditLog,用 JSONL 记录委派行为。JSONL 的好处是追加简单、易于 grep,也方便后续导入分析系统。审计记录不必保存全部上下文,但要能回答几个基本问题:什么时候委派,委派了什么,启动了几个 worker,状态如何,迭代和工具调用规模如何,当前深度是多少。
生产系统还要警惕共享状态。
多个 worker 同时读写同一文件、同一任务、同一记忆或同一外部系统,会带来覆盖、重复执行和责任不清。更稳妥的方式是共享黑板,而不是共享全部上下文。
共享黑板可以记录结论、证据、产物引用、阻塞问题和验证状态。每条记录标明作者、时间、来源、适用范围和状态;被采纳的结果与未验证假设分开;敏感资料只对有权限 worker 可见。
这样,研究 worker 可以写入证据,代码 worker 根据证据修改,审查 worker 验证产物,orchestrator 维护整体目标。协作有共同外部状态,但不让所有 worker 读取全部会话和内部推理。
生产可用性
一个多 Agent 系统是否生产可用,不能看它能启动几个 worker,而要看这些 worker 是否受控、可查、可停、可复盘。
| 检查项 | 合格标准 |
|---|---|
| 委派入口 | delegate_task 参数能区分单任务和多任务,并标准化为任务列表 |
| 工具边界 | worker 工具集合是 ready tools 的子集,并排除 blocked tools |
| 风险审批 | worker 工具调用继续经过 ApprovalGate,高风险动作不能绕过审批 |
| 上下文边界 | worker 只拿到 goal、必要背景、允许工具、输出格式和验收标准 |
| 深度限制 | max_depth 生效,worker 不能无限递归委派 |
| 并行限制 | max_parallel_workers 生效,超出任务被截断并记录 |
| 结果契约 | 返回 status、output、error、iterations、tool_calls、duration |
| 部分失败 | 单个 worker 异常不压垮整体,成功结果仍可见 |
| 审计日志 | 记录任务、worker 数量、状态、耗时、深度和工具调用规模 |
| 回归测试 | 覆盖 registry、executor、delegate 参数、blocked tools、allowed tools、深度限制 |
这里可以把判断说得更直接:会委派不等于会协作;能受限委派、能局部失败、能审计复盘,才是工程化多 Agent。
测试也应围绕这个性质设计。WorkerRegistry 要测空 ID 不注册、按 ID 查询稳定;WorkerExecutor 要测无工具调用、有工具调用、达到最大迭代次数、模型错误和超时;DelegateTool 要测单任务和多任务参数、并行数截断、请求工具与可用工具取交集、profile default tools 生效;安全边界要测 worker 不能调用 delegate_task、spawn_task、clarify、message、notify、cronjob。
这些测试保护的不是实现细节,而是“worker 受控”这一根本性质。
小结
多 Agent 协作不是让几个模型一起聊天,而是让主 Agent 在可控边界内分解责任。orchestrator 保留全局目标和最终责任,worker 承担明确子任务,并在受限工具、受限上下文、受限迭代、受限深度中执行。
echo-agent 当前的设计把这条边界拆成了几个清楚的工程对象:WorkerProfile 定义模板,WorkerRegistry 管理模板,DelegateTool 规范化任务并收束工具,WorkerExecutor 运行受限循环,ApprovalGate 控制副作用,DispatchAuditLog 记录委派链路。
这套设计的核心不是“人数”,而是责任分解。好的主 Agent 不会把复杂性甩给 worker,而是把适合并行和隔离的部分交出去,自己保留判断、合并、验证和最终解释。
下一篇要进入另一个边界:当对方不是同一运行时里的 worker,而是另一个独立 Agent 系统时,任务、消息和状态应该如何交换。这就是 A2A 要解决的问题。
(全篇完)
本文为 echo-agent 设计笔记系列第 21 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣,欢迎加入技术交流群(QQ群号:47572014)参与日常讨论。下一篇我们将探讨 《A2A 设计笔记:让 Agent 之间交换任务》,敬请期待。
-
07.20
王者荣耀雅典娜执弈定局什么时候上线 雅典娜执弈定局上线时间介绍
-
07.20
红色沙漠发售前夕引爆Steam 预估销量已达40万
-
07.20
漫画网址资源下载入口在哪?-漫画网址下载热门站点大全
-
07.20
王者荣耀雅典娜执弈定局多少钱 雅典娜执弈定局价格介绍
-
07.20
三国志异闻录平民前期阵容搭配推荐
-
07.20
闪耀暖暖x哪吒传奇联动「风起朝歌」版本时装汇总
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏