随笔

Claude Code 深度源码解读 06:Memory 是受控上下文,不是玄学记忆

区分 memory prompt、CLAUDE.md、relevant memory、nested memory 与 session memory 的入口和边界。

2026-04-13 Claude Code源码解读Agent Harness技术深读

简化版入口:Memory 连续性

机制图
机制图

我的设问与回应

设问 1:Memory 是不是模型真的记住了?

回应答案: 不是。源码层的 Memory 是外部材料按条件进入当前上下文,包括 memory prompt、CLAUDE.md、relevant memory、nested memory、session memory。

证据与证明路径: 证据来自第 6 期对 loadMemoryPrompt、userContext、relevant memory prefetch、nestedMemoryAttachmentTriggers、session memory forked agent 的拆解。

还值得继续学习或反思: 继续学习 memory 的 source、scope、freshness、visibility 怎样被记录。

设问 2:Memory 为什么可能变成风险?

回应答案: 因为 stale memory、noisy memory、scope leak、compact distortion 都可能污染当前任务。Memory 提供连续性,也会带来长期上下文污染。

证据与证明路径: 证据是第 6 期风险边界部分对上下文污染、写入权限、记忆作用域的讨论。

还值得继续学习或反思: Memory 不是越多越好,而是越可治理越好。

源码调研主体

本组解读基于冻结源码快照进行教育、防御和架构研究。

本期主题:Harness 如何把长期记忆、项目指令、相关记忆召回、嵌套目录规则和 session memory 接入当前任务,同时避免把“记忆”误写成每轮动态事实。

1. 研究问题

本期围绕一个问题展开:

Claude Code Harness 如何让跨回合经验进入当前任务,又不让旧信息、重复信息或场景化记忆污染主循环?

Memory 在 Harness 中不是单一数据库,也不是每轮都强制召回完整历史。源码中至少有五条不同路径:

  • system prompt 中的 memory mechanics:告诉模型如何使用持久化文件记忆。
  • CLAUDE.md / 规则文件:作为用户/项目上下文进入模型窗口。
  • relevant memories:按当前 prompt 异步检索少量相关记忆。
  • nested memory:由文件路径触发的目录级 CLAUDE.md / rules 注入。
  • session memory:长会话中由 post-sampling hook 抽取当前会话笔记,并可参与 compact。

本期重点是区分这些机制的触发条件和边界。不能把某个 feature gate、agent memory 或 session memory 写成所有会话必经路径。

2. 对应总览节点

对应第 1 期总览图中的节点:

  • Memory
  • Context Preparation
  • Query Loop
  • Attachments
  • Compaction / Recovery
  • Transcript / Session State

这部分机制参考前文「Memory 连续性 机制图」。

3. 核心流程图

路线图中的代表链路是:

memory prompt / relevant memory / nested memory / session memory

源码中这几条链路并行存在,颜色标注如下:蓝色是主线前置上下文,绿色是按需召回,橙色是异步/恢复支撑,红色是权限隔离。

这部分机制参考前文「Memory 连续性 机制图」。

4. 关键源码入口

职责文件
memory prompt 注入点src/QueryEngine.ts
memory prompt 构造、auto/team/KAIROS 分支src/memdir/memdir.ts
memory 类型说明src/memdir/memoryTypes.ts
relevant memory 检索src/memdir/findRelevantMemories.ts
relevant memory / nested memory 附件src/utils/attachments.ts
agent memory 目录src/tools/AgentTool/agentMemory.ts
agent memory snapshotsrc/tools/AgentTool/agentMemorySnapshot.ts
session memory 抽取src/services/SessionMemory/sessionMemory.ts
session memory promptsrc/services/SessionMemory/prompts.ts
session memory compactsrc/services/compact/sessionMemoryCompact.ts
auto compact 中 session memory 优先路径src/services/compact/autoCompact.ts
/memory 命令src/commands/memory/memory.tsx
remember skillsrc/skills/bundled/remember.ts

