最近因为事情太多了,claude code源码浅层研究断更了一段时间,今天这篇继续讲超级费钱(token)的会话管理。跟本文相关的内容包含之前更新的System Prompt 上篇 以及 System Prompt 下篇可配合阅读

一、误解先破:AI 的"记忆"不是你想的那种记忆

大多数人把 Claude Code 的对话想象成聊天软件——消息永久保留,AI 随时能翻阅历史记录。实际情况完全不同。

Claude Code 的"记忆"是一个上下文窗口(Context Window):一块固定大小的内存,每轮请求时把当前所有对话内容打包发给模型。这块内存有上限,填满了就会挤掉旧内容;关掉会话,内容清零。模型本身没有跨会话记忆能力,它看到的只是这次请求里的文本。

理解这一点,后面所有策略才有意义。


二、一次对话在底层长什么样——会话的消息结构

2.1 四种角色,构成一轮对话的完整单元

每条消息都有一个固定角色(源自 rust/crates/runtime/src/session.rs):

角色 含义
System 系统提示,每轮自动注入,用户不可见
User 你的输入
Assistant 模型的回复
Tool 工具执行结果

这四种角色交替出现,构成一个完整的对话序列。

2.2 内容块的三种形态

每条消息的实际内容是一个"内容块(ContentBlock)",有三种类型:

Text        → { text: "..." }                                      纯文字
ToolUse     → { id, name, input }                                  AI 请求使用工具
ToolResult  → { tool_use_id, tool_name, output, is_error }         工具返回结果

2.3 一次工具调用在会话中的完整流转

以"让 AI 读取一个文件"为例,完整序列是四步:

[User]      → Text: "帮我读取 config.py"
[Assistant] → ToolUse: { name: "FileReadTool", input: "config.py" }
[Tool]      → ToolResult: { output: "文件内容...", is_error: false }
[Assistant] → Text: "这个文件的主要功能是..."

这四步全部以消息形式存进上下文窗口。AI 看到的不是"工具被调用了"这个抽象事件,而是这四段完整文字。每次工具调用都会直接增加上下文体积。


三、Token:上下文里流通的"货币"

3.1 四个 Token 字段分别计什么

Claude Code 在 rust/crates/runtime/src/usage.rs 里定义了 Token 用量的追踪结构:

pub struct TokenUsage {
    pub input_tokens: u32,                  // 本轮输入消耗的 token
    pub output_tokens: u32,                 // 本轮输出消耗的 token
    pub cache_creation_input_tokens: u32,   // 首次建立缓存的额外开销
    pub cache_read_input_tokens: u32,       // 命中缓存节省的 token 数
}

前两个字段是"这轮请求真实花了多少",后两个字段是 Prompt Caching 机制的产物。

3.2 缓存命中与缓存创建的区别

Prompt Caching 的逻辑:System Prompt 和 CLAUDE.md 等固定内容,首次请求时按全量 token 计费并建立缓存(cache_creation_input_tokens);同一会话后续请求命中同一内容时,只收取缓存读取费用(cache_read_input_tokens),远低于正常输入价格。

实际影响:同一会话内连续请求比频繁开新会话更省钱。缓存只在会话内有效,约 5 分钟不活跃后失效。

3.3 怎么看当前花了多少

在对话中输入 /cost,查看当前会话的累计 token 用量,包含四个字段的明细。这是判断是否需要 /compact 或开新会话的主要依据。


四、会话存在哪里——持久化机制

4.1 会话以什么形式保存在磁盘上

每个会话对应一个 JSON 文件,核心字段(源自 src/session_store.py):

class StoredSession:
    session_id: str            # 会话唯一标识
    messages: tuple[str, ...]  # 消息记录序列
    input_tokens: int          # 累计输入 token
    output_tokens: int         # 累计输出 token

文件存储路径:~/.claude/projects/<编码路径>/,其中 <编码路径> 是当前工作目录的编码形式,不同工作目录的会话分开存放。
示例:C:\Users\你的用户名.claude\projects\D–ProjectSpace-Projects-Production-WebApp\memory

4.2 怎么恢复上一次对话

启动时加参数:

claude --resume    # 或 -r

加载最近一次会话的历史消息,继续对话。

当前版本的两个限制

  • 不支持指定 session ID 恢复,只能恢复最近一次
  • 没有内置的 /sessions 列表查看命令或 /session switch 切换命令——网上部分教程和 AI 回答里提到这两个命令,实际上在当前版本并不存在

