
1. 当上下文被压缩代理为何会“失忆”做过 AI 编码代理的人大概都遇到过这种场景你花了大半天时间把项目结构、依赖关系、历史决策、踩过的坑一条条喂给代理它也确实表现得很聪明能改代码、能跑测试、能根据报错自我修正。但聊到某个临界点之后它突然像换了个人——之前说好的接口约定忘了刚修过的 bug 又改回去了甚至连项目根目录在哪都开始胡猜。这不是模型变笨了而是上下文窗口被压缩或截断了。大上下文模型动辄宣称支持 20 万甚至上百万 token但实际使用中很多运行时环境、代理框架、成本控制策略都会在后台做上下文压缩把早期对话摘要化、把长文件截断、把工具调用结果折叠。这个过程我习惯叫它“脑叶切除术”——切掉的不一定是垃圾信息很多时候恰恰是代理维持行为一致性的关键记忆。标题里提到的《记忆碎片》是个很贴切的隐喻主角只能靠纹身、纸条、拍立得维持记忆连续性而我们要做的就是给 AI 编码代理装上它自己的“纹身和纸条”。这篇文章面向的是正在自建或深度定制 AI 编码代理的开发者尤其是那些被上下文压缩坑过、想让代理在长会话中保持稳定行为的人。我会从整体设计思路讲起拆解 Unix init.d 的进程管理哲学和《记忆碎片》式的外部记忆机制如何结合再落到具体实现、参数选择、排查技巧。整套方案我在几个模拟项目里反复跑过实测下来对上下文压缩有相当强的抵抗力。2. 整体设计思路把代理当成一个需要持久化的守护进程2.1 核心问题不是“记不住”而是“记的东西不可靠”很多人第一反应是换更大的模型、开更长的上下文。这条路我试过短期有效但有两个硬伤。第一成本和延迟随 token 线性甚至超线性增长长会话跑到后面每轮都贵得离谱。第二也是最关键的压缩策略是运行时决定的你控制不了它切哪里。你以为重要的系统提示词可能在某个阈值之后被摘要成一句话你以为代理会记住的文件路径可能在某次工具调用后被折叠掉。所以真正的问题不是“如何让它记住更多”而是“如何让关键信息不依赖模型的工作记忆”。这就像 Unix 系统里的守护进程进程本身可能被重启、被 kill、被调度走但只要它的状态文件、pid 文件、日志还在磁盘上它就能恢复。代理的“磁盘”是什么是项目里的真实文件、是版本控制历史、是外部存储的记忆条目。2.2 为什么借用 Unix init.d 的思路Unix 的 init.d 体系现在很多系统换成了 systemd但思路一脉相承解决的是一个经典问题系统启动时如何按依赖顺序、可重复、可恢复地拉起一堆服务。每个服务脚本都是幂等的都有 start/stop/restart/status 这些标准动作状态通过 pid 文件和锁文件管理。我把这套思路搬到代理上核心是三点。第一代理的每次“会话”都当成一次进程启动启动时从磁盘读取状态而不是从对话历史里回忆。第二关键状态写成幂等的脚本或配置文件代理可以随时重新读取、重新执行结果一致。第三用锁文件和状态标记防止重复劳动比如某个迁移已经跑过就不要因为代理失忆再跑一遍。2.3 《记忆碎片》式外部记忆的三层结构光有 init.d 还不够因为代理需要的不只是“服务状态”还有“我为什么做这个决定”“上次那个报错是怎么解的”。这部分我借鉴了《记忆碎片》的做法把记忆分成三层。第一层是即时便签对应电影里主角手里的拍立得。代理每完成一个关键动作就往一个固定文件里追加一条结构化记录包含时间、动作、结果、下一步。这个文件永远在上下文里占一个固定位置压缩策略一般不会动它因为它短且高频更新。第二层是纹身对应主角身上那些不可磨灭的标记。这是项目根目录下的约定文件比如接口契约、目录规范、禁止修改的文件列表。这些内容不随对话变化代理每次启动都强制读取相当于刻在身上的规则。第三层是档案袋对应主角收集的那些纸条和照片。这是按主题归档的历史决策记录代理需要时通过检索工具去查而不是全部塞进上下文。这样既省 token又保证信息可追溯。3. 核心细节解析让代理对压缩“免疫”的关键机制3.1 状态外置把工作记忆变成磁盘文件最直接有效的一招是不让代理依赖对话历史来记住任何关键信息。具体做法是定义一个状态目录比如.agent/state/里面放几类文件。session.md当前会话的即时便签代理每轮结束前必须更新。contracts/接口契约、数据结构定义只读。decisions/按日期或主题归档的决策记录。locks/锁文件标记某个长任务是否在进行中。代理的系统提示词里明确写任何需要跨轮次记住的信息必须写入上述文件任何需要回忆的信息必须先读文件再回答。这样即使上下文被压缩到只剩最近几轮代理也能通过读文件恢复状态。这里有个细节要注意文件写入必须是幂等的。比如更新session.md不要用追加导致无限膨胀而是用“覆盖最近 N 条”或“按区块更新”的方式。我一般让代理维护一个固定格式的区块每次更新对应区块而不是无脑追加。3.2 启动脚本每次会话都像一次 init借鉴 init.d 的启动流程我给代理定义了一个bootstrap阶段。每次新会话开始代理必须先执行一组固定动作相当于运行启动脚本。读取contracts/下所有契约文件加载到工作记忆。读取session.md最近的状态了解上次做到哪。检查locks/下是否有未完成的锁如果有先处理或询问。读取项目根目录的AGENTS.md或类似约定文件加载全局规则。这四步做完代理才被允许开始处理用户请求。这套流程的好处是可重复、可验证。你可以写一个检查脚本模拟代理启动看它是否真的读了这些文件。如果某次压缩导致代理跳过了启动步骤你立刻能发现。3.3 幂等操作与锁防止失忆后的重复劳动代理失忆最危险的后果之一是重复执行破坏性操作。比如它忘了已经跑过数据库迁移又跑一遍导致数据错乱。init.d 里的锁文件和 pid 文件就是干这个的。我的做法是给每个可能重复的长任务定义一个锁文件比如.agent/locks/migration_001.lock。任务开始前检查锁是否存在存在就跳过并报告“已完成”。任务成功后写入锁文件内容包含完成时间、结果摘要。代理即使完全失忆读到锁文件也知道不该再跑。这里有个坑锁文件本身也可能被压缩策略影响如果代理是通过对话历史“记得”锁文件路径那失忆后就找不到。所以锁文件路径必须写进contracts/或全局约定文件属于“纹身”级别不依赖工作记忆。3.4 记忆检索按需加载而不是全量塞入《记忆碎片》里主角不会把所有纸条都贴在身上而是需要时翻看。代理也一样decisions/目录下的历史记录不应该全部进上下文而是通过检索工具按需加载。我一般给代理配一个简单的检索命令比如按关键词或日期范围查decisions/。代理在需要回忆某个决策时先检索再把相关片段读进来。这样上下文里永远只有当前任务相关的信息压缩策略能切的空间也小。检索工具的实现可以很简单一个 grep 加格式化输出就够了。关键是检索结果要结构化包含文件路径、行号、摘要方便代理引用和后续追溯。4. 实操过程从零搭建一套抗压缩的代理记忆系统4.1 目录结构与初始化先建目录。我在项目根目录下建.agent/结构如下。.agent/ contracts/ api.md schema.md rules.md decisions/ 2025-01-01-init.md 2025-01-02-refactor.md locks/ state/ session.md scripts/ bootstrap.sh checkpoint.sh recall.sh初始化时contracts/下的文件由人写好代理只读。state/session.md初始为空模板。scripts/下的脚本是给代理调用的工具也可以由代理自己维护。4.2 编写 bootstrap 脚本bootstrap.sh的作用是让代理一键加载所有必要状态。内容大致如下。#!/usr/bin/env bash set -euo pipefail AGENT_DIR.agent echo Contracts for f in $AGENT_DIR/contracts/*.md; do echo --- $f --- cat $f done echo Session State cat $AGENT_DIR/state/session.md 2/dev/null || echo (empty) echo Active Locks ls $AGENT_DIR/locks/ 2/dev/null || echo (none) echo Global Rules cat AGENTS.md 2/dev/null || echo (none)代理每次会话开始先跑这个脚本输出直接进上下文。因为输出是固定格式、固定来源压缩策略很难把它切得面目全非。即使被切代理也可以随时重新跑一遍。4.3 checkpoint 脚本把关键状态写回磁盘checkpoint.sh是代理在完成关键动作后调用的用来更新session.md和写锁。我一般让它接受几个参数动作名、结果、下一步。#!/usr/bin/env bash set -euo pipefail ACTION$1 RESULT$2 NEXT$3 AGENT_DIR.agent TS$(date -u %Y-%m-%dT%H:%M:%SZ) cat $AGENT_DIR/state/session.md EOF # Session State - Last update: $TS - Last action: $ACTION - Result: $RESULT - Next step: $NEXT EOF echo checkpoint written at $TS注意这里是覆盖写不是追加。这样session.md永远只有最近一次状态体积可控压缩策略也不会因为它太长而动手。4.4 recall 脚本按需检索历史决策recall.sh接受关键词去decisions/里搜。#!/usr/bin/env bash set -euo pipefail KEYWORD$1 AGENT_DIR.agent grep -rn --include*.md $KEYWORD $AGENT_DIR/decisions/ || echo (no match)代理需要回忆时先跑这个把匹配的片段读进来。因为只读相关片段上下文占用小也不容易被压缩影响。4.5 把流程写进系统提示词工具和目录建好后最关键的一步是把使用规则写进代理的系统提示词并且放在靠前、稳定的位置。我的提示词里通常有这几条硬性规定。每次会话开始必须先运行bootstrap.sh并基于其输出工作。任何跨轮次需要记住的信息必须通过checkpoint.sh写入磁盘不得仅依赖对话历史。需要回忆历史决策时先运行recall.sh检索不得凭记忆猜测。执行任何可能重复的长任务前先检查locks/存在则跳过。contracts/下的文件只读不得修改。这些规则本身也属于“纹身”级别应该同时写在AGENTS.md里双保险。4.6 参数选择与阈值设定有几个参数需要根据项目调整。第一是session.md的更新频率我一般要求代理每完成一个原子动作就更新一次动作粒度大概是“改一个文件”“跑一次测试”“修一个报错”。太粗会丢状态太细会频繁写盘。第二是decisions/的归档粒度按天或按主题都行。我倾向按主题因为检索时关键词匹配更准。每个决策文件控制在几百字太长就拆。第三是锁的过期策略。有些任务可能中途失败锁没释放。我一般给锁文件加时间戳超过一定时长比如 24 小时视为过期代理可以询问后清理。这个阈值不要设太短否则长任务会被误判。5. 常见问题与排查技巧实录5.1 代理不读文件直接凭记忆回答这是最常见的问题。原因通常是系统提示词里规则不够硬或者规则位置太靠后被压缩切掉了。解决办法是把规则放在提示词最前面并且用AGENTS.md做冗余。另外可以在bootstrap.sh输出里加一行醒目提示比如“你必须基于以上内容工作不得凭记忆”。排查时你可以故意在对话中途清空上下文看代理是否还会去读文件。如果不会说明规则没生效。5.2 锁文件导致任务被永久跳过有时候任务其实失败了但锁文件已经写入后续代理看到锁就跳过问题被掩盖。解决办法是锁文件里记录结果状态成功才写“done”失败写“failed”并允许重试。代理检查锁时看到 failed 应该询问是否重跑。我一般让checkpoint.sh在写锁时带上状态字段bootstrap.sh输出锁列表时也显示状态方便代理判断。5.3 session.md 被压缩策略影响虽然session.md很短但如果它被放在对话历史里而不是每次重新读取仍可能被切。解决办法是每次会话开始都重新跑 bootstrap而不是依赖上一轮读到的内容。这样session.md的内容总是新鲜的不依赖历史。5.4 检索结果太多上下文爆炸recall.sh如果匹配太多输出会很长。解决办法是加限制比如只输出前 20 行或者要求代理先给更具体的关键词。我一般让脚本支持-m参数限制匹配数默认 10。5.5 常见问题速查表问题现象可能原因排查动作解决方向代理重复执行已完成任务锁文件未生效或未检查查看locks/是否有对应锁补写锁检查 bootstrap 输出代理忘记接口约定契约未进上下文跑 bootstrap 看 contracts 是否输出把契约写进 contracts 并确保 bootstrap 读取代理凭记忆猜历史决策未用 recall 检索检查提示词是否要求检索强化规则加 AGENTS.md 冗余session.md 内容陈旧未重新读取看代理是否每轮跑 bootstrap强制每轮开始跑 bootstrap检索输出过长关键词太宽泛看 recall 输出行数加匹配数限制细化关键词5.6 几个我踩过的坑第一个坑是把状态写在对话里而不是磁盘。早期我图省事让代理在回复里维护一个“当前状态”区块结果压缩一来全没了。后来全部改成写文件才稳定下来。第二个坑是锁文件路径写死在对话里。代理失忆后找不到锁等于没锁。后来把锁目录写进contracts/rules.md属于纹身级别才解决。第三个坑是bootstrap 输出太长。一开始我把所有 decisions 都塞进 bootstrap结果上下文瞬间爆掉。后来改成 bootstrap 只读 contracts 和 sessiondecisions 按需 recall才平衡。第四个坑是代理偷懒不跑脚本。有些模型会“假装”跑了实际没跑。解决办法是让脚本输出带时间戳和随机数代理必须把输出贴回来你一眼能看出真假。6. 进阶让记忆系统自我维护6.1 自动归档与清理跑久了decisions/会膨胀。我一般加一个归档脚本把超过一定时间的决策按主题合并或者移到decisions/archive/。代理在 checkpoint 时可以顺便触发归档保持目录清爽。6.2 记忆一致性校验可以写一个校验脚本检查contracts/和decisions/是否矛盾比如接口定义变了但决策记录没更新。代理在 bootstrap 时跑一下发现矛盾就报告。这能防止失忆后的行为漂移。6.3 多代理共享记忆如果多个代理协作.agent/目录可以作为共享记忆。每个代理启动都跑 bootstrap写 checkpoint 时加代理标识避免互相覆盖。锁文件也要加代理前缀防止误判。这套东西搭起来不复杂但效果很明显。我在几个模拟项目里跑长会话上下文被压缩多次代理依然能保持行为一致该读的文件读该跳的任务跳该查的历史查。核心就一句话别让代理的工作记忆承担它承担不起的责任把记忆外置到磁盘用 init.d 的纪律和《记忆碎片》的坚持去维护它。