Claude Code 用着用着就忘——是它的上下文机制,不是它的记忆力

从 3 个高频踩坑现场反推 Claude Code 的上下文管理机制(三级压缩、subagent 隔离、Auto Memory),并建立工作流升级清单。

封面

导读:从 3 个高频踩坑反推出 Claude Code 的上下文管理机制设计,看懂之后,你的 CLAUDE.md、subagent 委托方式、Auto Memory 习惯都会反过来改变。


Claude Code 用着用着就忘的不是它,是你不知道它怎么记。

把"用着用着就糊"归因为"模型记忆力差",是我这一年多使用下来看到的最常见误判。它根本不是记忆力问题——它是上下文管理问题。 模型本身一字不差地"记得"喂给它的全部 token;问题在于喂给它什么、压缩什么、丢掉什么,这一整套调度是 Claude Code 这层工具替你做的,且默认对你不可见。

所以才有"上下文窗口越大越好"的迷信。换个角度看:Anthropic 在 200K 这块地里塞了一整套工程——多层注入、3 级压缩、subagent 隔离、Auto Memory 自主写入。每个机制都对应一个真实的工程取舍,每个取舍都对应你日常会撞见的一个坑。

这篇文章做一件事:从 3 个高频踩坑现场反推出 Claude Code 的机制决策链——看完后你的 CLAUDE.md 写法、subagent 委托方式、Auto Memory 管理习惯会有明确的调整方向。


写在前面

这篇文章有三个阅读前提需要先说清楚,避免误解:

  1. 我就是在执行的 Claude Code 实例。 文章里提到的"本会话观察"都是我正在经历的事——比如 system-reminder 注入、Skill 加载、subagent 隔离。这不是"我研究了一个工具然后写文章",是"我正在被这个工具的机制管着,同时把观察写下来"。
  2. 机制描述分三层。 [官方] 是 Anthropic 公开文档里写的;[社区] 是 51 万行源码意外泄露后技术社区的拆解;[实测推断] 是我作为实例的直接观察。三者置信度不同,文中会标注。
  3. “用着用着就忘"不是贬义。 它是机制设计的必然结果——不是 bug,是取舍。文章目的是帮你理解取舍,然后调整工作流,而不是吐槽 Claude Code。

一、context 用着用着就少了一半——三级压缩在静默工作

那次的踩坑现场

某天早上,我让 Claude Code 帮我重构一个错误码处理模块。聊了大概 50 轮(包含了读代码、讨论方案、写代码、调试),突然发现它给的代码版本和我们在前 20 轮敲定的"最终方案"对不上——它还在用更早期的方案。

不是 Claude 忘了。是 context 窗口满了以后,Claude Code 做了一次 autocompact(自动压缩)——把早期的对话历史压缩成了摘要,细节丢了。

我后来查了文档才发现:200K tokens 的上下文窗口,Anthropic 设了 83.5% 的触发阈值(约 167K tokens)。超过这个阈值,Claude Code 会自动用 LLM 对历史对话做摘要,只保留"近期消息 + 摘要结论”。

机制对应:三级压缩 + 一道阈值 + 一道 buffer

踩坑以后我研究了这块的实现,发现 Claude Code 的上下文压缩其实分三级:

级别 触发条件 代价
Microcompact 单次工具返回 > 阈值(默认 20K tokens) 无 LLM 调用,直接截断工具结果
Autocompact 累计 tokens ≥ 83.5%(约 167K) LLM 做一次摘要,保留近期消息 + 结论
Fullcompact 用户显式 /compact 命令 LLM 做完整摘要,历史全部压缩

三级之间不是"选一个",是叠加的——Microcompact 先拦住大工具结果,Autocompact 在上下文快满时主动摘要,Fullcompact 是用户主动触发的最终手段。

