记忆系统
Claude Code 如何跨会话和跨队友记住您是谁、您关心什么以及您的团队知道什么。
Claude Code 提供三个不同的内存子系统,每个子系统解决不同的持久性范围。 它们共享通用的文件格式,但在生命周期、受众和同步行为方面有所不同。
自动记忆
关于您的持久事实——用户资料、反馈、项目背景、外部参考。 在所有未来的会话中仍然存在。
会话内存
随着上下文的增长,会议笔记会在后台更新。 增强上下文压缩功能,以便工作在上下文窗口之后继续存在。
团队记忆
共享内存同步到服务器 API,范围为 GitHub 存储库。 阅读相同存储库的每个组织成员都会看到相同的团队事实。
background agent"| AL UA -->|"extractSessionMemory
post-sampling hook"| SL UA -->|"write to team/
+ watcher push"| TL style AL fill:#22201d,stroke:#7d9ab8,color:#b8b0a4 style SL fill:#22201d,stroke:#6e9468,color:#b8b0a4 style TL fill:#22201d,stroke:#c47a50,color:#b8b0a4
自动内存是主要的持久存储。 它住在 ~/.claude/projects/<sanitized-git-root>/memory/,默认启用,并使用两级结构:一个名为 MEMORY.md 和单独的主题文件。
MEMORY.md — 索引
该索引被加载到每个对话的系统提示中。 它的上限为 200 行/25,000 字节。 超出该范围的行会被默默地截断并发出警告。 索引永远不是内容所在的地方——它是一个指针列表:
# MEMORY.md (index file — no frontmatter)
- [User Role](user_role.md) — Senior engineer, Go expert, new to React frontend
- [Feedback — No mock DB](feedback_no_mock_db.md) — Always hit real DB in tests
- [Auth Rewrite Context](project_auth_rewrite.md) — Compliance-driven, not tech debt
- [Linear Project](reference_linear.md) — Pipeline bugs tracked in "INGEST"
主题文件——记忆本身
每个内存都存在于自己的 Markdown 文件中,其中 YAML frontmatter 声明了四个必填字段:
---
name: Feedback — No Mock Database
description: Integration tests must hit a real database, never mocks
type: feedback
---
Don't mock the database in tests.
**Why:** We got burned last quarter when mocked tests passed but the prod
migration failed. Mock/prod divergence masked a broken migration.
**How to apply:** Any test touching the data layer must use a real DB
instance. This is a project-wide testing policy, not a personal preference.
description 字段不是装饰性的。 这是选择器模型在决定加载哪些文件以实现相关性时看到的文本。 将其编写为与正确的用户查询相匹配的精确单行语句。
内存类型——封闭的分类法
该代码强制执行四种类型。 类型在解析时进行验证 - 未知值会优雅地降级(文件仍然加载,但类型字段已 undefined).
什么不该保存
来源明确排除了整个类别 - 即使用户询问:
// From memoryTypes.ts — WHAT_NOT_TO_SAVE_SECTION
// Code patterns, conventions, architecture, file paths → read the project
// Git history, recent changes, who-changed-what → `git log` is authoritative
// Debugging solutions or fix recipes → fix is in the code, commit msg has context
// Anything already in CLAUDE.md files
// Ephemeral task details: in-progress work, current conversation context
//
// "These exclusions apply EVEN when the user explicitly asks you to save."
// If they ask to save a PR list → ask what was *surprising* about it.
深入探讨——路径解析和安全性
内存路径通过分层优先级链来解析:
CLAUDE_COWORK_MEMORY_PATH_OVERRIDEenv var — 由 Cowork/SDK 用于重定向到空间范围的安装autoMemoryDirectoryinsettings.json— 支持~/扩展,但仅从 可信来源 (策略/本地/用户设置)。 项目设置(.claude/settings.json致力于回购)被故意排除,以防止恶意回购设置autoMemoryDirectory: "~/.ssh"- Default:
<memoryBase>/projects/<sanitized-git-root>/memory/
同一 git repo 的工作树共享一个内存目录,因为路径解析使用 findCanonicalGitRoot() — 主存储库的根,而不是工作树路径。
export const getAutoMemPath = memoize((): string => {
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
if (override) return override
const projectsDir = join(getMemoryBaseDir(), 'projects')
return join(projectsDir, sanitizePath(getAutoMemBase()), 'memory') + sep
}, () => getProjectRoot())
记忆不会在谈话过程中积累——而是被提取出来 after 每个完整的查询循环。 两个不同的代理处理这个问题:
与主代理人相互排斥
主代理在其系统提示中始终有完整的保存指令。 当它自己写入内存文件时,提取代理会完全跳过该范围 - 它通过以下方式检测到这一点: hasMemoryWritesSince(),它扫描助理消息 Write/Edit 针对内存路径的工具调用:
function hasMemoryWritesSince(
messages: Message[],
sinceUuid: string | undefined,
): boolean {
for (const message of messages) {
// ... scan assistant message content blocks
const filePath = getWrittenFilePath(block)
if (filePath !== undefined && isAutoMemPath(filePath)) {
return true
}
}
return false
}
相关性召回——选择器模型
当新消息到达会话中途时,克劳德不会加载所有内存文件。 相反,轻量级 Sonnet 调用充当选择器 - 它读取 frontmatter 清单并选择 5 相关文件:
// findRelevantMemories.ts — SELECT_MEMORIES_SYSTEM_PROMPT (excerpt)
"Return a list of filenames for the memories that will clearly be useful
to Claude Code as it processes the user's query (up to 5). Only include
memories that you are certain will be helpful based on their name and
description. Be selective and discerning."
// If the model is actively using a tool, its reference docs are skipped
// (the conversation already contains working usage — adding docs is noise)
// BUT warnings, gotchas, and known issues ARE still selected.
[type] filename (ISO-timestamp): description。 这就是为什么描述质量如此重要——它是选择器拥有的唯一信号。
深入探讨——陈旧性检测和漂移警告
每个内存文件都带有一个 mtime。 当相关记忆出现在模型中时,就会计算新鲜度注释:
export function memoryFreshnessText(mtimeMs: number): string {
const d = memoryAgeDays(mtimeMs)
if (d <= 1) return ''
return (
`This memory is ${d} days old. ` +
'Memories are point-in-time observations, not live state — ' +
'claims about code behavior or file:line citations may be outdated. ' +
'Verify against current code before asserting as fact.'
)
}
该模型还在系统提示符下的标题为“ “凭记忆推荐之前” — 节标题故意避免抽象名称,如“信任你记得的内容”,因为评估数据显示抽象标题会导致指令被忽略(A/B 测试中的 0/3 与 3/3 合规性)。
会话内存解决了与自动内存不同的问题:它是短暂的会话内状态,可以使长时间对话在上下文窗口之后保持一致。 它与自动压缩集成。
模板
每个会话内存文件都遵循固定的部分结构。 自定义模板可以放置在 ~/.claude/session-memory/config/template.md。 默认部分:
# Session Title ← distinctive 5-10 word title, info-dense
# Current State ← active work, pending tasks, immediate next steps
# Task specification ← what the user asked to build + design decisions
# Files and Functions ← important files + why they're relevant
# Workflow ← bash commands, order, how to read output
# Errors & Corrections ← errors encountered + what failed + what to avoid
# Codebase and System Documentation
# Learnings ← what worked, what didn't
# Key results ← exact outputs the user requested (tables, answers)
# Worklog ← terse step-by-step of what was attempted
提取触发器
会话内存提取从后采样挂钩触发。 它受到两个必须同时满足的独立阈值的限制:
// sessionMemoryUtils.ts — DEFAULT_SESSION_MEMORY_CONFIG
{
minimumMessageTokensToInit: 10_000, // init threshold
minimumTokensBetweenUpdate: 5_000, // growth since last extraction
toolCallsBetweenUpdates: 3, // AND tool call count
}
// OR: if no tool calls in the last turn AND token threshold is met
// → extract at natural conversation breaks even without tool activity
autoCompactEnabled 是真的。 会话注释文件被注入到上下文窗口边界的压缩消息中,为压缩后对话提供有关正在处理的内容的完整上下文。
深入探讨——代币预算和部分大小执行
会话内存文件的上限为 总计 12,000 个代币,每个部分仅限于 2,000 个代币。 当某个部分超出限制时,提取代理会收到明确的警告:
// If over budget, the update prompt includes:
"CRITICAL: The session memory file is currently ~N tokens, which exceeds
the maximum of 12000 tokens. You MUST condense the file to fit within this
budget. Aggressively shorten oversized sections by removing less important
details, merging related items, and summarizing older entries. Prioritize
keeping 'Current State' and 'Errors & Corrections' accurate and detailed."
对于压缩插入,在音符进入压缩消息之前应用硬截断作为安全阀 - 它在行边界处剪切并附加 [... section truncated for length ...].
自定义更新提示可以放置在 ~/.claude/session-memory/config/prompt.md using {{currentNotes}} and {{notesPath}} 作为替代变量。
团队内存是一个服务器同步子目录,位于 …/memory/team/。 它被门控后面 TEAMMEM 构建标志并需要 OAuth。 团队目录中的每个文件都映射到 Anthropic 服务器上平面键值存储中的键,其作用域为 GitHub 存储库。
同步语义
// From index.ts — the core sync contract:
// GET /api/claude_code/team_memory?repo={owner/repo}
// PUT /api/claude_code/team_memory?repo={owner/repo}
//
// Sync rules:
// - Pull: server wins per-key (local files overwritten)
// - Push: delta upload — only keys whose sha256 hash differs from
// cached serverChecksums are uploaded
// - File deletions do NOT propagate. Deleting a local file won't
// remove it from the server; the next pull restores it locally.
// - PUT body is batched at 200KB max; larger sets split into
// sequential PUTs (server upsert-merge semantics make this safe)
文件观察者
会话级文件观察器监视团队内存目录。 当克劳德写入团队记忆文件时, 2 秒去抖推送 自动触发。 观察者使用原生 fs.watch({ recursive: true }) 而不是 chokidar 以避免保存数百个文件描述符。
秘密扫描——客户端守卫
在进行任何推送之前,都会扫描所有内容以查找源自 gitleaks 规则集的 35 多个秘密模式。 检测会阻止推送——秘密永远不会到达服务器:
// secretScanner.ts — sample rules (high-confidence patterns only)
{ id: 'anthropic-api-key', source: `\\b(sk-ant-api03-[a-zA-Z0-9_\\-]{93}AA)...` },
{ id: 'github-pat', source: 'ghp_[0-9a-zA-Z]{36}' },
{ id: 'aws-access-token', source: '\\b((?:A3T[A-Z0-9]|AKIA|ASIA)...)' },
{ id: 'stripe-access-token', source: '\\b((?:sk|rk)_(?:test|live|prod)_...)' },
// 35 rules total: cloud providers, AI APIs, VCS tokens, payment, crypto
path.resolve() eliminates .. 段, (2) realpath() 在最深的现有祖先上捕获符号链接转义。 即使是团队目录中的悬空符号链接也会在任何写入之前被检测到并拒绝。
深入探讨——私人范围路由与团队范围路由
当团队内存处于活动状态时,系统提示符会描述两个目录和类型分类增益 <scope> 指导每种类型去向的标签:
- user - 始终是私有的(个人角色/偏好永远不应该被共享)
- feedback — 默认为私有; 团队仅适用于项目范围的约定(测试策略、构建不变量——而不是个人风格)
- project - 对团队的强烈偏见(大多数项目背景是共享知识)
- reference — 通常是团队(外部系统指针适用于每个人)
萃取剂还受到团队特定的禁令: “您必须避免在共享团队内存中保存敏感数据。例如,切勿保存 API 密钥或用户凭据。” 这对于秘密扫描器来说是多余的,但创建了一个独立于模式匹配层的行为防御层。
冲突解决使用 ETag 版本控制和 ?view=hashes 探测端点——客户端可以刷新每个密钥的校验和,而无需下载完整的条目主体,从而保持冲突解决的成本。
当作为长期助手会话运行时(功能标志 KAIROS),Claude 从两步先写后索引模式切换为 仅附加每日日志:
// buildAssistantDailyLogPrompt() — different memory behavior
// Writes to: <autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md
// Append-only, timestamped bullets
// MEMORY.md is read-only (maintained by a separate nightly /dream skill)
// What to log:
// - User corrections and preferences
// - Facts about the user, role, or goals
// - Project context not derivable from code
// - Pointers to external systems
// - Anything explicitly asked to remember
//
// The nightly /dream skill distills logs → topic files + MEMORY.md
这在架构上很重要:KAIROS 模式不与 TEAMMEM 组合。 附加到个人日志与双方读写的共享索引从根本上不兼容。 该代码将它们相互排斥。
记忆系统是深门控的。 了解终止开关对于企业部署和测试非常重要:
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
CLAUDE_CODE_SIMPLE=1
autoMemoryEnabled: false
localSettings or userSettings in settings.json。 支持项目级选择退出。 为了安全起见,在projectSettings(致力于回购)中不可用。tengu_passport_quail
要点
- 记忆是一个 三层系统:自动(持久的用户事实)、会话(用于压缩的会话中注释)和团队(服务器同步的每个存储库知识)。
- The
MEMORY.md索引总是在上下文中; 主题文件已加载 通过 Sonnet 选择器模型按需 只读取 frontmatter 描述 - 编写与用户查询匹配的描述。 - 自动记忆使用 封闭式四类分类法:用户、反馈、项目、参考。 类型驱动范围路由(私人与团队)和模型的行为指令。
- 萃取剂运行 在每个查询循环之后,从不在期间。 当主代理自己写入内存时,提取器会检测到并跳过,以防止重复。
- 反馈记忆应该捕获修正和确认。 仅保存修正会导致模型随着时间的推移偏离经过验证的方法。
- 团队内存使用 使用 SHA-256 校验和进行增量推送。 文件删除不会传播——从客户端的角度来看,服务器只是追加的。
- A 35规则秘密扫描仪 在任何团队内存推送之前运行客户端。 无论模型写入什么,秘密都永远不会到达服务器。
- 内存记录有 时间点快照。 在从内存推荐之前,指示模型根据当前代码验证文件路径和函数名称。
检查你的理解情况
~/.claude/projects/myapp/memory/feedback_testing.md。 它有什么作用?processAuthToken() 你三个月前重新命名的。 克劳德回忆起了这段记忆,并打算建议使用它。 它首先应该做什么?