4.3 导出与归档

/export notes.txt

把当前会话的完整对话记录导出为文本文件。内容包含所有工具调用的输入输出,适合存档备查,不适合作为跨会话恢复上下文的主力方式(原因见第七节)。


五、上下文窗口:你以为有 200K,实际能用多少

5.1 窗口的空间是怎么被瓜分的

Claude 的上下文窗口约 200K tokens,但不是全部留给对话内容:

实际可用空间 = 总窗口 - System Prompt - 指令文件 - 项目上下文

估算:
  总窗口         ≈ 200,000 tokens
  System Prompt  ≈ 2,000-5,000 tokens    (系统级,固定注入,每轮必有)
  指令文件       ≈ 12,000 字符上限       (CLAUDE.md 等,每轮自动注入)
  项目上下文     ≈ 动态                  (Git status / diff,视项目规模而定)
  ─────────────────────────────────────
  实际可用       ≈ 180,000 tokens        (约 45 万字中文)

5.2 每一次工具调用吃掉多少 Token

180K 看起来宽裕,但工具调用的消耗速度比预期快:

操作 大约消耗
AI 读取一个 500 行的文件 ~2,000-4,000 tokens
AI 执行一次搜索并返回结果 ~500-2,000 tokens
10 轮来回对话(不含工具调用) ~10,000-50,000 tokens

关键认知:上下文是被"填入"的容器,不是"引用"历史的索引。每一条历史消息——包括工具调用的完整输入和输出——都实实在在占据空间。

5.3 上下文填满之后会发生什么

当总 token 接近模型上限时,系统自动压缩:旧的对话轮次被替换为摘要,具体的代码内容、行号、函数名等细节在这个过程中永久丢失。保留的只有最近几轮的完整消息和一段压缩摘要。

这个过程不可逆,这也是为什么需要在压缩发生前主动把关键信息写入文件。


六、实战策略一:分会话 + 单一职责

6.1 把所有事塞进一个会话,会出什么问题

你:帮我分析项目结构         ← 会话开始,上下文干净
你:修复 login 的 bug        ← 上下文开始积累
你:重构 database 模块       ← 第 1 步的分析细节已被稀释
你:写单元测试               ← AI 对第 2 步的修复记忆已模糊
你:帮我写 README            ← 上下文噪音严重,AI 开始泛泛回答

这不是 AI 在"变笨",是上下文容量限制的必然结果。越往后,早期内容的权重越低,等价于 AI 的注意力从早期消息上移开了。

6.2 什么信号说明该开新会话了

信号 说明
任务性质切换 从"修 bug"变成"写文档",两件事不需要共享上下文
/cost 显示 input_tokens 超过 50% 上下文快满,后续每轮成本急剧上升
AI 开始重复你之前说过的信息 上下文被稀释,AI 通过重复来"补充"理解
AI 的回答开始泛泛而谈 上下文噪音过多,注意力被分散
当前任务已完成 没有理由保留这段上下文,清空更高效

6.3 分会话的操作方式

当前版本 Claude Code 没有内置会话切换命令,分会话的实际操作:

# CLI 用法
exit          # 或 Ctrl+D,退出当前会话
claude        # 重新启动,默认开新会话
claude -r     # 重新启动并恢复上一次会话

# Desktop App / Web App
# 从左侧会话列表新建对话,每个任务对应一个独立条目

分会话的基本工作流:

会话 1(侦察)  → 只读分析,产出写入文件 → 退出
会话 2(修改)  → 读取会话 1 的产出文件,执行修改 → 退出
会话 3(验证)  → 独立验证,更新进度文件 → 退出

七、实战策略二:跨会话记忆——让 AI 自己管理"档案柜"

7.1 为什么不能靠 AI 的"记忆"跨会话

新会话 = 上下文清零。AI 不知道你上次做了什么,也不知道项目进展到哪一步。这是设计本身的约束,不是缺陷。

解决方式不是"让 AI 记住",而是把需要跨会话保留的信息写进文件,下个会话让 AI 去读。你每次的跨会话"交接"只需要一句话。

7.2 方案一(推荐):AI 维护进度文件

在项目里建一个 docs/project-status.md,每个会话结束时让 AI 更新它,下个会话开头让 AI 先读它。

每次会话结束时(你说):

任务完成了。请更新 docs/project-status.md:
标记刚才完成的项为 ✅,更新"进行中"和"待办"列表。

每次会话开始时(你说):

