Claude对话机制深度解析:为什么 Claude Code 和你越聊越懂你?每句对话都要读一整个上下文吗?
和 Claude Code 聊了 20 天、100 多个会话、十几万字后------它居然记得我三天前随口说的偏好、知道我习惯用哪个工具、甚至能预判我下一步想干什么。这是魔法吗?
当然不是。背后是一套精心设计的多层记忆系统 + 上下文管理引擎。我翻了自己机器上的
.claude目录,把整套机制拆了一遍。
一、先回答标题的第二问:每句对话都要读一整个上下文吗?
答案:是的,但也并不完全是。
要理解这一点,得先搞清楚 Claude API 最根本的工作方式:
1.1 LLM 是"无状态"的
Claude 的 API 是纯 HTTP 调用,无状态。每次你敲回车,发生的事情是:
css
claude.exe 把 messages[] 数组完整打包 → POST 给 API → API 返回一段新文本 → 结束
下一次你再敲回车,claude.exe 重新打包一个新的 messages\[\](包含上次的回复),再次 POST。API 本身不记得你上一轮说过什么。 每一轮调用,整个 messages\[\] 都完整发送过去。
也就是说,你每说一句话,模型确实要"读"整个上下文------到你当前消息位置为止的全部历史。
1.2 那上下文不会爆掉吗?------Compaction 自动压缩
每轮对话都往 messages\[\] 追加新内容,迟早撞上 200K token 的 context window 上限。
Claude Code 的解决方案叫 Compaction(自动压缩)。从 System Prompt 中的原话来看:
当对话变得很长时,部分或全部当前上下文会被摘要化;摘要连同剩余的未摘要上下文一起提供给下一个上下文窗口,因此你不必提前收尾或中途交接。
工作机制:
less
触发条件: messages[] 的 token 数接近 context window 阈值
压缩过程:
① 调用一个轻量模型(如 Haiku)
② 把 messages[] 前半部分(旧的)作为输入
③ 生成一段摘要,概括:
- 用户的主要目标
- 已完成哪些步骤
- 哪些文件被创建/修改
- 当前在什么阶段
④ 旧消息被替换为一条简短的 compaction summary
⑤ 后半部分(最近的对话)原样保留
压缩前:
messages[0] MEMORY.md 索引
messages[1] Git 状态
messages[2] 用户的第1句话
messages[3] 模型第1轮回复
... (几十轮) ...
messages[20] 用户最新一句话
压缩后:
messages[0] MEMORY.md 索引
messages[1] Git 状态
messages[2] [COMPACTED: 前面的对话已摘要为一段文字...]
messages[3] 用户最近几轮消息 (完整保留)
messages[4] 模型最近几轮回复 (完整保留)
messages[5] 用户最新一句话
所以准确来说:每句对话确实读到"整个上下文",但"整个上下文"不等于"全部历史"。旧的被压缩成摘要,新的原样保留------模型读到的永远是"摘要 + 近期全文"的组合。
这也是为什么 System Prompt 中有一条专门指令告诉模型:
"压缩是正常的,你继续工作就行,不需要重新来过。"
如果不加这句话,模型看到摘要块可能会困惑:"前面细节丢了?我要不要重新问?"------这条指令是在消除压缩带来的认知偏差。
相关配置:
| 配置 | 作用 |
|---|---|
autoCompactEnabled |
是否启用自动压缩(默认开) |
autoCompactWindow |
压缩触发阈值 |
precomputeCompactionEnabled |
后台预计算压缩摘要 |
DISABLE_COMPACT 环境变量 |
强制关闭压缩 |
二、现在回答第一问:为什么越聊越懂你?
上面说的是"每次调用的上下文怎么来的"------但真正让 Claude Code "越来越懂你"的,远不止 messages\[\] 这一个维度。实际上它有三套独立的记忆机制在同时工作。
2.1 第一重记忆:System Prompt 中的动态注入
这是最隐蔽也最关键的一层。每次调用 API 前,claude.exe 会动态构建 System Prompt,往里注入:
- MEMORY.md :你的
.claude/projects/<path>/memory/MEMORY.md,这是你的"个人档案"。你之前跟它说的偏好、习惯、项目背景,都存在这里。每次对话启动,System Prompt 自动加载这个索引。 - gitStatus:当前仓库的 git 状态(分支、最近提交、变更文件)。模型从一开始就知道你在哪个分支上、改了什么。
- currentDate:今天的日期。
- CLAUDE.md:项目级别的指令文件。
举个例子------我这台机器上,System Prompt 中自动注入了:
scss
# claudeMd
- [渗透核心信念层](pentest-core-belief.md) --- 明确提到要渗透特定资产时读这个
- [补天漏洞响应平台提交规范](butian-report-rules.md) --- 触发关键词"生成补天报告文件"才读
- [3D 背景 + 玻璃 UI 叠加开发手册](3d-glass-ui-handbook.md) --- 写前端页面时才读
这些不是模型"记性好",而是 claude.exe 在每次 API 请求时都把它们塞进了 System Prompt。模型每轮调用都能"看到"你之前沉淀下来的偏好和规则------这就是"越来越懂你"的第一重保障。
2.2 第二重记忆:对话转录的完整恢复(--resume)
当你敲 claude --resume <sessionId> 时,发生的事情不是"打开聊天记录浏览",而是一次完整的状态重建:
bash
① 读取 .jsonl 转录文件
② 提取所有 type="user" 和 type="assistant" 的消息
跳过 attachment、queue-operation、ai-title 等元事件
③ 用提取的 messages[] 重建内存会话状态
每条消息的 role/content 原样恢复
④ 重新构建 System Prompt(日期、Git 状态更新到今天)
⑤ 重新收集 MCP 服务工具定义(当前连接状态可能与上次不同)
⑥ 把恢复的 messages[] + 新的 system/tools → 发给模型
关键点:System Prompt 和 tools\[\] 不存档------每轮实时构建。 所以恢复后的对话既保留了完整的历史记忆,又更新了日期和工具状态。模型收到的是几乎一模一样的 messages\[\],体验就是"无缝接上"。
2.3 第三重记忆:文件快照(/rewind)
还有一个独立于对话记忆的系统------file-history:
javascript
~/.claude/file-history/
每次 Claude Code 修改你的文件前,它会先创建一份快照。当你用 /rewind 回退修改时,就是从这些快照恢复。这保证了"即使模型改错了,你的代码也能回到之前的状态"------也是一种"记得你之前文件长什么样"的表现。
三、底层怎么承载这些记忆?------三层存储架构
上面说了"记忆了什么",现在说"这些东西存在哪"。
Claude Code 的对话存档分了三个物理层,每层职责不同:
xml
C:\Users\HI\.claude\
├── history.jsonl ← 第1层: 全局索引(轻量)
├── projects\<sanitized-cwd>\<uuid>.jsonl ← 第2层: 完整转录(重量)
└── sessions\<pid>.json ← 第3层: 进程注册表
第一层:history.jsonl --- 对话目录
每条记录只存:用户输入文本 + 时间戳 + 项目路径 + sessionId。 不存模型回复,不存 System Prompt,不存工具调用。
职责:敲 claude --resume 时,列出所有历史对话标题,供选择恢复。
第二层:会话转录文件 --- 完整事件流
这是真正的重量级存储。每个 .jsonl 文件存的是完整的对话事件流:
arduino
第 1 行: queue-operation(消息入队)
第 2 行: queue-operation(消息出队)
第 3 行: user message ← 用户说的话
第 4 行: attachment ← 当时的所有 skill 定义全文
第 5 行: ai-title ← 自动生成的会话标题
第 6 行: assistant message ← 模型思考 + 文本 + 工具调用
第 7 行: tool_result ← 工具执行结果
第 8 行: assistant message ← 模型基于结果的下一段思考
... 数百到数千行 ...
每条 assistant 消息都完整记录了当时的 token 消耗、思考过程、工具参数:
json
{
"type": "assistant",
"message": {
"role": "assistant",
"model": "deepseek-v4-pro",
"content": [
{"type": "thinking", "thinking": "...模型的内心独白...", "signature": "..."},
{"type": "text", "text": "让我看看你的配置文件..."},
{"type": "tool_use", "id": "call_00_...", "name": "Glob", "input": {...}}
],
"usage": {
"input_tokens": 58036,
"cache_read_input_tokens": 0,
"output_tokens": 206
}
},
"sessionId": "231732fa-...",
"timestamp": "2026-06-29T07:00:12.920Z",
"uuid": "ea3c1513-..."
}
每条消息有 uuid,通过 parentUuid 形成完整因果链:
less
user (uuid: aaa)
→ assistant (parentUuid: aaa, uuid: bbb)
→ tool_use: Glob
→ tool_result (parentUuid: bbb, uuid: ccc)
→ assistant (parentUuid: ccc, uuid: ddd)
→ tool_use: Read
→ tool_result (parentUuid: ddd, uuid: eee)
→ assistant (parentUuid: eee, uuid: fff) ← 最终回复
第三层:sessions/.json --- 进程注册表
最轻的一层,只有会话元数据(pid、sessionId、cwd、启动时间、版本、状态)。作用是告诉进程管理器:哪些会话还在运行、哪些已结束。
四、写入时机:崩溃也不丢数据
存档不是"会话结束后一次性写入"------那样进程崩了全丢。
JSONL 格式天然支持追加写入。每产生一个新事件,就往文件末尾追加一行。即使进程中途崩溃,已写入的行不丢。
写入顺序就是事件发生的顺序:
java
用户敲下 "介绍一下harness"
→ 写入: user message (行 N)
模型开始思考
→ 写入: assistant + thinking (行 N+1)
模型决定调用 Glob
→ 写入: assistant + tool_use (行 N+2)
Glob 返回结果
→ 写入: tool_result (行 N+3)
模型继续生成
→ 写入: assistant + text_delta (行 N+4)
五、TTL 与自动清理:记忆也有"保质期"
存档不会无限增长。cleanupPeriodDays 配置项(默认 30 天):
markdown
cleanupPeriodDays = 30
含义:
- 30 天前的对话转录文件 (.jsonl) 被自动清理
- sessions/<pid>.json 过期后删除
- 清理是后台定时任务,不影响正常使用
这也就是为什么你不能 --resume 一个月前的对话------转录文件已被清理。
相关配置:
| 配置 | 作用 |
|---|---|
cleanupPeriodDays |
保留天数(默认 30,最小 1) |
--no-session-persistence |
完全不写转录文件 |
六、实际数据量
我这台机器上,从 6 月 7 日到 6 月 29 日,22 天内:
less
C:\Users\HI\.claude\
├── history.jsonl 493 KB (全局索引)
├── sessions/ ~1 KB (进程注册表)
└── projects\C--Users-HI\
├── 1300f0f4-....jsonl 1.86 MB (最大的单次会话)
├── 07910846-....jsonl 1.43 MB
├── ... 100+个文件 ... 总计 ~114 MB
114MB 的对话转录中,最大单次会话 1.86MB------大约相当于 30 万字的对话量(含模型思考、工具调用、token 统计)。
七、一图总览
ini
┌─ 为什么越聊越懂你? ──────────────────────────────────────────┐
│ │
│ 三重记忆同时作用: │
│ │
│ ① System Prompt 注入 │
│ MEMORY.md / CLAUDE.md / gitStatus → 每轮都注入 │
│ 模型"看到"你沉淀的偏好 → 不靠记忆力,靠注入 │
│ │
│ ② 上下文延续 │
│ messages[] 携带全部历史 → 模型知道前面聊了什么 │
│ 太长时 Compaction 自动压缩旧对话为摘要 │
│ │
│ ③ --resume 状态恢复 │
│ 从 .jsonl 重建完整 messages[] → 无缝接续 │
│ │
├───────────────────────────────────────────────────────────────┤
│ │
│ 每句对话都读整个上下文吗? │
│ │
│ 是的------每次 API 调用都发送完整 messages[] │
│ 但不等于全部历史------旧的被 Compaction 压缩为摘要 │
│ 模型实际读的是: [压缩摘要] + [近期完整对话] + [当前消息] │
│ │
├───────────────────────────────────────────────────────────────┤
│ │
│ 底层存储三件套: │
│ history.jsonl → 对话目录(轻量索引) │
│ projects/*.jsonl → 完整转录(事件流 + token 统计) │
│ sessions/*.json → 进程注册(运行状态) │
│ │
│ 写入: 实时追加(JSONL,崩溃不丢数据) │
│ 清理: 30 天 TTL(自动过期删除) │
└───────────────────────────────────────────────────────────────┘
八、总结
回到标题的两个问题:
"为什么 Claude Code 和你越聊越懂你?"
因为它不是靠模型的"记性"------而是靠三层机制:System Prompt 每轮注入你的偏好记忆(MEMORY.md)、messages\[\] 携带完整对话历史、--resume 可以完整恢复会话状态。这三重保障叠加,让你感觉它"记得"越来越多关于你的事。
"每句对话都要读一整个上下文吗?"
是的------LLM 是无状态的,每轮 API 调用都发送完整 messages\[\]。但"整个上下文"不等于"全部历史"------Compaction 会把旧对话压缩为摘要,模型实际读的是"摘要 + 近期全文"的混合。这保证了即使聊了上百轮,也不会超出 context window。
底层,这一切由三层两速的追加式事件流系统承载:实时追加保证崩溃不丢数据,后台压缩保证上下文不爆窗,TTL 清理保证磁盘不爆炸。
数据来源:Windows 11 上 Claude Code v2.1.187 的实际存储目录