A LATE-NIGHT DEEP DIVE

Claude Code 的骨架
Session · Memory · Cache · Compaction

从「session 究竟存哪里」一路追问到「compaction 怎么不丢约束」—— 一次完整理解 Claude Code 工作记忆机制的旅程。

📅 2026-05-21 👤 Carrey × Claude 📚 11 章
01

Session 是什么?硬盘上到底存了啥?

从一个最基础的好奇心开始

Claude Code 的所有对话都以 .jsonl 文件保存在:

C:\Users\m1526\.claude\projects\<cwd-编码>\<uuid>.jsonl

其中 <cwd-编码> 把工作目录的 :\ 替换成 -。 比如在 D:\ 启动的 session 就在 projects\D--\

📖 CWD 是什么?

CWD = Current Working Directory(当前工作目录) —— 你打开终端启动 Claude Code 时所在的那个文件夹路径。
比如 PowerShell 里先 cd D:\claude,CWD 就是 D:\; 在 D:\Projects\paperclip 启动,CWD 就是 D:\Projects\paperclip

这个概念后面会反复出现 —— 它决定了 session 存到哪个文件夹、memory 自动加载哪一套, 是 Claude Code 一切"按项目隔离"行为的根基。

Session 不是聊天记录本,是"全状态磁带"

记录类型具体内容
用户输入你打的每一句话
模型回复我说的每一段文字
工具调用Read / Edit / Bash / Agent 的入参
工具结果命令输出、文件内容
系统事件压缩点、模型切换、hook 触发
Todo / Plan 状态TaskCreate 的列表快照

4 个实际用途

  1. /resume 复活 —— 第二天能续上昨天的对话,靠的就是它
  2. 压缩后回看 —— compact 之后原始细节只在 jsonl 里留着
  3. 审计取证 —— 想知道"上周让 Claude 改了啥",翻这里最准
  4. 与 memory 分工 —— session 是草稿纸;memory 是跨 session 的笔记

你的 session 现状

2,150总文件
340MB 总大小
1,743JSONL 数
155MB 在 D-- 目录你最常用的工作区
💡 关键洞察

Session 是 Claude Code 的"录音"。它和 memory 的根本区别在于: session 是这次的草稿,memory 是跨次的笔记。 340 MB 看似不少,但分散在 80+ 项目中,每个其实都不大。

jsonl 文件长什么样?

JSONL = JSON Lines:每一行是一个独立的 JSON 对象,行与行之间用换行分隔。 它不是一整个大 JSON 数组,而是"逐行流式"。

❌ 普通 JSON 数组

[
  {"type":"user","text":"hi"},
  {"type":"assistant","text":"hello"}
]

追加一条要重写整个文件;必须文件完整才能解析;写入慢

✅ JSONL 流式

{"type":"user","text":"hi"}
{"type":"assistant","text":"hello"}

追加只 append 一行(O(1));断电只丢最后一行;可流式读

Claude Code jsonl 里实际有哪些对象类型

type 字段说明何时出现
user用户消息你每发一句话
assistant助手回复含 thinking 块、text 文本、tool_use 工具调用
attachment附件元数据工具 schema 增量、MCP 指令、skill 列表
permission-mode权限模式状态session 开始 / 切模式时
ai-titleAI 自动生成的会话标题对话开始几轮后
file-history-snapshot文件历史快照每次 Edit / Write 时记录原版备份
system / compact_boundary压缩分隔点触发 /compact 或 auto-compact
last-prompt缓存最后一条用户输入用于 /resume 恢复时显示

jsonl 的 4 个特点

  1. 追加友好 —— 新事件只需在文件末尾 append 一行,O(1) 操作
  2. 抗崩溃 —— Claude Code 进程突然挂了,前面所有完整行都安全;最多丢最后半行
  3. 流式可读 —— 可一边写一边读(tail -f 风格),适合实时观察
  4. 易解析 —— grep / wc -l / awk 都能直接扫,不需要 JSON 解析器
💡 为什么 Claude Code 选 jsonl

对话是天然「时间序列」结构 —— 一句话一个事件,append 比 random write 频繁得多。 用 jsonl 不仅写入快,还让 /resume 可以从任意中断点续上,因为只要前面的行完整就能解析。 这也是为什么 340 MB 的 session 数据不会让磁盘 IO 卡顿 —— 全是顺序追加,硬盘最爱的写入模式。

02

为什么 Session 默认在 C 盘而不是 D 盘?

这是个"硬编码 + 系统约定"的结果

简单答案:Claude Code 写死了用 ~/.claude/,而 Windows 上你的 ~ 就是 C:\Users\m1526\

