第 7 章 · 上下文管理:协调的「通信带宽」

一句话结论:多 agent 协调里,信息不是越多越好,而是「恰好够用」最好。上下文既是每个 agent 的工作记忆,也是 agent 之间通信的「带宽」——不管理它,协调会在「传话失真」和「窗口爆掉」中悄悄崩坏。这是 agent 工程里最容易被低估、却最容易翻车的一环。


1. 一个真实案例:任务 ID 是怎么「蒸发」的

这是 DeerFlow 2.0 里一个非常有名的 issue(#4652),问题直白得让人冒冷汗:

你让 agent 起一个要跑几小时的活,然后合上笔记本去吃饭。回来一看:任务还在跑吗?结果出来谁告诉你?

问题的根源链条是这样的:

  1. 任务 ID 靠「对话上下文」携带:agent 启动了一个长任务,拿到一个任务 ID,这个 ID 存在对话上下文里。
  2. 多轮对话后,上下文压缩(compaction)会把任务 ID 压没:为了不爆窗口,系统会把历史对话压缩成摘要,任务 ID 这种「小事实」很容易在压缩中丢失。
  3. ID 丢了,模型开始编造:等 agent 要查「任务跑完没」,它发现自己「记得有个任务」,但 ID 想不起来了,于是凭空编一个 ID 去查询——幻觉出现了。
  4. 想绕过就持续轮询,又触发工具循环风暴:反复查状态,掉进死循环。

这个案例的深刻之处:它把「上下文管理」的三种失败模式(压缩丢信息、幻觉、工具循环)串成了一条因果链。而所有问题的根源,是把「需要精确保持的事实」(任务 ID)和「可以压缩的历史」(对话过程)混在了同一个上下文里。


2. 思考的分叉:上下文到底该怎么管?

面对「上下文会爆、会丢关键信息」这个根本矛盾,有几条路:

方向 A:不压缩,全量塞(简单粗暴)。

  • 代价:窗口必然溢出,长任务直接死。不可行。

方向 B:定期压缩成摘要(compaction)。

  • 优点:控制了长度。
  • 致命伤:压缩是有损的,关键事实(ID、路径、数值)会被摘要「吃掉」——就是上面那个 issue 的病灶。

方向 C:分层管理——「事实」和「叙事」分开存。

  • 把「需要精确保持的小事实」从「可以压缩的叙事」里拆出来,前者用结构化的、不会被压缩的通道保存(数据库、显式变量、文件句柄),后者才允许被摘要压缩。

为什么 DeerFlow 的解法是 C:issue #4652 的终极解法(PR #4833)就是给长任务引入一个「持久化任务运行时」——把任务状态、ID 这些事实存进 PostgreSQL 一张表里,作为唯一事实源,而不是靠对话上下文携带。这样即使上下文被压缩一百次,任务 ID 也稳稳地躺在表里,模型要查就去查表,而不是去「回忆」。

一个可迁移的洞察:「事实」和「叙事」要分家。 需要精确保真的东西(ID、路径、结论),必须走「不可被压缩的结构化通道」;可以被概括的东西(过程、闲聊),才交给「有损压缩」。把两者混在一起,是绝大多数 agent 系统上下文 bug 的根源。


3. 伪代码:上下文管理的几个关键手段

# 手段一:工具结果不塞原文,塞「句柄」(handle)
def worker_call_tool(name, args):
    result = run_tool(name, args)          # 比如抓取了一个网页、生成了一个文件
    if result.is_large():
        handle = save_artifact(result)      # 大结果存成「产物文件」
        ctx.append(f"[产物句柄: {handle.id}]")  # 上下文里只放一个短句柄,不放原文
    else:
        ctx.append(result.summary())        # 小结果放摘要
 
# 手段二:需要精确保真的「事实」走结构化通道,不进会被压缩的对话
def persist_fact(key, value):
    db.set(key, value)                      # 写数据库,唯一事实源
    # 上下文里只放「引用」,不放「值」
 
# 手段三:技能/工具「渐进式加载」,用到才进上下文
def maybe_load_skill(name):
    if skill_needed(name):                  # 只有任务真需要,才把 skill 的说明塞进上下文
        ctx.append(load_skill_prompt(name))

这三个手段,对应三种「带宽管理」策略:

  1. 句柄化:大结果(网页全文、生成的文件)不塞上下文,塞一个短句柄。上下文里传「引用」,不传「内容」。 需要时再按句柄取回。这直接解决了「报告员的窗口被一堆原文撑爆」的问题。
  2. 事实外置:ID、路径等精确保真信息,存结构化存储,上下文只放引用。这是 issue #4652 的教训。
  3. 渐进加载:一个 agent 可能挂了几十个技能/工具,但当前任务只用得上 2 个。别把 50 个工具的说明全塞进上下文,用到哪个再加载哪个——DeerFlow 管这叫「progressive skill discovery」。

4. 为什么「上下文管理」是协调的一部分

很多人以为上下文管理是「单个 agent 内部的优化」,其实它是协调的核心:

  • 协调的本质是信息在 agent 之间流动。如果 A 传给 B 的信息是「未经管理的一大坨原文」,B 的窗口被撑爆,协调就断了。
  • 反过来,如果 A 传给 B 的是「恰到好处的结构化结论 + 句柄」,B 就能轻装上阵。

所以可以把协调想象成一条信息管道,而上下文管理就是这条管道的「带宽控制」:

Planner ──(计划)──▶ Supervisor ──(分派步骤)──▶ Worker ──(句柄/结论)──▶ Reporter
                     ▲                                    │
                     └──── 事实存数据库,上下文只传引用 ────┘

每一段管道上流什么、流多少、什么该进结构化存储、什么该压缩,都是「协调设计」的一部分,而不是可有可无的优化。

类比:一个团队协作,如果每个人汇报都把自己查到的原始材料整个发给下一个人,下一个人光整理就要累死;好的协作是——「结论写清楚,原始材料放共享盘,需要的人自己去取」。上下文管理的精髓,就是这个「共享盘 + 短引用」的分工。


思考题

Q:第 1 章说「多 agent 是为了破解单 agent 的上下文上限」,但这一章又说「多 agent 自己也有上下文管理问题」。那多 agent 到底比单 agent 好在哪?会不会只是把问题转移了?

参考答案:

好就好在**「问题被从『不可控』变成了『可控』」**,而不是「问题消失了」。

  • 单 agent 的上下文溢出,是一个无法分治的物理问题:所有信息必须挤在同一个窗口里,你只能「买更大的桶」。
  • 多 agent 把「一个大窗口」拆成了「多个小窗口」,每个小窗口的问题变成了一个可以逐个管理的工程问题——这个 worker 窗口爆了,我压缩它、句柄化它、外置事实,就能解决,而且不影响其他 worker。

换句话说:单 agent 是「一个你无法局部优化的全局瓶颈」,多 agent 是「一堆你可以逐个优化的小瓶颈」。 前者靠运气(模型变强),后者靠工程(你能控制)。这就是为什么「上下文管理」是 agent 工程里实打实的一门手艺,而不是可有可无的玄学。拆分的真正价值,是把一个不可解的全局问题,转化成一批可解的局部问题。