Skip to content
源码分析手册

auto-memory:基于本地文件系统的自增长项目知识库

对应官方文档:claude-code-docs/docs/memory.md 里的 Auto memory

它解决了什么问题

auto memory 并不是一个“聊天摘要生成器”的黑盒,也不是把整段旧会话塞回 Prompt 的简单缓存。

在源码层面,它是一个落在本地文件系统、按 Canonical Git Root 归档的“持久记忆层”。 Claude 会在会话运行中将值得跨会话保留的发现、决策、环境特征等信息写入特定的 Markdown 文件中,并在后续启动时自动“召回(Recall)”这些内容。

它与 CLAUDE.md 的区别在于:CLAUDE.md 是显式指令(告诉我该怎么做),而 auto memory 是隐式积累(告诉我上次发生了什么)。

代码里的真实逻辑

实现这套机制的核心在于 src/memdir/ 目录下的几个关键逻辑:

  1. 分层启停与路径解析src/memdir/paths.ts 里的 isAutoMemoryEnabled() 将环境变量、--bare 模式、以及 settings.jsonautoMemoryEnabled 串成了一条决策链。最重要的一点是目录定位:它默认定位在 ~/.claude/projects/<sanitized-git-root>/memory/。由于使用了 Git Root,同一个仓库的不同 worktree 或子目录会自然地共享同一份记忆。
  2. 记忆协议注入(Memory Protocol):Claude 之所以会主动写记忆,不是因为后台有秘密线程,而是因为 loadMemoryPrompt() 在 System Prompt 里注入了一套“行为协议”。它告诉 Claude 什么是值得记的(跨会话有用的事实),什么是不该记的(琐碎的单轮细节),以及如何操作 MEMORY.md 索引。
  3. 索引截断与入口加载:在 src/utils/claudemd.ts 中,MEMORY.md 作为 AutoMem 被自动加载。为了保护上下文空间,源码强制执行 truncateEntrypointContent(...),只保留前 200 行或 25KB 以内的内容。这意味着 MEMORY.md 必须是一个精简的“地图”,详细内容应由模型按需读取子文件。
  4. 后台提取器(Post-turn Extractor)src/services/extractMemories/extractMemories.ts 运行在一个 forked subagent 中。当一轮对话结束,且主代理没有直接写 memory 时,这个提取器会异步分析对话,补全缺失的持久记忆。
  5. 相关性召回(Semantic Recall)src/memdir/findRelevantMemories.ts 实现了除索引外的“按需召回”。它会扫描 memory 目录下的其他 Topic Files(.md),提取 Frontmatter 里的描述,再根据当前 Query 挑选出最多 5 个最相关的记忆,以 Attachment 形式带回会话。
  6. 结构化内存目录(memdir Structure):在 src/memdir/memdir.ts 中,记忆不再只是一个扁平的 MEMORY.md 文件,而是一个结构化的目录。MEMORY.md 作为索引被严格截断(200 行/25KB),强迫模型通过 Read 工具按需加载详细的话题文件(Topic Files),有效平衡了上下文消耗与信息召回量。
  7. 团队记忆共享(Team Memory Sharing):核心逻辑位于 src/services/teamMemorySync/。当开启 TEAMMEM 特性时,系统会创建 memory/team/ 目录并通过 watcher.ts 同步成员间的关键决策记录。为了安全,teamMemSecretGuard.ts 在同步前会自动拦截泄露的 API Key 等敏感信息。

实战注意事项

  1. 本地/项目限制:记忆是本地的、按 Git 仓库隔离的。它不是全局云同步的,也不会随账号在不同机器间漂移。
  2. 团队同步前提:Team Memory 需要显式开启且绑定特定组织。若 autoMemoryEnabled 被禁用,团队同步也会失效。
  3. 索引非全文:自动注入上下文的只有 MEMORY.md 索引且有硬截断。不要在索引文件里写长篇大论,那是 Topic Files 该做的事。
  4. 安全围栏:项目级的 .claude/settings.json 不能重定向 autoMemoryDirectory
  5. KAIROS 模式优先:在 Assistant 模式下,每日日志(Daily Log)模式优先,这种模式目前与 Team Sync 的共享写模式不兼容。