路径由谁决定
~/.claude/projects/Claude Code 源码硬编码(Anthropic 官方约定)
~ = C:\Users\m1526\%USERPROFILE% 环境变量决定,Windows 安装时定的

Anthropic 设计时遵循的是 Linux/Mac 的"dotfile 在 home 目录"习惯(类似 ~/.npm~/.cache),Windows 没做特别适配。

想搬到 D 盘?三个方案

方案难度副作用
A. 改 %USERPROFILE%🔴 高全系统所有 ~ 都跟着搬,破坏性极大
B. Junction 目录软链接(推荐)🟢 低C 盘只留快捷方式,数据实际在 D 盘,对 Claude Code 透明
C. CLAUDE_CONFIG_DIR 环境变量🟡 中只搬 Claude Code 自己的,最干净;需确认 CLI 是否支持
💡 关键洞察

Junction (mklink /J) 方案最稳:物理数据搬到 D 盘,C 盘留一个"快捷方式",Claude Code 完全不知道有变化。

03

我栽的跟头:/btw 到底走不走 Session?

一次"知识盲区"的诚实记录

❌ 我先犯的错

当你第一次问 /btw 时,我说"没听过这个命令",并猜测可能是打错字。 你立刻指出 /btw 是 Claude Code 内置的"侧边提问"功能。我让 claude-code-guide agent 实查官方文档后才确认你是对的。

/btw 的官方定义

"by the way" 侧边提问 —— ephemeral overlay,看得到当前对话上下文,但问答不写入对话历史

维度行为
能看到当前上下文吗✅ 能 —— 已读代码、之前决策、对话历史都可见
写入 session jsonl 吗不写。官方原话:"never enter the conversation history"
能用工具吗❌ 不能 Read/Bash/Grep/Edit
答案怎么呈现可关闭的浮层,Space / Enter / Esc 关掉
💡 关键洞察

/btw 的设计目的:不想污染主线对话、不想消耗 context window、不想留对话痕迹时用。 适合临时好奇心、跟当前任务无关的提问。

04

/btw 关闭后真的零痕迹吗?

本地无痕 ≠ 全网无痕

位置有无痕迹说明
主 jsonl 转录✅ 无官方明确不写
当前对话上下文✅ 无后续轮次的我看不到
终端 scrollback❓ 未明确是 overlay UI,关闭后是否擦终端缓冲文档没写
Anthropic 服务端⚠️ 有API 调用必然有计费 + 安全审计日志
Console Usage⚠️ 有仅显示 token 数和时间,不显示内容
Token 配额⚠️ 照扣仍是一次模型调用
本地恢复手段✅ 无一次性 overlay,关掉就没了
⚠️ 一句话总结

本机基本无痕,但 Anthropic 服务端跟任何其他 API 调用一样有调用记录。 想让服务端都没记录,需走企业 Zero Data Retention 之类的合规通道。

05

Memory 的三层架构

不是一个东西,而是三个独立层级

层级物理位置加载方式容量限制
A. 全局指令~/.claude/CLAUDE.md每次对话自动注入无硬上限
B. MEMORY.md 索引projects\<cwd>\memory\MEMORY.md每次对话自动注入200 行硬截断
C. 详细记忆projects\<cwd>\memory\*.md按需读取无上限

工作流:索引常驻 + 详情懒加载

你提问 ↓ 系统自动塞入:CLAUDE.md + MEMORY.md (93行索引) ↓ 我扫一眼索引,看到比如 "/btw 命令" 相关 ↓ 用 Read 工具打开 reference_claude_btw.md,看详情 ↓ 回答你

你的 memory 当前规模

93行 / 20053% 余量
88详情文件
173KB 总量
~2KB 平均
💡 关键洞察

真正占常驻 context 的只有 MEMORY.md 索引(200 行)。88 个详情文件躺在硬盘睡觉, 用到才叫醒。所以"成本"几乎全在那 200 行桌面位上,不在硬盘上。

06

怎么决定一个知识点该不该写进 Memory?

一次反思:/btw 该不该存?

4 类该写的

类型什么场景
user用户的身份、角色、偏好、知识背景
feedback用户纠正过的做法、明确赞同过的判断
project项目的状态、决策、deadline、动机
reference外部事实指针("X 系统在哪里"、"Y 工具怎么调")

反思:/btw 该不该存?

我建议把 /btw 加进 memory,你问了一个关键问题:"对未来协作有什么影响吗?" —— 这一问让我意识到:

❌ 我加 memory 的真实动机

"防我自己再栽跟头" —— 这是我的需要,不是协作的需要。

✅ 该有的判断

