# 多Agent上下文、消息和Token怎么治理
上一篇《Planner、Worker、Reviewer怎么分工》讲了角色边界:Planner定义任务,Worker交付Artifact和证据,Reviewer负责验收,编排器执行硬约束。
但角色拆开以后,系统才刚开始变复杂。
三个Agent如果复制同一份聊天记录、把并行结果按返回时间拼接、允许子Agent继续无限派生,角色虽然分开了,上下文、状态和预算却还是一锅粥。
这篇就解决一个问题:多Agent跑起来以后,怎么让每个Agent只看到该看的信息,让消息可追踪、可去重,让Token在失控前被系统截住。
# 面试官会怎么问
“你们多个Agent之间怎么传上下文?并行消息怎么保证不乱序?子Agent还能创建子Agent时,怎么防止Token爆掉?”
很多录友会回答:
“主Agent把历史发给子Agent,子Agent完成后把结果传回来;上下文太长就做摘要,Token超了就停止。”
这个回答的问题很具体。
第一,完整复制历史会把角色污染掉。 Worker会看到与当前任务无关的讨论、其他Worker的猜测和过期工具结果,不仅浪费Token,还可能把未经验证的内容当成事实。
第二,按返回时间拼消息会破坏因果。 并行任务谁先完成只代表谁更快,不代表它应该先被消费。重试还可能把同一结果送两遍。
第三,超限后才停止已经太晚。 多个子Agent并发执行时,如果没有预留和原子扣减,每个Agent都以为预算还有剩余,总消耗很容易一起穿透上限。
面试官真正想听的是:上下文怎样隔离和按需组装,事件怎样标识因果与幂等,预算怎样分配、回收、降级和熔断。
# 简要回答
- 每个Agent使用独立Thread。 子Agent不继承完整聊天,只接收结构化任务包、必要约束、依赖Artifact引用和本任务工具。
- 大结果放Artifact Store。 消息只传状态、摘要、证据引用和版本,不在线程之间反复复制日志、网页和代码全文。
- 消息顺序按因果关系处理。 单个Agent用
seq_no保序,跨Agent用task_id、DAG依赖和parent_event_id判断谁依赖谁,不按墙上时钟强行排成一条总序列。 - 预算由编排器统一记账。 派发前预留子任务额度,运行中同时限制输入、输出、工具结果、重试、步数和时间,不能让Agent靠Prompt自觉省Token。
- 递归派生默认关闭。 必须开启时,限制最大深度、子节点数量和祖先环,并让子Agent的额度从父Agent剩余预算中分配。
- 不同任务走不同模型。 摘要、分类、格式转换可以路由到小模型,复杂规划、最终合成和高风险审查再使用强模型,但路由必须经过评测。
多Agent治理不是把一个大上下文切成几份,而是把对话变成独立工作区,把协作变成带因果的事件,把Token变成可预留、可回收的资源。

这张图回答的是:一个子任务从编排器派发到最终汇总,如何通过独立Thread隔离过程、通过Artifact与事件引用交接结果,并让所有并行分支共同受全局预算和递归边界约束。
# 为什么多个Agent不能共享一条消息历史
假设主Agent要完成一份线上故障报告,同时派出三个Worker:
- Worker A查监控;
- Worker B查日志;
- Worker C查发布记录。
如果三个Worker共享一个messages数组,A返回的几千行指标、B返回的错误堆栈、C看到的版本信息会不断混在一起。
问题不只是“窗口容易满”。
A的一句“可能是连接池问题”会进入B的上下文,B后面更容易围绕这个假设找证据;C的一次工具超时也可能被其他角色误解为“没有发布记录”。
共享历史会让并行探索变成相互暗示,多个Agent看似独立,错误却高度相关。

