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

echo-agent 前身为 2025 年 11 月启动的个人助理项目 fubot,最初面向长期陪伴型个人智能体,围绕认知记忆、上下文延续、用户偏好沉淀、任务闭环与持续自我优化展开。随着真实场景迭代,项目逐步形成多入口接入、统一事件模型、消息总线、Agent Loop、多模型抽象、工具调用、MCP 接入、任务调度、权限审批、运行轨迹、长期记忆和受控自演进等能力。目前已支持微信、QQ、CLI、Gateway、Webhook、Cron 等入口,服务用户超过 20 万、累计下载超过 50 万,是面向长期运行、记忆增强和可持续成长智能体的开源 Agent Runtime。

项目地址:github.com/fuyuxiang/e...

你让 Agent 检查一个复杂项目的测试失败。

它要读实现代码,要看测试用例,还要对照文档判断行为是否变更。单个 Agent 当然也能顺着做,但上下文会越来越重,探索路径也会互相污染:刚读完日志,又被长文档里的旧设计带偏;刚定位到实现,又忘了测试断言的细节。

这时很容易想到一个方案:多启动几个 Agent,让它们分头干活。

但工程上真正难的不是"多几个模型实例",而是:谁来拆任务,worker 能看到什么,能调用什么工具,失败后谁负责,以及整条链路如何审计。

多 Agent 的价值不是更多声音,而是受限并行、上下文隔离和可验证的责任分解。

问题入口

多 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 用于工具参数引用,namedescription 用于角色提示,instructions 补充执行规则,default_tools 给出默认工具集合,模型、最大迭代次数、token 和温度约束推理行为。

这里最容易误解的是 default_tools。它只是默认值,不是最终权限。最终可用工具必须同时满足几个条件:存在于当前工具注册表,处于 ready 状态,没有出现在 worker 禁用列表里,并且通过 DelegateTool 的过滤。

WorkerRegistry 则保持得很轻。它只把配置中的 profile 转成可按 ID 查询的模板,不执行 worker,不决定任务拆分,也不持久化状态。

这种简单性是有意的。多 Agent 系统里最危险的设计,是把模板、实例、任务、会话、权限揉成一个对象。echo-agent 把模板发现放在 WorkerRegistry,把执行循环放在 WorkerExecutor,把工具入口放在 DelegateTool,把权限收束放在工具上下文与 ApprovalGate

最小化理解可以写成这样:

python 复制代码
@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 禁用工具。

makefile 复制代码
WORKER_BLOCKED_TOOLS = frozenset({
    "delegate_task",
    "spawn_task",
    "clarify",
    "message",
    "notify",
    "cronjob",
})

这些禁用项都有明确理由。delegate_task 防止 worker 继续委派造成递归扩散;spawn_task 防止 worker 创建归属不清的后台任务;clarifymessagenotify 防止 worker 直接打扰用户或绕过主 Agent 汇总;cronjob 会制造未来自动执行,worker 不应拥有这种能力。

最终工具集合的计算逻辑可以概括为:

ini 复制代码
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 工具调用还要经过 ApprovalGateDelegateTool 会生成一个闭包 _execute():先检查工具名是否在 allowed tools 中;如果不在,直接返回错误;如果在,再调用审批检查。只有通过审批后,系统才构造 ToolExecutionContext 并执行工具。

这说明 worker 没有独立身份去绕过用户会话。它沿用父上下文的 session_keyuser_idtrace_id,同时标记自己的 agent_idparent_execution_id。权限归属仍在原会话,日志里也能区分 worker 调用。

受限 worker 不是缩小版主 Agent,而是带最小能力租约的子执行循环。

执行循环

WorkerExecutor 负责运行单个 worker。它没有复用完整主 Agent Loop,而是实现一个更窄的循环:

  1. 构建 worker system prompt。
  2. 将 goal 作为 user message。
  3. 调用模型。
  4. 如果没有工具调用,返回文本结果。
  5. 如果有工具调用,执行允许的工具并追加 tool message。
  6. 重复直到完成、出错、达到最大迭代次数或超时。

这个循环看起来和普通 Agent Loop 相似,但边界更窄。worker 的目标是完成分配任务,不负责理解用户全部意图,也不负责最终对话输出。

模型和参数也有层级。有效模型通常来自调用方传入值、profile 默认值和系统默认值。迭代次数则取请求值与 profile 限制的较小值,防止调用方突破模板边界。

ini 复制代码
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 就是为这个目的存在的:

ini 复制代码
@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_taskspawn_taskclarifymessagenotifycronjob

这些测试保护的不是实现细节,而是"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 之间交换任务》,敬请期待。

相关推荐
用户77833661321115 小时前
SERP API + Claude function calling:从 tool use 到 agent 的完整实现
agent
新知图书16 小时前
工作流编排
人工智能·agent·ai agent·智能体·扣子
不能只会打代码18 小时前
Day 006 — Multi-Agent + MCP/A2A + 安全 + 可观测性
agent·token·sse·multi-agent·mcp·a2a
新知图书19 小时前
测试与发布(新闻早报智能体开发)
人工智能·agent·ai agent·智能体·扣子
Flandern111120 小时前
从“能跑”到“可运营”:Agent Harness 工程化建设指南
学习·agent·claudecode·harness
一个处女座的程序猿1 天前
Agent之Skill:ui-ux-pro-max-skill的简介、安装和使用方法、案例应用之详细攻略
ui·agent·ux·ui-ux-skill
TrisighT1 天前
让 Claude 半夜自己审 PR:Headless 模式 + GitHub Actions 实测
aigc·agent·ai编程
leeyi1 天前
多 Agent 编排:Eino ADK 的三种协作模式(第57篇-E43)
aigc·agent·ai编程
kisbad1 天前
Day 012|Embedding 和向量数据库:知识库检索到底在检什么
数据库·python·embedding·agent