reference 的价值是"我会主动联想到并据此做事",不是"事实查询缓存"。 /btw 你比我懂,我不会主动用,所以应该撤回。

💡 核心判断标准

不要把"我别丢脸"伪装成"对协作有用"塞进 memory。下次再有人问 Claude Code 内置命令, 直接用 claude-code-guide agent 查官方文档 —— 缓存会过时,文档不会。

07

Memory 是按工作目录强隔离的(震惊)

不只 C/D 不通,连 D 盘不同子目录也各管各的

实测发现:你机器上有 13 个独立 memory 池

工作目录(启动时 cwd)独立 memory 文件数
D:\\(你今天的对话)88 条 ← 主力库
D:\\Projects\\paperclip9 条(完全独立)
D:\\Projects\\newyoule8 条
D:\\Users\\Niko\\Downloads\\kelly-agent7 条
D:\\Projects\\lark-claude-bridge4 条
... 其他 8 个池共 30 条

两个关键问题的答案

Q1: C 盘能看到 D 盘记忆吗

完全看不到。C 盘启动加载 C--xxx/memory/,D 盘那 88 条系统不会自动注入。

Q2: D 盘深层项目和 D 盘根能互通吗

不能。paperclip 那 9 条只在进入 paperclip 启动时才加载;回到 D:\\ 根就看不到。

⚠️ 设计陷阱

还有 24 个工作目录完全没有 memory 文件夹 —— 进这些目录我是"失忆白板",全靠重新认识你。

3 个应对策略

策略优点缺点
A. 固定从 D:\\ 启动 + cd 进项目始终用同一套 memory(88 条)失去项目专属 memory
B. 通用 memory 复制到每个项目进哪都能想起你维护多份,容易漂移
C. 全局 CLAUDE.md 放最核心信息100% 跨目录加载容量有限,只放"必知"
08

"D:\\ 启动 + cd 切项目" 真的能继承 memory 吗?

memory 是启动时一次性锁定的

1. cd D:\ ; claude ← 此刻 Claude Code 拍照:cwd=D:\ 立即加载 D--/memory/ 的 88 条 2. (session 启动完毕,加载完成,再也不刷新) 3. 你说"切换到 paperclip 工作区" → 我能做的只有 cd D:\Projects\paperclip → 只是 Bash 工具的 cwd 变了 → memory 不会重新加载 4. 结果:我手上还是 D-- 那 88 条,paperclip 的 9 条对我不存在

三种状态的对照

启动方式D-- 88 条paperclip 9 条
D:\\ 启动 + 不动
D:\\ 启动 + cd 到 paperclip
从 D:\\Projects\\paperclip 启动
想两个都看?需主动让我跨目录 Read
💡 一句话本质

Memory 的"工作区"是启动时的 cwd 决定的,跟你启动后语义上"切到哪个项目"无关。 想跨池子借记忆,必须显式让我跨目录读,且只是临时塞进当前 context,不是合并。

09

API 请求是什么样?静态 vs 动态都是啥?

Prompt cache 的物理基础

每次 API 请求的物理结构

┌─────────────────────────────────────────────────────────┐ │ 1. tools[] ← 工具 schema (Read/Edit/Bash/...) │ 🟢 静态 ├─────────────────────────────────────────────────────────┤ │ 2. system[] ← System prompt + CLAUDE.md │ 🟢 静态 ├─────────────────────────────────────────────────────────┤ │ 3. messages[] ← 历史轮次 + 当前轮 │ 🟡 混合 │ ├─ msg 1 (user) ┐ │ │ ├─ msg 2 (assistant) │ 历史快照 │ 🟢 静态 │ ├─ msg 3 (tool result) │ │ │ ├─ ... ┘ │ │ └─ msg N (本轮新输入 + system-reminder + memory) │ 🔴 动态 └─────────────────────────────────────────────────────────┘

固定顺序tools → system → messages,前缀匹配,只要前面没变后面才有机会命中。

静态 vs 动态对照

内容在哪里类型
工具 schematools[]🟢 静态
全局 CLAUDE.mdsystem[]🟢 静态
环境信息(cwd/git 状态)system[]🟢 静态
历史对话messages[] 前段🟢 静态
历史工具结果messages[] 中段🟢 静态
MEMORY.md 索引注入 system-reminder🟡 半静态
本轮新提问messages[] 末尾🔴 动态
本轮工具结果messages[] 末尾🔴 动态
💎 Anthropic 的精妙设计

用户/项目 memory 不塞进 system prompt,而是用 <system-reminder> 塞进当前轮 message。 这样改 memory 时只让"最后一条"miss,前面所有 cache 都保留,省了大量 token 钱

TTL(缓存生存时间)

