Context Rot: Why Claude Code Sessions Decay, and How to Govern Them
TL;DR · AI 摘要
Context Rot: Why Claude Code Sessions Decay, and How to Govern Them Towards Data Science Agentic AI Context Rot: Why Cla...
核心要点
- 主题聚焦:Context Rot: Why Claude Code Sessions Decay, and
- 来源:Towards Data Science,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
上下文腐烂:Claude代码会话为何退化及如何治理 | Towards Data Science
Agentic AI
上下文腐烂:Claude代码会话为何退化及如何治理
会话在达到令牌限制前就会悄然退化。解决方案不是增加上下文,而是实现对上下文的治理。
Jake Minns
2026年7月13日
20分钟阅读
分享
作者绘制
上下文窗口是每个前沿模型的核心功能。以令牌为单位进行衡量,通常由系统提示和一段尽可能长的历史记录组成,包括提示、响应和工具调用。
每当模型生成一个令牌时,它会首先回顾整个上下文窗口。这是模型记忆对话会话的唯一机制。模型在连续对话中不会保留任何持久的内部状态。正如我们将看到的,上下文窗口并非被动存储,其中的每个内容都可能以好或坏的方式影响模型输出。
这是一种常见体验:会话起初表现良好,但逐渐失去主线。由上下文窗口内容引发的模型输出质量逐步下降现象,通常被称为上下文腐烂。
我们可以将上下文腐烂的来源分为两类。第一类是内在腐烂:模型在生成输出时,对上下文窗口中注意力分配机制的固有属性。我们稍后会详细探讨,但目前的关键结论是:无论上下文多么相关,模型对信息的回忆和区分能力都是不完美的。
第二类我们称为内容腐烂:这是会话过程中陈旧、错误和矛盾信息的累积。模型反复回到失败的方法,或处理数十个没有结果的边缘工具调用。所有这些内容被反复处理,逐渐塑造会话结果。
幸运的是,与内在腐烂不同,内容腐烂是我们可以管理的——掌握其控制方法,就能将Claude Code等工具从偶尔令人沮丧转变为始终如一的精准。
内在腐烂:模型的底层限制
内在腐烂指的是模型架构本身固有的性能限制。我们无法通过提示来规避或改进它;它是所有其他因素的基础。但了解其机制有两点重要意义:一方面,它解释了本文后续部分提到的一些实践方法背后的动机;另一方面,它有助于破除"模型内部有工程师默默理解你的项目"的幻觉。(关于注意力机制的完整解析,3Blue1Brown的课程非常出色。)
每次交互,你的完整会话(提示、响应、文件读取、工具输出)都会被展平为一个连续的令牌序列,输入模型处理。尽管LLM架构复杂,但注意力头是影响上下文的关键组件。每个注意力头都在回答一个问题:每个令牌应如何依赖其前面的所有令牌?相关令牌对后续预测有显著贡献;不相关令牌贡献较小,但永远不会完全无关。
“从不为零”正是关键所在。每个头部的相关性分数会经过softmax函数处理,该函数强制要求分数总和为1。这意味着注意力资源是固定的。由于softmax基于指数运算,没有任何一个标记的资源占比能恰好为零。然而,无论相关性如何,窗口中的每个标记都会争夺相同的固定资源预算。这不是实现上的偶然;softmax正是注意力机制的核心,因为它能将竞争性分数转化为平滑且可学习的分布。但这一有用特性也意味着权衡:无关上下文永远无法免费被忽略。需要明确的是,稀释效应只是长上下文退化的主要解释之一。模型确实学会了部分变通方法,例如将多余注意力分配到临时位置,但这些只是缓解措施,并非彻底解决。
这一后果表现得较为微妙。随着上下文增长,模型仍可能将正确标记排在首位;排序关系得以保留。真正被削弱的是“间隔”:关键标记与其他无关信息的差距。即使模型“知道”该在哪里查找,读取结果也会变得模糊,混杂着来自周围窗口数千个微弱信号。将其视作稀释效应:固定资源被不断摊薄,信噪比持续恶化。
所声明的上下文限制并非性能断崖;退化过程是渐进的,且在早期就已开始。
位置同样关键。词序是语言的基础,因此模型会编码标记的位置(开源模型通常采用旋转位置编码;封闭系统极少公开细节)。具体实现机制不如实际影响重要:将事实置于长上下文的不同位置时,检索准确率会呈现U型曲线,起始和末尾位置最高,中间位置最低(Liu等,2024)。确切原因仍在争论中。对我们而言重要的是中间区域的下降:在数小时的会话中,主要内容大多位于中间区域,而此处并非安全存储空间。
这些被称为“针尖在干草堆中”的基准测试。对于简单情况,供应商曲线相对平缓且已基本解决。仅略微增加检索难度(例如去除问题与答案间的简单词重叠,迫使模型基于语义匹配,NoLiMa,Modarressi等,2025),准确率下降会比宣称的限制提前出现。当同时处理多个信息片段并进行跨片段推理时:即使检索完美且所有干扰项都被移除(Du等,2025),性能在声明的窗口范围内就会显著下降。能够从10万个标记中找到事实的模型,未必能在此处基于该事实进行推理。
因此,任何试图完成工作的人都应秉持的假设是:你实际可用的上下文资源预算远低于任何宣称的限制。
内容腐化:会话如何退化
本节讨论会话期间加载到上下文中的内容。此前我们将内容腐化描述为可控领域。虽然确实如此,但实践中会话涉及多方参与者:我们、模型以及沿途生成的任何子代理。
我们赋予的自主权范围通常由一组允许的命令决定:在项目工作区中搜索、从远程Git仓库拉取代码、通过MCP访问公司知识库。这些工具的核心价值在于适度的自主权,正是这种自主性带来了生产力的提升。当我们逐步分析导致内容腐化的各种失败模式时,目标并非收紧限制,而是认识到我们仍然是系统中最有效的纠错机制:能够识别出过于宽泛的文件读取操作、发现实际上缺失配置却被误认为是错误的提示,并在造成损害前及时阻止这一循环。
你的会话本质上是一个反馈循环。每次交互中,模型上下文窗口内的所有内容、每次文件读取、工具输出和失败尝试都会被重新输入模型,塑造其后续生成内容,最终融入下一个token的生成过程。输出即成为输入:清晰的步骤会生成清晰的语句,混乱的步骤则生成混乱的语句,而模型会持续基于自身的困惑进行条件判断。错误持续存在并向前传播。
以下四种失败模式源自Drew Breunig对上下文失败的分类(混淆、冲突、分心和中毒),出自他的文章《How Long Contexts Fail》,此处结合代理式编码会话的日常实践进行阐述:
- 过度加载作用域(混淆)
如今,每个会话开始时加载的工具、技能和MCP服务器清单正变得越来越庞大。这在孤立场景下是有道理的:专用工具配合专业指令集往往能实现高效运行。但你是否注意到模型会为最简单的任务甚至错误地调用工具?更糟糕的是,臃肿的工具体系不仅会混淆工具选择(Kate等,2025),还会在每次交互中增加输入内容,而我们已证明输入长度远低于声明限制时,推理性能就会显著下降(Levy等,2024;Du等,2025)。
面对任务时,拥有大量工具的模型倾向于优先调用工具,即使自身知识足以完成任务:存在一种倾向性,即通过调用工具来弥补推理漏洞(Zeng等,2026)。更直白地说,即使闲置的工具也并非免费:其定义会在每次交互中占据窗口空间,无论是否调用,都会占用注意力预算的一部分。
- 调试支线干扰理论构建(冲突)
你是否经历过这样的场景:费尽心力说服模型其诊断错误,而它却不断将新矛盾观测结果强行纳入已失效的假设框架,最终在任务完成之后又回到原点?已有研究证实:模型会过早锁定某种解读并难以放弃。当一次性获得完整信息时,它们能干净利落地解决问题,但当相同信息以碎片化形式分散在多次交互中时,却会变得手足无措(Laban等,2025)。此处的诊断表明,问题路径本身可能变得顽固。
- 广泛搜索并引入类似干扰项(分心)
当代理程序广泛抓取或转储目录以寻找所需内容时,它会引入测试用例、废弃实现、模拟代码和名称相似但来自无关模块的函数,这些内容看起来都似乎合理。问题在于模型会过度依赖窗口中的上下文:它们不会将上下文视为可选的参考资料,而是可靠地将其纳入考虑,即使这些内容与当前任务无关(Shi 等人,2023)。一个与主题相关但无关的干扰项就足以显著降低性能(Mirzadeh 等人,GSM-Symbolic,2024),而代码库中充斥着这类干扰项。模型过度关注搜索结果中引入的内容,反而牺牲了原本可以清晰完成的推理过程。这直接导致了"地板效应":目标与邻近选项之间的边界消失,原本旨在澄清问题的搜索反而在窗口中填充了大量看似合理的替代方案,模型无法避免关注这些内容。
让错误信息固化为真理(污染效应)。
这种现象在长期任务中尤为普遍。通常源于善意:随着项目规模扩大和决策累积,代理程序会将发现的内容记录在 NOTES.md 文件或一系列命名文件中,用于跟踪已采取的步骤和完成的工作,以提供未来有用的上下文。但后来你在实时聊天中发现其中某个假设是错误的,于是打断、纠正并继续推进。但该笔记从未更新。它静静躺在文件中,之后会被重新读取,并可能比你的聊天纠正更持久。一旦纠正内容滑出窗口,过时的文件版本就会成为最终留存的版本。模型生成的笔记不是证据,而是等待验证的主张。这种失效模式是"过时状态",而非状态本身。
在 Breunig 的四种模式基础上,我还要补充"复利效应"。每种模式都会引发下一种:文件中的错误笔记会让代理程序去搜索一个根本不需要修复的问题,搜索过程又会引入一堆看似合理的替代方案,这些错误引导其形成新的错误理论,而为解决问题而创建的调试分支又会生成新的错误。每一步都依赖于前一步积累的混乱,错误不仅会叠加,还会让后续错误更可能发生。
这引出了一个更微妙的要点:模型在性能下降时很少会表现出犹豫。在走错方向后,它会继续在错误基础上构建,而不是标记错误(Laban 等人,2025)。一个深陷错误的会话可能在输出崩溃前看起来和正常会话毫无二致。你不能指望模型自行终止,因此要获得最佳结果仍需要外部观察者的介入。
如果真正的限制因素是内容质量,那么上下文管理的重点就不再是节省token,而是识别这种模式并改变未来行为。
上下文管理:日常实践
实用原则很简单:上下文不是存储空间,而是主动输入。以下内容以我最熟悉的 Claude Code 工具为框架进行说明,但每种实践首先都是一种原则:这些指导原则适用于任何智能体工具,即使命令名称存在差异。
在首次提示前:精心筛选
如果这一点还不明显,那么一个良好的开端比任何恢复路径都更有价值。这就是前期工作的价值所在:一个精心策划的项目(严谨的 CLAUDE.md 文件、恰当的技能和钩子)能让新会话的启动成本极低。成本只需一次性支付,且是有意为之,这样每次重置都几乎可以免费进行。
在你工作时:保持简洁
保持目标的最新性。我们知道,在长上下文窗口的中间部分,模型的回忆能力最不可靠。在处理长期任务时,每个里程碑都要刷新会话目标:重新陈述当前目标、实时约束条件,以及代理需要重写的简短任务列表。
将冗长的工作交给子代理处理。测试运行、日志检索、依赖项审核:任何生成大量输出且你不需要保留的内容。我们只需要结论。
将持久状态外部化并确保其准确性。代理维护的笔记文件存在于窗口之外,并且在重置后仍能保留;任何需要跨会话持续存在的内容都可以放在这里。但要保持有意图:过时的记忆比没有记忆更糟糕。它可能比你在聊天中做出的修正更持久,并在之后重新出现为看似真实的事实。
回归真实情况,并养成习惯。检查文件、测试、实际错误,而不是模型对它们的记忆。! 前缀运行shell命令,并将真实输出直接插入上下文,因此 !git status 或 !npm test 会再次将会话锚定在真实情况上,而不是模型的回忆。如果你发现自己经常这样做,请将其作为自己的技能:一个小型技能,可以一次性注入项目的真实状态(git状态、测试结果、类型检查)。
当它转换时:重置,不要推进
/plan
这是最难遵循的指导原则;此时,你已投入的对话历史仿佛是即将放弃的进展。这就是沉没成本谬误:无论你保留还是清除对话窗口,这些token都已经消耗,唯一的问题在于下一次对话是从腐烂的上下文中开始,还是从健康的上下文中开始。
对同一问题的两次纠正是一个信号。当你已经两次强调错误不在认证层,但问题仍反复出现时,立即停止。重置。
如果能及时发现,就在步骤进行中拦截。如果你看到它朝着错误的方向发展,按下Ctrl+C并重定向,而不是让错误的改动落地堆积。如果已经发生,按下Esc+Esc开启回放功能:将对话、代码或两者都回退到之前的检查点。
重新生成,不要延续旧历史。对于自包含且可重新推导的工作,用干净会话重新生成结果通常优于浓缩的历史记录。当尝试已经出错时,这种效果最为明显。在Letta的恢复基准测试中,那些被提供完整失败历史记录的代理表现不如从干净状态开始的代理,腐烂的上下文实际上阻碍了恢复,而非提供帮助。
何时行动以及携带什么信息,取决于当前会话的状态:
- 新任务且与之前无关 → 使用/clear。你不需要任何旧上下文。
- 相关工作的下一阶段,会话健康 → 传递状态(见下文)。
- 任务中途达到上下文限制,会话健康 → 使用/compact。你需要浓缩的历史记录。
- 会话已偏离正轨 → 手动快照状态并清除。你不能依赖模型对它无法良好读取的上下文进行总结,因此你需要自己从文件、差异、测试和已知决策中提取关键信息,然后重置。
传递状态,而非对话记录。在健康的边界处,让代理撰写一个交接简报。
在此处,有指导性的失败是有用的,而你保留它们的方式也很重要:将每个失败浓缩为一行教训(“尝试了X,失败是因为Y”)。
在简报和真实数据基础上开启新会话。当新窗口的首次操作是定位而非猜测时,新窗口启动最快。让它查看交接简报,然后直接建立现实:阅读相关文件、运行测试、检查git状态。简报告诉它情况曾处于何种状态;实时检查则告诉它当前实际状态。
对于长期或并行任务
在可验证的接口处进行分解。将任务拆分在结果易于验证的位置(通过测试、构建或数值对账),而非拆分到最小可能单元。
仅当各部分独立时才分发子代理。每个子任务拥有干净的上下文并返回浓缩摘要,这对横向扩展(并行研究、独立查询)效果良好。对于编程任务,由于子任务通常耦合,独立代理会产生冲突假设,你将继承协调它们的责任。紧密耦合的工作应保留在单一主线程中,若确实需要隔离且避免协调风险,git工作树(claude -w)可为并行任务提供独立分支。
隔离调试过程。当遇到bug时,分支操作本身就能带来回报。将调查发送到分支或从头开始新会话,让它在你将丢弃的上下文中形成并抛弃错误理论,仅带回原因和修复方案。
引入新视角。通过/code-review或从干净上下文读取的专用评审子代理,可能发现腐烂会话自身无法看到的问题。
将其串联:会话作为git树
本节描述了我根据上述发现形成的工作流程。其目的是让这些原则自动化,并消除同时运行多个会话时的决策疲劳。该流程未经基准测试,因此请将其视为一位实践者的综合方案。
将一次会话构想为一个 git 分支。你的主对话线程是任务的协调者,目标是让其上下文保持干净且专注于任务。当出现需要耗费资源的岔路(如研究、死胡同或数据挖掘等)时,将其分支到独立的会话中(使用 claude --continue --fork-session 命令),并在该分支中处理复杂工作。这与子代理的分支存在差异。子代理已经能够处理并行的独立工作:生成、探索、返回摘要。而分支则用于主线程的顺序岔路:需要完整会话历史才能理解的复杂调查,其中没有自动返回路径,你需要选择返回内容。
这为上下文管理提供了简单且熟悉的模式:分支、探索、提炼、合并。分支的设计本就是可丢弃的上下文。它可以积累任意多的冗余内容,因为你会丢弃上下文窗口,只保留结果。分支中发生的一切都不应污染主会话。
当分支得出答案后,你将其提炼并合并回主会话。这个过程的两个环节是我在原生分支功能基础上构建的技能。/conclude 技能会将分支内容写入专用文件夹(我使用 ./cc-branches)中的单个文件;类型参数会从多个模板中选择,这些模板描述了不同类型的分支结束方式,与执行的工作类型匹配。在主会话中,/merge 技能会加载该文件,将结论追加到上下文中,而不会包含产生结论的任何噪声内容。
除了上下文卫生管理,git 的框架还为我的工作流程带来了清晰度,减少了同时切换多项任务时的疲劳感。它提供了一个清晰的思维模型:每个命名分支都成为一个独立的问题,附带一个清晰的答案,供未来参考。
到目前为止的所有内容都假设存在一个校正者在循环中,大多数情况下这个校正者是人。不是因为人类比模型更擅长这项工作,而是因为模型没有内部刹车。它无法看到自己的冗余内容,因此校正必须来自模型外部。这就是上下文治理的本质:由窗口外的某人决定哪些内容进入上下文,哪些内容被排除,哪些内容得以保留。
受控上下文
花些时间观察 Claude Code 的运行,看它如何读取文件、执行命令、编写补丁、生成子代理并维护笔记,很快就会感觉它像一个小小的工程师在后台工作:内化你的代码库,理解任务与错误之间的边界,并专注于解决问题。
但事实并非完全如此,也并非以这里相关的方式。
模型不会将你的项目视为一个项目。它只关注当前上下文:指令、可见文件、工具输出、近期对话。所有这些内容共同构成工作输入。其中一些是信号,一些是噪声,一些曾经是真实的,一些从未真实过。模型仍需继续行动。当我们把会话视为累积的理解时,我们的行为会发生变化,输出质量会下降,有时甚至会将问题归咎于模型。
这就是为什么上下文管理不是次要问题。Claude Code 是控制进入模型上下文的界面,其核心命令(clear、compact、rewind、branch、subagents、skills 和 hooks)不仅仅是便利功能。它们决定了哪些内容进入上下文、哪些保留在外部、以及会话之间哪些内容得以保留。
治理上下文并非控制模型本身。这是在架构设定的底层之上,对属于你的内容部分进行管理。底层的改进超越了实验室的时间表,而内容层则随着你的成长而提升。
使用这些工具取得成功的关键在于:不是追求最大化的上下文,而是实现受控的上下文。
撰写人
查看 Jake Minns 的所有文章
AI 编程助手
,
人工智能
Claude Code
上下文工程
深度解析
分享本文
- 在 Facebook 上分享
- 在 LinkedIn 上分享
- 在 X 上分享
Towards Data Science 是一份社区出版物。提交你的见解以触达全球读者,并通过 TDS 作者支付计划获得收益。
请将 href 更新为你的实际投稿 URL
为 TDS 撰写文章
✦ 结束 CTA ✦