4.1 主线与场景特殊处理分类

机制/模块分类为什么这样归类
loadMemoryPrompt()场景增强 + 主线前缀支撑只有 auto memory 开启或 SDK 自定义 prompt + memory path override 时注入;注入的是使用说明,不是完整动态记忆内容
getUserContext() 中 CLAUDE.md主线机制 + 场景增强Context 构建会读取用户/项目上下文,但具体是否有 CLAUDE.md、local rules、managed rules 取决于文件存在和设置
buildMemoryLines() / buildCombinedMemoryPrompt()场景增强auto/team memory 分支由 feature、settings 和路径状态决定
KAIROS daily-log prompt场景增强assistant 长会话模式下改变“新记忆写到哪里”,不是普通 REPL 的默认路径
startRelevantMemoryPrefetch()场景增强 + 可观测/恢复支撑只在 auto memory 与相关 feature gate 开启、prompt 有足够文本且未超过会话 memory 字节阈值时启动;不会阻塞当前 turn
filterDuplicateMemoryAttachments()主线支撑对已经通过工具读取或此前 surfaced 的 memory 去重,避免重复注入
nestedMemoryAttachmentTriggers场景增强由文件访问路径触发目录级规则加载;没有文件触发时为空
loadedNestedMemoryPaths可观测/恢复支撑QueryEngine 持有跨 turn set,避免同一路径重复注入
agent memory场景增强只对定义了 memory 的 agent 生效;相关记忆召回在 @agent mention 时会改查 agent memory dir
session memory post-sampling hook可观测/恢复支撑 + 场景增强只在主 REPL、auto compact 开启、gate 通过且阈值满足时运行;subagent / teammate / remote 不走这条
createMemoryFileCanUseTool()安全/治理横切session memory forked agent 只能 Edit 指定 memory 文件,其他工具请求被 deny
/memory 命令场景增强用户主动编辑 memory 文件的交互命令,不是自动召回主链路
remember skill场景增强只在 auto memory enabled 时启用,用于整理和迁移 memory,不自动修改

5. 机制拆解

5.1 Memory prompt 是“机制说明”,不是完整记忆注入

QueryEngine.submitMessage() 在构造 system prompt 时,只在特定条件下调用 loadMemoryPrompt()。普通自定义 SDK system prompt 下,还要求 hasAutoMemPathOverride() 为真,注释明确这是调用方显式接入 memory directory 的信号。

src/memdir/memdir.tsloadMemoryPrompt() 会先检查 isAutoMemoryEnabled()。关闭时返回 null 并记录 tengu_memdir_disabled。开启后,根据 KAIROS、TEAMMEM、team memory enabled 等条件选择不同 prompt:

  • KAIROS assistant daily-log mode:提示模型把新记忆追加到按日期组织的 log。
  • team memory:构造 auto + team combined prompt。
  • auto only:构造单目录 memory 使用说明。

因此,system prompt 层的 memory 更像“使用持久化文件记忆的协议说明”。它不等于每轮把所有 memory 文件全文塞入上下文。

5.2 CLAUDE.md / rules 属于项目上下文,不等同 auto memory

前几期已经确认 fetchSystemPromptParts() 会获取 userContext。CLAUDE.md 类文件通过用户/项目上下文进入模型窗口。它与 memdir 的 auto memory 是两套来源:

  • CLAUDE.md 更像项目规则、指令和可共享约束。
  • auto memory 更像跨会话持久化的用户偏好、反馈和项目背景。
  • team memory 是 auto memory 下的团队共享扩展。

remember skill 的提示也强调:整理 auto-memory 时应判断是否应该提升到 CLAUDE.md、CLAUDE.local.md 或 team memory。这说明源码把 memory 层级作为治理问题处理,而不是单一桶。

5.3 relevant memory 是异步预取,不阻塞当前 turn

