上下文压缩导致代理失忆?用init.d式启动脚本让编码代理保持记忆

发布时间:2026/10/12 5:01:20
上下文压缩导致代理失忆?用init.d式启动脚本让编码代理保持记忆
1. 当上下文被压缩代理为何会“失忆”做过 AI 编码代理的人大概都经历过这种崩溃瞬间你花了半小时跟代理把需求聊清楚它理解了项目结构、记住了命名约定、甚至记住了你反复强调的“别动那个配置文件”。结果对话一长上下文窗口一压缩它转头就把这些全忘了开始重新问你“这个模块是干什么的”甚至把你之前明确否定的方案又提了一遍。这个现象我管它叫“脑叶切除术”——上下文压缩机制为了腾出空间把早期对话摘要化甚至直接丢弃代理的“人格”和“记忆”被切掉了一大块。20 万 token 听起来很多但一个真实的中大型项目光是把关键文件读一遍、把几轮讨论塞进去很容易就逼近上限。一旦触发压缩代理的行为就会变得不可预测。这篇文章要聊的是我怎么用一套“Unix init.d 式”的启动脚本思路配合《记忆碎片》那种碎片化记忆重建的机制让编码代理在上下文被反复压缩的情况下依然能保持对项目核心约定的“免疫”。核心不是让代理记住所有东西而是让它知道哪些东西必须记住、从哪里重新加载、以及怎么验证自己没记错。适合谁看如果你正在用任何支持长上下文的编码代理做真实项目被“聊着聊着它就忘了”折磨过或者你正在设计一套多轮代理工作流那这套思路可以直接抄。它不依赖某个特定平台核心是几个文件加一套约定。2. 核心思路把“记忆”从上下文里搬出来2.1 为什么不能指望上下文窗口先说一个反直觉的结论上下文窗口再大也不该用来存“必须记住”的东西。原因有三。第一压缩是概率性的。不同平台、不同版本的压缩策略不一样有的按轮次摘要有的按 token 比例截断有的用模型自己总结。你没法保证“我强调过三遍”就一定会被保留。把关键约定寄托在压缩算法的“仁慈”上本身就是不靠谱的。第二上下文越长注意力越稀释。20 万 token 里塞满历史对话模型对中间部分的关注度会下降这是注意力机制本身的特性。你写在第三轮的那句“数据库连接串不要硬编码”到第五十轮时可能已经被淹没。第三上下文是易失的。会话一断、窗口一关全没了。而项目约定、架构决策、踩过的坑这些是跨会话、跨天、甚至跨人协作都要保留的。所以我的核心策略是上下文只放“当前任务需要的工作集”所有“必须记住”的东西全部外置成文件代理在需要时主动读取。这就像 Unix 系统里进程的内存可以被换出但配置文件永远躺在磁盘上重启后 init.d 脚本会重新把它们加载进来。2.2 init.d 给我的启发启动即加载约定即脚本Unix 的 init.d 机制很朴素系统启动时按顺序执行一系列脚本每个脚本负责把某个服务拉起来。脚本本身是幂等的、可重复执行的不依赖上一次运行的内存状态。我把这个思路搬到代理上设计了一套“代理启动脚本”。每次新会话开始或者我感觉到代理开始“犯迷糊”时我就让它先执行这套启动流程读取AGENT_CONTRACT.md——项目铁律比如“所有 API 调用必须走统一封装层”“禁止直接修改 generated 目录”。读取ARCH_DECISIONS.md——关键架构决策及原因比如“为什么选了这个状态管理方案而不是那个”。读取CURRENT_TASK.md——当前任务的精确描述、验收标准、已知约束。读取PITFALLS.md——踩过的坑和对应规避方法。这四个文件加起来通常不到 3000 token但它们承载的是“不可压缩”的核心信息。代理每次读完相当于重新“启动”了一次记忆被重建。注意这套文件必须是人可读、代理可解析的纯文本或 Markdown不要用二进制或复杂格式。代理读 Markdown 的准确率远高于读 JSON 嵌套结构。2.3 《记忆碎片》的启示碎片化线索 主动重建电影《记忆碎片》里主角没法形成新记忆但他靠纹身、纸条、拍立得照片这些外部线索硬是拼出了真相。关键在于他不试图记住一切他只记住“去哪里找线索”和“当前目标是什么”。代理也一样。我不要求它记住“上周三我们讨论过缓存策略最后决定用 LRU 而不是 LFU因为写多读少”。我只要求它记住“缓存策略的决策记录在ARCH_DECISIONS.md第 3 节需要时去读。”这就把“记忆负担”从上下文转移到了文件系统。上下文里只需要保留一个极短的“索引”当前任务是什么、相关决策在哪个文件、下一步该读什么。这个索引可以小到几百 token几乎不可能被压缩掉。更关键的是我让代理养成一个习惯在做出任何重要修改前先重新读取相关约定文件。这就像主角每次行动前先看一遍纹身。多花几秒读文件比改错了再回滚便宜得多。3. 落地实现四个文件 一套触发规则3.1 文件一AGENT_CONTRACT.md铁律这个文件是最高优先级内容要短、要硬、要无歧义。我通常控制在 500 字以内每条都是“必须”或“禁止”不写“建议”。# 代理契约 ## 必须 - 所有网络请求必须通过 src/api/client.ts 的封装禁止直接使用 fetch。 - 新增组件必须放在 src/components/ 下文件名用 PascalCase。 - 提交前必须运行 npm run lint 和 npm run test:unit。 ## 禁止 - 禁止修改 src/generated/ 下任何文件那是代码生成产物。 - 禁止在组件里直接写死颜色值必须用 theme.ts 里的 token。 - 禁止引入新的第三方状态管理库现有方案已够用。 ## 验证 - 每次修改后用 git diff --stat 确认改动范围符合预期。这个文件我放在项目根目录代理每次启动必读。它不解释原因原因在另一个文件里。铁律就是铁律先遵守再讨论。3.2 文件二ARCH_DECISIONS.md决策及原因这个文件记录“为什么这么做”防止代理在压缩后提出已经被否决的方案。格式用轻量 ADR架构决策记录# 架构决策记录 ## ADR-001: 状态管理选 Zustand 而非 Redux - 日期: 2024-XX-XX - 状态: 已采纳 - 原因: 项目规模中等Redux 样板代码过多Zustand 的 selector 机制足够满足性能需求。 - 后果: 禁止再引入 Redux 相关依赖。 ## ADR-002: 缓存用 LRU 而非 LFU - 日期: 2024-XX-XX - 状态: 已采纳 - 原因: 实际访问模式是写多读少LFU 的频次统计开销不划算。 - 后果: 缓存实现固定在 src/utils/lruCache.ts。代理读到这个就不会再问“要不要用 Redux”也不会把缓存换成 LFU。如果它真的想推翻某个决策它必须明确说“我建议重新审视 ADR-002理由是……”而不是悄悄改掉。3.3 文件三CURRENT_TASK.md当前任务这个文件是动态的每个任务开始时我手动或让代理更新。它包含任务目标一句话验收标准可测试的条目已知约束比如“不能改数据库 schema”相关文件列表代理需要读哪些文件来理解上下文# 当前任务 ## 目标 给用户列表页增加分页功能。 ## 验收标准 - 每页 20 条可切换页码。 - 切换页码时 URL query 同步更新。 - 加载中显示 skeleton不阻塞页面。 ## 约束 - 不能改后端 API 的响应结构。 - 必须复用现有的 usePagination hook。 ## 相关文件 - src/pages/UserList.tsx - src/hooks/usePagination.ts - src/api/user.ts代理读完这个就知道该干什么、干到什么程度算完、不能碰什么。这比在对话里反复交代靠谱得多。3.4 文件四PITFALLS.md踩坑记录这是最有价值但也最容易被忽略的文件。每次代理犯了一个“本可以避免”的错误我就把坑记下来格式是“现象 → 原因 → 规避方法”。# 踩坑记录 ## P-001: 分页时重复请求 - 现象: 快速切换页码会触发多次相同请求。 - 原因: useEffect 依赖了 page 和 filters 两个对象filters 每次渲染都是新引用。 - 规避: 用 useMemo 稳定 filters 引用或在 effect 里做请求取消。 ## P-002: 测试环境时区不一致 - 现象: 日期格式化测试在本地过、在 CI 挂。 - 原因: 本地是 UTC8CI 是 UTC。 - 规避: 测试里显式设置 TZUTC或用固定时间戳。代理每次启动读一遍就能避开之前踩过的坑。这比让它“从错误中学习”高效得多因为它的学习不会跨会话保留但文件会。3.5 触发规则什么时候重新加载不是每轮对话都要读这四个文件那样太浪费。我设了几个触发点新会话开始必读全部四个。代理连续两次答非所问让它重读 CONTRACT 和 CURRENT_TASK。代理提出已被否决的方案让它重读 ARCH_DECISIONS。代理犯了 PITFALLS 里记录过的错让它重读 PITFALLS并说明为什么又犯了。任务切换更新 CURRENT_TASK让代理重读。这套规则我写成一个简短的AGENT_BOOT.md放在项目根目录代理自己也能读。有时候我直接说“执行启动流程”它就懂了。4. 实操过程一次完整的“抗压缩”演练4.1 场景设定假设我在做一个中等规模的前端项目代理已经工作了 40 轮对话上下文接近 15 万 token。此时我让它加一个“导出 CSV”功能。按照以往经验它很可能会忘记“所有导出必须走src/utils/export.ts的统一封装”这条约定直接自己写一个 Blob 下载。4.2 启动流程执行我先让它执行启动流程。它依次读取四个文件输出确认已读取 AGENT_CONTRACT.md确认导出必须走统一封装。 已读取 ARCH_DECISIONS.md确认 ADR-005 规定导出格式统一用 UTF-8 BOM。 已读取 CURRENT_TASK.md当前任务为“用户列表页增加导出 CSV 按钮”。 已读取 PITFALLS.md注意到 P-003 记录过“导出中文乱码”问题。这一步只花了不到 2000 token但把关键约束全部重新加载了。4.3 代理执行与验证代理接着读src/utils/export.ts发现已有exportToCsv函数直接复用。它没有自己造轮子也没有忘记 BOM。改完后它主动运行npm run lint和npm run test:unit因为 CONTRACT 里写了。如果它中途又开始“犯迷糊”比如问“要不要用 xlsx 库”我就让它重读 ARCH_DECISIONS 里关于“禁止引入新依赖”的条目。它读完就老实了。4.4 压缩发生后的恢复假设在第 45 轮上下文触发压缩早期对话被摘要。代理突然说“我建议用一个新的导出库这样更灵活。”这时候我不需要重新解释一遍。我只需要说“重读 ARCH_DECISIONS 和 PITFALLS。”它读完就会看到 ADR-005 和 P-003然后自己纠正“抱歉根据 ADR-005应复用现有封装根据 P-003需注意 BOM。”这就是“免疫”的含义压缩可以删掉对话但删不掉文件。代理只要知道去哪里读就能恢复记忆。4.5 参数与成本核算这套机制的成本主要是读取文件的 token。四个文件加起来约 2500 token每次启动读一遍。假设一个任务触发 5 次启动流程总共 12500 token。相比上下文压缩导致的返工一次返工可能浪费 5000-10000 token 的重新解释和错误修改这个成本完全可以接受。更重要的是它把“记忆可靠性”从概率问题变成了确定性问题。文件在记忆就在。提示文件不要写太长。CONTRACT 超过 1000 字代理的遵守率会下降。PITFALLS 超过 50 条考虑归档旧条目。保持精简才能保证每次都被认真读。5. 常见问题与排查技巧实录5.1 代理不读文件怎么办这是最常见的问题。代理可能会说“我已经了解了”然后不实际读取。我的对策是要求它输出读取确认。比如“请读取 AGENT_CONTRACT.md 并逐条复述你必须遵守的规则”。它一旦复述就证明它真的读了而且复述过程本身就是一次强化。如果它复述得不对说明读取或解析有问题我就把文件改得更短更直白。有时候是 Markdown 层级太深代理解析错了改成扁平列表就好了。5.2 文件之间冲突怎么办比如 CONTRACT 说“禁止引入新依赖”但 CURRENT_TASK 的验收标准里写“使用某新库实现某功能”。这种冲突必须由人解决不能留给代理猜。我的做法是在 CURRENT_TASK 里显式写“本任务已获准引入 X 库作为 CONTRACT 的临时例外”。代理看到这个就知道优先级。永远不要让代理自己判断哪个文件优先那是人的责任。5.3 压缩后代理“人格”变了有时候压缩后代理的语气、风格、甚至对项目的理解都变了。这时候光读文件可能不够还需要“重新锚定”。我会让它做一件具体的小事比如“读一下 src/pages/UserList.tsx 的前 50 行然后告诉我这个文件用了哪些 import”。通过一个具体动作把它拉回项目语境。这就像《记忆碎片》里主角通过看照片重新确认自己是谁。动作比陈述更有效。5.4 多代理协作时的记忆同步如果多个代理同时工作每个代理都有自己的上下文压缩时机也不同。我的做法是共享同一套文件但每个代理有自己的 CURRENT_TASK 副本。共享文件只读任务文件各自维护。合并时以共享文件为准任务文件冲突由人裁决。5.5 常见问题速查表问题现象可能原因排查动作解决方式代理重复问已确认的事上下文压缩丢失让它重读 CONTRACT执行启动流程代理提出已否决方案未读 ARCH_DECISIONS检查是否跳过启动强制读取并复述代理犯过的错又犯PITFALLS 未生效确认文件是否更新补充条目并重读代理不遵守约定文件太长或太模糊检查文件长度和措辞精简、改硬性措辞多代理行为不一致任务文件不同步对比各自 CURRENT_TASK统一共享文件版本5.6 独家避坑技巧第一个技巧把最重要的三条约定放在 CONTRACT 最前面。代理读取时对开头内容的注意力最高后面的容易忽略。我试过把“禁止修改 generated 目录”从第 8 条挪到第 1 条遵守率明显提升。第二个技巧PITFALLS 里每条都加一个“检测方法”。比如“P-001 的检测方法是看 Network 面板是否有重复请求”。这样代理不仅能规避还能自查。它自查一次比我说十次都管用。第三个技巧定期让代理自己更新 PITFALLS。每次它犯新错我让它自己写一条记录。写的过程就是学习的过程而且它写的措辞往往比我写的更贴合它自己的理解方式。第四个技巧文件用英文命名内容用中文。英文文件名在各类工具链里兼容性好中文内容对国内代理的理解准确率更高。混用没问题但别反过来。6. 扩展思路从单代理到工作流这套机制最初是为单个编码代理设计的但后来我发现它可以扩展到更复杂的工作流。比如我把“启动流程”做成一个可调用的函数代理在每次任务切换时自动调用。再比如我把 PITFALLS 按模块分类代理只读当前模块相关的坑减少 token 消耗。还有一个有意思的扩展让代理在完成任务后自动生成一份“交接文档”内容包括本次改了什么、为什么改、遗留问题。这份文档下次启动时作为 CURRENT_TASK 的补充。这样即使换了一个全新的代理会话也能快速接手。我甚至试过让两个代理互相“交接”代理 A 写完交接文档代理 B 读文档后复述A 确认无误后 B 才开始工作。这比人肉转述靠谱得多。最后分享一个我个人的体会这套东西的核心不是技术而是纪律。文件要短、要更新、要强制执行。我见过太多人建了一堆文档但从不维护最后代理读了过时的信息反而更糟。文件的生命力在于持续修剪就像 init.d 脚本每个都得是能跑通的跑不通的就删掉。如果你也在被上下文压缩折磨不妨从建一个AGENT_CONTRACT.md开始。不用一次建四个先建最重要的那个跑一周你会回来感谢自己的。