Session 是什么?硬盘上到底存了啥?
从一个最基础的好奇心开始
Claude Code 的所有对话都以 .jsonl 文件保存在:
C:\Users\m1526\.claude\projects\<cwd-编码>\<uuid>.jsonl
其中 <cwd-编码> 把工作目录的 : 和 \ 替换成 -。
比如在 D:\ 启动的 session 就在 projects\D--\。
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 个实际用途
- /resume 复活 —— 第二天能续上昨天的对话,靠的就是它
- 压缩后回看 —— compact 之后原始细节只在 jsonl 里留着
- 审计取证 —— 想知道"上周让 Claude 改了啥",翻这里最准
- 与 memory 分工 —— session 是草稿纸;memory 是跨 session 的笔记
你的 session 现状
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-title | AI 自动生成的会话标题 | 对话开始几轮后 |
file-history-snapshot | 文件历史快照 | 每次 Edit / Write 时记录原版备份 |
system / compact_boundary | 压缩分隔点 | 触发 /compact 或 auto-compact |
last-prompt | 缓存最后一条用户输入 | 用于 /resume 恢复时显示 |
jsonl 的 4 个特点
- 追加友好 —— 新事件只需在文件末尾 append 一行,O(1) 操作
- 抗崩溃 —— Claude Code 进程突然挂了,前面所有完整行都安全;最多丢最后半行
- 流式可读 —— 可一边写一边读(
tail -f风格),适合实时观察 - 易解析 ——
grep/wc -l/awk都能直接扫,不需要 JSON 解析器
对话是天然「时间序列」结构 —— 一句话一个事件,append 比 random write 频繁得多。 用 jsonl 不仅写入快,还让 /resume 可以从任意中断点续上,因为只要前面的行完整就能解析。 这也是为什么 340 MB 的 session 数据不会让磁盘 IO 卡顿 —— 全是顺序追加,硬盘最爱的写入模式。
为什么 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 完全不知道有变化。
我栽的跟头:/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、不想留对话痕迹时用。
适合临时好奇心、跟当前任务无关的提问。
/btw 关闭后真的零痕迹吗?
本地无痕 ≠ 全网无痕
| 位置 | 有无痕迹 | 说明 |
|---|---|---|
| 主 jsonl 转录 | ✅ 无 | 官方明确不写 |
| 当前对话上下文 | ✅ 无 | 后续轮次的我看不到 |
| 终端 scrollback | ❓ 未明确 | 是 overlay UI,关闭后是否擦终端缓冲文档没写 |
| Anthropic 服务端 | ⚠️ 有 | API 调用必然有计费 + 安全审计日志 |
| Console Usage | ⚠️ 有 | 仅显示 token 数和时间,不显示内容 |
| Token 配额 | ⚠️ 照扣 | 仍是一次模型调用 |
| 本地恢复手段 | ✅ 无 | 一次性 overlay,关掉就没了 |
你本机基本无痕,但 Anthropic 服务端跟任何其他 API 调用一样有调用记录。 想让服务端都没记录,需走企业 Zero Data Retention 之类的合规通道。
Memory 的三层架构
不是一个东西,而是三个独立层级
| 层级 | 物理位置 | 加载方式 | 容量限制 |
|---|---|---|---|
| A. 全局指令 | ~/.claude/CLAUDE.md | 每次对话自动注入 | 无硬上限 |
| B. MEMORY.md 索引 | projects\<cwd>\memory\MEMORY.md | 每次对话自动注入 | 200 行硬截断 |
| C. 详细记忆 | projects\<cwd>\memory\*.md | 按需读取 | 无上限 |
工作流:索引常驻 + 详情懒加载
你的 memory 当前规模
真正占常驻 context 的只有 MEMORY.md 索引(200 行)。88 个详情文件躺在硬盘睡觉, 用到才叫醒。所以"成本"几乎全在那 200 行桌面位上,不在硬盘上。
怎么决定一个知识点该不该写进 Memory?
一次反思:/btw 该不该存?
4 类该写的
| 类型 | 什么场景 |
|---|---|
| user | 用户的身份、角色、偏好、知识背景 |
| feedback | 用户纠正过的做法、明确赞同过的判断 |
| project | 项目的状态、决策、deadline、动机 |
| reference | 外部事实指针("X 系统在哪里"、"Y 工具怎么调") |
反思:/btw 该不该存?
我建议把 /btw 加进 memory,你问了一个关键问题:"对未来协作有什么影响吗?" —— 这一问让我意识到:
❌ 我加 memory 的真实动机
"防我自己再栽跟头" —— 这是我的需要,不是协作的需要。
✅ 该有的判断
reference 的价值是"我会主动联想到并据此做事",不是"事实查询缓存"。
/btw 你比我懂,我不会主动用,所以应该撤回。
不要把"我别丢脸"伪装成"对协作有用"塞进 memory。下次再有人问 Claude Code 内置命令, 直接用 claude-code-guide agent 查官方文档 —— 缓存会过时,文档不会。
Memory 是按工作目录强隔离的(震惊)
不只 C/D 不通,连 D 盘不同子目录也各管各的
实测发现:你机器上有 13 个独立 memory 池。
| 工作目录(启动时 cwd) | 独立 memory 文件数 |
|---|---|
D:\\(你今天的对话) | 88 条 ← 主力库 |
D:\\Projects\\paperclip | 9 条(完全独立) |
D:\\Projects\\newyoule | 8 条 |
D:\\Users\\Niko\\Downloads\\kelly-agent | 7 条 |
D:\\Projects\\lark-claude-bridge | 4 条 |
| ... 其他 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% 跨目录加载 | 容量有限,只放"必知" |
"D:\\ 启动 + cd 切项目" 真的能继承 memory 吗?
memory 是启动时一次性锁定的
三种状态的对照
| 启动方式 | D-- 88 条 | paperclip 9 条 |
|---|---|---|
| D:\\ 启动 + 不动 | ✅ | ❌ |
| D:\\ 启动 + cd 到 paperclip | ✅ | ❌ |
| 从 D:\\Projects\\paperclip 启动 | ❌ | ✅ |
| 想两个都看? | 需主动让我跨目录 Read | |
Memory 的"工作区"是启动时的 cwd 决定的,跟你启动后语义上"切到哪个项目"无关。 想跨池子借记忆,必须显式让我跨目录读,且只是临时塞进当前 context,不是合并。
API 请求是什么样?静态 vs 动态都是啥?
Prompt cache 的物理基础
每次 API 请求的物理结构
固定顺序:tools → system → messages,前缀匹配,只要前面没变后面才有机会命中。
静态 vs 动态对照
| 内容 | 在哪里 | 类型 |
|---|---|---|
| 工具 schema | tools[] | 🟢 静态 |
| 全局 CLAUDE.md | system[] | 🟢 静态 |
| 环境信息(cwd/git 状态) | system[] | 🟢 静态 |
| 历史对话 | messages[] 前段 | 🟢 静态 |
| 历史工具结果 | messages[] 中段 | 🟢 静态 |
| MEMORY.md 索引 | 注入 system-reminder | 🟡 半静态 |
| 本轮新提问 | messages[] 末尾 | 🔴 动态 |
| 本轮工具结果 | messages[] 末尾 | 🔴 动态 |
用户/项目 memory 不塞进 system prompt,而是用 <system-reminder> 塞进当前轮 message。
这样改 memory 时只让"最后一条"miss,前面所有 cache 都保留,省了大量 token 钱。
TTL(缓存生存时间)
| 用户类型 | 默认 TTL | 可调 |
|---|---|---|
| API key / Pro | 5 分钟 | 设 ENABLE_PROMPT_CACHING_1H=1 → 1 小时(2x 成本) |
| Max 订阅 | 自动 1 小时 | 无需设置 |
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%。
- 不要怕 /compact —— 它本身用旧 cache,没你想的贵
- subagent 内部用 /fork —— 想"借父 session 脑子"成本只要 10%
- MCP 多 → 开 defer_loading —— 省 85% token
- 长会话别离开太久 —— 超过 5 分钟回来要付冷启动税
- 关注 Max 订阅 —— 1h TTL 是免费的隐藏福利
📼 jsonl 里的真实证据(实测)
从你 2026-05-13 凌晨那次 Kelly Agent 的手动 /compact 现场扒出来的 jsonl 数据:
| 压缩前 tokens | 压缩后 tokens | 节省 | 耗时 | 触发方式 |
|---|---|---|---|---|
| 488,537 | 259,907 | 47% | 96.6 秒 | manual |
同一个 jsonl 文件、同一个 sessionId、不创建新文件。
compaction 只是在文件流里插入一个分隔事件 + 一条 summary 消息,之后继续追加。
关键:jsonl 里压缩前的 1169 行历史完整保留 —— 模型 API 调用时不再发送它们,但硬盘上还在。
想追溯压缩前细节?告诉我"读这个 jsonl 的第 X-Y 行",依然能调出来。这就是 jsonl 流式追加格式的隐藏价值:
压缩不是删除,只是模型视角的"折叠"。
Compaction 用什么模型?9 章节够不够保护约束?
官方 prompt 比想象的脆弱
Compaction 输出的 9 个固定章节
| # | 章节名 | 内容 |
|---|---|---|
| 1 | Primary Request and Intent | 用户初始诉求 |
| 2 | Key Technical Concepts | 核心技术概念 |
| 3 | Files and Code Sections | 涉及的文件 & 代码 |
| 4 | Errors and Fixes | 错误 & 修复 |
| 5 | Problem Solving Approach | 求解思路 |
| 6 | All User Messages | 声称"逐字保留所有用户消息" |
| 7 | Pending Tasks | 待办事项 |
| 8 | Current Work State | 当前工作状态 |
| 9 | Optional 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 加"保留约束"不是重复内置规则,而是强化优先级。