用户类型默认 TTL可调
API key / Pro5 分钟ENABLE_PROMPT_CACHING_1H=1 → 1 小时(2x 成本)
Max 订阅自动 1 小时无需设置
10

Compaction × Cache 的精妙交互

四个我之前不知道的关键设计

① Compaction 利用旧 cache 生成 summary

📖 官方原话

"Claude Code sends a one-off request with the same system prompt, tools, and history. Because it shares your prefix, that request reads the existing cache rather than reprocessing the full history."

翻译:触发 compaction 时,先用旧 cache 以 0.1x 价格快速读出全历史 → 让模型总结 → 生成 summary → 下一轮用 summary + 静态 cache 继续。不是"全部历史重新计算"

/fork subagent 共享父 cache(省钱大杀器)

Subagent 类型Cache 行为成本
普通 subagent独立 200K context + 自建 cache标准价
/fork subagent共享父 session 的 cache仅 ~10% input 价格

③ defer_loading 省 85% token(MCP 必看)

装多 MCP 工具,所有 schema 默认全塞 cache。开关 defer_loading: true → 工具名留着,schema 按需注入。

④ /resume 跨 session 的代价

5 分钟 TTL 一过就废了。第二天 /resume 必然冷启动。GitHub Issue 实测:冷启动会让该轮成本暴涨 20-32%

💡 5 个实战 takeaway
  1. 不要怕 /compact —— 它本身用旧 cache,没你想的贵
  2. subagent 内部用 /fork —— 想"借父 session 脑子"成本只要 10%
  3. MCP 多 → 开 defer_loading —— 省 85% token
  4. 长会话别离开太久 —— 超过 5 分钟回来要付冷启动税
  5. 关注 Max 订阅 —— 1h TTL 是免费的隐藏福利

📼 jsonl 里的真实证据(实测)

从你 2026-05-13 凌晨那次 Kelly Agent 的手动 /compact 现场扒出来的 jsonl 数据:

Line 1169 ← 压缩前最后一条普通对话 Line 1170 ← compact_boundary 系统事件(带 compactMetadata) Line 1171 ← isCompactSummary: true 的虚拟 user 消息(9 章节 summary) Line 1172 ← 继续追加新对话,同一个文件、同一个 sessionId
压缩前 tokens压缩后 tokens节省耗时触发方式
488,537259,90747%96.6 秒manual
⚠️ 反直觉发现

同一个 jsonl 文件、同一个 sessionId、不创建新文件。 compaction 只是在文件流里插入一个分隔事件 + 一条 summary 消息,之后继续追加。

关键:jsonl 里压缩前的 1169 行历史完整保留 —— 模型 API 调用时不再发送它们,但硬盘上还在。 想追溯压缩前细节?告诉我"读这个 jsonl 的第 X-Y 行",依然能调出来。这就是 jsonl 流式追加格式的隐藏价值: 压缩不是删除,只是模型视角的"折叠"

11

Compaction 用什么模型?9 章节够不够保护约束?

官方 prompt 比想象的脆弱

Compaction 输出的 9 个固定章节

#章节名内容
1Primary Request and Intent用户初始诉求
2Key Technical Concepts核心技术概念
3Files and Code Sections涉及的文件 & 代码
4Errors and Fixes错误 & 修复
5Problem Solving Approach求解思路
6All User Messages声称"逐字保留所有用户消息"
7Pending Tasks待办事项
8Current Work State当前工作状态
9Optional Next Step下一步建议

你的真实风险,不是模型档次,而是"约束蒸发"

📖 官方自己的警告

"Constraints like 'Must stay compatible with Node 16' are easy to drop from a summary but critical to keep."
—— 像"必须兼容 Node 16"这种约束很容易在 summary 里被丢掉,但又是关键要保留的。

实证退化模式

退化类型典型表现
🔴 约束/验收口径蒸发"别改这个文件""只修 bug 别重构" 这种否定式约束最容易压掉
🔴 需求漂移记忆失真中途改口的需求,摘要只记初始目标
🔴 错误信息丢失同一个 bug 反复踩
🔴 5 次压缩后明显退化重复修改无进展

你能 / 不能干预的

✅ 能干预

  • /compact 指令 加自定义提示
  • CLAUDE.md 写持久约束
  • 关闭 auto-compact

❌ 不能干预

  • 指定用 Opus 做压缩
  • 压缩前 preview
  • 标记某消息"勿压缩"
💡 反直觉真相

章节 6 "All User Messages" 字面上"逐字保留",但官方自己警告不可靠。 所以在 CLAUDE.md 加"保留约束"不是重复内置规则,而是强化优先级