上一篇《Agent 记忆系统设计》解决的是"跨会话记什么",这篇处理同一个问题的另一面:单次会话内部,上下文窗口被吃满之后怎么办。
场景很常见:一次跨三天的大重构、一个排查到第 40 轮的疑难 bug、一个要连续读几十个文件的任务。会话越长,模型对开头的记忆越"淡",而你迟早要在"压缩历史"和"新开 session"之间做选择。这篇把我们踩过坑之后的做法按「时机 → 摘要策略 → 不能压清单」展开。
压缩之前先知道钱花在哪。一个典型的编码 Agent 会话,上下文构成大致是:
read 一个 2000 行的文件就是上万 token,几轮下来历史里 70% 以上都是工具输出的尸体。这个比例决定了压缩策略的优先级:压工具结果的收益远大于压对话。很多框架一开始就在"总结对话"上用力,方向就反了。
最常见的错误是把压缩当成"窗口满了的急救"。问题是:压缩本身也要消耗窗口——让模型读完整段历史再产出摘要,输入输出都要占空间。等到 98% 才触发,摘要生成过程中窗口溢出,轻则截断摘要、重则直接失败。
我们的实践是两条线:
两条线的关系是"或":到阈值必须压,没到阈值但到了阶段边界也可以顺手续一刀。
摘要质量决定压缩质量。直接把 50 轮历史丢给模型说"总结一下",得到的是流水账。有效的做法是分层:
第一层:工具结果裁剪(成本最低,优先做)。历史里的工具输出按白名单裁剪——保留"当时调了什么工具、什么参数、结果的关键结论",丢弃原始输出。比如文件读取,只留"读了 config.py,发现数据库连接走环境变量",原文落盘按需取回。这一步通常能砍掉一半以上的体积,且几乎无损。
第二层:阶段摘要(按阶段边界做)。每个完结的阶段压成一段结构化记录,模板大致是:
【阶段 3:支付接口重构】
目标:将回调处理拆出独立模块
结果:完成,commit 3f2a91c
关键决策:回调验签改用中间件,理由是三处调用点逻辑相同
遗留:webhook 重试的幂等键未处理,已记 TODO第三层:全局状态卡(常驻窗口顶部)。当前目标、已完成/未完成、关键文件清单、未验证的假设——每轮都随窗口携带,模型随时能"想起自己干什么"。
一个关键纪律:摘要的输入必须包含最近 N 轮的原文,不允许用"摘要的摘要"滚雪球。摘要每滚一层,细节衰减一次,滚三轮后错误信息就被永久固化了。
不是所有内容都等价。这几类进了压缩区,后面一定付出代价:
我们的做法是给这些内容打标记(框架层面约定一个格式),压缩器遇到标记直接跳过。成本很低,返工率肉眼可见地下降。
最后说一个反直觉的结论:上下文管理的上限不在压缩器,在工具结果的设计。
很多膨胀是工具自己造成的:list_files 返回三千行、错误日志整页粘贴、搜索结果不带截断。工具结果在产生的那一刻就该克制——分页、截断、只返回摘要+按需展开的句柄。这比任何事后的摘要算法都便宜。
压缩是止损,不是优化。窗口里只放"当前决策需要的信息",其余的放外面(文件系统、记忆库、任务清单),这才是长会话能稳定跑下去的根本原因。
长会话上下文管理三步:80% 阈值或阶段边界触发压缩;先裁工具结果再分层摘要;硬约束和错误记录永远不压。压缩器做得再好也只是兜底,真正的杠杆在工具结果的源头设计和"什么该放窗口外"。
下一篇计划写多 Agent 场景的上下文隔离与交接——子 Agent 该带多少上下文出发、回来时怎么把结论交还给主会话而不污染现场,有兴趣的可以关注。
本文是 Agent 工程系列的一篇,前作:《Agent 记忆系统设计:上下文窗口装不下的长期项目,什么该记住、什么该忘掉》《MCP Server 的最后一公里:配置管理与安全分发实战》。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。