多代理系统为什么要做上下文隔离?父子代理协同的正确姿势
1. 从一次“翻车”说起:当子代理背负了整个家族的对话史
我先讲个真实场景。上个月我在调一个多代理协作的POC,让父代理去拆解一个“分析某开源项目代码质量”的任务,父代理把任务拆给了三个子代理——一个看核心模块、一个看测试覆盖率、一个看依赖安全。起初我图省事,让子代理直接继承父代理的完整上下文,结果翻车翻得很彻底:子代理A在处理核心模块时,被上下文里“父代理和用户讨论测试覆盖率”的内容带偏,开始大谈特谈测试策略;子代理B甚至开始引用父代理和用户闲聊时提到的某段无关历史。
这不是偶然事故。任何一个把LLM的上下文窗口当“通用水缸”的开发者,迟早会撞上这堵墙。今天聊的“父—子代理为什么要上下文隔离”,说白了就是一件事:当父代理把任务分拆给子代理时,子代理应该拿到多少“历史”?答案往往不是“越多越好”,而是“越精简越好”。
这篇文章适合什么人看?如果你在折腾Agent工作流、在做多代理系统、或者用Claude/OpenAI/国产模型搭过“任务分拆-并行执行-结果汇总”的管道,你会在这篇里找到那层窗户纸。我会把“上下文隔离”背后的原理、实操中的具体动作、以及我踩过的坑,一次性讲透。
2. 上下文隔离的本质:窗口不是U盘,拷不走也装不下
2.1 先给新手补个底:LLM的“工作记忆”是什么
关于“上下文窗口”这个概念,很多人理解成“LLM能记住多长的历史”,这个理解大致对,但容易漏掉一个关键点:上下文不仅是“历史”,还包括了任务说明、角色设定、工具定义、中间推理结果、临时变量。它相当于LLM正在工作时的“桌面”——桌面上摊了哪些文件、哪些草稿纸、哪些便利贴,决定了它能办成什么事。
当一个父代理和子代理之间做上下文传递时,很多人第一反应是“把父代理的对话历史打包扔给子代理”,这就像把整个办公室的文件柜搬到了新员工的工位上。问题是,新员工只需要那两张图纸,你却把十年来所有合同、报销单、零食采购记录全塞给了他。结果就是:
- Token被大量无关内容占据,有效工作空间缩小
- 模型注意力被无关信息稀释,相关性判断能力下降
- 父代理某轮对话里的错误信息或过期结论,成了子代理的“权威参考”
2.2 继承上下文 vs 隔离上下文:一张表看清差别
我自己在做多代理框架时,通常会把“上下文策略”列成一张对比表,凡是涉及架构评审我都直接往上甩:
| 场景 | 继承全部上下文 | 隔离上下文(只传必要信息) |
|---|---|---|
| 父代理解任务 | 好,但代价大 | 好,因为父代保留的是完整信息 |
| 子代执行效率 | 差,Token浪费在无关处 | 好,专注局部任务 |
| 子代输出稳定性 | 差,容易被带偏 | 好,提示词污染少 |
| 多子代并行 | 几乎不可用,互相干扰 | 可用,互不传染 |
| 失败重试成本 | 高,重试时全量重传 | 低,轻量化重试 |
| 结果可信度 | 低,混入父代偏见 | 高,子代独立判断 |
这张表提到的“提示词污染”,我解释一下。假设父代理的任务是“分析项目风险”,父代理在和用户对话时反复提到了“团队可能延期”这个话题。如果子代理继承了这段上下文,它自己的任务明明是“检查数据库表结构”,却可能因为上下文里“延期风险”的反复出现,而把自己的输出也往时间风险上带。LLM没有“选择性失忆”,它看到什么都会影响它。
2.3 隔离不等于切断:上下文隔离的实质是“责任边界”
这里要特别说清楚,“隔离”不是把子代理变成瞎子,更不是让子代理不携带任何信息去执行任务。隔离的本质,是把“任务上下文”和“环境上下文”区分开。
- 任务上下文:子代完成任务必须的信息,比如目标、约束、输入数据、验收标准
- 环境上下文:父代理和用户之间发生过什么、其他子代理在做什么、系统的全局状态
父—子代理之间应该传递的是精简后的任务上下文。环境上下文在父代理那儿留着,由父代理统一消化和调度。这像一个项目经理:他不会把客户的抱怨原话一字不差地转告给每位工程师,但会把“客户对交付日期非常敏感”这个结论写进任务简报里。
3. 五大理由:为什么必须做上下文隔离
3.1 Token预算的硬约束:窗口就那么大,别浪费
我得先上一个最直白的理由——Token。现在主流模型的上下文窗口从8K到200K不等,听起来200K很大,但用起来分分钟爆掉。我实测过,一段200K的上下文,光Recalling和Recitation(原文重复)就会占掉30%,真正能给推理用的空间远没有想象中那么大。
在父子代理场景下,如果你让N个子代理都继承父代理的全部上下文,假设父代理上下文是80K Token,三个子代理各调一次API,光输入就是240K Token的消耗。现实中你往往不止调一次,子代理要多次交互、自我反思、修正输出,最终成本可能翻到1M Token以上。
而做上下文隔离后,每个子代理只拿自己那几K的局部上下文,输出又只回传一个结构化摘要,整体成本能降一个数量级。在API计价时代,上下文隔离不是架构洁癖,是成本控制。
3.2 父代“桌面”太乱,子代无法聚焦
我做Agent开发时最大的一个体会是:模型对上下文的“绝对位置记忆”很强,但对“哪些是重点”的判断力,取决于信息密度。抽象一点理解,LLM的工作机制是预测下一个Token,上下文越长,它在决定复制哪些Token时就越容易“抓到哪个算哪个”。
具体到一个代码仓库审查任务:父代理读了整个README、读了源码目录、读了用户问的问题、甚至读了一段用户随手贴的日志。然后它把这段完整历史扔给子代理,子代理的任务是“列出所有未处理的安全漏洞”。但它的上下文里最显眼的信息是README里的项目简介和用户日志里的某次报错,这会让子代理在生成时分配过多注意力到错误方向。
隔离上下文,本质上是在帮LLM做“注意力规划”。你给子代理的上下文越集中,模型越有可能沿着你期望的方向走。
3.3 输出风格与格式的稳定性:隔离是“格式保证”的前提
我们做多代理,最怕的是子代理输出的结果格式五花八门。如果子代理被父代理上下文里的对话风格“污染”了——比如父代理用口语化的方式和用户聊天,子代理可能也照着用口语化的语气输出,但你的下游步骤(比如子代理输出要作为另一个子代理的输入)需要的是严谨JSON,这就会直接崩。
上下文隔离把子代理的“输入来源”定得很干净,只包含四类东西:
- 角色指令:你是谁、你要完成什么任务
- 任务输入:你给我什么数据、我要分析什么
- 输出格式:你输出的结构长什么样、字段是什么
- 少量示例:Few-shot,告诉模型“就像这样输出”
这四类内容都跟“父代理的历史对话”无关,自然就不会有风格污染。我团队里的SOP是:父代理永远是“会做摘要的高管”,子代理永远是“收到简报就干活的专员”。两边语言系统不同,才能保证系统稳定。
3.4 并行执行的需要:隔离才能“互不传染”
多代理系统最重要的一点是并行。要让三个子代理同时分析三份文档,互不干扰,关键前提就是它们的上下文空间是隔开的。如果共享一份全局上下文,子代理A在工作时写入了中间结果,这个中间结果又出现在子代理B的上下文里,那子代理B的分析就被“传染”了。
很多工程师在第一次做多代理时都会遇到“幻觉传染”的问题:子代理B说“根据前面的分析结果,我判定风险等级为高”,但实际上前面的分析结果是子代理A做出来的,可能A的分析基于了一个错误假设。上下文隔离是并行计算的防火墙。
3.5 可调试性和可解释性:出了问题能定位
最后这个理由可能比较“工程向”,但非常现实:上下文不隔离,出了错你根本没法归因。比如最终汇总结果里有条明显的错误结论,你不隔离的话,得翻完父代理80K的历史对话才能排查是哪一步带歪的;而上下文隔离后,问题域被切成了一个个独立切片,哪个子代理的输入输出有问题一目了然。
我自己排查问题时最常做的事就是“回放子代理的局部上下文”——看它收到什么、输出了什么、哪里偏离了预期。这个动作在隔离架构下只需要几秒钟,在继承了全量上下文的架构下可能需要反复调试半天的提示词。从运维角度看,隔离带来的可观测性提升,比它带来的性能提升更有价值。
4. 怎么落地:父-子代理上下文隔离的实操方法与模式
4.1 最基本的设计:简报式隔离
这种模式最接近真实管理经验。父代理先把用户的请求理解成一个“任务简报”,简报里包含:
- 任务目标(一句话)
- 关键约束(三个以内)
- 输入数据引用(不是全量数据,而是数据定位或摘要)
- 期望输出(格式和字段)
然后父代理把这个简报发给子代理,子代理只接收简报,之后它跟父代理之间仅通过“结果摘要”交互。我在用这种模式时,一般会在提示词里写下类似下面的结构:
这是你的任务简报。不要参考其他对话历史,你也不需要知道任务的全貌,只需完成简报中列出的局部目标。
目标:{任务目标} 约束:{约束1};{约束2};{约束3} 输入:{数据切片或引用} 输出:{JSON格式要求}
这种模式适用于子代理负责的是“清晰的单点任务”。优点是非常简单,缺点是父代理如果拆解任务不够精准,子代理拿到的简报信息不足,容易做偏。
4.2 扩展设计:黑箱代理模式
黑箱模式的思路是,子代理是“黑箱”——父代理只关心子代理喂进来的输入和吐出来的输出,完全不关心子代理内部的推理过程。这意味着子代理内部用什么样的提示词、做几次自我反思、用不用思维链,都是子代理自己的事,与父代理无关。
这个模式在实践中的关键设计点是“输入输出的接口契约化”。我会为每个子代理定义一套严格的输入输出Schema,比如一个“代码审查子代理”的Schema:
{ "input": { "code_snippet": "string", "language": "string", "review_focus": ["security", "performance"] }, "output": { "findings": [ { "severity": "critical|warning|info", "category": "string", "line_number": "int|null", "suggestion": "string" } ] } }在这个模式下,父代理向子代理传的不是自然语言的历史对话,而是结构化的JSON对象。上下文的隔离由“数据通道”实现,不经过自然语言的自由传递。
4.3 更进一步的冗余设计:有损摘要隔离
如果你的父代理上下文特别长,而你又不希望子代理漏掉关键背景信息,可以做一个“有损摘要”——父代理先用自己的能力把全量上下文压缩成一份摘要,再把摘要传给子代理。这里的“有损”是有意为之,就像给子代理一份“会议纪要”而不是会议录音。
我常用的一种做法是让父代理执行两次总结:
- 第一遍:提取与任务相关的全部事实,排除无关对话
- 第二遍:把提取出的事实浓缩成“要点列表”,限制点数为10-15条以内
实践表明,这个二次压缩能显著减轻子代理的Token压力,也能让子代理更聚焦。要注意的是,这个有损摘要动作本身会消耗一定调用量,所以只适用于高价值的长任务场景,不是每次都做。
4.4 任务回传协议:隔离之后,如何“收账”
上下文隔离不只是输入方向的隔离,输出方向也要“收账”。子代理完成任务后,回传给父代理的内容也需要打包降级。最常见的问题是子代理回传了“长篇大论的推理过程”,但没有把“结论”单独摘出来。父代理要处理的是一个“结果”,而不是一篇散文。
我在实践中定义了“四段式回传协议”:
| 段位 | 字段 | 说明 |
|---|---|---|
| 第一段 | conclusion | 直接结论,不含推理 |
| 第二段 | evidence | 支撑结论的关键证据清单 |
| 第三段 | confidence | 置信度评分(1-10) |
| 第四段 | raw_output | 原始输出归档(不作为下游输入) |
父代理只把前三段作为后续处理的输入,第四段留在日志里供排查。这个协议也倒逼子代理在输出时先做“思考压缩”,而不是把推理过程当成交付物交差。
4.5 状态管理:隔离了上下文,但别隔离了“记忆”
很多人在实现上下文隔离后遇到另一个坑:子代理在执行过程中需要“记住”自己做到哪一步了,尤其是长任务型子代理(比如一个子代理要执行5个子步骤)。如果你对子代理之间做的是完全无状态隔离,它每一步都是“失忆上岗”,会导致低效甚至重复劳动。
解决办法是给子代理内部做“任务内短程记忆”——子代理自己维护一个局部工作区,这个工作区用于记录它在本任务内的中间结果。我在实现时,通常是在子代理的提示词里加一个“工作日志”字段,要求子代理每完成一个子步骤就把结果更新到工作日志中:
{ "task_id": "sub_agent_001", "work_log": [ { "step": "parse_input", "status": "done", "result": "5 focus points extracted" }, { "step": "pattern_match", "status": "in_progress", "partial_result": "3 risk candidates found" } ] }这个工作日志是子代理的“私有记忆”,和父代理无关,也和兄弟子代理无关。做完这一步,任务内上下文依然隔离,但任务状态不再丢失。
5. 一个完整实操案例:用上下文隔离重写“市场调研”多代理流程
5.1 案例背景:为什么继承式流程必须改
我拿一个典型的“市场调研”多代理任务来说。假设用户丢给父代理一个需求:“对比三款主流项目管理工具,输出一份选型建议。”懂行的朋友都知道,这种任务特别适合分给多个子代理去调研,一个查功能清单,一个查价格模型,一个查用户口碑。
如果按“继承全量上下文”的做法,流程是这样的:父代理接收用户需求,三个子代理各复制一份完整对话历史,然后各自行动。结果会怎样?你大概率会遇到:
- 子代理A(查功能)在输出时,引用了父代理后来才提到的“用户偏好”信息——但这信息是父代理在和其他子代理聊天时才说的,这个子代理本不该知道,结果提前“剧透”了
- 子代理C(查口碑)看到的上下文里包含了大量功能特性讨论,它误以为用户主要关心功能,而忽略了口碑调研本身
5.2 改造后的隔离流程
我最终落地的流程分为四个阶段。
阶段一:父代理需求分解与任务建构
父代理第一次接收用户请求后,不做任何调研,先做任务分解。它把“对比三款工具”拆成三个子任务,并为每个子任务做一份“简报”。这个阶段的关键是约束说明——指定三款工具的名称、指定输出格式统一。
阶段二:子代理并行执行与局部上下文工作
三个子代理各领一份简报,各自只持有简报、工具信息、以及自己的局部工作区。子代理之间没有任何上下文的交叉引用。如果子代理A需要知道“某工具是否支持某功能”,它调工具去查,但查的结果只写入自己的局部工作区。
阶段三:回传结构化结果
子代理各自完成调研后,按四段式回传协议输出。这里父代理看到的“三份结果”是三个结论+证据+置信度的结构化信息,而不是三篇长文。
阶段四:父代理汇总与最终判断
最后父代理基于三份“结构化结果”,结合用户最初的需求(这部分是父代理独有的上下文),输出选型建议。在这个阶段,父代理的信息优势是子代理不具备的——它见过用户完整的原始需求,也知道其他子代理没有涉及的背景信息。
5.3 这个案例中的关键收获
做完这个案例后,我对“上下文隔离”有了更具体的理解:
- 隔离的不只是信息,隔离的更是“决策语境”。子代理在自己的决策语境里做出专业判断,父代理在自己的决策语境里做全局统筹。
- 一旦允许子代理“越界”访问父代上下文,子代理的决策语境就被污染了。它会把“父代理的焦虑”当成“任务约束”。
- 如果你发现某个子代理输出总不理想,先检查它的输入简报是否“太肥”——简报里写了太多和本任务无关的约束,这往往就是祸根。
6. 常见问题与排障实录:上下文隔离实战中的暗礁
6.1 子代理上下文被“截断”导致重要信息丢失
现象:父代理生成的简报太长,超过了子代理的局部Token限制,子代理拿到的是被截断的版本,导致它做出来的结论缺胳膊少腿。
排查思路:如果你看到子代理输出中频繁出现“根据我手头的信息”这种“打折扣”的表述,十有八九是简报超长了。这时候你要做的不是加Token,而是给简报“瘦身”——把“约束”“输入数据”压缩成更精准的字段。我自己的一句话是:简报不是越多越好,它应该像“驾驶舱仪表盘”,只显示要用的表盘,不放操作手册。
6.2 子代理“偷看”了其他子代理的输出
现象:并行执行时,子代理B的输出里出现了子代理A才应该知道的信息。这意味着你在设计并行管道时,可能不小心让B的上下文里包含了我一个共享变量。
解决方案:如果是技术原因,检查是否用了同一个“会话对象”去发多个子代理的请求;如果是架构原因,检查父代理是不是把子代理A的结果传给了子代理B作为“参考信息”。现在很多LangChain/LlamaIndex生态里的老坑就是这样——会话历史被全局共享,一定要打开日志看每个子代理实际收到的上下文。好的隔离架构永远该有一个“日志回放”功能,能够逐帧查看每个子代理的完整输入输出。
6.3 父代理拿到子代理结果后“消化不良”
现象:子代理结果回传是正常的,但父代理汇总时越汇总越乱,几份结果之间出现自相矛盾。
原因:多个子代理各自的理想假设不一致。比如调研功能的子代理假设用户用的是2人小团队,调研价格的子代理假设用户用的是50人企业。它们各自在自己的局部上下文里做了“隐性假设”,但把这些假设带回来时,父代理没发现冲突。
对策:在简报设计阶段就要给每个子代理写入“禁止自行假设”的约束,同时让子代理在结果里显式声明“我基于哪些假设得出此结论”。这样父代理在做汇总时,能够通过“假设对齐”消解矛盾。
6.4 隔离之后,子代理缺乏“常识”怎么办
现象:子代理隔离后,经常问出“很傻”的问题——比如某个明显不需要调研的常识性问题,它反复去查文档,或者在输出里报出常识性错误。
原因:通用大模型在微调时接触了大量世界知识,但如果你的子代理被隔离到一个专业领域内,它可能不再从“通用常识池”里取答案,而是从“局部上下文”里推断。
对策:在执行隔离时预留一个“常识参考区”。这部分上下文不来自父代理,而是来自一个固定的“常识提示”(比如“你是一个经验丰富的DevOps工程师”“你熟悉主流云平台差异”),它不是任务上下文,但能给子代理补足“背景知识”。
7. 写在后面:上下文隔离的真正意义是“降低协同复杂度”
做过多代理系统的人都知道一句话——每一个代理的智力上限不是模型本身,而是它手里握着那叠纸。上下文隔离的本质,就是在降低协同复杂度。父代理不需要理解子代理的每一个内部推理,子代理也不需要背负父代理的每一项历史包袱,它们之间只需要在“有效信息”的接口上进行交换。
我个人在实操中踩过的最大一个坑,就是企图让子代理“懂得更多一点,以便它们更智能一点”。结果适得其反。在这个系统里,子代理越专业,越好用;父代理只需要做好拆解、分发、汇总这三级跳。
如果你也在做多代理系统,我的建议是:下次你再做父代理和子代理之间传递上下文之前,先问一句——“子代理需要知道多少才能完成它的局部任务?” 然后把答案输入给它,其余的都锁在保险柜里。
这就像在真实团队里,项目经理最多只会跟测试工程师说“需求文档里与当前版本相关的PRD是这几页”,而不是把三个月以来所有邮件、周报、会议纪要全发过去。学会隔离,你的代理团队才能真正“各司其职”。
最后分享一个小技巧:在每次“父-子”交互后,我习惯加一条约束“如果你发现任何信息缺失,请向父代理索取,而不是自行脑补。”这一句话,能有效减少子代理“自作主张”的概率。上下文隔离不是不提供信息,而是让信息的通道刻意变窄,从而倒逼子代理在必要时请求补充——这个请求动作本身,就是你要捕获的“信息缺失信号”。