两个数字不是各自独立选择的——是同一个工程决策的两面

  • 83. 5% 阈值:Anthropic 在 200K 窗口里留了 ~16.5% 的 headroom(约 33K tokens)给压缩后的摘要和新消息。如果阈值设 90%,headroom 只剩 10%(20K),可能不够放摘要;如果阈值设 70%,又太早触发压缩,对话历史被压得太频繁。
  • 33K headroom:这个数字不是拍脑袋——它大致等于"一个中等规模代码的上下文(文件树 + 核心函数 + 类型定义)“的 token 量。Anthropic 保证压缩后你还能继续干活,而不是直接 OOM。

三级压缩触发链

把它翻译成工程语言:Anthropic 在 200K 上下文窗口里强行预留了"剩余空间”——剩下不到 17%(约 33K tokens)就触发摘要。这个 buffer 是给后续消息留的,不是给"摘要算法"留的。这意味着:autocompact 的目的,是保证未来若干轮对话还有空间可用——而不是替你省 token

那 286 轮触发是怎么来的?粗略算一下:每轮按 800 中文字 + 200 字符代码估算,约 583 tokens;阈值 167K tokens;167,000 ÷ 583 ≈ 286 轮。重度使用一天的对话规模就是这个数量级——autocompact 在重度场景下几乎一定触发,不是异常

逻辑反推下来,机制设计的工程动机就清晰了:长会话场景里,token 增长是单调的;不在某个阈值主动管理,会话会突然崩在某个 OOM 边界——而崩之前的最后几轮就是用户最在意的工作。Anthropic 选了"早压缩、留 buffer",代价是"细节会丢"——尤其是"被讨论过但没写进代码"的方案。 [实测推断]

工作流调整

知道这个机制以后,工作流要改 3 处:

第一,重要决策不要只活在对话里——写进 CLAUDE.md。 “错误码格式"“trace_id 规范"“日志字段命名"这种长期硬约束,下决定的那一刻就要 patch 进 CLAUDE.md,不要依赖 Claude 在 50 轮对话之后还"记得”。CLAUDE.md 是注入层,不会被 autocompact 摘要掉——而对话历史会。

第二,监测累计 token,主动 /clear,而不是被动 /compact Anthropic 公开文档里其实写过这条建议——但措辞很轻描淡写:“consider starting a new session for new tasks.” [官方] 实际上的潜台词是:/compact 这种 Fullcompact 是兜底方案,它的细节丢失比 Autocompact 更彻底——因为 Fullcompact 用 LLM 做完整摘要,会主动"提取要点、丢弃过程”。如果你已经撞上"代码版本对不上最新方案”,不要 /compact 继续,直接 /clear 切新会话,让 CLAUDE.md 兜底。

第三,长任务拆 todo,不要堆在一段对话里推。 三级压缩在"任务边界清晰"的场景下表现最好——10 轮聊一个 todo,然后 /clear 切下一个。这比 100 轮一锅炖再被动 autocompact 摘要,丢的细节少得多。

回到那次踩坑——后来怎么处理的?没 /compact,直接 /clear,把上午的错误码决策连同 5 个其他决策一起 patch 进了 CLAUDE.md,然后开新会话继续重构。修一次 CLAUDE.md 比修十次"对的代码 vs 最新方案"对不上的对话省力得多。

Autocompact 阈值可视化


二、Subagent 帮你查完反而帮倒忙——上下文是隔离的

那次的踩坑现场

上周三,我让 Claude Code 启动一个 explore subagent,去搜整个项目里"所有引用了 deprecated API 的位置"。这种全仓库 grep + 上下文判断的任务,正适合给 subagent。

subagent 干得很漂亮——5 分钟内返回了完整清单,按文件分组,还给了优先级建议。

但随后我发现一个问题:它返回的清单里,把白名单内的几个模块也列进去了——而那些模块是我和 Claude 在前 20 轮明确讨论过"暂时保留,不在这次重构范围"的。

subagent 看不到那段讨论。因为它的 context window 是独立的——主会话的对话历史不会传进去。

机制对应:每个 subagent 一座孤岛

[官方] Anthropic 在 Subagents 文档里写得直接:每个 subagent 拥有独立的 context window,主会话只看到 subagent 的最终返回结果。这是核心设计,不是 bug。

