☰
第四章:我是如何扒开 Claude Code 记忆与上下文压缩机制的
2026/10/4 9:50:27 网站建设 项目流程

1. 长会话为什么会“失忆”:从一次 400 报错说起

如果你用 Claude Code 跑过稍微大一点的重构任务,大概率见过这个报错:400 Token Limit Exceeded。前一秒它还在帮你改src/services/compact/里的逻辑,后一秒你贴了一段日志,它直接翻脸不认人,连刚才改过哪个文件都答不上来。这不是模型变笨了,而是上下文窗口被塞满了。

Claude Code 面对的是一个很现实的矛盾:代码库动辄几万行,排查一个 Bug 可能聊半小时,但 API 的上下文窗口是有限的,而且越长越贵。它必须做两件事——保留关键信息,同时不撑爆 Token 预算。这套机制拆开看,主要落在三个地方:src/services/compact/负责压缩,src/memdir/负责记忆分层,src/tools/AgentTool/负责分身试错。

我实测下来,理解这三块之后,你对“长会话里信息怎么被保留、裁剪、回填”会有一个非常具体的画面。这篇就按可跟做的顺序来:先讲压缩触发点怎么观察,再讲记忆文件怎么落地,然后给出可复制的配置片段,最后把常见报错一个个对照排查。适合已经在用 Claude Code 做长期编码、或者想自己搭 Agent 记忆层的人。

核心检索词先摆出来:Claude Code 上下文压缩、记忆机制、AgentTool、git worktree。这几个词贯穿全文,你可以在每一节里找到对应的操作动作。

2. 压缩链路拆解:stripImages 与 PTL 逃生舱怎么触发

2.1 压缩前先“瘦身”:图片和文档被替换成占位符

Claude Code 在发起压缩总结之前,会先做一次大瘦身。原因很直接:把几兆的截图重新发给模型,只为了让它生成一句“用户发了一张报错截图”,是极度浪费钱的。源码里stripImagesFromMessages这个函数干的就是这件事——扫描消息内容,把image和document类型的块替换成纯文本占位符[image]、[document]。

你可以这样理解:压缩不是一上来就总结,而是先把“体积大但信息密度低”的部分砍掉,再对剩下的文本做摘要。这个顺序很关键,因为如果先总结再剥离,总结请求本身可能就已经超载了。

2.2 连压缩请求都超载:truncateHeadForPTLRetry 逃生舱

这是我在源码里看到最硬核的设计之一。设想一个场景:用户一次性塞入太多巨大文件,导致连“发起压缩总结”这个请求本身都超过了 API 的最大 Token 限制。按普通逻辑,系统直接死锁崩溃。

源码里专门写了truncateHeadForPTLRetry作为最后的逃生舱。它的逻辑分两种:

  • 如果 API 明确返回了超出的 Token 数量(tokenGap),就精准计算要丢弃几轮对话,累加估算直到覆盖这个 gap;
  • 如果连超出多少都不知道,就默认抛弃最老的 20% 历史记录,dropCount = Math.max(1, Math.floor(groups.length * 0.2))。

官方注释写得很直白:这是最后的逃生舱,丢弃最老的上下文虽然有损,但能防止对话彻底卡死。真金白银买来的经验——宁可丢老信息,也不能让整个会话挂掉。

2.3 怎么观察压缩触发点

想亲眼看到压缩发生,可以构造一个多轮长上下文任务。我的做法是:先让 Claude Code 读一个中等大小的模块,然后连续追问十几轮细节,每轮都让它引用之前改过的文件。当对话历史累积到一定程度,你会观察到它开始“概括”前面的内容,而不是逐字引用。

验证动作:在会话里让它列出“到目前为止我们改过哪些文件”。如果它列出的条目比实际少,或者把早期改动合并成一句模糊描述,说明压缩已经触发。这时候你可以对比压缩前后的回答差异,记录哪些信息被保留了、哪些被裁剪了。

注意:压缩是有损的。关键决策、文件路径、报错原文这类信息,最好在对话早期就写进记忆文件,而不是指望压缩帮你留住。

3. 记忆分层落地:MEMORY.md 索引 + 详情文件的可复制配置

3.1 没有向量数据库,只有文件系统加一段 Prompt

外界一直猜 Claude Code 连了向量数据库管理长期记忆。翻开src/memdir/memdir.ts会发现,它极其务实地用了本地文件系统,外加一段教科书级的 System Prompt。核心是“两步走法则”:

  • Step 1:把详细记忆写进独立的.md文件,比如user_role.md、feedback_testing.md,带name、description、type的 frontmatter;
  • Step 2:在MEMORY.md里加一行指针,每个条目一行,控制在约 150 字符以内。

MEMORY.md是索引,不是记忆本身。它会被无条件塞进每一次对话上下文,所以必须严防死守。源码里做了强制截断:MAX_ENTRYPOINT_LINES = 200,MAX_ENTRYPOINT_BYTES = 25_000。一旦超标,直接从末尾一刀切,并附上大写警告> WARNING: MEMORY.md is ... Only part of it was loaded.。模型下次读到这句警告,自己就会去精简索引。

3.2 可复制的 settings 片段

