一个 agent 跑到第 60 步突然开始胡言乱语,十有八九不是模型变笨了,而是它的上下文窗口已经被自己的历史撑爆。过去两年,工程团队把大量精力花在 prompt 雕花上,最后发现真正决定长任务成败的战场在更下面一层——harness。这个词不好翻译,你可以把它理解成"套在模型外面的那圈骨架":它决定每一轮该给模型看什么、藏什么、复述什么。上下文工程做到深处,做的其实是这四件事:预算与卸载、压缩、todo-state 复述、跨会话记忆。它们不是四选一的备选方案,而是针对四种完全不同的失败模式下的药。
上下文是预算,不是仓库
把 token 当成预算表来排
新手的直觉是"信息越多越好",于是把历史消息、工具返回、检索结果一股脑塞进窗口。这套做法在短任务里看不出问题,一到长任务立刻崩盘:工具返回的原始 JSON 动辄几千 token,几轮下来就把真正重要的推理链挤到边缘。成熟的 harness 会反过来做——先给窗口划出固定配额:系统提示占多少、当前任务描述占多少、最近若干轮对话占多少、外部检索结果占多少。配额定死之后再谈内容取舍,这跟财务做预算的逻辑一模一样,先分科目,再决定每一项花多少。
卸载不等于删除
早期实现里最常见的动作是截断——超过窗口就砍掉最老的消息。粗暴、有效,但代价很高:模型会丧失"我做过什么"的连续性,于是开始重复调用同一个工具、重复验证同一个假设。更聪明的做法叫卸载,把大块原始内容挪到上下文之外的存储里,窗口内只留一个轻量引用,比如一句"文件 X 的内容已读取,共 400 行,存于工作区"。模型需要时可以按引用重新取回,而不需要时就只占一行。这个区别听着小,实际影响巨大:截断是单向丢失,卸载是可逆的外部化。
阈值这道题,各家答案不一样
什么时候触发卸载?主流 agent 产品的做法并不统一,触发点大致落在窗口占用的六成到八成之间。定得太早,模型会频繁丢失刚刚建立的工作记忆;定得太晚,一次工具调用就可能把窗口顶穿,触发不可控的强制截断。这里没有普适最优解,因为阈值取决于任务的信息密度——一个跑代码库重构的 agent 和一个做客服问答的 agent,同样占 70% 窗口,剩下的可用空间完全不是一回事。
压缩这件事,难在什么时候动手
摘要压缩最先崩在哪
压缩最直观的实现就是让模型自己写摘要,把前面 30 轮对话浓缩成一段话。问题是摘要不可逆。模型在压缩时做的是一次有损判断,它认为不重要的细节被永久抹掉了——而偏偏在长任务里,被抹掉的常常是那个后来才被证明关键的异常返回值。更麻烦的是摘要会累积误差,第二十轮的摘要压缩了前十轮的摘要,几轮之后原文里的事实已经被稀释成一句空话。所以纯摘要方案通常只适合处理"已经确定不再回看的中间过程"。
结构化压缩与可回滚
另一条路是不产出自然语言摘要,而是把会话拆成几个结构化字段:已确认的事实、尚未完成的待办、已经被排除的假设、当前所处的阶段。这种压缩的损失面更窄,因为它保留的是决策状态而不是叙述文本。更关键的一点是它天然可回滚——原始消息还在外部存储里,压缩只影响窗口内的呈现形式。当模型发现状态字段之间有矛盾时,还能把原始片段拉回来核对。
时机比算法值钱
真正拉开差距的不是压缩算法本身,而是触发时机。一种做法是周期性压缩,每隔 N 轮执行一次;另一种是事件驱动,在任务阶段切换、工具连续失败、或者模型开始自我重复时才压缩。后者效果好得多,因为压缩本身是一次高成本的元操作,它也会消耗 token、也会引入错误。在不该压的时候压,等于主动制造信息损失;在该压的时候不压,模型会开始在噪声里打转。
靠复述对抗遗忘
四十步之后,它还记不记得第一步的目标
上下文管理做得再好,也挡不住另一种失败:目标漂移。任务跑到后半程,模型被眼前的工具报错、格式问题、边界情况拖住,逐渐把"解决用户原始问题"替换成"让这个循环跑通"。这就是长任务里最隐蔽的退化。todo-state 复述是针对它的机制——每一轮都把当前的目标清单、已完成项、待办项重新注入上下文,让目标始终处于窗口里最显眼的位置,而不是沉在几十轮之前的用户消息里。
复述的颗粒度
复述的难点在于写多细。写成"完成剩余工作"等于没写;写成 30 条细碎步骤,模型会陷入机械执行,遇到计划外的分支就卡死。实践中的平衡点在 5 到 9 条之间,每一条对应一个可验证的产出。同时待办列表必须是活的:完成一条就标记,发现新分支就插入,走错了就回退。一份从第一次生成后就再没变过的 todo 列表,往往比没有还危险,因为它会误导模型以为进度比实际更快。
谁在执行目标
这里有个容易被忽略的分工问题。复述不应该完全交给模型自己维护——模型倾向于报告乐观进度。更稳的设计是让 harness 掌握状态的真实来源:工具调用成功了没有、文件写入了没有、测试通过了没有,这些由外部系统判定,模型只负责解释和规划下一步。当"我以为做完了"和"系统记录显示没做完"发生冲突时,以 harness 为准。这一层权威关系如果不明确,todo-state 会退化成一段自我安慰的文本。
记忆要跨过会话那道墙
该写进档案的是什么
前三类机制解决的都是单次会话内的问题。但长周期任务往往跨会话——今天的 agent 明天还要接着干。跨会话记忆的核心不是存得多,而是存得准。值得持久化的通常只有三类:用户和项目的稳定偏好、已经验证过的环境事实(比如某台机器的路径、某个 API 的限流阈值)、以及踩过的坑。而那些一次性的中间推理、临时文件路径、当次任务的进度细节,写进去只会污染后续检索。
检索精度才是瓶颈
记忆系统的容量从来不是问题,召回质量才是。把几十条记忆全量注入上下文,等于把长任务的老毛病搬到新会话里。可行的做法是分成两层:一层是始终注入的少量核心约束,条目控制在个位数;另一层按需检索,由当前任务描述去匹配。同时记忆要带时效和来源,让模型能判断这条经验是否还适用于现在——三个月前有效的绕行方案,可能因为依赖升级已经彻底失效。
四种机制不是并列关系
把这四类机制摆在一起看,会发现它们分别在处理不同层次的损耗:卸载对付容量,压缩对付密度,复述对付方向,跨会话记忆对付时间跨度。只做卸载和压缩的 agent,会在长任务里跑得很稳但跑偏;只做复述的 agent,方向很准却早早撞上窗口上限。真正能扛住小时级任务的 harness,是让这四条线互相兜底——压缩后的状态字段成为复述的输入,复述产生的进度又成为跨会话记忆的写入内容。任何一环缺席,长任务的表现都会从某个步数开始断崖式下滑。
所以下一次你的 agent 在半路失控,先别急着换模型。翻一翻它的 harness:窗口里现在装着什么,上一次压缩是什么时候,待办列表还准不准,上一个会话留下的东西有没有被带进来。答案大概率就在这几处。