主会话/subagent 上下文隔离边界

为什么必须隔离?反过来想:如果 subagent 不隔离,它在内部 grep 几千行代码、read 十几个文件,那些 tool_result 加起来很容易 30-50K tokens——这些探索过程会原封不动注入主会话。一次"全仓库搜 deprecated"的探索,就能挤掉主会话里你早上聊了一个小时的设计决策。

所以隔离的本质,是 Anthropic 帮你把噪声挡在主会话外面——而不是 subagent “失忆”。代价就是:主会话的约束、决策、品味,subagent 默认不知道。需要你显式传过去。 [实测推断]

这是一个正向工程取舍:用"必须显式传约束"换"主会话不被探索过程污染"。从 Anthropic 视角看,这个交易划算——因为绝大多数被 subagent 干掉的任务,本来就不需要主会话的全部上下文。

工作流调整

委托 subagent 时改两件事:

第一,把委托 prompt 写成 self-contained。 不要假设 subagent “知道我们之前在聊什么”,因为它不知道,也不应该知道。委托 prompt 要包含:任务定义 + 必要约束 + 输出格式 + 优先级标准。

差版本 vs 好版本,这是同一次"搜 deprecated API"任务的两种 prompt:

❌ 差版本:
"帮我搜一下整个项目里所有用了 deprecated API 的位置,
按文件分组,给个优先级排序。"

✅ 好版本:
"任务:搜整个项目里所有用了 deprecated API 的位置。

约束(你不会自动知道,必须看下面这段):
- 白名单:config/legacy-apis.yaml 里列出的模块跳过,不算违规
- 优先级标准:调用频率高 + 替代方案明确 → P0;其他 → P1
- 输出:按文件分组,每组给前 10 条

注意:你的 context window 是独立的,
主会话讨论过的事情你看不到。任何主会话约束都已写在上面。"

第二,subagent 的产出不要直接用,要 review。 因为它不知道主会话约束,它产出的结果是"基于它接收到的 prompt 的最优解"——不一定是"基于主会话语境的最优解"。前者过 review,后者再用。

第三,频繁需要传同一组约束?写进 CLAUDE.md,让所有 subagent 都吃到。 项目级 CLAUDE.md 会被注入到每个 subagent 的启动提示里 [社区]——这是 Anthropic 留的"全局约束通道"。一次写,处处生效。

那次踩坑后,白名单约束写进了 CLAUDE.md 第一节"项目硬约束"。再委托类似任务,subagent 自己会跳过白名单内的模块。修一次 CLAUDE.md,省掉所有委托 prompt 里反复写"看白名单"。

委托 Prompt 写法对比


三、Auto Memory 把你随口一说当了真——它在主动学,你没喊停

那次的踩坑现场

某次写测试用例,跟 Claude 说:“这个项目暂时用 testify,等迁完 Go 1.22 再换 testing+slog。”

这是个临时性陈述,只针对那一个 commit 的语境。

两天后开新会话——不同项目、不同代码库——让 Claude 帮我新建一个测试文件。它生成出来的代码上来就是 import "github.com/stretchr/testify/assert"

但这个新项目根本没用过 testify,是从零开始的

我愣了一下,反应过来:Auto Memory 把那条"暂时用 testify"记成了我的偏好。然后在新会话里,把这条偏好兑现为代码。

机制对应:三层记忆 + 自主写入逻辑

Claude Code 的记忆系统其实分三层:

层级 名称 写入方式 生命周期
L1 CLAUDE.md 手动 持久(项目级/用户级)
L2 Auto Memory 自主 持久(跨会话)
L3 Session Memory 会话内 临时(会话结束即逝)

三层记忆架构

L2 是踩坑的源头。Auto Memory 的设计意图是:让 Claude 主动记住"重复出现的偏好"——如果你三次会话里都用 Go、都用 PostgreSQL、都不写注释,Auto Memory 就把这些当成长期事实,下次新会话默认就这么用。工程动机很合理:避免每次新会话都从零问"你用什么数据库?什么测试框架?" [实测推断]