读取 docs/project-status.md,继续下一步待办。

进度文件的结构示例(AI 生成并维护):

# 项目:用户认证模块重构
最后更新:会话 3(2026-06-24)
以下为AI每次会话结束后更新的docs/project-status.md

## ✅ 已完成
- [会话 1] 分析 src/auth/ 目录,识别出 3 处明文密码比较
- [会话 2] 引入 bcrypt,改写 password.py 和 login.py
- [会话 3] 全量测试通过,git diff 已审查

## 🔄 进行中
- 添加密码强度验证(src/auth/validator.py,尚未创建)

## ⏳ 待办
1. 为 validator.py 写单元测试
2. 更新 API 文档(docs/api/auth.md)
3. 检查 admin 模块是否有类似的明文比较问题

## ⚠️ 关键约束
- 不要碰 src/database/migrations/ 下的任何文件
- bcrypt rounds 设定为 12
- 测试命令:pytest tests/auth/ -v

AI 读取这个文件,10 秒内恢复完整上下文——不需要你写任何背景介绍。

7.3 方案二:CLAUDE.md 写入当前任务状态

适合周期较短的任务,在 CLAUDE.md 末尾追加当前状态块:

## 当前任务状态(每次会话结束后更新)
- 正在进行:用户认证模块重构
- 已完成:password.py 的 bcrypt 改造 ✅
- 下一步:添加密码强度验证到 validator.py
- 约束:不碰 migrations/

优点:每次会话 AI 自动读取,无需额外指令。
限制:单个 CLAUDE.md 文件约有 4,000 字符上限,长期项目会撑满,适合阶段性短任务。

7.4 方案三:/export 存档(不推荐作主力)

/export session-2-summary.md

导出完整对话,下个会话让 AI 读取。

为什么不推荐作主力:导出内容是原始对话,包含大量工具调用的中间细节,冗长且信息密度低。AI 重新理解它需要消耗大量 token,性价比远低于方案一的结构化进度文件。适合存档备查,不适合作为跨会话记忆的传递载体。

三种方案对比:

方案 上手难度 Token 效率 适用场景
进度文件(project-status.md) 中(需初始化一次) 多会话协作项目,长期任务
CLAUDE.md 状态块 低(直接追加) 短期任务,规则简单的项目
/export 导出 最低 存档备查,非跨会话主力

八、实战策略三:防止上下文失真

上下文失真指的是:随着对话变长,AI 对早期信息的响应权重逐渐降低,在需要引用时产生偏差甚至凭空编造内容。这是 LLM 注意力机制的固有特性,无法消除,但可以用以下四种方法压制。

8.1 方法一:把不变的规则写进 CLAUDE.md

CLAUDE.md 的内容每轮都会被注入 System Prompt,不参与历史消息压缩。不随对话变化的规则写在这里,比在聊天里反复提醒稳定得多:

# 写进 CLAUDE.md(每轮生效,不会被压缩)
- 数据库用参数化查询,禁止字符串拼接 SQL
- 代码格式化用 Black
- 测试框架用 pytest

对比:在聊天里反复提醒

你:记住,要用参数化查询
(5 轮后)
你:我说了要用参数化查询!为什么又用字符串拼接?

两种方式的效果差距很大。写进 CLAUDE.md 的规则每轮强制注入,写在聊天里的规则随时间被稀释。

8.2 方法二:关键节点做检查点总结

每完成一个阶段,让 AI 用 3-5 个要点总结当前状态,然后在下一步开头引用它:

你:我们完成了登录模块的重构。请用 3-5 个要点总结:
    1. 改了哪些文件
    2. 核心改动是什么
    3. 还有哪些待办

(AI 输出总结后)

你:基于上面的总结,现在开始处理第 3 点——添加密码强度验证

为什么有效:总结被"重新说了一遍",关键信息从上下文深处被移到末尾。AI 的注意力对上下文末尾的内容权重最高(Recency Effect),等于把关键信息重新置顶。

8.3 方法三:/compact 有损压缩的正确姿势

/compact 触发后,系统将历史消息压缩为摘要,最近几轮保留原样。压缩会丢失细节(具体代码、行号、函数名等),且不可逆。

使用时机:

  • /cost 显示 input_tokens 超过 100K
  • AI 开始重复或偏题
  • 准备进入一个新的子任务

正确操作顺序:

