
被 20 万 token 的上下文窗口骗惨了。我在一个跨平台小工具的编码会话里跑到了接近 20 万 token原以为这么大的窗口足够把项目全貌“背下来”。结果上下文压缩机制一启动编码代理就像刚被做了一场“脑叶切除术”上一轮刚确认过的文件路径、变量命名、设计取舍全部消失。它甚至开始一本正经地建议我新建一个已经存在的函数那一刻我意识到大模型的所谓“记忆”很多时候只是临时贴在对话里的便利贴压缩一来便利贴就被撕掉了。后来我用 Unix init.d 的启动脚本机制加上电影《记忆碎片》里主角那套“外部线索”策略给编码代理搭了一套对上下文压缩近乎免疫的工作流。效果出乎意料地好最近几个长会话项目再也没出现过“代理论证一半突然失忆”的崩溃场面。这篇就完整记录一下我的思路、落地步骤以及踩过的坑。1. 失忆现场20 万 token 看起来很大压缩一启动照样“脑叶切除”1.1 上下文压缩是怎么发生的很多人的错觉是上下文窗口有 20 万 token那我只要把所有资料都塞进去模型就能一直记住。实际上窗口大小只代表“最多能放多少 token”并不代表“模型能稳定利用这么多 token”。我的实际体验里上下文压缩至少来自三个层面。第一是显式的摘要压缩当会话长度超过某个阈值系统会把早期对话浓缩成一段摘要原始细节直接消失。第二是隐式的注意力稀释模型在处理长上下文时对中间位置的信息注意力权重天然偏低放在第 3 万 token 处的一条关键约定很可能根本没进入模型的“决策视野”。第三是工具侧的截断很多编码代理会在请求超过预算时静默丢弃前面的内容只保留最近几轮。这三类情况有个共同点它们都发生在你看不见的地方。你以为模型还记得其实它已经只剩一段模糊的“大概意思”。1.2 失忆的三种典型症状我总结了编码代理在压缩后最典型的三种“临床反应”路径失忆它上一秒还在修改src/lib/queue.py压缩后却声称“项目里似乎没有这个文件”继而建议新建一个同名文件差点造成重复实现。约定失忆说好接口参数用驼峰命名压缩后它又按默认的下划线命名写出一堆新代码风格瞬间撕裂。决策反复前面几轮刚拍板“不用数据库用 JSON 文件持久化”压缩后它又一本正经地分析引入数据库的好处把已经消耗过的讨论重新来一遍。这种症状最伤效率的地方不是单个错误而是它会破坏你和代理之间的连续工作状态。你不得不在每次压缩后花大量时间“重新哄它想起之前的一切”长久下来长上下文反而变成了短上下文。2. 记忆外化的两个师傅init.d 的系统重启逻辑与《记忆碎片》的便签策略2.1 从 init.d 学到的第一课重启后靠脚本重建服务Unix 系统的 init.d 机制给了我第一个启发。早期 Linux 在系统启动时会扫描/etc/init.d目录按文件名顺序执行里面的脚本每个脚本负责拉起一个服务。开机那一刻系统没有“上一分钟的记忆”它只是老老实实运行脚本从配置文件和二进制程序里恢复出整个运行时环境。这个思路放在编码代理身上就是每次新开会话或者上下文被压缩后代理都应该像开机一样按固定顺序读取一系列外部文件从零重建自己的“项目认知”。它不需要在上下文里保存几十万字的历史会话只需要知道从哪里读取“启动脚本”。我把这个机制称为“代理重启仪式”。核心原则很简单一切重要信息都放在磁盘上而不是放在对话里。会话上下文是易失性存储器断电就丢文件系统才是持久化存储。2.2 从《记忆碎片》学到的第二课给“失忆者”留纹身和便签《记忆碎片》里主角的情况更极端他只有几分钟的短期记忆但依然能完成复杂任务靠的不是努力记住而是一套极端的外部记忆系统把关键事实纹在身体上用拍立得照片记录当前进展在纸条上写给未来的自己。这套策略映射到 AI 编码代理上非常精准纹身项目里不可变的硬规则比如架构选型、命名规范、强制约定。要纹在皮肤上让代理每次都能看到。拍立得照片关键决策的快照每个重要岔路口选了什么方案记录下来。纸条当前状态和下一步计划每次会话结束时写给下一个会话看。实物线索文件系统里真实存在的代码、测试、配置文件。代理通过读取这些“实物”来判断当前状态而不是靠对话里的描述。电影里的主角从来不会试图“提高自己的记忆力”他做的是“让环境替自己记住”。这个思维的转变对 AI 协作极其关键。2.3 两者的结合init.d 提供结构《记忆碎片》提供精神init.d 给了一套清晰的文件组织和加载顺序让你知道“启动时先读哪个后读哪个”《记忆碎片》则提供了一个心理模型编码代理永远处于“刚刚失忆”的状态你要做的不是抱怨它记性差而是像主角一样把信息变成它视线范围内的线索。我把这套组合方案落成了一个具体的项目目录结构下一节详细展开。3. 实操用 .agent/init.d 给代理搭一套“失忆后的重启仪式”3.1 目录结构与文件命名我在每个项目根目录下创建了一个.agent目录内部结构如下.agent/ ├── init.d/ │ ├── 00-project.md │ ├── 10-conventions.md │ ├── 20-status.md │ └── 30-glossary.md ├── notes/ │ ├── last-session.md │ └── next-actions.md └── memory/ ├── decisions.md └── debug-log.md用编号前缀是刻意模仿 init.d 的执行顺序。两位数字的排序保证了代理总是先读项目目标再读规则然后读当前状态最后读术语表00-project.md项目一句话目标、当前技术栈、核心目录说明。10-conventions.md代码风格、命名规则、架构约束。这些是“纹身”通常不允许代理擅自修改。20-status.md当前正在做什么、做到哪个阶段、最近一次决策。这是“拍立得快照”需要频繁更新。30-glossary.md项目里的领域术语和特殊缩写的解释。notes/next-actions.md是给下一次会话的便签明确写出“接下来应该做什么”。memory/目录则用来沉淀更长的决策记录和排障记录避免它们污染短小精悍的启动文件。3.2 每次会话启动的“重启仪式”无论新开会话还是上下文被压缩我都会先让代理执行一段固定的恢复指令。这里有一段我一直在用的提示词模板你是这个项目的资深工程师。请先执行以下恢复步骤 1. 依次读取 .agent/init.d/00-project.md、10-conventions.md、20-status.md、30-glossary.md 2. 运行 git log --oneline -10 查看最近的提交历史 3. 运行 git diff HEAD~3 查看最近三笔提交的改动内容 4. 读取 .agent/notes/next-actions.md 完成后用不超过 10 条列表向我总结你掌握的当前状态 包括当前阶段、上一步完成点、下一步行动、应遵守的关键规则。 不要闲聊不要重复文件内容。这段指令的核心不是让代理“记住”这些文件而是强制它“重新读取”。哪怕它上一轮的上下文已经被压缩成一团摘要只要这一轮它真的读过磁盘文件状态就恢复了。我还会要求代理把读取结果浓缩成 10 条以内的状态摘要。这是为了验证它真的读进去了而不是象征性打开文件后直接忽略。如果摘要里出现了明显错误我会先纠正再让它继续任务。3.3 迭代收尾的“记忆固化”光有启动时的恢复还不够必须每次迭代结束时把新知识写回外部文件。我在提示词里要求代理在完成一个子任务后额外执行一次“记忆固化”本轮任务完成后请执行以下操作 1. 更新 .agent/init.d/20-status.md把“当前阶段”改成最新进度 2. 更新 .agent/notes/next-actions.md写下下一步要处理的事项 3. 如果出现新的架构决策追加到 .agent/memory/decisions.md 4. 如果发现关键 bug追加到 .agent/memory/debug-log.md 更新时保持每个文件不超过 30 行用简洁的列表描述。有了这个步骤每次会话结束时磁盘上的文件都会比开始时多一段“记忆”。下次无论上下文窗口怎么被压缩代理都能通过读取文件找回全部关键状态。3.4 用 git 提交作为“记忆锚点”除了.agent目录我还会频繁使用 git 提交作为记忆锚点。每完成一个功能片段就提交一次并且把提交信息写得尽量具体比如“修复导出时文件名冲突采用前缀参数方案”而不是“fix bug”。原因很简单git 历史本身就是一份高质量的外部记忆。代理可以通过git log和git diff快速回顾“这个文件为什么长这样”。当上下文压缩发生后git 历史是少数不会被篡改的真相来源。我把这三件事合起来称为“重启铁三角”init.d 文件负责项目认知git 历史负责演化脉络notes 文件负责下一步动作。三者都放在代理可以直接访问的地方而不是藏在对话历史里。4. 实测对照压缩后的代理行为差异从反复失忆到快速恢复4.1 实验场景设计为了验证这套方案不是心理安慰我在一个内部练习项目“模拟项目X”上做了对照测试。项目规模不大约 120 个文件、1.8 万行代码但会话会持续拉长到接近 20 万 token中途必然触发上下文压缩。对照组使用传统工作流把项目说明写在系统提示词里靠上下文窗口承载历史记忆没有外部记忆文件。实验组使用.agent/init.d重启仪式并按前面提到的迭代收尾更新文件。整个测试在一个单独的本地环境里完成不涉及真实业务数据结果只是个人观察但趋势非常明显。4.2 压缩前后的行为差异上下文压缩后我在同一时间点继续发布任务要求代理完成一个与当前进度关联的后续功能。对照组的表现如下把已经存在的src/utils/result.py误判为不存在想要新建。重新提出数据库选型问题而这个问题在压缩前已经定性。三次重复修改同一个校验函数每次改动方向还不一致。需要我把项目背景从头复述一遍才能继续。实验组的表现则稳定得多。压缩发生后代理先执行重启仪式读取了00-project.md和20-status.md明确告诉我“当前正在处理导入失败问题已确认使用接口熔断方案下一步是补充重试逻辑”。随后它直接跳出恢复模式开始处理真正的后续代码没有一句“我失忆了”。4.3 几个值得记录的数据点观察指标对照组实验组压缩后第一次会话需要我重复背景次数4 次1 次误判文件不存在的次数5 次0 次重复修改同一段逻辑的次数3 次0 次恢复到可继续任务的时间约 12 分钟约 2 分钟这里的“1 次”出现在实验组刚开始接手的首个会话。当时我还没把20-status.md写得足够准确代理根据旧状态推断方向走了一小段弯路。修正后后续所有压缩场景都能在 2 分钟内恢复到工作状态。最让我意外的不是实验组不出错而是恢复后的“状态质量”反而更高。因为20-status.md是由上一次会话结尾精心总结的它比杂乱的对话历史更结构化相当于代理获得了一份比自己记忆更干净的“简报”。5. 避坑外部记忆系统在真实项目里最容易翻车的五个细节5.1 笔记越长越容易变成第二个上下文刚开始搭这套系统时我把所有想记的都往20-status.md里塞两三轮下来文件超过两百行。结果代理每次读取这个文件几乎占据了整个有效窗口反而挤占了真正该用的代码分析空间而且信息越冗长关键内容越不容易被模型注意到。后来我硬性规定每个启动文件控制在 30 行以内。具体状态写进notes/next-actions.md更长的讨论沉淀到memory/启动文件只保留“当前阶段、上一步、下一步、当前主要决策”四类信息。5.2 把“临时状态”和“永久规则”混在一个文件里这是我觉得最危险的一个错误。代理要分得清“这个世界目前处于什么状态”和“这个世界永远应该怎样”否则就容易出现它读了上一轮遗留的阶段性结论却当成长期约定之后所有工作都被带偏。解决方法是严格区分文件职能10-conventions.md是永久规则作者只能是我代理无权改动20-status.md是临时状态代理每次收尾都可以更新。在提示词里我明确写了“禁止修改 00、10、30 号文件”相当于把这些文件设为只读纹身。5.3 只让代理读不让代理写记忆系统只有闭环才有效。如果代理每次启动都能读到状态但任务结束没有写入最新状态那么下次启动读到的就是一条过期的旧线索比没有线索更容易误导。我在每个任务结尾必做一步“强制交还”先更新状态文件再让我检查。如果代理直接说“任务完成”但没碰状态文件我会让它补上更新步骤再去验证代码。宁可慢一分钟也要保证下一次会话的起点是干净的。5.4 提示词里的“请记住”是弱指令很多同事会习惯性地说“请记住我们用的是单例模式”。对于有即时上下文的短会话这句话还有效一旦压缩发生这句指令就烟消云散。我的改变是把“请记住”全部替换成“请读取”。“请读取10-conventions.md并确认架构模式” 比 “请记住” 可靠得多。前者是外部存储后者是内部易失记忆。记住AI 模型的“内部记忆”本质上是一次性的跨会话的稳定记忆只存在于外部世界。5.5 状态文件不写“基于哪一笔提交”过几天就失效.agent/init.d/20-status.md写的是当前状态但项目代码每天都在变。如果状态文件顶部没有标注“当前基线 commit”代理读取时可能把旧状态套在新代码上产生错位。我在 20 号文件的固定模板里加入了更新基线段落更新于YYYY-MM-DD 基线版本commit abc1234每次更新状态文件时同步更新这两个字段。这样代理既能知道“这些描述基于什么时间点的代码”也能自行判断当前代码是否已经偏离状态描述。6. 迁移同一套“外部记忆”策略还能救哪些 AI 长任务场景6.1 长文档写作用 outline 保持主线我在写一个多章节技术手册时也遇到过同样的失忆问题AI 协作者写着写着就忘了前面的章节约定人物称呼、术语翻译、排版风格逐渐漂移。我为文档项目搭了一套outline.md和style-guide.md每次开场先读取每次写完一节更新大纲效果和编码场景一样明显。6.2 数据分析用“步骤日志”避免重复清洗做数据分析时数据清洗往往需要几十个环节。上下文一压缩AI 可能会忘掉已经做过的过滤条件重新清洗一遍或者产生不一致的结果。我在工作目录放一个pipeline-log.md每一步处理的时间、方法、涉及列都写进去代理每次开始下一阶段前读一遍完美绕开上下文压缩。6.3 项目会议纪要管理如果 AI 帮你整理会议事项也可以在项目目录放一个action-items.md每次会后追加决议和负责人。即使上下文压缩AI 也能基于最新文件给出准确的跟进建议而不是凭一段模糊的记忆瞎猜。这个策略本质上是一种通用的人机协作模式不要试图扩大人工智能的“内部内存”而是把信息放到它每次都能看到的外部环境里。这套思路放到任何需要多轮协作的 AI 场景都成立。我在实际使用中最大的感受是上下文窗口越大越容易让人产生“它肯定记住了”的错觉。但只要经历一次压缩你就会明白窗口只是缓存不是记忆。把编码代理想象成一个每隔几分钟就会失忆的老朋友你自然会养成在它看得见的地方留线索的习惯。现在我的每个项目在启动时都会经历那套“重启仪式”而我也很少再被 20 万 token 的“脑叶切除术”打回原形了。