startRelevantMemoryPrefetch()query.ts 进入 query session 时启动。它有明确 gate:

  • auto memory 必须开启。
  • tengu_moth_copse feature gate 必须开启。
  • 必须存在最后一个非 meta user message。
  • prompt 不能只是单词。
  • 已 surfaced memory 总字节不能超过阈值。

函数返回 MemoryPrefetch handle,注释明确 promise “started once per user turn”,在 post-tools collect point 只在 settledAt !== null 时消费;未就绪不会阻塞本轮。query.ts 的消费点会调用 filterDuplicateMemoryAttachments(),并把 survivor 标记到 readFileState

这条链路的稳定性贡献是:相关 memory 可以利用模型 streaming / 工具执行期间的空档预取,但不会因为 memory selector 慢而拖住主循环。

5.4 relevant memory 有召回隔离和体积边界

getRelevantMemoryAttachments() 会先检查 prompt 中是否 @mention agent:

  • 如果有 agent mention,且对应 agent 定义带 memory,只搜索该 agent 的 memory dir。
  • 否则搜索 auto memory dir。

这说明 agent memory 不是全局混入主 agent 的记忆池,而是有隔离条件。检索结果还会被限制:

  • findRelevantMemories() 结果被 readFileStatealreadySurfaced 过滤。
  • 最多 slice 5 个。
  • readMemoriesForSurfacing()MAX_MEMORY_LINESMAX_MEMORY_BYTES 截断读取。
  • 如果截断,会附带提示让模型用 Read 查看完整文件。

因此,相关记忆是“有限候选 + 有界内容 + 可继续读取”的召回,不是无限历史灌入。

5.5 nested memory 由文件路径触发,并跨 turn 去重

ToolUseContext 中有 nestedMemoryAttachmentTriggersloadedNestedMemoryPathsQueryEngine 持有 loadedNestedMemoryPaths = new Set<string>(),并在每次构造输入/工具上下文时传下去。

getNestedMemoryAttachments() 首先检查 trigger set,空则直接返回。非空时按 filePath 调用 getNestedMemoryAttachmentsForFile(),最后清空 trigger set。memoryFilesToAttachments() 会:

  • 跳过已经在 loadedNestedMemoryPaths 里的 memory file。
  • 跳过已经在 readFileState 中的路径。
  • 将新 memory file 作为 nested_memory attachment。
  • 把内容写入 readFileState,供后续工具和 memory 去重共享。

这条链路说明 nested memory 是由具体文件访问触发的目录上下文补充,不是每轮全目录扫描。

5.6 session memory 是 post-sampling 后台抽取,不是用户 turn 的前置主线

initSessionMemory() 会先检查 remote mode,remote 下直接返回;再检查 auto compact 是否开启,不开启则不注册 hook。注册的是 post-sampling hook extractSessionMemory()

extractSessionMemory() 内部再次收窄条件:

  • querySource 必须是 repl_main_thread
  • session memory gate 必须开启。
  • shouldExtractMemory(messages) 必须满足阈值。

真正抽取时,它会创建 isolated setup context,读取/准备 session memory 文件,然后调用 runForkedAgent(),并传入:

  • querySource: 'session_memory'
  • forkLabel: 'session_memory'
  • canUseTool: createMemoryFileCanUseTool(memoryPath)
  • overrides: { readFileState: setupContext.readFileState }

createMemoryFileCanUseTool() 只允许 FILE_EDIT_TOOL_NAMEfile_path === memoryPath,其他全部 deny。这是 session memory 后台写入的关键安全边界。

5.7 session memory 与 compact 有协作,但有递归边界

autoCompact.ts 中有 session memory compaction 优先路径,同时注释明确 session_memorycompact 是 forked agents,需要递归 guard。session memory 的目的不是替代 transcript,而是为长会话 compact 提供更结构化的当前状态、错误修正和待办摘要。