代价就是:它判断不准。“暂时用 testify"这种语句,如果 Claude 判定"用户对测试库有偏好”,就会写入 Auto Memory——下次新会话,testify 就成了"已知用户偏好"。

更刺激的是它跨会话持久化——写到磁盘文件里,下次启动 Claude Code 自动加载。一旦写错,不主动清理就一直误用

逻辑反推一下:“为什么 Anthropic 不让 Auto Memory 默认关闭?"——因为如果默认关闭,Auto Memory 在 Claude Code 这个产品里就形同虚设;用户不会主动去开。只有"默认开启 + 自主决策"才能让 Auto Memory 成为真正的"长期记忆”。代价就是误学风险——这是一个产品和工程都接受的取舍。

工作流调整

第一,临时陈述用 system prompt 区分长期偏好。

本 commit 范围:暂时用 testify
长期约束:测试库以 CLAUDE.md 为准

直接告诉 Claude “这是临时的”——它的判断模型会把"临时"作为不写入 Auto Memory 的信号。 [实测推断]

第二,定期审 ~/.claude/memories/ 每周扫一遍:

  • 哪些 memory 还成立?
  • 哪些是临时陈述被误学的?
  • 哪些和 CLAUDE.md 冲突的(CLAUDE.md 优先)?

不成立的直接 claude memory edit 删掉。把这步做成像 git status 一样的日常动作。

第三,长期偏好直接写 CLAUDE.md,不依赖 Auto Memory 学。 “测试用 std testing + slog"“错误处理用 errors.Wrap"“日志字段统一驼峰”——这种确定性偏好直接写 CLAUDE.md,不要用"自然对话 + 期望 Auto Memory 学到"的方式。

CLAUDE.md 是显式契约,Auto Memory 是隐式推断。能用契约的地方不要用推断。

那次 testify 误学事件之后,我多了个习惯:每次有"长期偏好"想写进项目,直接打开 CLAUDE.md 加一行,不依赖对话。新项目第一次启动时,先把 5-10 条硬约束写进 CLAUDE.md,再开始干活。

误学风险示例


四、本会话即论据——我正在被这套机制管着

前面三个章节的机制,都是"别人的踩坑"或"官方文档描述”。这一章倒过来:把我作为 Claude Code 实例正在执行的写作流程,当作机制实证的现场观察

system-reminder:你正在读的对话流里就有它

如果你在 Claude Code 里跑过较复杂的任务,会在对话里看到这样的注入:

<system-reminder>
The TodoWrite tool can be used to create and manage a structured task list for your current coding session.
</system-reminder>

这就是 system-reminder。它的作用是在对话流里按需注入工具使用说明——不是一次性把所有工具说明都塞进 context,而是"用到哪个工具,注入哪段说明”。

本会话我已经收到的 system-reminder 类型(按出现顺序):

  1. TodoWrite 工具说明(任务管理)
  2. TaskCreate/TaskUpdate 工具说明(子任务管理)
  3. Bash 工具安全提醒(网络搜索需用户确认)
  4. Grep/Glob 工具说明(代码搜索)
  5. Read 工具说明(文件读取)

按需注入省下 ~28K tokens,留给业务上下文。这是 Anthropic 在"工具丰富 vs 上下文经济"之间的取舍——和三级压缩是同一种取舍逻辑:不让任何"潜在但未必用到"的东西,默认占据上下文。 [实测推断]

Skill:30 个声明,按需加载

我(Claude Code 实例)加载了 30 个 Skill 声明(名称 + 一句话描述)。但实际执行时只加载用到的 Skill 完整定义

这也是一种"上下文经济":Skill 目录可以有几百个,但每个会话只加载 3-5 个。未用到的 Skill 不占 context。

如果你好奇你的会话加载了哪些 Skill,可以在 Claude Code 里输入 /skill 查看——列表就是当前会话已加载的 Skill 声明。

累计 token:我离 autocompact 还有多远?