如果你在 Claude Code 里配置记忆目录,可以参考下面这个结构。路径按你项目实际位置调整,字段名保持一致:

{ "memory": { "enabled": true, "entrypoint": "MEMORY.md", "directory": ".claude/memory", "maxEntrypointLines": 200, "maxEntrypointBytes": 25000, "autoCompact": true } }

对应的目录长这样:

.claude/memory/ ├── MEMORY.md # 索引,每行一条,<150 字符 ├── user_role.md # 详情文件 └── feedback_testing.md # 详情文件

MEMORY.md内容示例:

# 全局记忆索引 - [用户角色](./user_role.md): 后端工程师,主用 TypeScript 和 Go - [测试反馈](./feedback_testing.md): 集成测试必须跑真实数据库,不用 mock

3.3 三件套对齐:Base URL + Key + Model ID

如果你是通过兼容接口接入 Claude Code,配置里必须写全三件套,缺一个都会在请求阶段报错:

# 示例配置,字段名按你的客户端要求调整 base_url = "https://taotoken.net/api" api_key = "你的 API Key" model = "claude-sonnet-4-20250514"

Base URL 用https://taotoken.net/api,不要加多余路径。Key 在控制台的 API Keys 页面生成。Model ID 按你实际要用的模型填,别照抄示例里的名字。这三项对齐之后,记忆文件和压缩机制才有稳定的请求通道。

4. 验证请求与成功结果:构造多轮长上下文任务

4.1 构造任务

打开 Claude Code,进入一个真实项目。第一步,让它读一个模块并总结:

请阅读 src/services/compact/ 下的文件,列出每个文件的职责,写进 .claude/memory/compact_module.md

第二步,连续追问,每轮都要求它引用之前的内容:

基于刚才的总结,compact.ts 里处理图片剥离的函数叫什么?它把 image 块替换成了什么?

第三步,累积到十几轮后,触发压缩,再问:

到目前为止,我们讨论过哪些文件?分别改了什么?

4.2 成功结果长什么样

如果配置正确,你会看到:早期写入MEMORY.md的条目在压缩后依然被引用,因为索引文件每次都会加载;而对话历史里的细节可能被概括。验证点是——它能否准确说出compact_module.md这个文件的存在,以及里面记录的函数名。

请求层面,成功的标志是返回200,响应里choices字段有正常内容。如果走的是流式,你会看到 token 逐步返回,没有中断。

4.3 记录可复现的排查步骤

每次验证都记下三样东西:触发压缩的轮数、被保留的信息、被裁剪的信息。我试过在同一个项目里跑两遍,第一遍不写记忆文件,第二遍先写MEMORY.md再聊。结果很明显:第二遍在压缩后仍能准确回答早期决策,第一遍则开始含糊。这个对比就是你判断记忆机制是否生效的最直接证据。

5. 常见报错对照排查:401、local proxy failed、reading choices、OAuth

5.1 401 Unauthorized

最常见的原因是 Key 没配对,或者 Base URL 写错。检查顺序:先确认api_key字段填的是控制台生成的 Key,没有多余空格;再确认base_url是https://taotoken.net/api,没有拼错路径。如果 Key 刚生成,等几秒再试,避免缓存。

5.2 local proxy failed

这个报错通常出现在本地网络配置层面。先确认你的请求地址是直连的 API 地址,不要经过额外的本地转发层。检查配置文件里有没有残留的代理字段,删掉后重启客户端。如果用的是环境变量,确认HTTP_PROXY、HTTPS_PROXY没有被设置成无效值。

5.3 reading choices 相关报错

这类报错说明请求发出去了,但响应结构不符合预期。常见原因是 Model ID 填错,或者接口返回了错误对象而不是正常的choices数组。排查动作:把 Model ID 换成确认可用的值,重新发一次最小请求。如果还报错,检查请求体里有没有多余的字段导致服务端拒绝。

5.4 OAuth 相关报错

如果你用的是需要 OAuth 的客户端,报错通常和 token 过期有关。重新走一遍授权流程,确认回调地址和客户端配置一致。OAuth 和 API Key 是两套体系,别混用——用 API Key 的场景就不要再配 OAuth。

5.5 三件套检查清单

出现任何请求类报错,先过一遍这个清单:

检查项正确值常见错误
Base URLhttps://taotoken.net/api多了/v1或结尾斜杠
API Key控制台生成复制时带了空格
Model ID实际可用模型照抄示例名

6. 把记忆层接进你的工作流

压缩和记忆这两块跑通之后,下一步是让 AgentTool 和 git worktree 发挥作用。当你要做破坏性实验时,让子代理在独立的 worktree 里跑,主分支不受影响。配置里isolation: 'worktree'这个字段就是干这个的,底层会自动调git worktree add创建平行目录。

如果你想把记忆能力接到自己的工具链里,可以从 API Keys 页面拿到 Key,再对照接入文档把 Base URL 和 Model ID 填好。验证模型是否通,直接用模型对话发一条最小请求最快。长期跑编码任务、需要 Agent 持续记忆的场景,Coding Plan 更合适,省得每次手动配。

最后留一个实用技巧:每次开新会话前,先让 Claude Code 读一遍MEMORY.md,把索引加载进来。这一步花不了几秒,但能让它在长会话里少失忆好几次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询