一、上下文窗口里到底装了什么
很多开发者认为 Claude Code 的上下文就是"我说了什么、它回了什么"。实际上,对话历史只是七类内容之一。理解这个认知偏差,是用好 Claude Code 的第一步。
完整的七类上下文内容
| # | 类型 | 大概占比 | 压缩优先级 | 生命周期 |
|---|---|---|---|---|
| 1 | 系统指令(框架规则+工具定义) | 5-10% | 永不清除 | 会话全程 |
| 2 | CLAUDE.md(持久化规则) | 1-3% | 压缩后重新注入 | 跨会话 |
| 3 | 对话历史(用户交互记录) | 40-70% | 优先压缩 | 会话全程 |
| 4 | 文件内容(Claude 读取/编辑过的源码) | 15-30% | 清除优先级第2 | 按需存在 |
| 5 | 命令输出(终端执行结果) | 10-25% | 清除优先级第1 | 单轮有效 |
| 6 | Skills(按需加载的指令文本) | 2-8% | 压缩后保留前5000 token | 触发后常驻 |
| 7 | 自动记忆(跨会话学习记录) | <1% | 不压缩 | 跨会话 |
经验数据:一个典型的 10 轮对话,对话历史+文件内容占到上下文窗口的 70% 以上。这也是为什么上下文管理如此关键------token 消耗的大头永远是"说了什么和读了什么"。
压缩的两步策略
Claude Code 的压缩不是简单的"删旧消息",而是一个有优先级排序的过程:
第一步:移除命令输出和文件读取结果
这两类内容有几个共同特征:
- 占用大(一个文件几千 token、一条命令几百 token)
- 时效性低(第十轮时第五轮的 ls 结果已经没用)
- 可替代(需要时重新执行命令就行)
第二步:摘要压缩对话历史
具体过程:
- 把具体措辞浓缩为语义概要
- 把数值和代码片段替换为占位描述
- 把多轮讨论折叠为单轮总结
示例:
压缩前:
"第五轮时我们讨论过 PostgreSQL 连接池配置,
最终决定 maxPoolSize=10, connectionTimeout=5000ms,
连接串是 jdbc:postgresql://127.0.0.1:5432/ragent"
压缩后:
"数据库连接池配置已确定(具体参数见代码)"
信息丢失的代价与应对
压缩必然丢失信息。丢失的不是随机的,而是细节------具体数值、精确措辞、代码片段都被浓缩掉了。
丢失模式:
| 内容类型 | 压缩后状态 | 影响程度 |
|---|---|---|
| 跨轮强调的规则 | 可能变成模糊总结 | ★★★★★ |
| 讨论过的架构决策 | 决策在,原因丢失 | ★★★★ |
| 具体参数/配置值 | 精确值丢失,变成"已配置" | ★★★★ |
| 代码实现细节 | 思路在,代码丢失 | ★★★ |
| 文件结构发现 | 目录关系在,文件名模糊 | ★★ |
三个递进式的解决方案:
方案 A(入门):规则写进 CLAUDE.md
CLAUDE.md 每次压缩后都会被重新注入上下文,不受摘要影响。可以把 CLAUDE.md 理解为"不会被遗忘的提醒便签"。
# CLAUDE.md 里的压缩指令示例
## 对话压缩时必须保留的信息
- 数据库使用 PostgreSQL,连接池 maxPoolSize=10, connectionTimeout=5000ms
- RocketMQ nameserver 是 127.0.0.1:9876
- 项目不使用 Spring AI,所有 AI 调用走自建的 infra-ai 层
方案 B(进阶):主动触发 /compact 并指定焦点
普通压缩是自动触发的,Claude 自行判断保留什么。/compact 让你介入决策:
/compact 我只关心数据库 schema 的变更历史,其他的都可以压缩
/compact 聚焦在 API 接口签名上,把具体实现细节压缩掉
/compact 保留所有配置相关的讨论,省略代码实现
方案 C(系统级):善用自动记忆的"长尾存储"
自动记忆由 Claude 自己写入,存在 ~/.claude/projects/ 目录。每次新会话启动时加载 MEMORY.md 的前 200 行(或 25KB)。
它和 CLAUDE.md 的本质区别:
| 维度 | CLAUDE.md | 自动记忆 |
|---|---|---|
| 写入者 | 人 | Claude 自己 |
| 加载量 | 全部 | 前 200 行/25KB |
| 适合内容 | 核心规则、硬性约束 | 细碎经验、模式偏好 |
| 稳定性 | 高(人不删就不会变) | 中(Claude 会重写) |
实战心法:CLAUDE.md 放"假设立场"(用什么库、遵循什么规范),自动记忆放"历史教案"(之前吃过什么亏、发现什么规律)。
二、CLAUDE.md 的加载机制:层级叠加的陷阱
Claude Code 启动时从四个位置按顺序查找并加载 CLAUDE.md:
| 层级 | 位置 | 共享范围 |
|---|---|---|
| 托管策略 | /Library/Application Support/ClaudeCode/CLAUDE.md |
组织内所有用户 |
| 用户级 | ~/.claude/CLAUDE.md |
所有项目通用 |
| 项目级 | ./CLAUDE.md 或 ./.claude/CLAUDE.md |
通过 Git 共享给团队 |
| 本地级 | ./CLAUDE.local.md(被 gitignore) |
仅限本机使用 |
拼接而非覆盖:最大设计细节
四层文件不是覆盖关系,而是从根目录到当前工作目录全部串联注入。
这意味着一个常见陷阱------层级间规则冲突:
~/.claude/CLAUDE.md: 使用 four 空格缩进
./.claude/CLAUDE.md: 使用 two 空格缩进
两条规则同时存在于上下文里,Claude 自行判断优先级------结果可能符合预期,也可能不符合。这是很多"规则不生效"投诉的根本原因。
排查清单:
规则 X 没生效?
├── 检查各层级 CLAUDE.md 是否有冲突规则
│ find . -name "CLAUDE.md" -o -name "CLAUDE.local.md" | xargs grep "规则关键词"
├── 检查规则是否超过 200 行限制
│ (官方建议每个文件 < 200 行,超出的执行力度会下降)
├── 检查规则是否足够具体
│ "写好好代码" ❌(不可验证)
│ "方法不超过 30 行" ✅(可验证)
└── 检查是不是只写在对话里
(对话里的规则不跨压缩)→ 改写到 CLAUDE.md
CLAUDE.md 最佳实践结构
一个经过实战检验的 CLAUDE.md 模板:
# 项目速览
- 项目名: ragent
- 框架: Spring Boot 3.5.7 / Java 17
- 向量DB: Milvus 2.6.6 + pgvector
- 消息队列: RocketMQ
# 编码约定
- 代码风格: Google Java Format (Spotless)
- 命名规范: 大驼峰类名 / 小驼峰方法+变量
- 方法长度: < 30 行
- 必须用中文注释公共方法
# 不可违反的架构边界
- 所有 AI 调用走 infra-ai 包,严禁直接 import OpenAI SDK
- 数据库访问走 MyBatis-Plus Mapper,禁止写裸 SQL
- MCP 相关代码集中在 rag/core/mcp/ 包
# 压缩时必须保留的配置信息
- DB: postgresql://127.0.0.1:5432/ragent, pool=10
- Redis: 127.0.0.1:6379
- RocketMQ nameserver: 127.0.0.1:9876
- Milvus: http://localhost:19530
三、Hooks:CLAUDE.md 管规则,Hooks 管纪律
CLAUDE.md 的"建议式管理"有一个短板:Claude 读了会参考,但不保证每次都遵守------它有自己的判断,有时认为规则在当前场景不适用就跳过去了。
如果是必须执行的规则(如提交前跑 lint、禁止 force push、所有写入必须审批),就需要 Hooks 来"物理拦截"。
五种处理器类型详解
| 类型 | 退出码控制 | 典型场景 |
|---|---|---|
command |
0=通过, 2=阻断 | 跑 lint、校验格式、安全扫描 |
http |
0=通过, 2=阻断 | 触发 CI 流水线、通知外部系统 |
mcp_tool |
0=通过, 2=阻断 | 把 MCP 能力引入生命周期钩子 |
prompt |
Claude 模型判断 | 需要语义理解的动态审查(如"这次提交有没有看起来像密码的东西") |
agent |
Subagent 多步判断 | 复杂校验(实验性,如 Subagent 跑完完整代码审查链路) |
Hook 事件矩阵
Hooks 覆盖了 Claude Code 的完整生命周期,核心事件:
会话开始
└── SessionStart → 可以用来注入动态信息、连接工具
用户输入
└── UserPromptSubmit → 校验输入内容(比如阻止含敏感信息的提交)
工具调用前(最重要)
└── PreToolUse → 可以 allow/deny/ask/defer,还可能篡改参数
工具调用后
└── PostToolUse → 记录日志、触发后续流程
压缩前
└── PreCompact → 在压缩前把关键信息写入 CLAUDE.md 或记忆文件
Claude 回复完毕
└── Stop → 校验输出内容、触发代码审查
会话结束
└── SessionEnd → 清理资源、生成会话摘要
PreToolUse Hook 实战示例
PreToolUse 是最常用的 Hook 类型。它有两个杀手级能力:
能力 1 --- 权限控制 ,通过 permissionDecision 字段:
{
"matcher": "Bash",
"hook": [{
"type": "command",
"command": "check-command.sh",
"async": false
}]
}
permissionDecision 取值:
-
allow:直接放行,不弹确认 -
deny:禁止操作 -
ask:弹确认框让用户决定 -
defer:交给默认逻辑
能力 2 --- 参数篡改 ,通过 updatedInput:
# PreToolUse Hook 改写所有 git commit 命令,强制加签名
PreToolUse:
- matcher: "Bash"
hooks:
- command: |
if [[ "$COMMIT_CMD" == *"git commit"* ]] && [[ "$COMMIT_CMD" != *"-S"* ]]; then
echo "${COMMIT_CMD/-m /-S -m }" > /tmp/modified.sh
fi
可以用来:强制 git commit 加签名、文件写入前追加版权声明、给敏感命令加审计日志等。
Hook 配置位置与超时
配置位置与 CLAUDE.md 层级体系完全对齐:
-
用户级 :
~/.claude/settings.json -
项目级 :
.claude/settings.json(Git 共享,团队统一) -
本地级 :
.claude/settings.local.md(本机专用)
默认超时:command/http/mcp_tool 类型 600 秒,prompt 30 秒,agent 60 秒。
异步 Hook:不阻塞的后台校验
在配置中加 "async": true,Hook 会在后台执行:
{
"matcher": "Write",
"hook": [{
"type": "command",
"command": "python scripts/security_scan.py",
"async": true,
"asyncRewake": true
}]
}
asyncRewake: true 的含义:后台 Hook 返回退出码 2(阻断)时,会向 Claude 发送系统提醒,通知它异步校验未通过。适合耗时较长的检查,如远程代码扫描、大规模依赖漏洞检查。
设计哲学:CLAUDE.md = 规则手册(员工看了参考着做),Hooks = 安检门(过不了就别想走)。
四、Skills 机制:按需加载的专业知识模块
CLAUDE.md 每次都加载(常驻),Skills 是被触发才进入上下文(按需)。这是两者的核心分界线。
Skills 的存在价值
为什么不能把所有规则都塞进 CLAUDE.md?
↓
因为 CLAUDE.md 有 200 行限制,而且每次都注入
↓
想象一下把"修 MySQL""修 Redis""修网络""写前端""写算法"的全套手册都塞进去
↓
Claude 辩认当前该用哪部分的信息成本极高
→ 这就是 Skills 存在的意义:按任务运行时动加载对应的"专业手册"
三步触发引擎
三步决定了一个 Skill 能否被成功激活:
Step 1 --- 启动时构造索引
Claude Code 收集所有 Skill 的名称和描述文本,拼成一张索引清单注入上下文。关键限制:
-
整张清单总占用不过上下文窗口的 1%
-
单个 Skill 描述最多 1536 个字符
Step 2 --- 逐轮决策
每轮对话时,Claude 扫一遍清单。此时它只能看到描述文本,看不到 SKILL.md 正文。
Step 3 --- 触发加载
匹配上了,SKILL.md 全文才被加载进上下文。匹配过程没有"试探",没有"预览",要么全加载,要么不加载。
超高门槛:1536 字符的军备竞赛
1536 字符是什么概念?大约是一个段再多两三行。你必须在这么短的篇幅内说清楚:
-
这个 Skill 是干什么的(一句话)
-
什么时候该用(触发条件)
-
什么时候不该用(排除条件)
-
和同领域其他 Skill 怎么区分
写详细了?被截断。写简略了?Claude 不知道该不该用。
描述优化对比:
# ❌ 糟糕的描述(Claude 不知道怎么触发)
description: Spring 相关的帮助
# ✅ 精准的描述(告诉模型具体边界)
description: |
处理 Spring Boot 配置问题。
WHEN: 用户修改 application.yaml/pom.xml 或遇到 Bean 初始化错误时。
WHEN NOT: @RestController/@Service 注解的开发或 HTTP 接口层代码编写。
Skill 的上下文驻留规则
被触发加载后,Skill 的内容会一直留在当前会话------不会因为开启新一轮对话而消失。
但上下文压缩时 Skill 也会被处理:
压缩后的 Skill 保留策略:
- 每个 Skill 保留前 5000 个 token
- 所有 Skill 的重新注入总预算 25000 个 token
- 超出总量时,按 LRU(最近最少使用)淘汰
实战启示:
- 同时生效的 Skill 总量不要超过 4-5 个
- 核心 Skill 保持在最近使用位置以免被淘汰
Skill 目录结构与 frontmatter 全字段
my-skill/
SKILL.md # 主文件
references/ # 参考资料(加载时自动引入)
scripts/ # 可执行脚本供 Claude 调用
examples/ # 示例输出(展示正确用法)
SKILL.md frontmatter 字段一览:
---
name: my-skill
description: 精准描述(1536字符内)
version: 1.0.0
# 控制加载行为
disable-model-invocation: false # true = 只能手动触发
allowed-tools: Read, Bash, Grep # 免确认工具
model: sonnet # 覆盖当前模型
context: fork # 在独立子agent 运行
# 权限控制
allowed-tools: Read, Write, Edit, Bash, Grep
---
动态内容:让 Skill 感知运行时
用 ``!`command``` 语法在加载时执行 Shell 命令,输出直接替换到正文里。
应用:
## 当前项目状态
生成时间:!`date "+%Y-%m-%d %H:%M:%S"`
数据库连接状态:!`psql -h 127.0.0.1 -U postgres -c "SELECT 1" 2>&1 | tail -1`
最近 3 次提交:!`git log --oneline -3`
服务健康检查:!`curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/api/ragent/health`
每次加载时自动刷新这些信息,不需要手动编辑 SKILL.md。
五、Subagent 的上下文隔离:让主线程轻装上阵
Subagent = 独立上下文窗口 = 独立记忆 + 独立工具权限 + 独立 token 预算。
为什么需要 Subagent
没有 Subagent 的多步任务是怎样的?
"帮我分析哪个 Service 最复杂,然后重构它"
主 Claude:
1. 读取 20 个 Service 文件 → 消耗 15000 token
2. 分析复杂度 → 消耗 5000 token
3. 根据分析结果重构目标文件 → 消耗 20000 token
总计:40000 token,主上下文被"分析过程"的中间数据塞满
有 Subagent 时:
主 Claude:
1. 派 Subagent 去分析 + 重构 → 几乎不消耗主上下文 token
2. Subagent 返回一份精简报告 → 消耗 2000 token
3. 主 Claude 根据报告决策下一步 → 轻装上阵
对比:主上下文节省了 95% 的消耗
三种内置 Subagent 的定位
| 类型 | 模型 | 工具权限 | CLAUDE.md | 适用场景 | 响应速度 |
|---|---|---|---|---|---|
| Explore | Haiku(快速模型) | 只读(Glob/Grep/Read) | 不加载 | 搜索文件、浏览代码结构 | 最快(2-5秒) |
| Plan | 继承主模型 | 只读(Glob/Grep/Read) | 不加载 | 架构分析、方案设计 | 中等 |
| General-purpose | 继承主模型 | 全部(含编辑和执行) | 完整加载 | 复杂修改、多步任务 | 最慢 |
Explore 和 Plan 跳过 CLAUDE.md 的原因:
它们主要做信息收集和分析,不需要遵守项目的代码规范和提交约束,跳过 CLAUDE.md 可以让上下文更干净、响应更快。General-purpose 要改代码,必须了解项目规则才能产出合格结果。
自定义 Subagent 的定义
在 .claude/agents/ 目录下创建 Markdown 文件:
# .claude/agents/performance-analyst.md
---
name: performance-analyst
description: Analyzes code performance and suggests optimizations
tools: Read, Glob, Grep, Bash
model: sonnet
skills: sql-optimization, redis-best-practices
mcpServers:
- name: metrics-server
url: http://localhost:9999/mcp
---
You are a performance analysis specialist.
Focus on:
1. Database query patterns and N+1 issues
2. Memory allocation and GC pressure
3. Network call batching opportunities
4. Caching strategy optimization
When analyzing, always provide:
- Quantitative metrics (current vs proposed)
- Risk assessment (what could break)
- Incremental adoption plan
定义后主 Agent 在需要性能分析时自动委派------专属工具集、独立的 MCP 服务器(只在启动时连接,结束时不占主上下文)。
Fork Subagent:继承上下文的快捷方式
通过 /fork 命令或 context: fork 启用。
和普通 Subagent 的本质区别:
| 维度 | 普通 Subagent | Fork Subagent |
|---|---|---|
| 上下文 | 空白开始 | 继承完整对话历史 |
| 适用场景 | 全新独立任务 | 在当前讨论基础上深入探索 |
| Prompt 缓存 | 独立计算 | 共享主 Agent 缓存 |
| 典型用法 | "帮我调研 X 方案" | "基于刚才的讨论,再评估一下它的可行性" |
六、五大机制的协作关系
这五个机制不是孤岛,它们在每一轮对话里动态协作。一个典型的多机制协作流程:
用户: "帮我重构 UserService"
1. [CLAUDE.md] 命中规则:
- "Service 层代码放在 rag/service/ 包下" ✓
- "重构需先跑全部测试" ✓
2. [System Prompt] 提供工具定义:
- Read, Edit, Bash 等可用工具
3. [Skills 索引] 逐轮扫描:
- 发现 "refactoring" Skill 可能相关
- 读取描述文本判断...
4. [Skill 加载] 触发 "refactoring" Skill:
- SKILL.md 完整指令进入上下文
- 包含重构检查清单、最佳实践、常见陷阱
5. [PreToolUse Hook] 执行 Edit 前触发:
- 校验目标文件路径合规
- 检查是否已跑测试
6. [Subagent] 执行重构分析(可选):
- Explore 子 Agent 搜索所有调用方
- 结果返回给主 Agent
7. [对话历史] 累积上下文:
- 用户原始请求 + Claude 的中间决策
8. [自动记忆] Claude 边做边记:
- "发现 UserService 有 3 个未处理的 null pointer 风险"
- "项目偏好保留 Service 接口,只改实现"
协作关系总结:
CLAUDE.md ──────────────────────────────────────────────────────────
│ │
│ 提供"项目级知识" │ 提供"全局架构规则"
▼ ▼
System Prompt Context Window
│ │
│ 注入框架规则 │ 接受四项内容
│ 注入工具定义 │ ① 对话历史 ② 文件
│ │ ③ 命令输出 ④ 记忆
▼ │
Skills ──────────── Subagent ──────────── Hooks │
│ │ │ │
│提供专业知识 │提供隔离执行环境 │强制执行 │
│按需加载 │防止过程数据溢出 │权限控制 │
▼ ▼ ▼ │
Skills ──────── Subagent ──────── Hooks ────────┘
(压缩后保留前5000 token) (独立token预算) (退出码决策)
七、实战调优清单
规则层问题的排查思路
症状:Claude 不遵守规则
│
├── 规则放在哪?
│ ├── 对话里 → 对话被压缩就忘,改写到 CLAUDE.md
│ ├── CLAUDE.md 但超过 200 行 → 删除冗余规则
│ └── 多层级冲突 → 检查各层 CLAUDE.md 的一致性
│
├── 规则写得不够具体?
│ └── "写好好的代码" → "方法不超过 30 行,圈复杂度不超过 10"
│
└── 规则必须强制执行?
└── 改 Hooks(带退出码 0/2 拦截)
上下文污染的排查思路
症状:Claude 注意力不集中,回答偏离预期
│
├── 太多无关文件被读取?
│ └── 在 .gitignore 里排除 /target, /node_modules
│
├── 太长的不相关命令输出?
│ └── 在 CLAUDE.md 加"压缩必须保留"清单
│
├── 同时激活太多 Skill?
│ └── 合并相关 Skill,确保总数 < 5
│
└── 主上下文数据量不够隔离?
└── 用 Subagent 隔离分析过程(特别是代码搜索+分析)
Subagent 选型指南
任务类型 → 推荐 Subagent 类型 → 原因
─────────────────────────────────────────────
"搜索/浏览" → Explore → 快、便宜、只读
"分析方案/架构规划" → Plan → 继承主模型推理能力
"实际改代码" → General-purpose → 需要完整工具权限
"需要项目规则约束" → General-purpose → 加载 CLAUDE.md
"在当前讨论基础上深入" → Fork Subagent → 共享对话历史
总结
Claude Code 的上下文窗口有七个维度,其中有五个是最具工程价值的:
| 机制 | 角色 | 关键词 | 本质 |
|---|---|---|---|
| CLAUDE.md | 方向 | 持久化规则 | 每次压缩后重新注入 |
| Hooks | 纪律 | 强制拦截 | 退出码决定行止 |
| Skills | 专业 | 按需加载 | 1536 字符触发窗口 |
| Subagent | 分工 | 隔离执行 | 独立 token 预算 |
| Context Compression | 记忆 | 有损压缩 | 细节丢失的根因 |
一句话总结:CLAUDE.md 管方向,Hooks 管纪律,Skills 管专业,Subagent 管分工,压缩机制管理记忆。
我们在 Prompt 优化上花了很多时间,但决定 Claude Code 能不能持续稳定产出的,从来不是某一句话写得多巧妙,而是对这五个机制的配置质量和理解深度。
把时间花在理解工具的运作方式上,比花在猜测工具的脾气上,回报大得多。