这也解释了为什么 session memory 被放在可观测/恢复支撑分类:它帮助长会话在 compact 后保留关键状态,但它不是每个 prompt 进入模型前的必经输入。

5.8 /memoryremember 是用户主动治理工具

/memory 命令会打开 MemoryFileSelector,允许用户选择并编辑 memory 文件,完成后清理 memory file caches。它是显式用户操作。

remember skill 的 prompt 要求“Review auto-memory entries and propose promotions”,并明确 “Do NOT apply changes”。这说明 memory 治理也区分“建议整理”和“实际写入”,不是后台自动迁移。

6. 对稳定解决问题能力的贡献

6.1 把长期经验变成可控输入

Memory mechanics、CLAUDE.md、relevant memory、nested memory 和 session memory 分别进入不同上下文层,避免所有历史无差别进入模型窗口。

6.2 减少旧信息重复污染

readFileStatealreadySurfacedloadedNestedMemoryPaths 和 compact 后扫描机制共同减少重复注入。已经通过工具读取的 memory 不会再作为 relevant memory 反复 surfaced。

6.3 长会话恢复更稳

session memory 把长会话的当前状态抽取到文件,再与 compact 协作。即使 transcript 被压缩,模型仍可通过 session memory 保留关键连续性。

6.4 agent 记忆有隔离

@agent mention 会让相关记忆搜索转向 agent memory dir;未 mention 时才走 auto memory。这个边界防止 specialized agent 的私有工作记忆默认污染主 agent。

6.5 后台写 memory 有权限收缩

session memory forked agent 只能编辑指定 memory file。这个限制把“让模型维护记忆”从泛化文件写入收缩成单文件编辑任务。

7. 风险、边界与待验证问题

风险/边界影响防御性解读
memory prompt 可能被误读为“记忆已加载”研究者容易把机制说明当成内容注入必须区分 loadMemoryPrompt() 的行为说明与 relevant_memories / CLAUDE.md 的实际内容注入
relevant memory 是异步预取未 settled 时本轮不会消费不能声称每个 turn 都一定有相关记忆;最多说“满足 gate 且预取完成后注入”
session memory 只在主 REPL 且 gate 满足时运行subagent、remote、compact fork 不走普通抽取路径不能把 session memory 写成所有 agent 的跨回合机制
nested memory 由文件触发没有文件路径触发时不会加载目录规则不能把 nested CLAUDE.md 写成 conversation start 全量扫描
memory 文件由模型可编辑可能保存错误、过时或敏感信息源码通过 prompt taxonomy、remember skill、单文件权限和用户命令提供治理,但内容质量仍依赖模型与用户审核

8. 下一期衔接

第 7 期进入“多 Agent / Task 协作”。Memory 为多 Agent 提供两类支撑:一是 agent definition 可以声明自己的 memory;二是 background / forked agent 的进度、通知和 sidechain transcript 会进入任务协作与恢复链路。下一期将分析 AgentTool、fork subagent、background task、teammate / swarm、remote agent 和 SendMessage / pendingMessages 如何把复杂任务拆分、跟踪与汇总。

机制主线收束

站在架构师视角,我会把这组源码机制收束为三个问题:为什么需要它,什么时候必须引入它,以及真正要设计的是什么。

  • why:Memory 解决的是长期经验如何进入当前任务,同时不破坏上下文清洁度。
  • when:任务跨会话、跨目录、跨项目规则,或者需要保留长期操作经验时使用。
  • what:把 memory 按类型、作用域、来源、有效期分层;写入 memory 必须有权限和路径约束;session memory 要可追溯。

Memory 连续性 这组源码最值得带走的,不是某个函数或某个配置项,而是它如何把模型能力放进一组可验证、可拒绝、可恢复、可审计的工程边界里。读源码时,如果只记住名词,会很快散;如果抓住 why、when、what,就能把这组机制迁移到自己的 agent 架构判断中。