
断断续续啃了一个多月这个系列终于走到了第五篇。前四篇里我把Claude Code的对话调度、工具注册、权限校验和命令解析挨个拆了一遍基本摸清了“一次请求从进来到出去”的主链路。但有个问题始终绕不过去——一个只能“聊完就忘”的Agent凭什么能连着好几个小时跟着你改代码、跨文件重构、始终保持上下文一致答案就是它背后的记忆体系。这篇专门拆Claude Code的分层记忆架构讲清楚它如何把对话过程中产生的临时状态、项目级的长期规则、用户级的全局偏好分别管理、按需加载、冲突仲裁。适合三类人读正在做Agent应用、被上下文管理折磨的开发者想给自己的AI工具加记忆能力的后端工程师以及单纯好奇Claude Code项目目录里那一堆配置文件和存档文件到底各司何职的人。读完你会明白“知识持久化”没有玄学无非是一套务实的读写策略加上几条挺聪明的取舍规则。1. 记忆体系全景图三层分类与各自职责1.1 为什么不能只靠一个“大列表”存所有记忆先说个残酷的事实单纯把对话历史攒成一个数组往模型上下文里一塞这种方案在小demo里跑得通一旦进入真实项目立刻崩。我见过不少自建Agent项目记忆方案简单粗暴——把所有历史消息拼成一大段文本每次请求全量带上。结果就是token开销随轮数线性暴涨单次请求的计费数字越来越难看对话超过一定长度后模型开始“选择性失忆”早期的重要决策全被淹没更糟糕的是修改一条旧规则要重新拼接整个文档写代码变成了一场字符串手术。Claude Code的思路不是这样。我在源码里看到的是一条清晰的工程路线把记忆按生命周期和价值密度拆开。这背后其实是计算机体系结构里最经典的“分层缓存”思想在Agent场景下的迁移。你可以把三层记忆类比成书桌、书架和档案馆——手头正在用的资料放书桌立刻能取工作相关的材料放书架随拿随用需要长期留存的历史档案进档案馆按需调阅。如果所有东西都堆在书桌上后果可想而知。记忆分层与计算机存储体系对照表 运行时上下文 CPU/寄存器 易失、极快、容量小 会话级存档 内存 断电即失、速度中等 持久化规则 磁盘文件 永久保存、按需加载1.2 三层的划分逻辑Claude Code的记忆体系按我读源码时梳理的结果大致可以分成这么三层第一层是运行时上下文对应的是单次会话中模型实际看到的那部分内容。它由消息队列、工具调用记录、系统提示词拼装而成每一轮对话都会全量重建。这一层的核心矛盾是容量模型上下文窗口有上限必须精打细算。第二层是项目级持久记忆以项目目录下的规则文件为核心载体。这一层解决的是“换了个新会话项目背景还在不在”的问题。只要工作目录正确新会话启动时就能把项目规范、技术栈说明、代码风格约束重新读进来。第三层是用户级全局记忆存放跨项目的个人偏好和通用指令。它独立于任何具体项目常驻用户主目录下保证你在这台机器上的任何新项目里一些基础行为习惯都会被尊重。这三层各自的职责边界很清楚运行时管“此刻的状态”项目级管“这个项目的规矩”全局级管“你这个人做事的风格”。代码里虽然有交叉引用但职责划分不模糊。1.3 记忆的生命周期写入、驻留、淘汰、回读任何记忆系统的核心问题都是同一个什么时候该记住什么时候该忘掉。Claude Code的处理方式如果抽象成代码逻辑是四条不断循环的操作写入会话中的关键决策、工具执行结果、用户明确表达的偏好会被记录到对应的记忆载体。驻留运行时上下文中保留最近N轮的关键信息其余老消息折叠压缩。淘汰超出预算的旧内容要么被摘要替换要么被移出上下文但不删除落盘副本。回读新会话启动时按“全局规则 - 项目规则 - 运行时续接”的顺序重新装配记忆。整个循环里最见功底的是第3步的淘汰策略。我在源码里看到的不是简单“从头删掉最老的”而是保留对话骨架、压缩过程细节。具体实现我们放在第二章细说。2. 第一层运行时上下文中的“瞬时记忆”2.1 消息序列的数据组织方式Claude Code的会话核心在内存里维护的是一条结构化消息链。每个节点保存着消息来源、消息类型、文本内容和关联的工具调用结果然后统一交给一个序列化模块拼装成模型接口要求的消息数组。读源码时有个细节值得注意消息并不是只存用户说了什么和助手回了什么。工具调用的入参和返回值也会进入消息链并且各自带一个角色标识。这样做的好处是模型在后续推理时能完整回溯“我上一轮调了哪个工具、返回了什么结构、据此得出了什么结论”而不是只看到一句孤零零的“文件已保存”。一条消息节点的字段骨架逻辑示意 sender: user | assistant | tool content: string tool_call_id: string # 关联到具体某一次工具调用 timestamp: number summary_of_previous: string | null # 折叠摘要时填充这个结构看起来平淡无奇但决定了后面摘要压缩、滑窗淘汰、会话恢复的实现复杂程度。数据结构设计得好后面所有策略都有操作空间设计得扁平后面只能对着一个大字符串发愁。2.2 Token预算分配与滑窗淘汰策略运行时上下文最核心的机制是token预算。Claude Code在装配每轮请求时会先算出一个总预算然后按优先级逐块分配系统提示词和当前轮次的用户消息最先保障接着是最近的几轮对话再往前的历史则按重要程度参与分配。我读源码时最直观的感受是它的淘汰粒度不是“轮”而是“块”。每一块由若干连续消息构成淘汰时按块移除这样可以避免把一轮对话里的问题和回答拆得七零八落。触发条件也很硬当预计算出的提示词token数超过预算阈值的某个比例时折叠流程自动启动。token预算分配优先级示意从高到低 1. 系统级指令与全局规则不可折叠 2. 当前轮用户消息与正在执行的工具调用 3. 最近N轮“热对话”原样保留 4. 较早轮次的“温对话”允许折叠为摘要 5. 历史搜索记录 / 项目规则明细按需才加载这个优先级其实挺反直觉的——很多自研Agent倾向于给历史对话很高的权重生怕模型忘了前面的决策。但Claude Code的选择是超过热区的东西统统先压缩真要细问时可以再查原文。这样换来的是单次请求响应更稳定、计费更可控。实际测试下来压缩处理后长会话的回复质量反而比硬塞译文要好。2.3 旧对话折叠成摘要摘要压缩策略折叠策略是这套体系里最有意思的部分。它先把一段连续对话切成小块逐块生成要点摘要再把多条摘要合并成一条更精炼的梗概层层上卷。每一步摘要生成都会在消息链上挂一个“这句其实是从哪几条原文提炼出来的”指针方便需要细节时反向追溯。这个设计暗合一个工程经验摘要不能只做一次。单次把两小时对话压成三句话丢失的信息量是灾难性的。分块压缩、逐级上卷每层丢掉的都是相对次要的过程描述关键的决策依据和明确的用户指令会在更上层保留下来。我自己的一个模拟项目里复刻过这套折叠逻辑把同样的分级压缩方案套在一个文档问答Agent上。结果有两个一是token成本下降约四成二是用户明确说过的偏好比如“以后回复尽量用表格”在对话进行到第两百轮时依然能准确执行。这在原来全量拼接的方案下基本做不到。3. 第二层项目级记忆——以文件为载体的持久化3.1 CLAUDE.md项目记忆的主入口项目级记忆的落地场景就是每个项目根目录下的CLAUDE.md文件。这个文件名你只要用过Claude Code就不陌生但源码层面的加载逻辑远比“读一个文件塞进提示词”复杂。我读到的实现大致是这样会话启动时运行时模块会从当前工作目录开始逐级向上查找CLAUDE.md找到后读取内容并解析其中的指令块、约束块、任务说明块。解析结果不是一股脑塞给模型而是先做分类哪些属于“全局风格指令”哪些属于“具体功能约束”哪些是“流程类要求”然后分别挂载到系统提示词的不同位置。这里有个很实用的设计CLAUDE.md支持多文件合并。项目根目录放通用规范子目录里可以放针对该模块的专用说明。加载时运行时模块会按目录层级从下往上回溯把各级文件合并成一份“项目记忆包”。模块级说明只在该目录下的会话中生效避免无关规则污染其他模块的上下文。3.2 从目录读取到规则合并的完整流程规则合并的具体流程可以简化成下面几步运行时模块定位当前工作目录确认项目根路径。列出从项目根到当前目录之间所有包含CLAUDE.md的层级。按“先根后叶”的顺序逐级读取浅层文件作为基础规则深层文件作为覆盖规则。合并出的文本经过预处理剔除无效空行、标注指令来源文件路径、计算其token占用。将合并结果注入系统提示词的“项目知识区”。这个过程每次新开会话都会重新执行。很多人改完CLAUDE.md发现不起作用十有八九是没重启会话或者工作目录定位错了、加载的根本是另一个层级的文件。这个坑我在后面第七章还会专门展开讲。还有个容易忽略的细节CLAUDE.md里用注释语法标记的段落会被单独解析出来当作“只给开发者看、不给模型看”的元信息。这个机制非常实用——你可以在文件里给未来的自己写维护说明模型不会把这些内容读进上下文去消耗token。3.3 记忆文件的更新时机与版本管理项目记忆是写进git的文件天然带着版本管理的能力。这一点我觉得是整个设计里最聪明的决策——它没有造一个私有的记忆数据库而是直接复用代码仓库现有的协作和版本控制工具。在实际操作中你会发现CLAUDE.md的更新流程非常有“代码味道”新增需求时加一段指令重构完成后删掉过时约束重大设计变更时改写技术背景说明。一切变更都有commit记录出了问题可以回滚。这比把记忆存在某个数据库表里、变更不可追溯的方案高出一个段位。源码层面上运行时模块对这份文件的态度是“只读重用”。它负责加载和解析但不会自动改写文件内容——除非你显式要求执行记忆更新操作。这里的取舍很明确自动写入会造成文件内容漂移用户不可控只读加载则保证了规则的稳定性和可审计性。我把这理解为Claude Code的一个设计原则记忆的写入权必须掌握在用户手里模型只能利用记忆不能偷偷修改记忆。4. 第三层跨项目的全局记忆与优先级仲裁4.1 用户级全局记忆的加载场景全局记忆存的是“你这个人”的偏好跟具体项目无关。它常驻在用户主目录下和我前面提到的项目级记忆互不干扰。从源码层面的加载逻辑看会话启动时最先加载的就是全局记忆。它的解析方式和项目级CLAUDE.md类似但注入位置不一样——项目规则注入在“项目知识区”全局偏好则直接参与系统提示词的“人格与风格区”构建。这带来的效果就是你在任何项目里写代码模型回复代码注释的风格、使用中文还是英文、偏向详细解释还是简洁结论这些偏好都会稳定生效。有意思的是全局记忆也可以共享团队规则。源码里支持把用户主目录下某份规则文件当作“团队共识”来加载所有成员同步同一份基线规范。这个场景特别适合一个团队接入同一套Agent工作流时使用——个人偏好放本地团队规范放共享目录互不冲突。4.2 多来源指令的优先级规则多层记忆同时存在最棘手的问题就是冲突全局说“回复尽量简洁”项目说明书里写“必须逐步展开解释设计决策”到底听谁的源码里的仲裁逻辑规则非常朴素越具体的优先级越高。按优先级从高到低排序当前对话轮次内用户明确说的最新指令高于项目记忆里的约束项目记忆里的内容高于全局记忆里的通用偏好全局记忆中的明确偏好高于模型默认行为。有一个反直觉的细节是当前对话轮用户的“最新指令”优先级最高会覆盖项目规则。这意味着你在对话里说一句“这次例外不要用表格”模型会遵从这个即时指令而不是项目规则。这个设计合理之处在于它尊重了“人的即时意图高于预先设置”的原则。但是在多轮长对话中临时指令如果没有被记忆系统捕获下一轮可能就失效了。所以我给了一个建议长期有效的特殊偏好最终还是写进CLAUDE.md比较稳妥。4.3 项目隔离为什么记忆不能随便串门全局记忆负责跨项目共享项目记忆负责单项目沉淀但两者之间有条看不见的隔离线。我在阅读源码时发现项目记忆被绑定到“工作目录”上一旦目录切换加载出来的项目规则就完全不同。这个绑定关系在实现上非常简单就是拿当前工作目录的路径作为查找键。但这带来一个现象同时也容易埋下一个坑。好处是你在A项目里积累的模块说明不会污染B项目的上下文B项目的Agent不会莫名其妙知道A项目的内部接口规范。这对多项目并行开发至关重要。坏处是如果你习惯在一个目录里同时操作两个项目比如某个常见的聚合仓库模式记忆系统可能分不清你当前到底在跟哪个项目对话加载出的规则集就可能是两份的并集。这是多项目使用者最常遇见的鬼打墙问题。后面第七章节会有更细致的排查方法。5. 读写路径拆解一次请求如何携带三层记忆5.1 写路径记忆从哪来、写到哪去记忆不是凭空产生的写路径决定了一套系统能积累什么。Claude Code的写路径主要有三条一是用户显式写入。你编辑CLAUDE.md、修改全局偏好文件这是最高质量的记忆来源语义清晰、目的明确系统只需要负责读取。二是运行时自动记录。每次工具调用的入参和返回结果都会被追加到当前会话的消息链中。这部分不会自动落盘到项目记忆文件但会保留在会话存档里是后续摘要压缩的原料。三是半自动的操作确认。“要不要把这条经验写进项目记忆”这类确认操作源码中实际上是触发一个文件写入动作把对话中提炼出的一段文本追加到CLAUDE.md。这个流程巧妙的点在于它把“模型理解用户意图”和“更新持久记忆”这两件事分开了——先由模型给出建议再由人来确认。我自己用下来最大的体会是写路径必须可控。自动写入很爽但风险也大模型可能把一次性的临时指令当成长期规则写盘导致后续所有会话都被带偏。5.2 读路径请求到来前的记忆装配流程读路径是Claude Code每次请求开始前必经的流水线。第一步启动时加载系统级模板包括默认的行为规范、能力声明、模型使用指南。第二步往同一个上下文池子里注入全局记忆让用户的通用偏好成为最底层的人格底色。第三步根据当前工作目录定位项目根加载项目级规则文件按层级合并后注入。第四步从会话存档里恢复最近的续接信息——如果你在恢复一个之前的会话这里会把当时的消息链尾部重新装配进来。第五步新用户消息进入系统把所有内容拼装成最终提示词。完整流程走下来你会发现三层记忆装配是有严格顺序的但最终都落到同一个上下文池子里。这个“先全局、再项目、再会话”的顺序保证了每一个更具体的层能覆盖掉更通用层的默认行为。5.3 冲突与淘汰当记忆互相矛盾时怎么办记忆矛盾是实际操作中躲不开的问题。比如项目规则里写着“禁止使用某个依赖库”但全局偏好里没有这条限制而对话历史里你早就让模型绕过这个限制用了别的方案。源码里对冲突的处理分两部分加载期的仲裁和运行期的覆盖。加载期仲裁会按前面说过的优先级规则处理静态冲突——以项目规则覆盖全局偏好。运行期覆盖更灵活——对话中出现了一条最新的、与既有规则矛盾的指令时模型默认执行最新指令并且不会反向修改已加载的规则文件。这种做法相当务实它把“规则冲突”从预防转向了“实时响应”。不会因为一条新指令就回写磁盘也不会在冲突时直接拒绝执行。代价是可能在长对话中产生“这一次性指令漂移”的问题。我的解决办法是重要的一次性指令立刻手动补写进项目记忆把“临时豁免”转成“正式规则”。6. 源码阅读地图从入口到核心实现的调用链6.1 三个关键入口文件如果你要自己动手读源码建议从这三条线入手。以我手上这份源码版本为例路径在不同版本间会略有差异但模块职责划分是一致的第一条线是CLI启动入口。它负责解析启动参数、初始化运行时环境、确认工作目录是整个记忆加载流程的起点。第二条线是会话管理器。它内部维护消息链、上下文组装、记忆装配。读这里重点看一个方法如何把全局规则、项目规则、已有会话历史合并成最终提示词。第三条线是存档服务。它负责会话状态的持久化和恢复解决“断开之后还能不能续上”的问题。我建议阅读顺序先看启动入口摸清目录结构再跳进会话管理器看记忆装配流程最后切到存档服务看落盘格式。三块看完整个记忆体系的骨架就立起来了。6.2 核心数据结构与字段设计源码里记忆相关的数据结构最有研究价值的是一张“会话快照表”。它记录了会话当前的消息状态、已经折叠的摘要、引用的项目规则文件路径、当时注入的全局记忆版本。会话快照的核心字段逻辑示意 session_id: string project_root: string messages_snapshot: [] summaries: [] rules_version: int persisted_at: timestamp另一个值得关注的结构是“规则索引表”。全局规则、项目规则、当前会话瞬时指令分别用不同标识记录并保存各自的来源路径和优先级序号。在实际读取时规则索引表是决定各个记忆来源如何排序的关键。6.3 扩展记忆来源接口预留与自定义接入源码中专门留了记忆扩展接口。第三方可以实现一个记忆提供方再通过配置切换加载来源。这个设计非常有利于团队内部做知识库整合——比如把公司内部的代码规范库作为项目记忆的一部分加载进来。不过我对这个扩展点的建议是谨慎。记忆加载是每次会话启动必经的路任何外部依赖的引入都会增加启动耗时和故障面。我见过一个团队把项目记忆改造成从内部的在线文档平台拉取结果每次启动会话都要等待网络请求体验反而更差。记忆扩展适合做“低频更新、高价值内容”不适合接高频变动的在线服务。7. 实操问题速查记忆不生效、上下文超限、跨项目串扰7.1 修改了规则文件却不生效这个是我看到最多人踩的坑也是我自己最开始遇到的问题。改完CLAUDE.md满怀期待地继续对话结果模型还在沿用旧规则。排查思路按顺序走第一步确认当前工作目录是不是项目根目录。如果在一个子目录里开会话加载的可能是子目录级规则而不是根目录那份。第二步确认会话有没有重启。运行时装配记忆发生在会话启动阶段正在进行的会话不会重新加载规则文件。第三步确认是否有另一份同名文件在“上游”被加载了。先全局后项目的加载顺序里高优先级文件会覆盖低优先级文件的同名规则。7.2 对话越长回复越差怎么判断是不是上下文超限如果你发现对话进行到很深的位置时模型开始重复问一些前面已经给过答案的问题基础决策突然记不起来大概率是运行时记忆进入了折叠阶段早期细节被摘要替换了。判断方法很直接查看单次请求的token花费和上下文占用。如果上下文占用长期超过预算阈值说明折叠机制在频繁触发摘要丢失的信息量在增加。这时候有两种调整路径一是把关键约束固化进CLAUDE.md让项目规则层帮运行时托底二是手动开启一个“续接模式”直接从当前会话档案恢复上下文而不是重新开一个空会话。7.3 多项目并行时记忆串扰两边项目同时开工窗口切来切去结果这边项目的Agent忽然说出那边项目的代码结构这种串扰现象多数时候不是模型出幻觉了而是记忆加载错了项目上下文。排查方法先看会话启动时锁定的项目根路径是不是预期的那个。再检查是不是在两个项目被嵌套在同一目录层级下触发了标记识别失败的问题。根治办法是让每个项目拥有独立的项目根目录并且保持各自的CLAUDE.md不跨项目引用。7.4 查看记忆内容的两个实用入口排查记忆问题最直接的办法就是去看系统实际注入了什么。这里分享两个我常用的入口第一个入口是会话的调试日志。启动会话时打开调试开关运行时会把最终拼装好的提示词和记忆装配顺序打出来一眼就能看出全局规则、项目规则、会话历史各自占了多大块、排了什么顺序。第二个入口是直接查看存档目录下的会话快照文件。快照里记录了当前会话折叠了哪些摘要、引用了哪个版本的规则文件能帮你确认到底是加载问题还是折叠问题。这两个入口配合绝大多数记忆相关的问题都能在五分钟内定位。我个人读完这套源码最大的感受是Claude Code的记忆体系并没有发明什么玄乎的新概念它只是把“分层缓存”“冷热分离”“写读分离”这些计算机系统里最古老的思想老老实实地用在了Agent的知识持久化上。真正拉开差距的是对各种边角情况的处理——摘要怎么折叠才不丢关键信息、规则冲突时谁说了算、临时指令和长期规则怎么共存。这些细节在源码里落得很细每一处都是实际使用中会真正遇到的痛点。最后分享一个自己的小习惯我会在每份CLAUDE.md的顶部维护一个三行的“记忆索引”写清楚这份文件里有哪些分区、每段规则大概在哪个位置、最近一次主要更新是什么时间。看起来挺土但它带来的收益很大——模型加载规则时能更快定位关键段落我自己回溯记忆变更时也不用从头读整份文件。小成本长期回报值得一试。