最短上下文不一定是最低成本
继续同一个 coding agent 任务:“定位失败测试、修改实现、运行完整测试并交付结果”。Agent 已经读过测试与实现文件,尝试过一种错误修复,随后找到根因并改对代码。历史里同时积累了文件内容、失败命令、测试日志和决策理由。此时把旧内容删掉,下一轮输入当然会更短;但“更短”只描述了删除之后的请求,没有计算建立这份新前缀的一次性成本,也没有计算被删证据对后续质量的影响。
若原历史一直以追加方式增长,较早的相同前缀在 Provider 条件允许时可能按缓存读取处理。裁剪发生后,从首个删除或不匹配位置起,幸存的后续内容可能需要重新处理;压缩还要先生成摘要,并让后续请求围绕摘要建立另一份历史。省下的未来 token,要先偿还这次重写、摘要生成和潜在返工。
因此目标函数不是“让当前 prompt 最短”,而是让完整任务的成本、延迟和质量风险可接受:Agent 能否保留约束,正确修改,运行完整测试,并给出可审计的交付结果。窗口余量只是约束之一;缓存读取、冷 prefill、摘要延迟、重读文件、重跑测试与错误交付都应进入比较。
任务级报表也要跟着改变。不要只记录压缩前后 token 数,而要关联这次上下文事件之后的缓存读写量、首 token 延迟、文件重读、工具重试、测试覆盖范围和最终交付状态。某次裁剪若节省了十轮输入,却让 Agent 忘记“必须运行完整测试”,它不是优化成功;某次追加暂时让输入变长,却避免重复定位根因,也可能是更经济的选择。
Append:不要重写历史,追加事实
Append(追加)最适合处理“新事实覆盖旧状态,但旧记录仍有来源价值”的情况。假设 Agent 先读取了 src/parser.ts,这份文件内容已经进入历史;随后工具修改了该文件。不要回头把旧的 read result 改写成新版本,而是在末尾追加一条文件变化提醒、一次带版本标识的新读取,或补丁结果:
earlier: read src/parser.ts at commit abc123
later: src/parser.ts changed by patch; current blob sha256=…
later: read current src/parser.ts; use this result for subsequent edits
这样做保留了此前的连续前缀,也保留“模型当时依据什么内容作出判断”的审计链。旧读取没有变成真空中的错误文本,而是被更晚、版本更明确的事实 supersede。代价是过时 token 仍会留在每轮输入中;若版本标记不清,模型也可能混用新旧内容。因此追加需要单调的事件顺序、文件路径与版本标识,并明确“以最新记录为准”。
更稳妥的实现会为每类可变事实维护 supersession 指针:新文件读取指向它替代的旧读取,新测试结果注明命令、代码版本与它覆盖的旧结论。Context Builder 仍按事件顺序追加,模型却能快速找到 current state。若同一文件已经反复读取多次,旧版本不再承担审计价值,窗口又接近上限,就应转向 prune 或 compact,而不是把 append 当成永不清理的日志策略。
截至 2026-07-26,Claude Code 的 Prompt Caching 文档记录了一些以追加通知保持既有前缀的产品行为。这只能说明该日期、该产品与文档覆盖配置下的实现选择,不能外推成所有 Agent、Claude API 或其他 Provider 都会自动追加文件变化提醒。
Prune:删除发生在前缀的哪个位置
Prune(裁剪)是从已有上下文中删除选定的旧消息或工具结果,不生成一份替代旧历史的新摘要。它的经济性首先取决于删除位置,而不只是删除量。
设历史是 [stable][read A][test log][decision][recent work]。若删除靠前的 read A,首个 mismatch 很早,后面的测试结论、决策和近期工作即使原文不变,也不再属于与上一轮连续相同的完整前缀;若只删除尾部一个已放弃分支的最后几条记录,前面更长的共同前缀仍可能复用。即使两种方案删除相同数量的 token,早删与尾删的一次性重处理范围也可能完全不同。精确结果仍受 Provider 的缓存断点、匹配粒度、路由和缓存可用性约束。
所以裁剪器不能只接受“删除最老内容”这一条规则。它至少要输出删除区间、首个编辑位置、编辑后幸存 token、预计未来轮数,以及被删内容的证据分类。删除最老的大工具结果可能释放最多空间,却也最早截断共同前缀;删除靠后的重复日志释放较少空间,却可能用更小重写范围换来足够余量。若多个旧 tool result 分散在历史中,还要按第一次清除的位置估算,而不能把各段删除量相加后当作一段尾删。
截至 2026-07-26,Anthropic Context Editing是一个有文档的 Claude API 机制:编辑在服务端、模型推理前作用于模型实际可见的 prompt;客户端发送并自行存储的完整 conversation history 不会因此被改写,除非应用另行修改它。文档中的默认 tool-result clearing 会清除符合条件的旧工具结果,同时保留 tool call 的 inputs/parameters;只有在相应功能支持范围内显式设置 clear_tool_inputs=true,工具输入才会一并清除。清除点会使其后的缓存前缀失效,API 会报告 applied edits,文档也建议用 clear_at_least 确保一次清除足够多内容,才值得承担重写。具体策略类型、触发条件、保留数量、beta、平台与模型支持必须按该日期文档核对。它不是通用 Prompt Cache 的自动行为,也不是免费的 generic prune;其他 Provider 即使有相似功能,也不能套用同一语义。
Compact:用新状态替换旧历史
Compact(压缩)不是简单删除,而是用一份更短的新状态或摘要替换旧历史的一部分。对本例,摘要可能保存目标、约束、根因、已修改文件、测试状态和未解决风险,再接上最近几轮原始消息。摘要生成本身会消耗输入、输出 token 与时间;摘要的措辞又是不同内容,所以它建立的是新前缀,并可能漏掉后来才证明重要的细节。
摘要还会把不同证据强度压成同一种自然语言。原历史也许清楚区分“工具实际返回”“模型推断”“用户批准”,摘要若只写“已经确认”,后续 Agent 就无法知道确认来自哪里。压缩输出因此应被当作一个有版本、有来源范围、需要评估的新状态对象,而不是事实真相的无损编码。重要原文仍要留在耐久工件中,摘要中的引用也要能回指。
截至 2026-07-26,OpenAI Compaction 文档提供 Responses API 的 server-side compaction 与 standalone compact endpoint。前者在配置阈值后由服务端产生 compaction item;后者接收完整上下文并返回新的 compacted window。文档说明其中可能包含不透明、并非供人阅读的 encrypted compaction item,也可能保留旧 window 的其他 items。应用不应猜测内部摘要算法或解析不透明状态:使用 standalone endpoint 时,应把返回的 canonical window 原样传入后续 Responses 请求;使用 previous_response_id 时,也不要自行叠加一套手工 prune。
Claude Code 给出另一种具体路径。同样截至该日期,官方缓存说明描述:生成 compaction summary 的一次性调用使用原有 system、tools、history,并在尾部追加摘要指令,所以该调用共享既有前缀、可以读取缓存;下一轮则用更短摘要重建 conversation layer,而稳定的 system 与未变化的项目上下文层仍可能复用。若刚探索的是一条准备彻底放弃的错误路线,rewind 回一个已知的早期前缀,可能比生成新摘要更直接、更便宜。以上是 Claude Code 在 2026-07-26 的产品行为,不是任意摘要器都具有的缓存保证;Context Window 文档还列出哪些规则会重注入、哪些可能在压缩后暂时丢失。
同样截至 2026-07-26,Pi Sessions与 Pi Compaction提供了可检查的数据结构实例:Pi 的会话是追加式树;压缩不会改写旧条目,而是追加包含 summary 与 firstKeptEntryId 的 CompactionEntry,随后从“summary + kept messages”重建模型可见上下文。Pi 也支持在切换树分支时生成 branch summary。这些是 Pi 当前的 session/compaction 产品行为,不代表所有 Agent 都必须使用树、同样的摘要 schema 或相同切点。
一次性重写成本与未来节省
可以先用一个粗略模型判断 prune 是否有回本机会。假设同一 Provider、模型与计价单位下,编辑后的幸存后缀原本可按缓存读取处理,现在因更早的删除而需要作为未缓存输入重新处理;被删除部分则在每个未来回合省下一次缓存读取:
one-time rewrite cost
≈ surviving tokens after edit × (uncached rate - cache-read rate)
future savings per turn
≈ removed tokens × cache-read rate