第一步:确认关键信息已写入文件(docs/ 下的自定义文件或 CLAUDE.md)
第二步:执行 /compact
第三步:压缩完成后,立即重申当前任务的核心约束:
        "我们正在重构 src/auth/ 模块,
         已完成 login.py 的改造,下一步是 rate limiting,
         约束:不碰 migrations/ 目录。"

跳过第一步直接压缩,等于主动放弃细节——被压掉的内容无法找回。

8.4 方法四:中间结果写文件,而非留在对话里

对话会被压缩,文件不会。把重要的中间产出写入文件:

你:把这次代码审查的发现写入 docs/review-notes.md
你:更新 TODO.md,标记"登录模块重构"为已完成,"密码验证"为进行中

下个会话或 /compact 之后,直接让 AI 读取文件恢复上下文:

你:读取 docs/review-notes.md,基于上次审查结果继续修复第 2 个问题

四种方法的分工:

跨会话防失真:
  CLAUDE.md(方法一)          ← 固定规则,自动注入,不受压缩影响
  进度文件(第七节方案一)      ← 结构化状态,低 token 成本恢复

会话内防失真:
  检查点总结(方法二)          ← 利用 Recency Effect 刷新注意力权重
  中间结果存文件(方法四)      ← 持久化,不怕压缩
  /compact(方法三)            ← 主动压缩,先落地再压缩

九、实战策略四:省 Token 的七个具体动作

9.1 七个操作级技巧

技巧 具体做法 节省原理
指定行号范围 “读 file.py 第 10-30 行"而非"读整个文件” 减少 FileReadTool 返回的内容量
用搜索代替全文阅读 “搜索 src/ 下包含 authenticate 的行"而非"读整个 src/” GrepTool 只返回匹配行,远少于全文
一步到位的 Prompt 第一条消息就给清楚:目标文件、需求、约束 减少澄清来回的轮次
限制输出长度 “用 5 个要点总结"而非"详细分析” 直接减少 output_tokens
及时 /compact 上下文膨胀前主动压缩 减少后续每轮的 input_tokens 基数
新会话复用结果 新会话开头"读取 docs/progress.md" 几百 token 替代几万 token 的重复分析
保持会话连续性 不频繁开新会话,利用 Prompt Caching 固定内容命中缓存,cache_read 费用低于正常输入价格

9.2 Pro / MAX / API 用户的 Token 优先级不同

用户类型 Token 限制 省 Token 优先级 核心策略
Pro($20/月) 有用量上限 精准 Prompt + 分会话 + 限制输出长度
MAX($100-200/月) 大幅提升上限 控制单会话质量,不必过度节省
API(按 token 计费) 无上限,纯按量付费 每条 Prompt 都直接影响账单

十、完整示例:用三个会话完成一次模块重构

假设任务:一个 3 小时技术演讲的字幕,你想把它变成一份可读的学习笔记

═══ 会话 1:侦察(只读,消耗最小)═══
你:读取 talk.srt,把内容按主题切分,列出每段的关键词和核心论点
你:把分段大纲写入 notes/outline.md
→ 退出

═══ 会话 2:实施(写作,消耗中等)═══
你:读取 notes/outline.md
你:把每个主题块扩写成结构化的中文笔记,加上延伸问题
你:存入 notes/learning-notes.md
→ 退出

═══ 会话 3:验证(独立,消耗最小)═══
你:对比 outline.md 与 learning-notes.md,确认没有漏掉章节
你:检查笔记里引用的内容是否与字幕原文一致(排除幻觉)
→ 退出

为什么这样分三个会话:

  • 会话 1 只读字幕不写笔记,token 消耗最低,结果持久化到文件——后续不需要重新过一遍字幕
  • 会话 2 上下文干净,AI 专注写作本身,不会被之前的字幕原文细节干扰
  • 会话 3 独立验证,避免同一会话里"自己检查自己作业"的认知偏差

结语:上下文窗口是白板,文件系统是档案柜

上下文窗口是工作台:容量有限,用完了会被擦掉,关掉就清零。文件系统是档案柜:写进去的东西不会因为会话结束或 /compact 而消失。

高效使用 Claude Code 的核心只有一条原则:需要跨会话、跨压缩保留的信息,必须落地到文件。把 AI 当成能读写文件的协作者,而不是有记忆的聊天对象——这两种用法的效率差距,在稍微复杂一点的项目里会非常明显。


Hey🌺我是一只肥罗,坚持做一些有意思的事情
「愿始于我,但不止于我」
🍻研究+码字+反复修正不易,路过的朋友麻烦点赞关注~

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