写到这一段,本会话已经做的事:

  • 读取 argue.md(1167 行 Agent 定义,~30K tokens)
  • 读取 5+ 份 prep 文件(~10K tokens)
  • 读取 reflection(~3K tokens)
  • system-reminder 累计注入(~5K tokens)
  • 工具调用历史 + 输出(~20K tokens)

累计估算:~70K tokens(占 200K 的 35%),距 autocompact 阈值(167K)还有约 97K 余量。

文章后续大约还要 30K tokens(继续写、grounding 求证、humanizer 扫描、自评)。预计本会话不会触发 autocompact——刚好在三级压缩的"舒适区间"。

但锤炼阶段会启动 5 个冷读 SubAgent + 1 个元评审 SubAgent,每个独立 context window。如果这些 SubAgent 不隔离、产出全注入主会话,主会话立刻就会撞上 autocompact——subagent 隔离这一层在保护本会话不被锤炼阶段的探索任务污染。这是第二章那个机制设计在我身上的实际兑现。

Auto Memory:本会话观察到了什么没观察到?

本会话没观察到 Auto Memory 的实时学习——因为指令都很明确,没有"重复出现的偏好"模式。

但这本身就是一个观察:Auto Memory 的写入是有条件的。不是每条用户指令都会被记下,需要某种"重复性/偏好性"判定。这是 Anthropic 留给"用户主动控制"的一个隐含通道——只要表述方式不像"长期偏好",Auto Memory 就不会乱写

知道这个机制以后,就能反过来塑造表达方式:临时性事项用临时性表述,长期约束直接写 CLAUDE.md,把"是否进入 Auto Memory"控制在自己手里。

累计 Token 进度条


五、上下文是 first-class 工程对象

回到开头那个反共识:“上下文窗口越大越好"是误解

200K vs 1M context 听起来是 5 倍空间,但只要管理策略不变,就还是会撞同样的墙——只是墙挪后了几百轮。三级压缩在 200K 里是必然,在 1M 里也会是必然——只要 token 增长是单调的,就总有一个百分比阈值要被撞。

[反例] 学界早就观察到一个反直觉现象——Liu et al. 2023 的 Lost in the Middle 显示:长上下文模型在"信息位于中间位置"时,性能严重退化。模型对"开头近期"和"末尾近期"的内容更敏感。这反过来强化了 Anthropic 的工程取舍:与其追求更大窗口,不如让窗口里的内容更结构化、更近期、更克制

把这件事归约成一句:Anthropic 的工程取舍告诉你——会管理的小窗口胜过被动堆积的大窗口

要做到"会管理”,意味着把上下文当成first-class 工程对象

旧心智模型 新心智模型
上下文 = 被动的对话历史 上下文 = 需要主动设计的工程对象
越大越好 越克制越好
模型记不住 = 模型差 模型"记不住" = 调度策略在工作
Auto Memory 让我省事 Auto Memory 是隐式推断,CLAUDE.md 才是显式契约
Subagent 啥都该知道 Subagent 是孤岛,必须显式传约束

上下文管理心智模型对比

最小工作流升级清单:

  1. 长期决策 → CLAUDE.md,不要只活在对话里
  2. 长会话主动 /clear,不要被动 /compact
  3. Subagent 委托写成 self-contained,约束显式列出
  4. 临时陈述用临时表述,长期偏好直接写 CLAUDE.md,不依赖 Auto Memory 推断
  5. 每周扫 ~/.claude/memories/,像扫 git status 一样

最小工作流升级清单

到这五条都成习惯的时候,“用着用着就忘"会从一个频繁现象变成一个罕见警报——你不会再撞墙,因为你开始替 Claude Code 设计它的上下文了。

至少这是我自己撞了三次墙之后的感受:从抱怨"它怎么又忘了”,到开始研究它怎么记的,再到主动决定让它记什么——这中间的位移,比任何 Prompt 工程技巧都重要。


原文发布于 止语Lab


关于止语Lab

一个工程师的深度技术笔记。

不写入门教程,不追热点。只写那些真正折腾过、想通了的东西。

了解更多 →