压缩发生后,我盯着终端发呆了三秒钟。我的 AI 编码代理刚刚经历了一次彻头彻尾的“脑叶切除术”——二十万 token 的项目上下文被平台自动压缩成一个干巴巴的摘要,它从“记得每一个设计取舍的资深协作者”变成了“一个热情但失忆的新实习生”。就在压缩前五分钟,它还在跟我讨论某个模块的重构方案,压缩结束后的第一条消息,它毫不犹豫地准备把配置文件重写一遍,理由是“这样更整洁”。
那种感觉不是沮丧,是后怕。如果我没有在压缩发生前养成习惯,项目的架构约定、踩坑记录、尚未完成的接口约定,就会随着那二十万 token 一起蒸发。
我最后找到的解药,说出来有点奇怪:一个是 Unix 世界的老古董 init.d,一个是诺兰那部讲短期记忆丧失的电影《记忆碎片》。前者教会我把“上下文压缩”当作系统重启来对待,后者给了我一套外部记忆的思路。这套思路我实践了两个多月,经历了十几次真正的上下文压缩,代理再也没在压缩后干过蠢事。这篇文章就把这套完整方案摊开讲。
1. 先看清“脑叶切除术”切掉的是什么
1.1 上下文压缩到底干了什么
很多 AI 编码代理平台都有这个机制:当对话上下文接近上限(比如 20 万 token),会自动触发“压缩”,把之前的完整对话历史折叠成一小段摘要,释放出空间让对话继续。本意是好的,就像内存不够时操作系统开始交换页面。
问题在于,摘要不是原样保留,而是“概括”。二十万 token 里包含的东西,压缩后往往只剩下一个故事梗概。举个例子,你的代理之前通过十几轮对话才确认了“用户配置采用 TOML 而非 YAML,解析时保持兼容旧版字段名”这个决策,压缩后的摘要可能只剩一句“用户配置模块已按既定方案实现”。等到下一步对话涉及配置格式时,代理大概率会基于常识重新选择一个格式——YAML,因为那是它训练数据里最常见的。
更隐蔽的是,一些隐性的操作习惯会被压缩掉。比如说,我习惯在代码里用中文注释,并且明确告诉过代理“注释用中文,代码命名用英文”。这种指示如果在压缩时没有出现在摘要里,代理解释行为就会出现偏差。它并不是“变笨了”,而是“失忆了”。
1.2 压缩后的典型症状:勤奋且危险
我把自己经历过的“压缩后遗症”归纳成几类:
| 症状 | 具体表现 | 危险程度 |
|---|---|---|
| 约束丢失 | 忘记之前定的技术选型,按训练数据里的“主流方案”重新选择 | 高 |
| 事实虚构 | 记不清某个函数是否存在,直接生成一个同名但逻辑不同的版本 | 极高 |
| 风格漂移 | 代码风格、注释语言、目录组织方式突然变化 | 中 |
| 重复劳动 | 把之前已经解决过的问题重新解决一遍 | 中低 |
| 攻击性重构 | 认为现有代码“结构混乱”,主动重写原来完全正常的部分 | 高 |
最危险的显然是“攻击性重构”。压缩后代理面对一大段自己不完全理解的代码,最常见的补偿策略就是“重写以建立确定性”。我有一次真实经历:压缩后代理强烈建议我把整个配置文件加载逻辑改成一种更流行的模式,理由是“这套代码缺少模块化”。实际上,原来的代码是为了兼容三套老旧系统而特意保留的顺序加载逻辑,只是这个原因写在代码注释里,而注释没有被摘要保留。
1.3 为什么“加大窗口”解决不了问题
很多人说,上下文不够就加窗口呗。但我在实践里发现这条路的边际效益非常低。窗口变大,你塞进上下文的内容也变多,最终还是会触顶;而且更长的上下文意味着更高的成本,每次请求的 token 费用直线上升。单位逻辑没变,只是往后推迟了爆炸时间。
真正需要改变的不是“怎么装更多”,而是“丢了怎么找回”。这个东西在工程上有一个现成的名字:无状态重启。服务器重启后不需要记得上一次进程在内存里的状态,它只需要按照启动脚本重新把服务拉起来。而这个思路,正好被我迁移到了 AI 代理的场景里。
2. 把 init.d 当作代理的“操作系统”
2.1 init.d 教给我的第一课:启动脚本不是记忆
Unix 系统里的 init.d,是 System V 风格初始化体系的核心。系统启动时会按照脚本名顺序执行/etc/rc.d/init.d/里的启动脚本,依次拉起网络、存储、应用服务。这个体系有一个关键特性:它不依赖系统“记住”上一次会话的状态。哪怕上一次系统是崩溃的,只要脚本本身定义正确,启动过程就是确定性的。
我第一次意识到这个类比的价值,是在一次系统重启之后。我重启了我的开发机,SSH 连上去,所有服务都按预期启动了。那一刻我突然想:如果我的 AI 编码代理也能像这样“启动”就好了——不依赖对话历史的连续记忆,每次压缩后都能按照一套定义好的脚本恢复工作状态。
这个想法的核心转变是:别把上下文压缩当成“灾难”,把它当成“重启”。服务器重启是常态,代理上下文压缩也是常态。你要做的不是阻止重启,而是保证重启后能恢复。
2.2 给代理设计一套 init 启动目录
具体做法并不复杂:在项目根目录建立一个初始化目录,把代理每次工作时必须知道的东西拆分成独立的“启动脚本”。
我自己的项目结构长这样:
project/ ├── .init.d/ │ ├── 00-project-brief.md # 项目全局说明,1-2 段话 │ ├── 01-active-thread.md # 当前正在进行的工作线程 │ ├── 02-critical-decision.md # 最近 10 条关键决策及理由 │ ├── 03-staging-notes.md # 未完成改动的暂存笔记 │ └── 99-recovery.md # 压缩后的恢复检查清单然后我会在项目根目录放一个专门的指令文件(名字叫AGENT_INIT.md或者直接配置为代理的初始指令),内容大概是这样的:
你是该项目的一个长期协作者。每次会话开始、上下文被压缩后、或者当你发现自己对项目了解不足时,请执行以下步骤: 1. 先阅读 `.init.d/00-project-brief.md`,了解项目是什么。 2. 阅读 `.init.d/01-active-thread.md`,了解当前线程目标和未完成任务。 3. 阅读 `.init.d/02-critical-decision.md`,了解哪些技术决策是必须遵守的。 4. 如果存在 `.init.d/03-staging-notes.md`,逐条检查其中记录的问题是否已解决。 5. 只有在完成以上阅读后,才开始处理用户的新请求。这套做法的生效机制在于:代理不是“记住”了什么,而是“每次启动都重新读取”了什么。就像 init.d 里的脚本,它们不需要靠内存,它们靠的是磁盘。对话上下文会被压缩,磁盘文件不会(除非你手动删)。
2.3 压缩并非危机,而是一个可控事故
当我把“压缩后执行 init”变成默认流程之后,我的心态发生了明显变化。以前压缩事件来临时我如临大敌,担心代理会突然降智;现在压缩来了,我只会在心里想:好,到了重启时间,跑一遍启动脚本就行。
这种心态转变还带来一个额外好处:我开始更认真地维护.init.d。以前我会把很多约定放在脑子里,随用随说;现在我会在每次做出重要决策后,立刻更新02-critical-decision.md。因为我知道,如果不写在文件里,下一次压缩就会把它带走。写文件不是在记录历史,是在给未来的自己写启动脚本。
3. 记忆碎片式的外部记忆:把线索留在世界里
3.1 电影的核心启发:不可靠记忆 + 可靠笔记
《记忆碎片》里,主角因为大脑损伤患上了短期记忆丧失,记不住新发生的事情。他没有靠“反复背诵”来对抗失忆,而是建立了一套外部记忆系统:宝丽来照片、纸条、甚至肉体上的纹身。每一张照片都记录了一个事实,每一行字迹都是一条给未来的自己的线索。当他读到这些线索时,他不需要“记得”发生了什么,只需要“相信”记录的内容。
这个设定完美对应了 AI 代理面对上下文压缩的处境。代理的“对话记忆”就像主角的短期记忆,随时可能被抹掉。但文件的持久性不依赖于对话历史——它们是代理的宝丽来照片。我曾经有过一个体验:压缩后的代理读到了我在代码里留下的一行注释,那行注释明确写着“此分支用于兼容旧版接口,不要移除则否则导致——”。代理看到后,立即中止了原本打算做的“清理”。那一刻我意识到,注释不是写给人类看的,也是写给我未来的 AI 协作者的。
3.2 决策日志:给代理留一张可靠纹身
如果说照片是用完即弃的线索,那么纹身是永久性的、不可忽视的约定。放在项目里,对应物就是决策日志——一份长期有效、优先级最高的技术决策记录。
我推荐的格式不需要很复杂,关键是要可索引、可追溯:
## DEC-0007: 配置文件格式保持 TOML,禁止迁移到 YAML - 状态: 已生效(2025-01-12) - 背景: 旧系统遗留了三套 TOML 配置文件,迁移成本高且影响面大。 - 决策: 新代码继续使用 TOML,并保留对旧字段名的兼容解析。 - 约束: 不引入新的 YAML 依赖,不重写现有加载逻辑。 - 关联文件: src/config/loader.py这样的条目我会累积至少十条。它们的作用不只是“给代理看”,更是给代理一个“违背前的自我警告”。当代理想要重构某个模块时,它会先读到相关决策,知道这个模块背后有历史包袱,从而避免盲目动手。
3.3 给未来自己留便签:暂存笔记与待办痕迹
决策日志适合写已经确定的事情,但压缩发生时,往往还有一些悬而未决的临时状态:某段代码改了一半、某个 bug 还在排查、某个接口的命名还有争议。这些内容如果不留痕,压缩后会彻底丢失,而且连线索都没有。
我的做法是维护一个STAGING.md,只记录当前时刻“进行中”的事情:
# 暂存笔记(压缩恢复优先阅读) 1. 正在重构 `src/service/payment.py` 的异步调用,已完成 60%,剩余: - 更新异常捕获逻辑 - 为超时重试补充测试 2. 昨天排查了一个偶发的连接超时问题,初步怀疑是连接池默认大小过小, 但尚未完全验证。相关日志见 logs/2025-01-10-timeout.log 3. 与用户口头约定的“错误码统一使用负数”尚未落实到文档。这就很像主角给自己写的便签。他不一定理解每条便签的完整背景,但只要知道“这件事还没做完”,就能继续推进。代理也是同理——它不需要理解为什么这个 bug 存在,只需要知道“排查到哪一步了”,就能从断点继续。
3.4 失败记录:让代理不重复踩同一个坑
还有一个容易被忽视的外部记忆类型:失败记录。我会在.init.d/下放一个FAILURES.md,记录代理做过哪些错误操作以及后果:
# 失败记录(不可重复) 1. 2025-01-08: 在配置加载模块中擅自使用 YAML 解析,导致旧配置读取失败。 教训:项目统一使用 TOML,禁止引入新序列化库。 2. 2025-01-09: 自动判断“结构重复”并合并了两个功能不同的函数。 教训:在没有咨询决策日志前,禁止自行合并或删除看似冗余的代码。听上去像在“管教”代理,但实际效果是双方共赢。代理看到失败记录后会变得警觉,不会轻易尝试危险的优化;我也会省去反复强调同一件事的沟通成本。人的长期记忆不可靠,代理的上下文更不可靠,唯一可靠的只有写下来的字。
4. 实现“压缩免疫”的完整启动流程
4.1 恢复三步曲:确认、重读、执行
当一次上下文压缩发生后,我不再手动把关键信息重新打一遍,而是直接让代理执行一套固定的恢复流程。我把这个流程称为“恢复三步曲”。
第一步:确认压缩发生。代理在检测到自己的“历史记忆”中出现了“之前内容已被截断/汇总”的提示时,主动声明“我将执行恢复流程”。
第二步:按顺序重读.init.d/中所有启动文件。特别注意00-project-brief.md与02-critical-decision.md,这两个文件奠定了项目的世界观。
第三步:检查STAGING.md和FAILURES.md,确认没有未了结的事项,然后才回到用户当前需求。
在代理平台的系统指令层面,我会把这一步做成可触发的显式指令:
当你发现自己处于“上下文被压缩”或“记忆模糊”状态时,立即运行恢复流程。 恢复流程 = 依次阅读 .init.d/00、.init.d/01、.init.d/02、.init.d/03, 然后报告:我已恢复项目上下文,当前活动线程为 X,接下来建议处理 Y。这套指令让压缩后的第一次回复不再是“惊吓”,而是一份状态报告。我至少能从报告里判断代理是否真的回到了正确的上下文轨道。
4.2 压缩前的预防性快照
恢复流程解决的是“压缩发生后怎么办”,但还有一件事值得养成习惯:在压缩发生前主动留下快照。平台的自动压缩时机我不一定控制得了,但我可以在意识到上下文即将触顶时,手动把一些“不该丢的临时状态”写入临时记忆文件。
我的实操方法是:每次连续工作超过一小时,或者代理已经讨论了三个以上技术决策,我就把它最后总结出的结论复制到session-archive.md。不需要格式,只要把“结论”和“尚存疑问”分两段写清楚就行。
这样做还有个额外收益:压缩后恢复时,我多了一个第三方的“证人记录”。即使.init.d/里某些文件忘记更新,session-archive.md仍然能提供当时的实际讨论内容,代理可以据此判断哪些目标已经完成,哪些还在进行。
4.3 压缩后自查:明确“我不知道什么”
只让代理读文件还不够,更重要的是让代理学会识别“自己不知道什么”。我设计了一组压缩后的自查提问,让代理在恢复后主动回答:
恢复检查清单: 1. 这个项目最核心的不可变约定是什么?(从 .init.d/00 中查找) 2. 当前进行中的改动有哪些?(从 STAGING.md 中查找) 3. 最近犯过的错误有哪些?(从 FAILURES.md 中查找) 4. 我有没有在恢复前向用户提问的需要?如果有,列出来。这组提问把代理从“我什么都知道”的幻觉里拉了回来。实践里我发现,压缩后的代理最容易犯的错误恰恰是“过度自信”——因为它不知道自己的记忆被切掉了,还以为自己拥有完整的上下文。自查提问迫使它先承认无知,再沟通。知道自己不知道什么,比知道自己知道什么更重要。
5. 常见问题与排查技巧实录
5.1 最常见的几个翻车现场
我整理了一份速查表,都是我在真实项目中遇到过的:
| 症状 | 根因 | 应对 |
|---|---|---|
| 代理毕业后第一次回复就建议重构 | 未执行恢复流程,直接基于当前上下文作答 | 强制其在回复前执行恢复三步曲 |
| 代理说“根据之前的讨论我建议……”但讨论内容它从未参与 | 压缩摘要把别的会话内容混入,产生虚假记忆 | 要求引用决策日志条目编号,无编号不得视为历史依据 |
| 代理在恢复了 init 文件后仍然用错格式 | 决策日志没写清楚“为什么”,代理认为旧规则不适应当前场景 | 在决策日志中增加“约束”字段,写明不可变更的原因 |
| 代理一直重复询问已经确认过的问题 | 对话被压缩但 init 文件没有同步更新 | 每当做出新决策,立刻更新.init.d/02-critical-decision.md |
5.2 处理“代理连 init 文件都错过了”
有一次遇到一个比较刁钻的情况:代理在压缩后执行了恢复流程,但它读完了01-active-thread.md,却跳过了02-critical-decision.md。结果它在一个关键决策上犯了常识性错误。
排查后发现,原因是我给02-critical-decision.md起的文件名开头是“02”,代理误以为它是某种可选的“追加记录”,优先级意识不强。调整方式很简单:把文件改名成00-MUST-READ-decision-log.md,甚至在文件内用粗体注明“此文件为强制阅读项”。这类命名上的小调整,实际效果立竿见影。
5.3 代理太依赖记忆文件导致行动力下降
另一个极端也出现过:代理每次操作都要先确认一堆 init 文件,做事变得黏糊。比如改个变量名也要先读一遍完整决策日志,效率明显下降。
我的处理是给 init 文件设了“失效时间”。在文件开头注明适用范围:
适用范围:本文件内容在 2025-04-01 前有效; 涉及子模块:src/service 与 tests/test_service; 不适用于 src/util 下的纯工具函数。这样代理可以快速判断哪些规则与当前任务相关,不至于全盘套用。恢复流程存在的意义是让代理更快回到正确轨道,而不是让它背完一本书再开始干活。
6. 工具选型与数据分层:三个层级的记忆
6.1 可供选择的“外部记忆”形态
把“外部记忆”落地时,可以选择不同工具,我分别试过几种:
| 形态 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 纯 Markdown 文本文件 | 零依赖、可读性高、人类维护方便 | 检索效率低、信息密度有限 | 决策日志、启动脚本 |
| 语义索引(本地向量库) | 支持自然语言查询、上下文无关 | 引入额外依赖、索引需要维护 | 大规模项目文档向量化检索 |
| 代码内注释与断言 | 直接附着在代码附近,压缩后也能被读取 | 不适合哦,过量注释干扰阅读 | 关键函数实现处 |
| 独立的记忆服务 | 存储结构化历史,代理主动查询 | 运维成本高,对小项目不划算 | 大型团队协作、长期多代理场景 |
我的原则是:大多数项目根本不需要往语义索引方向走。纯文本已经足够,因为代理本来就有很强的文档阅读能力。真正重要的不是存储媒介,而是你是否建立了“压缩后必须去读”的流程。
6.2 我目前的最终架构
实践了两个月,我目前的方案收敛成了三层:
第一层是“启动层”,对应.init.d/,解决“每次会话/压缩后必须知道什么”。第二层是“决策层”,对应DECISION_LOG.md与FAILURES.md,解决“哪些事情不能做、为什么不能做”。第三层是“工作层”,对应STAGING.md与临时档案,解决“当前正在做什么”。
会话开始 → 读取启动层(.init.d/) → 碰到决策类问题 → 查询决策层 → 执行中更新工作层 → 压缩发生 → 回到启动层这个循环看起来简单,但实践里有很强的惯性效应。最开始我经常忘记更新STAGING.md,压缩发生后代理丢掉的临时状态必须靠我重新口述。刻意训练了大概一周之后,我养成了“完成任何一个阶段性改动后顺手写一句话”的习惯,后面的运行就顺滑多了。
6.3 工具不是万能药,纪律才是
说句大实话,市面上已经有一些代理框架自带了“记忆功能”插件,号称能自动保存和恢复上下文。我也试过一两个,效果呢?有,但不稳定。自动记忆经常把不该缓存的东西存下来(比如中间过程的废话),而真正关键的决策反而时有时无。
我最终选择回到纯手工维护的.init.d/还有一个原因是:它透明。我看到的东西和代理看到的一模一样,不会出现“代理记得某件事但我不知道它从哪记得”的诡异感。对于一个小型项目来说,透明比智能更重要。与其让工具替我决定记住什么,不如我亲自定义必须记住什么。
7. 最后再分享一点实战体会
这套方案的终点,并不是让代理“永远不丢上下文”——那是不可能的。平台会继续压缩,上下文会继续波动,就像服务器永远可能宕机一样。我真正学会的是把“上下文压缩”从“事故”重新定义成了“常规流程中的一个节点”。
我个人的实操体会是:启动文件写得越久越有价值。刚开始维护.init.d/时我觉得自己在做杂务,像是在写没人看的文档;但经历十几次压缩后,我发现每次恢复几乎都无缝衔接,那些以前要花二十分钟重新解释的约定,现在启动文件里都写得明明白白。省下的时间远超维护成本。
如果你也想让 AI 编码代理真正成为“压缩免疫”的长线协作者,我只有一个建议:不要试图对抗上下文压缩,也不要寄希望于哪家平台能把记忆保存得完美。把关键信息写进文件,把启动流程交给代理,有这两件事,压缩来临时你就不会慌。