当两边单位与假设一致时,粗略的 break-even turns 才能写成“一次性重写成本 / 每轮未来节省”。它适合比较数量级,不适合给出小数点后的承诺。若只剩一轮,早期删除很可能来不及回本;若后面还有很多轮、被删内容很大且价值很低,裁剪才更有机会摊薄重写。
这个粗略模型明确省略了:输出与质量变化、摘要生成成本、Provider 特定的 cache-write 定价、breakpoints 与 TTL、routing 与 eviction,以及编辑后幸存后缀此前是否真的具备缓存资格。它也没有计算删错证据造成的重读、测试重跑或错误补丁。任何一项不成立,都应回到真实 usage、TTFT 和任务结果,而不是继续做虚假的精确除法。
对 compact 还要另列摘要调用和摘要后第一轮,不能把二者藏进“输入变短”这一格。对 append 则要累计旧 token 在未来每轮的读取或处理成本。最终应比较三条完整时间线:现在付多少、后续每轮付多少、任务失败时恢复要付多少;只有这样,缓存友好的长前缀和窗口友好的短状态才能放在同一张决策表里。
被删除的不只是 token,还有证据
在 coding agent 里,以下内容不是普通聊天噪声,而是完成任务与复核决策的证据:
- 用户给出的任务约束和明确批准;
- 已接受的技术决策及其理由;
- 文件路径、hash、依赖或模型版本;
- 能定位根因的精确 error snippet;
- 实际运行的测试命令与结果;
- 尚未解决的风险、失败条件和回退边界。
一段已经便宜命中缓存的证据,其任务价值可能远高于 token 成本。删掉“pnpm test 全部通过”的原始依据,只保留“测试没问题”,会让交付看似短,却失去命令范围、退出码和警告;删掉用户批准,Agent 可能再次停下来询问,或越过授权边界。
这并不要求永久保留几万行原始日志。应区分体量巨大的原始输出与紧凑的证据账本:原日志放在受控的 durable artifact 中,账本保留路径或 artifact ID、内容 hash、关键错误原文、命令、退出状态、时间与结论。只有在可访问性、保留期和权限都可靠时,引用外部工件才算证据;一个已经被清理的临时路径不是压缩。
证据账本也需要去重而不是失忆。例如多次运行同一测试,可以保留最后一次完整结果,并为较早失败保留最小 error snippet、对应代码 hash 和修复它的 decision ID;重复的成功日志可以折叠成命令、运行次数与最后状态。这样减少的是展示与模型输入中的体量,不是抹掉失败曾经发生、修复依据是什么。
在自然任务断点做压缩
最安全的压缩点通常是自然任务断点,而不是 token 仪表盘刚好变红的瞬间:根因已经确定、关键事实已进入证据账本之后;补丁和局部/完整测试阶段结束之后;开始一个新目标,或准备切换模型、工具与 prefix epoch 之前。不要在错误原因仍有争议、两条修复路线尚未验证、用户批准还未明确时压缩,因为摘要会过早固化一份可能错误的叙事。
摘要本身应有 schema,例如:goal / constraints / accepted decisions / files+versions / exact evidence / tests / unresolved risks / approvals / next step。然后用真实任务做 omission eval:压缩后能否说出禁止修改的文件,能否还原精确失败片段,能否区分局部测试与完整测试,能否知道哪项风险未解决。每次调整摘要 prompt 或模型,都应作为新的摘要版本回归这些 eval。
触发时还应先执行一个 checkpoint:刷新文件版本和工作树状态,把关键测试结果写入耐久工件,确认用户批准已经落账,再生成摘要。压缩后用只读问题验证新状态,失败就回到原 transcript 重新生成,而不是让 Agent 带着缺项摘要继续写代码。自动压缩若无法等待人工确认,至少要保留可恢复的原历史和失败回退路径。
耐久事实尽量放在 prompt 之外:补丁在 Git,测试报告与长日志在 artifact store,决定记录在任务文档,摘要只携带继续工作所需的索引与关键内容。这样压缩失败时仍有恢复源,同时避免把“外部有记录”误当成“模型无需看到任何证据”。
三种策略的决策树
面对一段增长中的上下文,可以按下面顺序判断:
- **新事实是否只是让旧状态过期?**如果是,而且旧记录仍用于审计或当前任务尚短,优先 append:追加变化通知、版本与新结果,不改写旧 read。
- **旧内容是否确定无用,且无需保留其证据?**如果是,再看删除位置。靠近尾部、体量可观且未来轮数较多时可以 prune;删除很早且后面有长幸存后缀时,先估算 prefix rewrite,确认删量足以抵消一次性成本。
- **旧历史仍有信息价值,但原文体量太大或逼近 context limit?**选择 compact,把目标、约束、证据、版本、测试和风险转成结构化新状态,并为摘要生成成本与遗漏风险留预算。
- **是否在自然任务断点?**若证据仍有争议,先 append 到事实稳定;若补丁/测试阶段刚结束,或即将进入新目标、模型、工具 epoch,compact 更容易定义完整边界。
- **后面还有多少轮?**未来轮数越多,prune/compact 的短请求收益越可能回本;任务马上交付时,保留已缓存证据往往比建立新前缀更稳。
- **质量或窗口是否已经成为硬约束?**若旧冲突信息显著误导模型,或窗口无法容纳下一步,不能为了缓存命中坚持 append;应 prune 明确垃圾,或 compact 有价值历史,并记录新的 prefix epoch。
没有跨任务的固定赢家。Append 用空间换前缀稳定与审计;prune 用一次性重写换未来更短请求;compact 用摘要成本与信息损失风险换一份新的可执行状态。正确答案取决于证据价值、首个编辑位置、未来轮数、缓存与窗口条件,以及任务失败的代价。
上一篇:The Price of a Miss:TTL、路由与模型切换的隐藏成本
下一篇:Context Budgeting:在质量、延迟与成本之间做决策