接下来看什么

  • 如果你关心 CLAUDE.md 与 Memory 的加载顺序,下一篇该看 Instruction Loading Chain
  • 如果你想了解每日日志如何被蒸馏进长期记忆,看 梦境过程:Daily Log 到 Topic Files 的演化
  • 如果你更关心如何手动管理记忆,去看 /memory 命令实现(memory.tsx)

源码锚点

  • claude-code-opensource/src/memdir/paths.ts:记忆库的路径定位、共享逻辑与安全隔离策略。
📄 src/memdir/paths.ts — 记忆库的路径定位、共享逻辑与安全隔离策略。L30-55 of 279
typescript
export function isAutoMemoryEnabled(): boolean {
  const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY
  if (isEnvTruthy(envVal)) {
    return false
  }
  if (isEnvDefinedFalsy(envVal)) {
    return true
  }
  // --bare / SIMPLE: prompts.ts already drops the memory section from the
  // system prompt via its SIMPLE early-return; this gate stops the other half
  // (extractMemories turn-end fork, autoDream, /remember, /dream, team sync).
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    return false
  }
  if (
    isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
    !process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR
  ) {
    return false
  }
  const settings = getInitialSettings()
  if (settings.autoMemoryEnabled !== undefined) {
    return settings.autoMemoryEnabled
  }
  return true
}
  • claude-code-opensource/src/memdir/memdir.ts:结构化 memdir 的管理、Index 与 Topic Files 的分离逻辑。
📄 src/memdir/memdir.ts — 结构化 memdir 的管理、Index 与 Topic Files 的分离逻辑。L83-89 of 508
typescript
    const cutAt = truncated.lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)
    truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES)
  }

  const reason =
    wasByteTruncated && !wasLineTruncated
      ? `${formatFileSize(byteCount)} (limit: ${formatFileSize(MAX_ENTRYPOINT_BYTES)}) — index entries are too long`
  • claude-code-opensource/src/services/teamMemorySync/watcher.ts:跨设备/跨团队记忆同步的监听与下发机制。
📄 src/services/teamMemorySync/watcher.ts — 跨设备/跨团队记忆同步的监听与下发机制。L23-26 of 388
typescript
  type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  logEvent,
} from '../analytics/index.js'
import {
  • claude-code-opensource/src/services/extractMemories/extractMemories.ts:后台异步提取器的触发与逻辑。
📄 src/services/extractMemories/extractMemories.ts — 后台异步提取器的触发与逻辑。L65-80 of 616
typescript
const teamMemPaths = feature('TEAMMEM')
  ? (require('../../memdir/teamMemPaths.js') as typeof import('../../memdir/teamMemPaths.js'))
  : null
/* eslint-enable @typescript-eslint/no-require-imports */

// ============================================================================
// Helpers
// ============================================================================

/**
 * Returns true if a message is visible to the model (sent in API calls).
 * Excludes progress, system, and attachment messages.
 */
function isModelVisibleMessage(message: Message): boolean {
  return message.type === 'user' || message.type === 'assistant'
}
  • claude-code-opensource/src/memdir/findRelevantMemories.ts:基于 Frontmatter 的 Topic File 相关性召回算法。
📄 src/memdir/findRelevantMemories.ts — 基于 Frontmatter 的 Topic File 相关性召回算法。L10-13 of 142
typescript
  scanMemoryFiles,
} from './memoryScan.js'

export type RelevantMemory = {
  • claude-code-opensource/src/utils/claudemd.tsMEMORY.md 作为核心 Context 附件的装载链。
📄 src/utils/claudemd.ts — `MEMORY.md` 作为核心 Context 附件的装载链。L236-245 of 1480
typescript
  // stripped frontmatter, truncated MEMORY.md) such that it no longer matches
  // the bytes on disk. When set, `rawContent` holds the unmodified disk bytes
  // so callers can cache a `isPartialView` readFileState entry — presence in
  // cache provides dedup + change detection, but Edit/Write still require an
  // explicit Read before proceeding.
  contentDiffersFromDisk?: boolean
  rawContent?: string
}

function pathInOriginalCwd(path: string): boolean {

基于 Claude Code v2.1.88 开源快照的深度分析