更稳的做法是每个Agent维护独立Thread:
root thread
├── worker-a thread:任务包A + A的工具调用 + A的局部状态
├── worker-b thread:任务包B + B的工具调用 + B的局部状态
└── worker-c thread:任务包C + C的工具调用 + C的局部状态
2
3
4
独立Thread不是完全断开联系。
编排器知道它们属于同一个run_id,也知道每个子任务的父节点和依赖;只是它不会把一个Agent的完整过程自动广播给所有人。
如果B确实需要A的监控结论,编排器传的是经过验收的Artifact引用,而不是A从第一轮到最后一轮的全部对话。
这和《Context Engineering入门》里的原则一致:给当前推理最少但足够的高信号信息。 多Agent只是把这个原则从“一个窗口怎么编排”扩展到了“多个窗口怎么隔离”。
# 子Agent到底应该收到什么
子Agent收到的不是父Agent聊天记录,而是一份可校验的任务包。
{
"run_id": "incident-20260908",
"task_id": "inspect-payment-logs",
"parent_task_id": "find-root-cause",
"goal": "检查22:00至23:00支付服务错误日志",
"acceptance_criteria": ["给出错误峰值时间", "结论附日志证据引用"],
"context_refs": ["artifact://release-record/v2"],
"allowed_tools": ["search_logs", "read_trace"],
"output_schema": "worker_result_v1",
"budget": {"max_input_tokens": 12000, "max_output_tokens": 2000, "max_steps": 8},
"deadline_ms": 45000
}
2
3
4
5
6
7
8
9
10
11
12
组装子Agent上下文时,可以固定成六层:
- 不可覆盖的全局安全规则;
- 当前角色的职责与禁止项;
- 当前任务包;
- 已通过依赖的Artifact摘要与引用;
- 当前任务允许使用的工具Schema;
- 剩余步数、时间和Token预算。
父Agent的语气、闲聊、完整思考过程,以及其他分支尚未验收的猜测,都不应该默认继承。
如果任务缺信息,子Agent要返回BLOCKED和缺失字段,由编排器补充或退回Planner。不要为了让子Agent“更懂背景”,先把所有历史都塞过去。
# 为什么消息里只传引用,不传完整产物
一次日志查询可能返回5万行,一次代码检查可能产生上百KB的Diff。
这些内容如果从Worker复制给Reviewer,再从Reviewer复制给主Agent,同一份材料会重复进入多个上下文。Agent越多,Token复制越严重。
所以要把“消息”和“产物”拆开:
- 消息负责协调:任务状态、简短摘要、失败原因、证据引用;
- Artifact负责承载内容:报告、补丁、日志快照、测试结果、结构化数据;
- 事件日志负责审计:谁在什么时候基于哪个版本做了什么判断。
Worker的完成消息可以很小:
{
"event_type": "TASK_COMPLETED",
"task_id": "inspect-payment-logs",
"status": "completed",
"summary": "22:14起第三方回调超时集中升高",
"artifact_refs": ["artifact://payment-log-analysis/v3"],
"evidence_refs": ["trace://log-query/8841"],
"unresolved": ["22:14至22:16网关日志缺失"]
}
2
3
4
5
6
7
8
9
Reviewer需要细节时,再按引用局部读取对应段落或证据。
这里有一条底线:摘要可以帮助定位,不能替代原始证据。 Artifact必须带版本、内容哈希或不可变快照,不能让Reviewer验收v3时,底层内容已经悄悄变成v4。
# 并行消息怎么保证不乱序
多Agent系统通常不需要一个“全局绝对顺序”。
Worker A在10:00:03完成,Worker B在10:00:02完成,并不能说明B的结果应该先进入汇总。真正重要的是:B是否依赖A,某条消息是否是另一条消息触发的,以及同一任务内部事件的先后关系。
一个最小事件信封可以包含:
{
"event_id": "evt-9382",
"run_id": "incident-20260908",
"task_id": "inspect-payment-logs",
"agent_id": "worker-log-02",
"seq_no": 7,
"parent_event_id": "evt-9310",
"idempotency_key": "inspect-payment-logs:completed:v3",
"event_type": "TASK_COMPLETED",
"artifact_refs": ["artifact://payment-log-analysis/v3"]
}
2
3
4
5
6
7
8
9
10
11
这几个字段各管一件事:
| 字段 | 解决的问题 |
|---|---|
run_id | 把同一次用户任务的事件串起来 |
task_id、agent_id | 定位事件属于哪个子任务和执行者 |
seq_no | 保证单个Agent线程内部顺序 |
parent_event_id | 记录“谁触发了谁”的因果关系 |
idempotency_key | 重试或重复投递时避免重复写入和重复汇总 |
artifact_refs | 指向确定版本的产物,不复制正文 |
编排器收到事件后,不是立刻把它拼进主Prompt,而是先做三步:
- 用
event_id或idempotency_key去重; - 检查
seq_no是否连续,缺号就暂存并等待或补拉; - 检查DAG依赖与
parent_event_id,依赖满足后才推进状态。
同一个Agent的seq_no=8先于seq_no=7到达,可以暂存在Inbox里;两个互不依赖的Worker同时完成,则都标记成功,等汇总节点的前置条件全部满足后再触发。
不要用时间戳代替因果关系。 分布式环境会有时钟偏差、网络延迟和重试,谁先到不等于谁先发生,更不等于谁先被业务消费。
# Token预算为什么要在派发前预留
很多系统有max_tokens,却依然会超预算,因为它只限制了一次模型输出。
多Agent真正消耗的是一整棵任务树:

总消耗 = 根Agent输入输出
+ 所有子Agent输入输出
+ 工具结果进入上下文的Token
+ 重试与返工
+ 最终汇总和验收
2
3
4
5
如果总预算是100K,根Agent同时派出4个“最多30K”的Worker,理论上限已经到120K,还没有给Reviewer和最终回答留额度。
因此,预算要像并发系统里的资源配额一样管理。
派发子任务时,编排器先从父任务的可用额度里原子预留一块预算。预留成功才启动,预留失败就缩小任务、排队、换模型或拒绝派生。任务结束后,未使用的额度再回收到父任务。
可派发预算 = 全局上限 - 已消耗 - 已预留 - 根流程保底 - 验收保底
这不是精确预测模型一定花多少Token,而是防止多个并行分支同时把同一份余额算给自己。
预算也不能只管输出Token。至少要同时限制:
- 单次和累计输入Token;
- 单次和累计输出Token;
- 工具结果返回大小与分页次数;
- 模型调用次数、工具调用次数和重试次数;
- 子Agent数量、递归深度和任务总时长。
关于输入、输出、成本和延迟之间的基础关系,可以先看《Token、成本与延迟》。
# 预算快用完时怎么降级
预算治理不是“用完就报错”,而是提前分级收束。
下面是一组示例阈值,不是行业标准,实际值要根据任务风险和评测结果调整:
| 预算水位 | 系统动作 |
|---|---|
| 0%—60% | 正常执行,仍受单节点上限约束 |
| 60%—80% | 禁止低价值扩展,压缩旧工具结果,优先复用Artifact |
| 80%—95% | 停止新建非关键子Agent,小任务切换到低成本模型 |
| 95%以上 | 强制收束,保留验收与最终回答额度,返回部分结果或升级人工 |
压缩也有边界。
聊天过程和重复日志可以压缩,订单号、文件路径、错误码、验收标准与原始证据引用不能被“概括掉”。高风险任务宁愿少做一个分支,也不要把验证预算拿去继续探索。
预算应该优先保护最终合成和验收。 Worker全都跑完,Reviewer却没有Token可用,这个任务仍然不能算完成。
# 子Agent还能创建子Agent怎么办
默认答案是:不允许。
Worker发现任务需要继续拆分,可以返回Planner重构DAG。只有层级任务确实需要局部自主拆分时,才开放受限派生能力。
一旦开放,至少加四道硬限制:
max_depth:最大递归深度;max_children:每个节点最多创建几个子节点;ancestor_task_ids:记录祖先链,禁止A派生B、B又派生A;child_budget <= parent_available_budget:子节点只能花父节点分出的额度。
还要限制“同义派生”。
有些Agent不会创建完全相同的任务ID,却会连续创建“继续搜索”“补充搜索”“深入搜索”。编排器可以用规范化目标、输入范围和工具集合生成任务指纹,相似任务超过阈值就拒绝,避免换个名字继续烧钱。
这部分和《Agent为什么容易翻车》里的循环控制是一回事:自由派生必须有深度、宽度、相似任务和预算四重停止条件。
# 模型路由怎么和预算配合
不是每个角色都必须使用最强模型,也不能简单规定“Planner用大模型,Worker用小模型”。
路由至少看三个维度:
- 任务复杂度:抽取、分类、格式转换通常更适合小模型;开放规划和冲突合成需要更强推理;
- 风险等级:涉及生产写操作、权限和合规时,模型能力之外还要增加规则校验或人工门禁;
- 可验证性:输出能被Schema、测试或数据库规则直接验证时,可以更积极地使用低成本模型。
一个实用做法是先让离线评测回答:“这个任务类型由小模型执行,质量下降多少,节省多少Token和延迟?”
如果小模型输出变短了,却导致返工次数翻倍,总成本可能更高。模型路由看的是单个合格任务成本,不是单次调用价格。
# 出错以后怎么恢复,而不是整棵树重跑
上下文、消息和预算治理最终都要落到恢复能力上。
如果Worker超时,编排器应该保留它已写入的Artifact和最后确认的事件序号,再决定从Checkpoint重试、换模型、缩小任务,还是降级交付。
如果消息重复,只做幂等确认,不重复触发Reviewer。
如果Artifact版本不一致,只让受影响的汇总节点重新执行,不推翻已经通过的无关分支。
如果全局预算触发熔断,则冻结新派生,取消非关键运行节点,把剩余额度留给状态总结和部分结果交付。
这依赖《Plan-and-Execute怎么落地成DAG执行器》里的状态机与Checkpoint。没有持久化状态,所谓“重试”往往只是把整段昂贵流程再跑一遍。
# 怎么验证治理真的有效
只看最终回答对不对不够。至少监控六组指标:
- 上下文:各角色输入Token、重复Token比例、Artifact局部读取命中率;
- 消息:乱序暂存数、重复事件率、幂等冲突率、依赖等待时间;
- 预算:预留量、实际消耗、回收量、各角色占比、熔断次数;
- 递归:任务树最大深度、分支数、相似任务拒绝次数;
- 质量:任务完成率、首次验收通过率、关键约束保留率;
- 业务效率:P95延迟、单个合格任务成本、人工接管率。
还要拿单Agent做基线。
如果多Agent让总Token翻了三倍,任务完成率只提高一点,说明拆分粒度或上下文交接有问题;如果Token下降但关键约束丢失,说明摘要压缩过度;如果成本可控但乱序等待很高,可能是DAG依赖设计得太重。
治理不是让指标都越低越好,而是让质量、成本和延迟的交换关系可解释、可预测。
# 面试时怎么讲
可以这样回答:
我们不会让多个Agent共享同一条消息历史,而是给每个Agent独立Thread。编排器只下发当前任务包、已通过依赖的Artifact引用、允许工具和预算,不复制父Agent的完整聊天。大结果写入带版本的Artifact Store,消息只回传状态、摘要和证据引用。
每条事件带run_id、task_id、agent_id、seq_no、parent_event_id和幂等键。单线程按seq_no保序,跨Agent按DAG依赖和父事件判断因果,重复投递不会重复推进状态。
Token由编排器统一记账。派发前从父任务余额中原子预留,未使用额度可以回收;预算接近上限时先停止非关键派生、压缩旧结果、切换低成本模型,最后保留验收和收束额度。递归默认关闭,开放时限制深度、分支、祖先环和子任务预算。
这套回答比“把上下文传过去,超了就截断”多出的,正是生产系统需要的隔离、因果、幂等和资源治理。
# 知识拓展
# 多个Agent一定要用消息队列吗
不一定。任务量小、进程内执行时,数据库状态表加内存调度器也能完成。消息队列解决的是异步投递、削峰和消费者解耦,但无论用什么基础设施,事件ID、幂等键、因果字段和状态机都不能省。
# seq_no能不能保证跨Agent顺序
不能。seq_no只对某个Agent或某个任务流有意义。跨Agent应该看DAG依赖和parent_event_id;互不依赖的事件不需要强行决定谁是第一。
# Prompt Cache能不能解决多Agent Token成本
它可以减少稳定前缀的重复计算或费用,但不能替代上下文治理。被缓存的无关内容仍可能占窗口并稀释信号,大型工具结果和跨角色历史也不会因为缓存就自动变得有用。
# Artifact和长期记忆有什么区别
Artifact是当前任务产生、可版本化和可验收的工作产物;长期记忆是跨任务保存的用户偏好、稳定事实或经验。Artifact不能自动写成长期记忆,是否保留仍要经过《Agent的记忆》里讲的写入门槛、过期和冲突治理。
# 为什么不能让Agent自己决定还剩多少预算
模型只能看到系统告诉它的数据,也无法和其他并行分支完成原子记账。它可以建议压缩或停止,但真实余额、预留、扣减和熔断必须由编排器执行。
# 写在最后
多Agent最危险的不是某个Worker答错,而是错误历史被复制、重复消息被执行、并行分支同时把预算花穿。
Thread隔离过程,Artifact承载事实,事件表达因果,预算决定边界。少一个,协作都可能重新退化成失控的群聊。
评论
验证登录状态...