资讯动态

AI编码代理上下文压缩失忆?用init.d和《记忆碎片》式外部记忆自救

发布时间:2026/10/14 22:23:07 来源:尧图企业网站定制
上周处理那个跨平台小工具的迁移项目时我的AI编码代理在同一个会话里跑到了接近20万token。一切看起来还算顺利直到它突然冒出来一句“上下文快满了我准备做一次压缩把早期讨论裁剪成摘要。”我点了同意然后它就变了个人——忘了我们约定用 Java 17 而不是 Kotlin忘了错误处理要走统一异常体系甚至忘了刚才重构到一半的接口签名。那一刻我脑子里蹦出一个词脑叶切除术。上下文压缩确实让会话得以继续但也把最宝贵的过程记忆全部切掉了。这篇文章记录我后来怎么靠两样老东西把这个问题解决掉Unix 的 init.d 启动脚本机制以及电影《记忆碎片》里“外部化记忆”的思路。如果你也被长会话的“失忆”折磨过下面这套方法可以直接抄走。1. 先看清病灶20万token的“脑叶切除术”到底切掉了什么1.1 上下文压缩的运作机制很多AI编码代理在长会话里都会遇到上下文上限。当消息总量逼近限制时工具会启动一个“压缩/归纳”流程把最早期的一大批对话全部折叠成一段摘要只保留最近几千字的消息原文然后继续跑。听起来很合理本质上就是给会话做了一次“高倍浓缩”。我在项目里遇到的情况非常典型。会话刚开始时我们花了大段时间讨论技术选型、目录结构、接口协议这些信息完整地躺在上下文里。到了接近20万token的时候代理提出要压缩。压缩完成后那些早期讨论全部变成了一段两百字的梗概“我们选择了Java技术栈并讨论了若干架构问题。”模式识别没问题但细节全没了。这就像整理房间时把所有发票倒进碎纸机只留下总额数字——账面上看没问题真要对账时就傻眼了。AI代理的压缩机制天然会保留“结论”而丢弃“过程”可工程恰恰靠过程细节吃饭。1.2 被切掉的不是“废话”而是“锚点”压缩丢掉的往往是三样东西技术栈和架构决策的具体理由。比如“我们不用公共库X因为许可证不兼容别浪费时间调研它了”压缩后可能变成“评估过一些库存在兼容性风险”。已经完成的工作清单。代理会忘记“登录接口已经改完只差集成测试”从而再次打开代码开始“重新改造”。临时约定和踩坑记录。例如“这个模块不要动重构会同步引爆三个下游服务”这类信息摘要里几乎不可能出现。我在那次迁移中印象最深的一次事故压缩前我们刚确认完数据库迁移脚本的命名规范压缩后代理自作主张把脚本目录重新整理了一遍结果CI直接挂掉。它不是故意捣乱是真的不记得我们说过什么了。这种“记忆断层”比纯粹的token不足更危险因为代理依然表现得自信满满只是基于错误或缺失的信息在行动。1.3 为什么“再解释一遍”救不了很多人遇到失忆后的代理第一反应是重新把关键信息说一遍。这在短会话里有效但在长会话里基本救不了。原因有三个第一压缩后的摘要已经占据了新上下文的“世界观”你再解释的内容要和一份并不完整的摘要对抗代理经常判断成“新信息”和“旧信息”存在冲突然后开始困惑。第二token预算本身就很紧张重新解释会占用大量空间挤掉后续真正需要的工作记忆。第三压缩会顺带削弱代理的“自我一致性”它连自己说过的话都不记得了自然更容易朝三暮四。典型的症状是“反刍行为”它反复问同一个接口的参数是什么反复打开同一个文件做同一种修改然后在下一次上下文刷新时又忘记。我试过在压缩后耐心从头补背景结果半小时后它又失忆了那次之后我决定换思路——与其被动治疗不如提前让代理“对压缩免疫”。2. init.d 方案把“启动即加载”当作救生索2.1 向老Unix学“确定性启动”Unix的系统初始化一直有一套非常朴素但可靠的设计开机时内核加载完后init进程按runlevel依次执行/etc/rc.d/init.d/里的脚本每个服务都有自己的启动脚本启动顺序固定、参数固定、行为可预期完全不依赖“上次关机前系统记住了什么”。这套机制给我的启发非常直接既然代理的长期记忆不可靠那就别把关键信息放在“它的记忆”里而是把所有初始化逻辑外置成文件新会话一打开就自动加载。记忆会失效脚本不会。当然这里的重点不是真的去写System V脚本而是借鉴它的设计哲学把所有“启动一个可用工作环境”所需的信息固定成可重复执行的顺序而不是依赖模型在长对话里记住。2.2 项目即系统三个固定启动文件我按init.d的思路在项目根目录建了三个固定文件模拟“启动脚本”AGENT.md项目身份和基本规则。写清楚项目是做什么的、技术栈是什么、必须遵守的约定有哪些。CONVENTIONS.md工程规范。错误处理方式、命名规则、目录职责、禁用依赖、命令执行方式。STATE.md当前工作状态。正在做什么、做到哪一步、下一步是什么、最近三个决策。下面是当时用的AGENT.md简化版# AGENT.md 项目跨平台小工具迁移 技术栈Java 17 Spring Boot 3 SQLite 构建命令./gradlew build 测试命令./gradlew test 核心约定 - 服务层抛业务异常控制器统一处理 - 数据库变更必须附带迁移脚本 - 禁止引入新的第三方HTTP客户端 当前工作状态见 STATE.md 完成里程碑后更新 worklog/这份文件不追求大而全只承载“代理一开始就必须知道、且无法通过猜代码快速确认”的信息。它承担的角色就是init.d里的启动脚本每次会话开始按顺序执行加载固定配置。2.3 启动加载序列的设计受init.d的runlevel排序启发我给每次新会话设计了一个四步启动序列先读AGENT.md建立项目身份和技术前提。再读CONVENTIONS.md明确工程规范和红线。然后读STATE.md知道当前进度。最后翻最近三天的worklog/了解最近的动作和结果。这个顺序是有讲究的先身份、再规则、然后状态、最后日志。如果顺序反过来代理容易陷入大量细节里反而抓不住重点。就像Unix启动必须先挂载根文件系统、再启动网络、然后启动服务——依赖顺序搞反了整个系统起不来。我用了固定句式把这些指令写进会话的第一条消息里每次会话开始时先读取 AGENT.md再读取 CONVENTIONS.md 然后读取 STATE.md最后按时间倒序阅读 worklog/ 里最近三天的记录。 之后开始处理 STATE.md 中标记的“下一步动作”。这套序列跑顺之后代理即使刚被压缩清空了记忆也能在两到三分钟内“重启”到之前的认知水平。它不需要回忆只需要加载。2.4 压缩后的“重启”康复流程当代理又一次提出要压缩时我不再慌张。压缩前我会主动做一轮快照更新把STATE.md刷新到当前进度。压缩后开一个新会话把启动序列执行一遍然后直接让它从STATE.md的“下一步动作”继续。实测下来有一个明显的体验变化压缩前代理经常会在中途跑偏因为它的“记忆”是隐式的、逐渐衰减的而改用init.d式加载后相当于每次工作都从同一个基准线出发即使中间被切掉也切不掉基准线本身。我把这个流程固定在项目的README里所有新会话都走同一套启动逻辑不再依赖“代理自己记得什么”。3. 《记忆碎片》方案不靠内部记忆靠外部便签3.1 用“纹身和纸条”对抗失忆电影《记忆碎片》里的主角患有短期记忆丧失他给自己设计了一套外部记忆系统把关键信息纹在身上把线索写成纸条用宝丽来照片记录现场。每条外部信息都充当一个“记忆锚点”让他即使失去短期记忆也能持续追踪目标。AI编码代理在长会话压缩后的状态跟电影主角非常像内部记忆被清除但外部文件系统还在。代理的短期记忆在token窗口里长期记忆在硬盘上。想让它对压缩免疫就得把关键信息从token窗口搬到硬盘上去。3.2 三条必须写的外部记忆在AGENT.md和CONVENTIONS.md之外我还维护了三类可写的外部记忆STATE.md——状态快照。只记录“当前”这一刻的信息不追溯历史。包含正在做的任务、已完成的关键步骤、下一步动作、当前所在分支、最近三个决策结论。DECISIONS.md——决策日志。记录重要选择“为什么这么做”。比如“数据库脚本用时间戳前缀命名是为了多环境按序执行”。这类信息在压缩后最容易丢但恰恰是避免代理重复调研、重复踩坑的关键。worklog/——工作日志。按日期追加每次里程碑结束就写一条。格式固定为“做了什么、结果如何、下一步是什么”。这三类文件的分工很明确状态快管“现在”决策日志管“为什么”工作日志管“过程”。代理压缩后如果只读一个文件读STATE.md如果还想理解来龙去脉读DECISIONS.md和worklog/。3.3 回写纪律比模板更重要设计再好的文件如果代理从来不更新也是废纸。我花了很大力气才理解回写是一种纪律不是一种格式。我规定两条硬性回写时机每次完成一个里程碑比如跑通一个接口、改完一个模块立即更新STATE.md并在worklog/追加一行日志。每次上下文压缩之前先停止手头工作强制刷新STATE.md的“下一步动作”。我把这条要求写进了启动指令里每当你完成一个可验证的里程碑或者上下文即将压缩时 必须更新 STATE.md 的“当前进度”和“下一步动作” 并在 worklog/ 下追加一条日志格式 日期 | 做了什么 | 结果 | 下一步一开始会觉得频繁更新文件很打断节奏但踩过几次失忆的坑后就会发现多花三十秒回写省下的是几个小时重新定位和重新决策的时间。3.4 压缩后的康复清单压缩发生后我把恢复流程做成了一份固定清单代理只需要按步骤执行读取AGENT.md和CONVENTIONS.md恢复工作环境认知。读取STATE.md确认当前进度和下一步。读取DECISIONS.md中最近的五条记录避免重新做决策。读取worklog/最近三条日志对齐最近动作。执行git status和git log --oneline -5确认代码仓库的真实状态。将以上信息压缩成一段“工作简报”复述给我确认。完成这套清单后代理通常能无缝接回之前的工作。即使它完全不记得对话历史外部文件已经把“该知道的”都告诉它了剩下的只需要执行。4. 落地配置一整套可直接抄走的工程实践4.1 文件目录怎么摆我建议的文件布局长这样project_root/ ├── AGENT.md ├── CONVENTIONS.md ├── STATE.md ├── DECISIONS.md ├── worklog/ │ ├── 2026-03-01.md │ └── 2026-03-02.md └── .gitignore这些文件全部纳入git版本管理历史记录天然成为第二层保险。万一某个状态文件被误改还能通过git diff找回来。实际使用中我把worklog/设计成纯追加模式不改历史只加新文件避免并发写入互相覆盖。4.2 提示词注入把启动脚本写进系统层外部文件只有被代理真正读到才有意义。我建议把“启动序列”写进项目说明文档或者代理工具支持的项目指令区域。我用的启动指令模板如下你是本项目的长期编码助手。 每次会话开始按顺序执行 1. 读取 AGENT.md了解项目背景与技术栈。 2. 读取 CONVENTIONS.md熟悉工程规范。 3. 读取 STATE.md掌握当前进度与下一步。 4. 读取 worklog/ 最近三天的日志。 5. 开始工作前先用一段话复述当前目标、当前状态、下一步动作。 注意每次完成任务里程碑或上下文即将压缩时更新 STATE.md 并追加 worklog/。 禁止跳过以上步骤直接处理代码。这条指令的核心思路是“强制加载复述确认”。强制加载保证信息进入上下文复述确认保证代理真的消化了文件内容而不是扫了一眼就当作没看见。我在多个项目里试过加上“复述”这一步后代理忘记约定的概率至少下降一半。4.3 自动化钩子用脚本守住纪律人的纪律会松懈代理也一样。我加了两条自动化钩子来兜底git pre-commit钩子检查STATE.md修改时间如果超过两小时未更新且本次提交涉及代码变更则拒绝提交并提示“先更新STATE.md”。定时提醒脚本每小时检查worklog/是否有当日记录没有就追加一条占位日志提醒下次会话从这里续写。钩子脚本不复杂核心逻辑就是检查文件的修改时间。这里给一个简化版参考#!/usr/bin/env bash if [ $(find STATE.md -mmin 120) ]; then echo STATE.md 已超过两小时未更新请刷新进度后再提交。 exit 1 fi这套自动化的价值不在于“管住人”而在于让“外部化记忆”这件事变得不可跳过。时间久了它会成为一种肌肉记忆。4.4 验证方法模拟压缩把一套机制搭好之后一定要主动测一次否则真出事时你根本不知道文件写没写够。我的做法是“模拟压缩演练”在一个进行到一半的项目上先刷新STATE.md和worklog/。清空当前会话完全不用旧对话。只把启动指令和四个文件投喂给全新会话。观察它能否在五分钟内接上工作说出“我在做什么”“下一步是什么”。如果新会话复述得七零八落说明外部化记忆还不够详细需要补文件。如果它直接能接着干活恭喜你的代理已经对“脑叶切除术”免疫了。我现在几乎每个长项目开始前都会做一次这种演练成本很低收益极高。5. 常见坑与排查实录5.1 外部记忆文件膨胀成天书用了一段时间后STATE.md越写越长从十几行膨胀到两三百行代理每次加载都花大量token去读历史包袱反而抓不住重点。我的解法是“状态文件只留当前态”。历史内容全部移入worklog/或归档目录STATE.md只保留“当前进度、下一步动作、最近三条决策”一旦超过一屏就该压缩。代理读得快理解也准。5.2 代理“看到了但不执行”有段时间我确实把启动指令写进去了但代理压缩后经常读完了AGENT.md转头还是按默认习惯干活。后来我发现问题出在“没有强制复述”上——文件进没进上下文是一回事模型遵不遵守是另一回事。解决方法是把“开始工作前用一段话复述当前目标、当前状态、下一步动作”写进启动指令并且要求复述中必须包含“我在哪个分支、正在改哪个文件”这类具体信息。复述这个动作相当于给代理打了一剂“确定性锚定”效果立竿见影。5.3 并发写入互相覆盖不同会话同时跑同一个项目时多个代理会同时改STATE.md后写的把先写的盖掉信息直接丢失。我的做法是给每个会话分配独立的工作分支并规定会话期间只更新自己分支下的worklog-session-id.md最后合并当天记录时人工确认STATE.md。如果你的代理支持多会话并行这一步千万不能省。5.4 陈旧状态仍然被信任STATE.md里的内容一旦没及时更新代理却依然把它当“最新真相”用就会按照错误状态行动。最典型的是“以为测试通过实际上代码已经改了”。我现在给STATE.md加了一个“最后验证时间”字段代理读到它时如果发现超过一定时间必须先跑一遍git status和对应测试再继续。时间戳字段让代理自己意识到“这可能过期了”比任何提醒都有效。5.5 问题排查速查表症状可能原因解决方式代理既不读文件也不按约定执行启动指令没进系统层把启动序列写进项目说明/系统级提示并加复述确认读了STATE.md但还是跑偏文件太乱、重点不突出精简状态文件只保留当前态和下一步压缩后代理重复已完成的步骤STATE.md没有“已完成清单”增加“已完成”字段压缩后作为禁止触碰列表多个会话互相覆盖状态缺少会话隔离按会话划分worklog文件人工合并代理自信地基于旧信息行动状态文件过期未被察觉加时间戳字段过期强制校验git状态6. 边界与扩展这套方案适合谁还能怎么玩6.1 适合与不适合的场景这套“init.d 记忆碎片”方案最适合的是那些需要跨多个会话、连续数天推进的长周期任务比如大型迁移、多模块重构、依赖升级、代码评审后的大批量修改。这类任务的共同特征是上下文很容易超过窗口上限且过程中的决策一旦丢失代价极高。但如果你只是用代理做一次性问答、快速原型、头脑风暴这套方法反而会拖慢节奏。给探索性任务强行套上启动脚本和状态文件就像写个“Hello World”还要走三层审批——纯粹是浪费。我的建议是分开对待探索性的会话保持轻量生产性的长期任务再启用完整方案。6.2 多代理协作的进阶玩法更进一步可以让多个代理共享同一份外部记忆库。比如一个代理负责写代码一个代理负责审查一个代理负责更新文档它们共同读写AGENT.md、STATE.md和DECISIONS.md。每个代理启动时都从同一份“公共便签板”加载信息做完自己的部分后再把结果写回去。这相当于把“上下文压缩免疫”升级成“团队级记忆共享”。不过这个玩法对文件规范要求更高字段命名的混乱会直接导致代理之间互相误解。我目前只在DECISIONS.md上做过小范围试验效果不错但还没完全铺开。6.3 这套思路的核心启发说到底AI编码代理和人都差不多短期记忆有限上下文窗口再大也有尽头。与其想尽办法扩大窗口不如接受“记忆会丢”这个现实把所有关键信息提前放到不会丢的地方。Unix init.d 提供了“启动顺序”的参考记忆碎片提供了“外部化记忆”的思路把它们组合起来就成了一套不依赖任何特定模型、任何特定工具的长任务工作流。文件系统是人类几十年来最稳定的记忆介质也是最可靠的记忆备份它天然适合做AI代理的“体外大脑”。这套方法不需要什么高端配置四个Markdown文件加一个启动指令就能让你的代理在20万token的压缩风暴中稳稳幸存。我在实际使用中最大的体会是这套方案的真正价值不在于“防止压缩”而在于“让压缩变得不再可怕”。以前一听到“上下文即将压缩”就紧张担心代理失忆、担心进度丢失现在反而把压缩当成一次自然重启提前拍完快照开新会话接着跑就行。最后再分享一个小技巧你可以故意做一次“模拟压缩”——把当前对话清空只丢给代理AGENT.md和STATE.md看它能不能在一分钟内接上活。如果接不上说明你的外部化记忆还不够好如果接上了恭喜你你的代理已经对脑叶切除术免疫了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