Claude Code 上下文管理深度解析
深入理解 Claude Code 的上下文窗口机制、五层递进压缩体系、Prompt Cache 策略及实践技巧
一、引言:为什么你的 Claude Code “用着用着就忘了”
你有没有遇到过这样的场景:刚跟 Claude Code 说了半小时的项目需求,它应答如流;可再过半小时,你让它改一个之前约定好的接口,它却像失忆了一样,问出一些让人血压飙升的问题。“明明刚才说好的,怎么现在就忘了?“这不是你的错觉,几乎每个 Claude Code 用户都会在某一天撞上这堵墙。
但请放心,这绝不是模型"记忆力差”——Claude 本身的推理能力是顶尖的。真正的原因,藏在一个你可能从未留意过的概念里:上下文窗口(Context Window)。
通俗地讲,上下文窗口就是 Claude 的"工作记忆”。每一次对话、每一个被读取的文件、每一条命令输出,都会塞进这个窗口里,就像往一张桌子上不断堆放资料。桌子再大,终归有边界。Claude 当前最新的 Opus 4.6 模型已支持最高 100 万 token 的上下文窗口(beta),听起来大得惊人——大约相当于 75 万个英文单词。不过在 Claude Code 的实际使用中,受定价策略和压缩机制的影响,当前有效窗口通常以 200K token 为基准运作(详见 2.1 节)。但即便是 200K,一次读取 2000 行代码就可能消耗掉上万个 token,一个复杂的调试会话轻松吃掉几万 token。
上下文窗口是模型的工作记忆,而不是长期存储。Claude 在会话之间不会保留任何内容。当新会话开始时,上下文窗口是空的。随着对话的增长,窗口逐渐填满。来源
那问题来了:既然窗口有限,Claude Code 又需要持续处理漫长的编程对话,它到底是怎么做到不"崩"的?答案就是一套精心设计的上下文管理机制——包括自动压缩(compact)、会话记忆(session memory)、持久记忆文件(CLAUDE.md 和 auto memory)等多层策略。它们在后台默默地帮你"整理桌面",把关键信息保留下来,把冗余的细节清出去。
Claude Code 的上下文管理不是在喂给模型"越来越多的历史"。它是在有限 token 预算内,持续地组装、预算规划、修剪、折叠、总结和重连信息。来源
本文的目标就是带你揭开这套机制的面纱。不需要你懂机器学习,不需要你会看源码——你只需要带着你用过 Claude Code 的真实体验,我们一起来理解:为什么它会"忘",它用什么办法"记住",以及你如何让它"记得更好"。读完这篇文章,你会知道 /compact 什么时候值得按,CLAUDE.md 该怎么写,以及如何在一个有限的窗口里,跑出一场无限流畅的对话。
二、核心概念:上下文窗口是什么
2.1 什么是上下文窗口——一张"工作桌"的比喻
想象你坐在一张书桌前工作。桌上放着今天要看的文件、你正在处理的资料、你和同事的往来便签,还有你自己摊开的笔记本——这张桌子的大小是固定的。这就是 AI 大模型的上下文窗口(Context Window)在扮演的角色:它是一张有尺寸上限的"工作桌",AI 只能"看见"和"记住"这张桌子上摆着的东西。
Claude Code’s context window holds everything Claude knows about your session: your instructions, the files it reads, its own responses, and content that never appears in your terminal. (大意:Claude Code 的上下文窗口装着会话中的一切——你的指令、它读过的文件、它自己的回复,以及从未在你终端上出现过的幕后内容。)来源
具体来说,这张桌上摆着什么呢?你下达的每一条指令(“帮我改这个 bug”)、Claude 读过的每一个文件内容、你们之间一来一回的对话历史,以及 Claude 自己的每一次回复——所有这些都会被摆到这张桌上。桌子就那么大,放不下了就得"收拾",也就是压缩或者丢弃一部分旧内容。用句大白话说:上下文窗口决定了 AI 一次能"想多远"。超出窗口的内容,它只能靠"笔记"(压缩摘要)来回想,就像你把重要信息抄在便签上收进抽屉里一样。
对于 Claude Code 而言,这张桌子的尺寸通常是 200,000 个 token。但请注意——这个数字并不是 100% 归你使用。
Claude Code has a 200K token context window. After system overhead, roughly 160K-170K tokens are available for actual conversation and tool outputs. (大意:Claude Code 拥有 200K token 的上下文窗口,扣除系统开销后,大约 160K-170K token 可供实际对话和工具输出使用。)来源
在你开口说话之前,桌子的一角已经被一些"固定摆设"占满了。就像一张新书桌还没开工呢,上面就摆了台灯、笔筒和日历——这些是系统提示词(告诉 AI “你是谁、你怎么干活"的基本规则)和工具定义(让 AI 知道它可以调用哪些工具来读文件、执行命令等)。这部分"固定开支"大约在 5,000 到 15,000 个 token 之间,如果你启用了很多 MCP 插件服务器,这部分开销还会进一步膨胀。
好消息是,Claude Code 并不会在你用到桌子边沿时戛然而止。每当上下文快要塞满时,它会自动执行一套"整理术”(称为自动压缩,Auto-compaction),把早期的对话内容缩写成摘要,腾出空间让对话继续下去。所以你不会突然遇到 “内存不足” 的报错——你只是会感觉到 AI 开始"忘事"了。理解这张桌子的容量边界,就是理解为什么长对话中 Claude 的行为会发生微妙的变化。
💡 桌子大了就万事大吉? 并非如此。研究发现,即使上下文窗口再大,模型对放在"中间段落"的信息注意力会显著下降——两头的内容被"看清",中间的内容容易被"看糊"。这被称为 “Lost in the Middle"现象,是 Transformer 注意力机制的固有特性,不是窗口大小的锅。来源 就像你看一篇长文章——开头认真读,结尾认真读,中间段落可能只是扫一眼。这个现象也是本书第四章要深入讨论的上下文压缩机制存在的基础——窗口再大,也需要主动管理信息结构。
2.2 Token 是什么——AI 的"最小阅读单位”
如果上下文窗口是一张桌子,那么 token 就是桌子上摆放物品的"最小计算单位"。人类阅读以字或词为单位,而 AI 大语言模型阅读以 token 为单位。你给 Claude 的每一段文字,都会先被切分成一串 token,然后模型才开始"理解"它。
那么 token 究竟有多大?一个最常见的经验法则:大约 4 个英文字符等于 1 个 token。
英语文本的粗略估算:~4 个英文字符 ≈ 1 个 token。 来源
按这个比例,200K token 大约相当于 15 万个英文单词——这大致是一部长篇小说的体量。也就是说,Claude Code 一次能"阅读和记忆"的内容量,差不多就是一整本大部头长篇小说的长度。听起来很大,但在编程工作中,读几十个源码文件就可能迅速逼近这个上限。
但这个"4 字符 = 1 token"的规则只适用于纯英文文本。中文的情况不同:1 个中文汉字大约对应 1 到 2 个 token,具体取决于模型使用的分词器(tokenizer)。Claude 的分词器在处理中文时,每个汉字通常对应约 1.4 到 1.6 个 token。
代码文件的情况介于英文和中文之间。代码中大量使用符号、括号、缩进和变量名。下面是一些实用的 token 估算参考:
| 内容类型 | Token 估算 |
|---|---|
| 一个 300 行的 TypeScript 文件 | 约 1,200 token |
| 读 40 个这样的文件 | 约 48,000 token(已占 200K 窗口的近 1/4) |
| 一段 500 字的 Python 代码 | 约 160 token(含符号和缩进) |
| 一段 500 字的 JSON 数据 | 约 130 token(括号和引号较多) |
| 一段 500 字的中文文本 | 约 700-800 token |
一个 300 行的 TypeScript 文件大约 1,200 token。读 40 个文件 = 48,000 token。来源
理解了 token 这个概念之后,你就能看懂一个重要的底线数字:在 200K 的总桌面上,扣除系统自身的固定开支,实际可供你自由支配的 token 大约在 160K-170K 左右。来源 再扣除约 20K 的压缩预留空间,留给文件和对话的实际活动空间就更紧凑了。这就是为什么理解上下文管理对高效使用 Claude Code 至关重要——它不是让你焦虑数字,而是帮你理解为什么"读 100 个文件然后重写"这种粗暴指令,在现实中被自动管控着。
2.3 上下文里都装了什么——桌子上的每一样东西
如果说上下文窗口是 Claude Code 面前的一张工作桌,那这张桌子上到底摆了哪些东西?我们可以把这些东西分成两类:固定摆设和工作杂物。
固定摆设——每次对话都要占的"起步价"
每次你启动 Claude Code,它都会在上下文中塞进几样"标配":
- 系统提示词:这是 Claude Code 的"身份说明书"和行为规范,告诉模型它是谁、能做什么、不能做什么。根据复杂度不同,这部分大约占用 5K 到 15K token。来源
- 工具定义:Claude Code 能用的所有工具(读文件、搜索代码、执行命令等)的函数签名和说明文档。这部分和系统提示词一样,几乎不随对话变化。
- CLAUDE.md 文件:你在项目中写的自定义指令,相当于给 Claude Code 的"项目备忘录"。
这三样东西构成了上下文窗口的"固定开支"。好消息是,正因为它们基本不变,Anthropic 可以对它们做高效缓存,不用每次请求都重新算一遍。来源
工作杂物——用着用着越堆越多
真正随着对话增长的是"动态内容":
- 对话历史:你和 Claude Code 的每一轮问答,包括你发的消息、模型的回复(含 thinking block——模型在回答前自己琢磨的"内心戏")。普通聊天模式下,你说一句话,模型回一句话,上下文只多一条文本。
- 文件读取内容:这是大头之一。你用
Read工具打开一个文件,文件的全部内容会被塞进上下文。一次读取 2000 行的文件,轻松消耗 15K token。你读得越多,上下文就胖得越快。 - 工具调用结果——上下文的最大消耗源。这一点需要单独拿出来说。
每一次工具调用,不是产生一条消息,而是两条。 这是 Claude Code 上下文中最重要的"双倍记账"机制:
- tool_use:模型说我需要用这个工具,参数是这些——这是模型"说出去"的话,算一次 token 消耗。
- tool_result:工具执行完返回的结果——这是系统"传回来"的内容,再算一次 token 消耗。
工具调用在上下文里留下两条记录,而且持续累积。这一点很多人不知道,是上下文爆得快的元凶之一。来源
换句话说,普通聊天是一来一回(用户说一句 + 模型回一句),而工具调用是"用户说一句 → 模型回 tool_use → 系统塞 tool_result → 模型再回一句",一条用户消息触发三次上下文增长。再加上工具执行往往返回大量文本(比如 grep 搜出一百行匹配结果、bash 打印整段编译日志),工具结果是实际中最大的上下文来源。
用一个简单的比喻来总结:系统提示词和工具定义是你桌子上的显示器、键盘——摆上去就不会动;而工具调用产生的 tool_use 和 tool_result,就像你每次查资料时搬上桌的文件夹——翻开一本就占一块地方,翻得越多桌子越乱,直到最后连鼠标都放不下了。
三、上下文组装:每次对话前,Claude Code 都在"搭台"
3.1 上下文是"活的"——不是静态存储,而是每轮动态重建
如果你用过 ChatGPT 的网页版,可能会觉得 AI 对话就像是两个人在微信上聊天——你说一句,它回一句,聊天记录越滚越长。在这个直觉下,“上下文"就是一份不断追加的聊天记录,每次对话时 AI 翻一翻前面的内容就行了。
但 Claude Code 的工作方式完全不同——它把"上下文"当成一个每轮都从零搭建的工作场景来源。可以这样理解:你是一位导演,每次开拍前都要重新布置舞台——灯光、道具、演员站位,缺一不可。上一场戏的布景不会自动留到下一场。Claude Code 在每一次 API 调用之前,都会重新收集所有必要的信息,组装成一个完整的"世界快照”,然后一次性地交给模型。
为什么要这样做?因为大语言模型本身没有跨请求的持久记忆。模型不会"记得"上一轮对话中聊了什么——它只是在每次收到请求时,根据请求中携带的全部内容来生成回复。上一轮的"记忆",实际上是被显式地打包进下一轮的请求里带过去的。Claude Code 要做的就是确保每次请求都携带了足够完整的上下文,让模型能在正确的"舞台"上表演。
具体来说,Claude Code 每次组装的内容包括:系统提示词(定义模型的角色和行为规则)、项目记忆文件(CLAUDE.md 中的编码规范和项目知识)、当前 git 状态(哪些文件改动了、在哪个分支上)、工具定义(模型可以调用哪些命令)、当前用户消息,以及对话历史。所有这些内容,都是每轮重新收集、重新拼接的,并不是一份躺在内存里的静态文本块。来源
打个比方:你要是去餐厅点菜,服务员不会假设你记得上次点了什么——他每次都会重新递上菜单、报上今日特色、询问你的需求。Claude Code 对模型的态度也是如此:每一轮,它都重新呈上完整的"工作台",确保模型看到的永远是最新鲜的当前状态。这种设计看似"笨拙"(每次都重新准备),但恰恰是它能在复杂项目中保持准确性的关键——不会有"脏数据"残留,不会有过期信息干扰。
3.2 三层结构:什么内容、何时加载、如何缓存
既然每次都要重新组装上下文,自然会冒出一个性能疑问:每次都把几十 KB 甚至上百 KB 的内容打包发给 API,会不会太慢了?Claude Code 的答案是——通过三层架构,按内容的稳定性和变化频率分层处理,最大化缓存命中率。
第一层:系统提示词(会话级,静态前缀可跨组织缓存)
系统提示词是整个上下文中最"稳固"的部分。它定义了模型的角色(“你是一个编程助手”)、行为规则(“先读文件再修改”)、输出规范(“使用中文回答”)等。在一次完整的对话会话中,这些内容基本不变。
Claude Code 利用了一个巧妙的设计:系统提示词被一个名为 DYNAMIC_BOUNDARY 的标记切分为两部分。标记之前的部分是纯静态的——无论用户在哪个项目、哪个操作系统上使用 Claude Code,这部分内容都完全相同。因此,这部分可以使用 Anthropic API 的 scope:'global' 参数来做跨组织 prompt 缓存——这意味着不同用户、不同项目之间都能共享同一份缓存,大幅降低首轮延迟和费用。而 DYNAMIC_BOUNDARY 之后的部分则包含会话相关的动态信息(比如当前日期、特定项目的指示),每个会话都不同,不能跨组织共享。来源
💡 注意:“跨组织缓存"共享的是计算缓存(模型处理文本后的中间结果 KV Cache),不是你的项目记忆文件。你的 CLAUDE.md、Auto Memory 等私有数据不会因此泄露给其他用户——缓存只是让服务端"不用重复计算"同一段文本而已。把这个理解为:所有餐厅共用同一台计算器来算账,但每家餐厅的账本(你的数据)是完全独立的。
你可以把它理解为餐厅的标准菜单(静态前缀,所有分店通用)和店长每日特荐(动态后缀,每店每天不同)。
第二层:用户/系统上下文(会话级,memoize 缓存)
这一层包含了与会话相关、但在整个会话期间保持不变的信息:git 状态(当前分支、改动摘要)、平台信息(操作系统、Shell 类型)、CLAUDE.md 文件内容、当前日期等。
由于这些内容在一次会话中不会频繁变化,Claude Code 使用 lodash/memoize 对 getUserContext() 和 getSystemContext() 函数做了记忆化缓存——函数第一次调用时计算结果,之后在同一个会话中直接返回缓存值,不再重复计算。这相当于给每个会话建立了一份"环境快照”,只在会话开始时拍一次照,后续所有请求都复用这份快照。
打个比方:这就像你去住酒店时,前台只登记一次你的身份证信息。你每次出入不需要重新登记,但酒店内部系统始终能查到你的入住记录。
第三层:附件(每轮重建,1 秒超时)
最底层是每轮都在变化的动态内容——用户最新的输入、工具调用结果、文件读取内容等,被封装在 getAttachments() 函数中。这些内容每轮都必须重新组装,因为它们反映的是对话的最新进展。
为了防止附件收集过程阻塞用户交互,Claude Code 使用了 AbortController 设置了 1 秒超时——如果某些附件的获取超过 1 秒,就会中止等待,直接使用当前已收集到的内容发起请求。这是一种务实的折衷:宁可少带一些辅助信息,也不能让用户干等。
总结一下三层的对比:
| 层级 | 内容 | 生命周期 | 缓存策略 |
|---|---|---|---|
| 系统提示词 | 角色定义、行为规则 | 整个会话 | 静态前缀用 scope:‘global’ 跨组织缓存 |
| 用户/系统上下文 | git 状态、平台信息、CLAUDE.md | 整个会话 | lodash/memoize,首轮计算后缓存 |
| 附件 | 用户输入、工具结果、文件内容 | 每轮重建 | 无缓存,1 秒超时放弃 |
这种按"稳定性"分层设计的精髓在于:让最稳定的内容享受最广泛的缓存,而最动态的内容只看当下,不拖泥带水。这正是 Claude Code 在保证上下文完整性的同时,依然能做到快速响应的核心原因。来源来源
3.3 CLAUDE.md 与 Auto Memory——跨会话的"长期记忆"
想象你在一家大公司入职。第一天,HR 给你发了一本《员工手册》——这就是 CLAUDE.md。而你每天和同事配合时,自己在笔记本上记下的那些"张工喜欢用 Bun 而不是 npm"“遇到端口冲突先查 3001”——那些小笔记就是 Auto Memory。两者都是"记忆",但一个是你写给对方的指令,一个是对方自己做的笔记。
CLAUDE.md 的层级体系
CLAUDE.md 并不是只有一个文件,它是一套分层叠加的指令体系。Claude Code 会从三个层级自动发现并加载 CLAUDE.md 文件:
- 全局层(
~/.claude/CLAUDE.md):放在你电脑的用户目录下,对所有项目生效。适合放"我始终用中文回复"“我不喜欢注释里加 emoji"这类跨项目的个人偏好。 - 项目根目录层(
./CLAUDE.md):放在项目根目录下,通常会提交到 Git 仓库,全团队共享。适合放项目技术栈、编码规范、构建命令等。 - 子目录层(
./src/api/CLAUDE.md):放在项目的特定子目录下,为该子目录提供专属于这一块代码的指令。在 monorepo 下尤其有用——frontend/和backend/可以各自拥有独立的 CLAUDE.md。
这些文件按从文件系统根目录到当前工作目录的顺序依次拼入上下文,离你启动位置越近的内容越靠后,对模型的影响力越大。
两种加载时机:启动加载 vs. 按需懒加载
- 当前目录及祖先目录的 CLAUDE.md,启动时立即加载。 没有长度限制。
- 子孙目录的 CLAUDE.md,按需懒加载。 只有当你让 Claude 去读或改某个子目录的文件时,那个子目录的 CLAUDE.md 才会被注入上下文。
Files loaded later have higher priority — the model pays more attention to them. (大意:后加载的文件优先级更高——模型会更关注它们。)来源
压缩后的恢复机制
- 项目根目录的 CLAUDE.md 会在压缩后从磁盘重新读取并重新注入——它"幸存"下来了。
- 子目录的 CLAUDE.md 不会被自动重新注入。它们只有在 Claude 下一次读到该子目录的文件时才会重新加载。
Project-root CLAUDE.md survives compaction: after /compact, Claude re-reads it from disk and re-injects it into the session. Nested CLAUDE.md files in subdirectories are not re-injected automatically. (大意:项目根目录的 CLAUDE.md 能在压缩后存活——compact 之后 Claude 会从磁盘重读并重新注入。子目录中的嵌套 CLAUDE.md 不会自动重新注入。)来源
Auto Memory:Claude 自己记的"学习笔记”
Auto Memory 存放在 ~/.claude/projects/<项目哈希>/memory/ 目录下,每个 Git 仓库拥有一份独立的记忆空间。核心文件是 MEMORY.md,充当所有记忆的索引目录,每次新会话启动时自动加载前 200 行(或前 25KB)。Claude 会把详细笔记拆分到单独的主题文件中按需读取。
Auto Memory 的触发逻辑完全自动化——你纠正它、说"记住这个"、或它发现重要项目线索时,都会自动写入。当你看到 “Writing memory” 提示时,说明 Claude 正在记笔记。
💡 如何手动操作? 你可以直接对 Claude Code 说"记住 XXX"(如"记住,这个项目用 pnpm 不是 npm"),Claude 会自动写入 Auto Memory。也可以用
/memory命令打开记忆管理界面,查看、编辑或删除已有的记忆条目。
💡 如何禁止自动写入? 在项目或全局的
settings.json中设置"autoMemoryEnabled": false即可关闭 Auto Memory 的自动写入。注意这是按项目或全局粒度的开关,无法针对"单条对话"禁用——要么关掉整个项目的 Auto Memory,要么开着接受所有自动写入。如果某条不想要的记忆已被写入,可以用/memory命令手动删除,或直接对 Claude 说"刚才那条不要记住"让它清理。
二者对比:
| 维度 | CLAUDE.md | Auto Memory |
|---|---|---|
| 谁写的 | 你(或你的团队) | Claude 自己 |
| 存放位置 | 项目仓库里 | ~/.claude/projects/ |
| 是否提交 Git | 是,团队共享 | 否,个人私有 |
| 加载方式 | 全部内容完整加载 | MEMORY.md 前 200 行,其余按需 |
| 本质 | 显式契约 | 隐式推断 |
最好的实践是两者配合——用 CLAUDE.md 定义明确的边界和规则,让 Auto Memory 自然而然地累积那些只属于你和 Claude 之间的微妙默契。
四、上下文压缩:桌面满了怎么办
4.1 压缩不是"删除",而是"记笔记"
想象你在开一场长达三小时的头脑风暴会议。会议全程都有录音笔在录制,逐字逐句地记下了每一句话。三天后你需要向老板汇报这次会议的成果,你会怎么做?你绝不会抱着录音笔进去播放三小时的音频,而是会整理出一份会议纪要:核心决策是什么、哪些方案被否决了及其原因、还有哪些问题悬而未决。
Claude Code 的上下文压缩,本质就是在做这件事。
在长时间编码对话中,Claude Code 会积累海量的上下文内容——每一轮对话、每一个工具调用、每一个文件读取结果,全都被记录在案。如果不加处理,这些内容会迅速撑爆模型的上下文窗口。这时候就需要"压缩"。但初学者最容易产生的一个误解是:压缩等于删东西。其实完全不是。压缩不是在扔垃圾,而是在记笔记。那些原始的工具输出(比如一个 ls 命令返回的上百行文件列表、一个 grep 扫出的整屏匹配行)本身并没有独立保留的价值,但它们所蕴含的"做了什么、发现了什么、改变了什么"才是真正重要的信息。压缩所做的,就是把这些分散在几千行原始日志中的关键决策、重要发现和未解决问题提炼出来,用结构化的方式重新组织——体积缩小了数十倍,但信息的"含金量"反而更高了。
压缩不是单一操作,而是由多个独立又互相配合的层次组成的记忆管理引擎。来源
4.2 五层递进压缩体系:从轻到重
Claude Code 的上下文压缩体系可以抽象为五个递进的层次,它们遵循一个核心设计原则:“能不压就不压,必须压时从最轻手段开始”——能局部处理就不做全局摘要,能折叠视图就不合并成摘要,主动压缩失败后再走恢复性压缩。来源
第一层:Microcompact(微压缩)——不需要动脑子的清洁工作
Microcompact 是整个体系中最轻量的压缩手段,完全不调用 LLM,只做纯机械的清理工作。它的任务是把对话历史中那些已经"过时"的工具调用的输出结果直接清理掉。
Microcompact 内部有两条互斥的执行路径,根据当前缓存状态自动选择:当 prompt cache 处于"冷"状态时,采用内容替换的方式直接修改消息文本——反正缓存要重建了,不如趁这个机会把旧工具输出一并清理;当 cache 处于"热"状态时,则利用 API 原生的 cache_edits 能力进行原地编辑——服务端直接删除工具结果,本地消息不变,做到零缓存开销。来源 这种"看缓存脸色行事"的策略很精妙:同一个目标(清理旧工具结果)根据缓存温度选择不同实现路径,cold 用替换(反正缓存要重建),hot 用 cache_edits(保护缓存命中率)。Cache Miss 的成本是 Cache Hit 的 200 倍,这个设计省下了真金白银。
第二层:Snip(裁剪)——去掉对话的"童年记忆"
Snip 关注的是消息序列的头部——它把对话早期的探索记录从上下文中移除,只保留从"目标明确之后"的对话段落。
💡 举例:假设你开了一个会话,一上来问"这个项目的用户认证是怎么实现的?"。Claude 为了回答,连续读了 12 个 auth 相关文件、跑了 3 次 grep 搜索——这些是"探索阶段"。然后你才说"好,现在帮我把 JWT 过期时间从 1 小时改成 24 小时"。Snip 就是在这个时机发挥作用:把前面 15 轮探索读文件和搜索结果的记录"剪掉",只保留从"改 JWT 过期时间"这条明确任务之后的对话。那些探索阶段的文件读取虽然丢了,但如果你需要,Claude 可以重新读。
第三层:Context Collapse(上下文折叠)——像数据库视图一样重新投影
Context Collapse 的关键思想不是"删除历史",而是"重新投影视图"——底层的完整日志未必被彻底抹掉,但当前喂给模型的视图被折叠了。这一层仍然不调用 LLM,基于结构化和规则化的信息聚合。
💡 举例:假设你在调试一个 Bug,对同一个文件
auth.ts连续修改了 5 次——每次修改都产生了 tool_use + tool_result 两条消息(共 10 条),外加 Claude 的 5 段解释文字。Context Collapse 会把这 10 条分散的消息"折叠"为一条:对 auth.ts 进行了 5 次修改,最终状态:JWT 过期时间改为 24h,修复了 refreshToken 空指针。底层的 5 次修改记录仍在,但模型看到的是一个精炼的聚合视图。这就像你看温度数据——不会看每一秒的原始读数,而是看每小时平均值。
第四层:Autocompact(自动压缩)——请 AI 帮忙写会议纪要
这是第一个真正调用 LLM 的压缩层。当 token 用量达到约 167K token 时触发(167K 的具体计算逻辑详见 4.3 节)。Claude Code 会 Fork 出一个独立的 Agent,专门负责阅读整个对话历史,将前半部分的冗长内容浓缩为一段结构化的摘要。来源
第五层:Reactive Compact(响应式压缩)——最后的保险网
当 API 直接返回 413 Prompt Too Long 错误时激活。采取最激进的策略:只保留最后 4 条消息,其余全部压缩。这是火灾发生时的紧急逃生方案——不再讲究优化和优雅,优先确保系统还能继续运行。
从 Microcompact 的轻轻一拂,到 Reactive Compact 的断臂求生,五层递进压缩体系构成了一个完整的梯度防御。这种分层递进的设计哲学使得 Claude Code 能够在不打扰用户、不打断工作流的前提下,悄无声息地维持着上下文窗口的健康水位。
4.3 Autocompact 深入:何时触发、如何执行、什么会被保留
上一节我们介绍了什么是 Autocompact。这一节我们深入它的内部机制。
触发阈值:167K 这道隐形门槛
Autocompact 并非等到上下文真被"撑爆"才行动,而是提前预留安全空间。触发的计算逻辑分三步:来源
- 先算出"有效上下文窗口":200K(标准窗口)- min(模型最大输出 token, 20K) = 180K。20K 是给摘要预留的空间——根据实际统计数据,99.9% 的压缩摘要都不会超过 17.4K token。
- 再扣除 13K 的安全缓冲,保证系统检测到需要压缩时还有空间完成当前轮的回复。
- 最终触发阈值 = 180K - 13K = 167K。当会话的 token 使用量累计到约 167K 时,Autocompact 就会自动启动。
此外,如果连续 3 次压缩失败,系统会启动"熔断"机制,本次会话不再尝试自动压缩。
Fork Agent:用"分身"做摘要,共享缓存省成本
当压缩被触发,Claude Code 会创建一个 Fork Agent 来专门完成这个工作:来源
- 共享缓存:Fork Agent 使用
model: 'inherit',复用父 agent 已经构建好的 prompt cache 前缀,不需要重新支付费用。 - 单回合、禁工具:
maxTurns=1且 NO tools allowed。提示词中有一条严厉指令:“Respond with TEXT ONLY. Do NOT call any tools. Tool calls will be REJECTED and will waste your only turn — you will fail the task."来源 - 递归防护:压缩子 agent 的
querySource被标记为'compact',防止压缩过程本身再次触发压缩。
9 章摘要结构
Fork Agent 产出的是一份遵循 9 个章节模板的"结构化档案”:首要请求和意图、关键技术概念、文件与代码片段、错误和修复、问题解决过程、所有用户消息、待办任务、当前进度、可选的下一步。它先把推理过程写在 <analysis> 标签里,再把精炼结论写入 <summary> 标签,注入上下文时剥掉 <analysis> 部分。
压缩后的新画面
压缩完成后,系统精心拼接出一幅完整的新画面:来源
[System 边界宣告] → 系统提示词 + MCP/Tools 声明 + 配置
[压缩边界消息] → "以下是之前的会话摘要..."
[9 章精简文本摘要] → Fork Agent 产生的内容
[重注入的文件内容] → 最近读取的 5 个文件(每个≤5K) + 异步任务状态 + 计划文件
[近期消息] → 压缩点之后的对话
重注入总预算为 POST_COMPACT_TOKEN_BUDGET = 50,000 token。来源
4.4 压缩生存清单:什么留下,什么消失
Autocompact 之后的上下文世界就像经历了一场大洪水——有的东西建在坚固的高地上安然无恙,有的东西漂流而去要等下一次"触碰"才能回归,还有的东西直接沉入水底。
| 内容类型 | 压缩后的命运 | 恢复条件 |
|---|---|---|
| 系统提示词 | 永久保留 | 不需要——它不在消息历史中 |
| 根目录 CLAUDE.md | 从磁盘重读并重新注入 | 自动恢复 |
| Auto Memory (MEMORY.md) | 从磁盘重读并重新注入 | 自动恢复 |
| 无 scope 的规则文件 | 从磁盘重读并重新注入 | 自动恢复 |
| 子目录 CLAUDE.md | 丢失 | 直到再次读取该子目录下的文件 |
带 paths: 的路径规则 | 丢失 | 直到再次读取匹配路径的文件 |
| 已调用的 Skill 正文 | 重新注入,但截断 | 每个上限 5K token,总预算 25K,最旧优先丢弃 |
| Hooks | 不受影响 | Hooks 以代码运行,不在会话上下文中 |
理解这张表的一个简单心法:构建在"对话历史"里的东西会被冲走;从"磁盘"上重新读取的东西能活下来。
重点分析:Skill 正文的"缓慢蒸发"
Skill 正文在压缩后会重新注入——但有严格的截断。来源
- 每个 Skill 上限:5,000 token
- 所有 Skill 总预算:25,000 token
- 淘汰策略:按调用时间排序,最旧的优先丢弃
- 截断方式:保留文件开头,截掉尾部
用一个具体场景说明:假设你调用了 5 个 Skill(A-E),每个约 8,000 token。第一次压缩后,每个被截断到前 5,000 token,尾部 3,000 token 消失。当你调用 Skill F 后,第二次压缩时 Skill E(最旧的)被整体丢弃。这就是 Skill 在长会话中"慢慢遗忘"的恶性循环。
“Auto-compaction 之后,Claude 完全忘记正在使用的 Skill,不再遵循 Skill 规定的方法论。Skills 在超过压缩阈值的任何任务中基本上不可用。” 来源
对抗"压缩失忆"的核心三原则:
- 持久指令住"根目录":无论如何都必须遵守的规则,放进根目录 CLAUDE.md,压缩后自动从磁盘恢复。来源
- Skill 头部住"核心指令":Skill 被截断时保留前 5,000 token。把最关键的规则、禁止事项和核心流程放在文件前部。官方建议 Skill 正文控制在 500 行以内,2,000-3,000 token 是最佳甜点区。
- 不能只依赖对话或 Skill:必须存活的内容写进根目录 CLAUDE.md。根目录 CLAUDE.md 是你的"保险箱"——压不碎、冲不走。Skill 正文是你的"随身行李"——前 5,000 token 能上飞机,剩下的可能被托运丢失。对话里的随口指令是你的"草稿纸"——压缩一来就面目全非。
4.5 Prompt Cache——为什么上下文管理要"照顾"缓存
想象一下,你是一家餐厅的厨师。客人点了一道"宫保鸡丁加两碗米饭",你花 10 分钟做好了。五分钟后,同一位客人说:“刚才的宫保鸡丁再加一碗米饭”。如果你是普通厨师,你得从头重新做一份——尽管大部分内容是一样的。但如果你足够聪明,你会把做好的宫保鸡丁先留在一旁备用,只需要多盛一碗米饭就行了。这就是 Prompt Cache 的核心思想。
大语言模型在处理输入时,会先将文本转化为一组叫做 KV Cache 的中间计算结果。传统做法是每一次对话请求都从头计算这些 KV 值。而 Anthropic 的 Prompt Cache 技术则让服务端"记住"上一次请求的结果:下一次请求时,如果前缀完全一致,就直接复用,无需重复计算。来源
用一个具体例子来感受缓存的力量——以及它有多脆弱。
假设某次对话的第一轮请求,上下文由以下内容拼成:
[系统提示词 10,000 token] ← 静态,所有用户相同
[DYNAMIC_BOUNDARY]
[用户/平台信息 2,000 token] ← 半静态,同一次会话不变
[CLAUDE.md 3,000 token] ← 半静态,你手动改才会变
[对话历史 5,000 token] ← 动态,每轮追加
[当前用户消息 1,000 token] ← 动态,每轮不同
────────────────────────────────
总计 21,000 token → 全部从头计算,成本 100%
第二轮请求来了。你只说了一句"继续",上下文变成:
[系统提示词 10,000 token] ← 字节完全一致 ✓
[DYNAMIC_BOUNDARY]
[用户/平台信息 2,000 token] ← 字节完全一致 ✓
[CLAUDE.md 3,000 token] ← 字节完全一致 ✓
[对话历史 6,000 token] ← 新增了上轮的一问一答
[当前用户消息 50 token] ← "继续"两个字
────────────────────────────────
缓存命中 15,000 token → 只需计算 6,050 token,成本约为原来的 30%
前缀 15,000 token 命中了缓存,服务端直接复用计算结果。你只付新增 6,050 token 的计算费——其中前 15,000 token 还是 90% 折扣价。
现在看缓存是怎么破的。假设你在两轮之间,给 CLAUDE.md 加了一行空行:
改前:[CLAUDE.md 内容...]\n ← 文件以 \n 结尾
改后:[CLAUDE.md 内容...]\n\n ← 多了一个换行符(1 字节)
第三轮请求:
[系统提示词 10,000 token] ← 字节一致,缓存命中 ✓
[DYNAMIC_BOUNDARY] ← 字节一致,缓存命中 ✓
[用户/平台信息 2,000 token] ← 字节一致,缓存命中 ✓
[CLAUDE.md 3,001 token] ← 多了一个 \n,缓存从这里断裂 ✗
[对话历史 8,000 token] ← 缓存全废,重新计算 ✗
[当前用户消息 500 token] ← 缓存全废,重新计算 ✗
────────────────────────────────
只命中了 15,000 token → 剩余 11,501 token 全部从头算,成本暴涨
一个换行符——1 个字节——毁掉了后面 11,501 token 的缓存。更隐蔽的坑:如果你的编辑器把文件编码从 UTF-8 变成了 UTF-8 with BOM,文件开头会多出 3 个不可见字节(EF BB BF)。这意味着整个缓存从系统提示词之后的第一行就断裂,原本 20,000+ token 的缓存全部报废。
这就是为什么 3.2 节介绍的三层缓存策略不是"锦上添花"而是生存刚需——把最稳定的内容(系统提示词、工具定义)钉死在缓存前缀的最前端,把会变化的内容(用户消息、对话历史)放在断点之后,让缓存金字塔的基座尽可能稳固。
Claude Code 的策略是:把请求内容拆成"静态前缀"和"动态后缀"两部分,使用 DYNAMIC_BOUNDARY 标记来精确划定分割线。三个关键的缓存断点:
- 断点一:系统提示词中的 DYNAMIC_BOUNDARY 标记处。 核心行为指令对所有用户几乎不变,跨用户共享缓存。
- 断点二:工具定义末尾。 工具定义跨对话不变,完整放置在缓存前缀内。
- 断点三:消息数组的最后一条消息。 声明之前的所有历史消息都在缓存前缀内,每轮只需处理新增的用户消息。
这三个断点共同构成了缓存分层体系——越往上一层,复用范围越广。最需要记住的原则:尽量让每一轮请求的开头部分保持不变,因为一旦前缀变化,整个缓存金字塔就会从底部坍塌。
这套缓存机制也深刻影响了 Microcompact 的设计——根据缓存温度选择不同策略路径:cache 冷时直接替换(反正要重建),cache 热时用 cache_edits API 增量修改(零缓存开销)。来源 在 Claude Code 的上下文管理体系中,缓存不是锦上添花,而是贯穿始终的架构核心。每一次上下文的增删改查,都必须回答同一个问题——“这么做会破掉缓存吗?”
断点三值得展开说——它解释了一个关键疑问:对话历史每轮都在变(追加新消息),为什么还能用缓存?
答案在于缓存断点的位置选择。Anthropic API 允许在消息数组中指定一个"缓存截止点"——该点之前的内容走缓存,之后的内容重新计算。Claude Code 把这个截止点放在倒数第二条消息上:
第 N 轮请求:
[msg1, msg2, msg3, ..., msg_倒数第二] ← 缓存前缀,命中 ✓
[msg_最后] ← 仅此一条重新计算
────────────────────────────────
只算 1 条消息的 token,其余全部复用
第 N+1 轮时,上一轮的"最后一条消息"已经变成了"倒数第二条",自动归入缓存前缀:
第 N+1 轮请求:
[msg1, msg2, ..., msg_旧最后, msg_新倒数第二] ← 缓存前缀,命中 ✓
[msg_新最后] ← 仅此一条重新计算
────────────────────────────────
每轮只算最新一条消息,历史消息永远在缓存里
这就是为什么"对话历史每轮都变"并不破缓存——变的是尾部追加,不是中间修改。缓存断点就设在尾部,新增内容永远在断点之外,已经缓存的历史消息绝不被扰动。这也是为什么 Microcompact 在 hot cache 时用 cache_edits API 清理旧工具结果——它是在缓存前缀内部做"局部手术",只删除不需要的行,不修改周围文本,从而保住剩余内容的缓存有效性。
五、实践与应用:你可以怎么做
5.1 三个必备命令:/context、/compact、/clear
在 Claude Code 的日常使用中,有三个命令是你必须认识的。它们就像你驾驶汽车时的仪表盘、手刹和档位——帮你随时感知状态、主动干预、以及在必要时彻底重置。
/context —— 看看"钱"都花在哪了
/context 命令的作用是查看当前上下文窗口的占用分布。它会清晰地告诉你,每一部分的 token 都消耗在了什么地方:系统提示词占了多少,加载的工具定义占了多少,记忆文件(如 CLAUDE.md)占了多少,技能(Skills)占了多少,以及最重要的——你和 Claude 之间的对话历史占了多少。来源 这张"账单"对于理解 Claude Code 的行为至关重要。建议每次新开一个会话,第一件事就是跑一遍 /context。很多初学者抱怨"Claude 怎么突然变笨了",往往就是因为从来没看过 /context,完全不知道对话历史已经悄悄吃掉了 80% 的上下文预算。
/compact —— 主动压缩,掌握节奏
/compact 是对当前对话历史进行压缩的命令。它不会删除你的对话,而是为 Claude 生成一份摘要,将冗长的聊天记录浓缩成关键信息的精华,从而释放出大量上下文空间。更妙的是,你可以给 /compact 附加指令,告诉 Claude 在压缩时重点保留什么。例如 /compact focus on the auth bug fix,Claude 在生成摘要时就会着重保留与认证问题相关的讨论。来源
使用时机方面有一个黄金法则:上下文占用到达 50%-60% 时就主动执行 compact,而不是等到 90% 了才手忙脚乱。来源 另一个重要的使用节奏是:每完成一个功能模块就立即 compact。虽然 Claude Code 内置了自动压缩机制,但手动压缩依然是更优的选择——自动压缩无法判断你心中的轻重缓急。来源
/clear —— 清空画布,从头开始
/clear 是最彻底的操作——它会清空整个对话历史,相当于撕掉写满的草稿纸,换上一张全新的白纸。当你需要从一个项目切换到另一个完全不相关的项目时,/clear 是必备操作。如果你不清理,上一个项目的对话历史会持续残留在上下文中,不仅浪费 token,还会对新任务产生"上下文污染"。
💡 Skill 能调用这些命令吗? 可以。Skill 正文被注入到 Agent 上下文后,可以要求 Agent 在特定时机执行
/context、/compact、/clear等斜杠命令。但注意:Skill 正文在压缩后会被截断(上限 5K token),依赖 Skill 触发这些命令并不完全可靠——更稳妥的做法是写在根目录 CLAUDE.md 中,或通过 Hook 自动触发。
5.2 主动清理上下文——什么时候该"扔东西"
理解 /compact 和 /clear 的区别,是掌握上下文管理的关键。有一个非常形象的比喻:/clear 是"Burn the whiteboard"(烧掉白板),直接擦掉一切;而 /compact 是"Take notes from the whiteboard so you can erase it"(从白板上抄好笔记再擦掉)。来源
什么情况下用 /clear? 当你需要切换到一个完全无关的新任务时。比如你刚刚用 Claude Code 写了一个 Python 数据分析脚本,现在要转而搭建一个 React 前端组件——这两项任务在技术栈、代码风格上毫无交集。此时对话历史中的所有内容对新任务都是零价值的累赘,直接 /clear 是最佳选择。
什么情况下用 /compact? 当你在同一个任务中工作了很长时间,对话量堆积起来了,但其中包含大量需要保留的关键决策、调试发现和设计思路。比如你花了一个小时修 Bug,试了三种方案才找到根因——这段探索过程的价值巨大,/compact 可以将这段经历浓缩成摘要保留下来。
清理时机的具体策略:
- 每个功能完成后 compact。 不要等到 Claude 开始忘事时才想起压缩。来源
- 上下文占用 50%-60% 时主动 compact。 始终保持至少 40% 的冗余空间。
- 切换无关任务时用
/clear避免上下文污染。 但需注意:/clear之后,之前读过的文件内容会从上下文中消失,需要重新读取。好消息是 CLAUDE.md 等记忆文件会从磁盘自动重读。
清理后你会失去什么? /compact 会丢失对话中的细节性推理和中间步骤——摘要能保留"做了什么"和"为什么",但无法完整复现每一个试探方案的具体输出。而 /clear 则更是彻底,连摘要都不会留下。
5.3 CLAUDE.md 最佳实践——让重要信息活过压缩
CLAUDE.md 文件本质上是你与 Claude Code 之间的显式契约。正如官方文档所述:“CLAUDE.md 是显式契约,Auto Memory 是隐式推断”。来源 当你把项目的构建命令、编码规范、技术栈选型和团队约定写入 CLAUDE.md,这些信息不会因为上下文压缩而丢失——它们在每次压缩后从磁盘文件重新读取恢复。
根目录 vs 子目录: 必须活过压缩的全局信息放根目录,特定子目录专属的规范放对应子目录。根目录 CLAUDE.md 适合存放构建命令、项目整体结构、技术栈选择和团队编码约定。子目录 CLAUDE.md 避免全局文件过于臃肿,也让规范的作用范围更精确。
什么内容适合放进 CLAUDE.md:
- 构建与运行命令:
npm run build、pytest、docker compose up等精确拼写 - 编码规范:缩进风格、命名约定、错误处理模式
- 项目结构说明:每个目录的用途和模块依赖关系
- 技术栈选择:为什么选择了 FastAPI 而不是 Flask
- 团队约定:PR 工作流、分支命名规则
什么时候用 Auto Memory: 重复出现的个人偏好和工作流习惯——比如你每次生成测试都用 pytest 而不是 unittest。全局的、必须绝对保真的信息写入 CLAUDE.md;细微的、个性化的习惯交给 Auto Memory。
最后,保持 CLAUDE.md 精简。CLAUDE.md 的内容在每次对话启动时都会被完整加载到上下文中,每一个 token 都是成本。一个好的检验标准:如果一个规则你只需要在特定场景下才用到,那它就不配占据 CLAUDE.md 的每一行。精炼,克制,只写那些"如果没有,Claude Code 一定会做错"的内容。来源
5.4 Subagent——把"脏活"隔离在另一个桌子上
Subagent 的核心原理可以用一句话概括:在自己的上下文窗口里工作,只把摘要返回给主 Agent。来源 主 Agent 就像坐在一张大桌子上处理核心任务,而 Subagent 是被派到旁边小桌上处理"脏活"的助手——它可以在自己的上下文里打开大量文件、浏览长篇网页,所有噪音都留在它自己的上下文空间里。任务完成后,它只把一份精简的摘要和少量元数据交还给主 Agent。
重点来了:Claude Code 在进行本地搜索(Grep/Glob)或网络搜索(WebSearch)时,会自动创建 Subagent 来执行搜索任务。 这意味着你对"搜索会不会吃掉我的上下文"的担心是多余的。当你要求 Claude Code 在项目中搜索某个函数的所有引用时,系统会默默启动一个 Subagent,让它去遍历成百上千个匹配文件,最后只返回一条"找到了 137 处引用,分布在这 5 个模块中"的摘要。那成百上千个文件的内容,从未进入过你的主对话上下文。来源
每个 Subagent 可能探索数万 token 的内容,但只返回 1000-2000 token 的精简摘要。来源
手动使用 Agent tool 的场景:
- 大规模代码审查:让 Subagent 扫描整个模块并返回审查报告
- 多文件探索:需要同时理解几十个文件的架构关系时
- 独立测试任务:让 Subagent 在隔离环境中运行测试套件
Subagent 的限制:
- 最大深度 = 1,Subagent 不能再创建 Subagent。来源
- 每次只返回摘要+元数据,不返回完整对话记录。如果任务需要多轮交互、需要看中间推理过程,Subagent 不适合。
给 Subagent 编写提示词时,遵循"单一职责"原则——一个 Subagent 只做一件事。任何会产生大量中间信息但最终只需要一个结论的任务,都适合交给 Subagent 处理。
💡 多 Subagent 协调:你的理解是正确的。多个 Subagent 各自独立运行、互不知晓。如果 Subagent A 的搜索结果要作为 Subagent B 的输入,必须由主 Agent 来推理和传递信息——这部分协调逻辑消耗的是主 Agent 的上下文。所以 Subagent 隔离节省的是"搜索/执行"阶段的上下文,但"协调/决策"阶段的上下文无法省略。这也解释了为什么主 Agent 的上下文管理依然重要。
5.5 通过 Hook 补充上下文
前面几节讲的都是"被动防御"——压缩、清理、隔离,目标是不让上下文爆掉。而 Hook 机制走的是另一条路:主动进攻——在关键时刻自动把信息"塞"进上下文,让模型始终能看到最新、最全的状态。来源
在上下文管理的视角下,有四个 Hook 尤为重要:
一、SessionStart(compact 匹配器)——压缩后把丢失的东西补回来
这是上下文管理中最关键的 Hook 场景。回顾 4.4 节的压缩生存清单:子目录 CLAUDE.md 丢了、带 paths: 的路径规则丢了、Skill 正文被截断了——这些信息在压缩后不会自动恢复,直到你下次触碰到相关文件。
SessionStart + matcher: "compact" 就是为填补这个缺口而生的。它在每次 compact 完成后自动触发,让你可以运行脚本把那些"丢了就麻烦"的信息重新注入:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "cat ./CLAUDE.md ./src/CLAUDE.md 2>/dev/null; echo '---'; git log --oneline -5"
}
]
}
]
}
}
这段配置在每次 compact 后自动运行——把根目录和 src/ 子目录的 CLAUDE.md 内容重新打印出来,顺便带上最近 5 条 git 提交记录。Stderr 不会注入上下文,只有 stdout 会被模型看到,所以 2>/dev/null 可以安全地忽略不存在的文件。
核心价值:子目录 CLAUDE.md 在压缩后"活"过来了。你不用等 Claude 读到那个子目录的文件才能恢复——compact 一结束,Hook 就把它们重新灌进上下文。
二、UserPromptSubmit——每条消息前自动刷新状态
UserPromptSubmit 在你每次发送消息之前触发,是所有 Hook 中执行频率最高的。适合用来注入"每轮都可能变化"的动态信息:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo '当前分支:' && git branch --show-current && echo '改动文件:' && git diff --stat"
}
]
}
]
}
}
每次你按下回车,Claude Code 都会先跑这段脚本,把当前分支名和改动了哪些文件注入上下文。模型看到的不再是你 20 轮之前的 git 快照,而是此刻的真实状态。
注意频率:这个 Hook 每轮都跑,命令必须极快(< 1 秒),输出必须极短(< 500 字符)。git diff --stat 比 git diff 好得多——只显示文件名和改动行数,不输出具体 diff 内容。
三、PreToolUse——在工具执行前把关和补充
PreToolUse 在工具调用之前触发,有两个上下文管理价值:
- 拦截危险操作:匹配
Bash工具,检测命令中是否有rm -rf、git push --force等危险模式,阻止执行或要求二次确认。 - 注入操作提示:匹配
Edit或Write工具,在修改文件前自动注入该文件所属模块的编码规范。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '注意:当前在 main 分支,禁止 force push 和删除操作'"
}
]
}
]
}
}
每次 Bash 工具被调用前,这条提醒会自动注入上下文。它不阻止执行,但模型会"看到"这条警告并在决策时考虑它。
四、SessionStart(startup 匹配器)——会话一开就有全局视野
startup 在每次新会话开始时触发(/clear 后重新启动也会触发)。适合注入项目级的"全局快照":
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "echo '项目结构:' && ls -1 src/ && echo '---' && echo '最近提交:' && git log --oneline -10"
}
]
}
]
}
}
这样 Claude Code 一启动就能看到项目目录结构和最近 10 条提交,不需要你手动介绍"这个项目长什么样"。
四个 Hook 对比总结:
| Hook | 触发时机 | 上下文管理用途 | 频率 |
|---|---|---|---|
| SessionStart (compact) | 每次 compact 后 | 补回压缩丢失的信息 | 低 |
| UserPromptSubmit | 每次发消息前 | 刷新当前 git/项目状态 | 高 |
| PreToolUse | 每次工具调用前 | 校验+注入操作提示 | 中 |
| SessionStart (startup) | 每次新会话启动 | 注入项目全局快照 | 低 |
关键原则:Hook 的输出直接进入上下文,所以命令必须快、输出必须短。超过 1 秒的执行时间会让对话体验明显卡顿,超过 500 字符的输出会持续吃掉宝贵的 token 预算。用 --stat 代替完整 diff,用 --oneline 代替完整 log,用 head 截断长输出——每条 Hook 都是一次微型的上下文预算决策。来源
5.6 日常使用 Checklist
核心原则:上下文不是"越大越好",而是"越克制越好"。
- 启动会话后先跑
/context,了解当前上下文占用情况 - 大任务拆小,一次会话解决一个明确的子目标
- 读文件时指明路径和范围,避免不必要的全文件读取
- 每个功能或模块完成后执行
/compact - 切换任务时用
/clear,避免上下文污染 - 把重复出现的项目事实写进 CLAUDE.md
- 搜索类工作自动走 Subagent,你不需要额外操作
- 定期审视并精简 CLAUDE.md,删除过时的约定
六、总结:理解上下文管理,就是理解 Claude Code 的"思维方式"
走完这一整篇文章,我希望你已经悄然完成了那个核心的认知转变——上下文从来不是一段被动的对话历史,而是一个需要你主动设计、精打细算的工程对象。
用一张对比表,可以清晰地看到旧心智模型和新心智模型之间的分野:来源
| 旧心智模型 | 新心智模型 |
|---|---|
| 上下文 = 被动的对话历史 | 上下文 = 需要主动设计的工程对象 |
| 越大越好 | 越克制越好 |
| 模型记不住 = 模型差 | 模型"记不住" = 调度策略在工作 |
| Auto Memory 让我省事 | Auto Memory 是隐式推断,CLAUDE.md 才是显式契约 |
| Subagent 啥都该知道 | Subagent 是孤岛,必须显式传约束 |
从这张表延伸出去,四条贯穿全文的关键原则:
第一,上下文越克制越好。 给你的 AI 只提供它真正需要的信息。Claude Code 的上下文管理,本质上不是在喂给模型"越来越多的历史"——它是在有限 token 预算内,持续地组装、预算规划、修剪、折叠、总结和重连信息。来源
第二,压缩是基础设施,不是补丁。 五层递进压缩在后台持续工作,你感觉不到它的存在,但它无时无刻不在优化你的 token 预算。
第三,CLAUDE.md 是显式契约,Auto Memory 是隐式推断。 不要以为 Auto Memory 替你自动记录了偏好,你就能不写 CLAUDE.md。前者是从行为中"猜"的,后者是你"说"的——前者永远只是后者的补充。
第四,Subagent 隔离是上下文管理的利器。 把脏活、累活丢给分身去做,每个 Subagent 只携带完成任务所必需的最小上下文。恰如其分的无知,才是它的力量所在。
最后,回想我们在 2.1 节提到的 “Lost in the Middle"现象——模型对上下文中间段落的信息注意力天然下降,这是 Transformer 架构的固有特性,与窗口大小无关。来源 上下文窗口的扩大解决的是"能装多少”,解决不了"能看清多少"。这就是为什么无论窗口多大,主动管理信息结构永远不会过时——你需要把重要信息放在上下文的"头部"和"尾部",而不是任由它们淹没在中间。
七、参考
- Claude Code Docs - Context Window
- Anthropic - Effective Context Engineering for AI Agents
- Claude Reviews Claude - Context Assembly
- 小林面试笔记 - Compact 压缩机制详解
- Inside Claude Code - Context Compaction
- DeepWiki - Context Management & Compaction
- How Claude Code Works - Context Engineering
- Claude Platform Docs - Compaction
- ClaudeFast - Context Window Optimize Guide
- Claude Code Memory Documentation
- Claude Code Sub-agents Documentation
- Claude Code Hooks Documentation
- Claude Code 上下文管理深度解析 - wujiachen.com
- Claude Code Source Analysis - Context Management (DEV Community)
- Claude Code 五层压缩算法深度分析
- Claude Code Beginner’s Guide - Tokens & Context
- Context Window and Token Budget - Engineering Playbook

