跳转到内容

Memory 系统

Claude Code 的 memory 系统为 agent 提供了跨 session 持久保存的文件型知识库。与短暂的对话 context(在 compaction 或 session 结束后会丢失)不同,memory 文件会随时间积累学习内容、偏好和项目知识。

graph TD
    subgraph "Memory Directory (~/.claude/projects/<project>/memory/)"
        E["MEMORY.md<br/>(index file, max 200 lines)"]
        T1["topic-1.md"]
        T2["topic-2.md"]
        L["logs/YYYY/MM/YYYY-MM-DD.md<br/>(daily logs)"]
    end

    subgraph "System Prompt"
        SP[Memory Prompt Section]
    end

    subgraph "Team Memory"
        TM["Team member shared memory"]
    end

    E --> SP
    T1 --> SP
    TM --> SP

memory 目录遵循标准结构:

  • MEMORY.md — 索引/入口文件,加载到每个 system prompt 中
  • 主题文件 — 特定主题的详细 memory,从 MEMORY.md 引用
  • 日志文件 — 按时间顺序排列的活动日志(用于 KAIROS/助手模式)

Memory 路径由 src/memdir/paths.ts 中的 getAutoMemPath() 按优先级链解析:

src/memdir/paths.ts
export const getAutoMemPath = memoize((): string => {
// 1. Env var override (Cowork/SDK)
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
if (override) return override
// 2. Default: ~/.claude/projects/<sanitized-git-root>/memory/
const projectsDir = join(getMemoryBaseDir(), 'projects')
return join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
})

解析优先级:

优先级来源使用场景
1CLAUDE_COWORK_MEMORY_PATH_OVERRIDE 环境变量Cowork 空间级挂载
2settings.json 中的 autoMemoryDirectory用户自定义目录
3~/.claude/projects/<sanitized-path>/memory/默认

基础路径使用 findCanonicalGitRoot(),因此同一仓库的所有 worktree 共享同一个 memory 目录:

function getAutoMemBase(): string {
return findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot()
}

自动 memory 可通过多种机制禁用:

src/memdir/paths.ts
export function isAutoMemoryEnabled(): boolean {
// 1. Env var override (explicit off)
if (isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY)) return false
// 2. Bare mode (--bare / SIMPLE)
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) return false
// 3. CCR without persistent storage
if (isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
!process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) return false
// 4. Settings.json opt-out
const settings = getInitialSettings()
if (settings.autoMemoryEnabled !== undefined) return settings.autoMemoryEnabled
// 5. Default: enabled
return true
}

MEMORY.md 是加载到 system prompt 中的入口文件,具有严格的大小限制:

src/memdir/memdir.ts
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000 // ~125 chars/line at 200 lines

当 MEMORY.md 超出这些限制时,会被截断并附上警告:

export function truncateEntrypointContent(raw: string): EntrypointTruncation {
const contentLines = raw.trim().split('\n')
const wasLineTruncated = contentLines.length > MAX_ENTRYPOINT_LINES
const wasByteTruncated = raw.trim().length > MAX_ENTRYPOINT_BYTES
if (!wasLineTruncated && !wasByteTruncated) {
return { content: raw.trim(), /* ... */ }
}
// Line-truncate first (natural boundary), then byte-truncate
let truncated = wasLineTruncated
? contentLines.slice(0, MAX_ENTRYPOINT_LINES).join('\n')
: raw.trim()
if (truncated.length > MAX_ENTRYPOINT_BYTES) {
const cutAt = truncated.lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)
truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES)
}
return {
content: truncated + `\n\n> WARNING: ${ENTRYPOINT_NAME} is ${reason}.
Only part of it was loaded. Keep index entries to one line under ~200 chars;
move detail into topic files.`,
// ...
}
}

这一设计强制 MEMORY.md 充当索引而非信息堆砌处。每条条目应为约 200 字符以内的单行,将详细内容指向主题文件。

Memory 内容按类型分类,定义于 src/memdir/memoryTypes.ts

// Memory taxonomy (from memoryTypes.ts)
// - User preferences and style
// - Project architecture and conventions
// - Feedback and corrections
// - Reference documentation

memory prompt 包含关于应保存什么、不应保存什么的具体指导:

import {
MEMORY_FRONTMATTER_EXAMPLE,
TRUSTING_RECALL_SECTION,
TYPES_SECTION_INDIVIDUAL,
WHAT_NOT_TO_SAVE_SECTION,
WHEN_TO_ACCESS_SECTION,
} from './memoryTypes.js'

自定义 agent 可以声明自己的 memory 作用域:

// In agent frontmatter or JSON
memory: 'user' | 'project' | 'local'
作用域位置共享范围
user用户级目录跨所有项目
project项目级目录项目内
local仅本地不共享

当 agent 启用 memory 时,其 system prompt 会被动态追加:

src/tools/AgentTool/loadAgentsDir.ts
getSystemPrompt: () => {
if (isAutoMemoryEnabled() && memory) {
return systemPrompt + '\n\n' + loadAgentMemoryPrompt(agentType, memory)
}
return systemPrompt
}

此外,当 memory 启用时,文件 tool(Read、Edit、Write)会自动注入到 agent 的 tool 集合中:

if (isAutoMemoryEnabled() && parsed.memory && tools !== undefined) {
const toolSet = new Set(tools)
for (const tool of [FILE_WRITE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME]) {
if (!toolSet.has(tool)) {
tools = [...tools, tool]
}
}
}

AGENT_MEMORY_SNAPSHOT feature 支持项目级 memory 快照——一种向项目所有用户分发预构建 memory 的方式:

src/tools/AgentTool/agentMemorySnapshot.ts
async function initializeAgentMemorySnapshots(agents: CustomAgentDefinition[]): Promise<void> {
await Promise.all(agents.map(async agent => {
if (agent.memory !== 'user') return
const result = await checkAgentMemorySnapshot(agent.agentType, agent.memory)
switch (result.action) {
case 'initialize':
// Copy snapshot to local if no local memory exists
await initializeFromSnapshot(agent.agentType, agent.memory, result.snapshotTimestamp!)
break
case 'prompt-update':
// Newer snapshot available — flag for user prompt
agent.pendingSnapshotUpdate = { snapshotTimestamp: result.snapshotTimestamp! }
break
}
}))
}

TEAMMEM feature 支持跨团队成员的共享 memory:

// src/memdir/memdir.ts — conditional import
const teamMemPaths = feature('TEAMMEM')
? require('./teamMemPaths.js')
: null

Team memory 路径(src/memdir/teamMemPaths.ts)和 team memory prompt(src/memdir/teamMemPrompts.ts)允许团队成员共享同一个 memory 目录,实现同一 team 中 agent 之间的知识传递。

EXTRACT_MEMORIES feature 运行一个后台 agent 从对话中提取 memory:

src/memdir/paths.ts
export function isExtractModeActive(): boolean {
if (!getFeatureValue_CACHED_MAY_BE_STALE('tengu_passport_quail', false)) return false
return !getIsNonInteractiveSession() ||
getFeatureValue_CACHED_MAY_BE_STALE('tengu_slate_thimble', false)
}

后台提取 agent 扫描对话轮次中的可记忆信息并写入 memory 目录。当主 agent 自行写入 memory 时,后台 agent 会跳过相应范围(通过 hasMemoryWritesSince 去重)。

在 KAIROS/助手 session 中,memory 采用不同形式——日志文件:

src/memdir/paths.ts
export function getAutoMemDailyLogPath(date: Date = new Date()): string {
const yyyy = date.getFullYear().toString()
const mm = (date.getMonth() + 1).toString().padStart(2, '0')
const dd = date.getDate().toString().padStart(2, '0')
return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)
}

agent 不再维护 MEMORY.md 作为实时索引,而是在工作过程中向以日期命名的日志文件追加内容。一个单独的每夜 /dream skill 会将这些日志提炼为主题文件和 MEMORY.md。

Memory 路径经过严格的安全验证:

// src/memdir/paths.ts — validateMemoryPath
function validateMemoryPath(raw: string | undefined, expandTilde: boolean): string | undefined {
// Reject: relative paths, root/near-root, Windows drive-root,
// UNC paths, null bytes
if (
!isAbsolute(normalized) ||
normalized.length < 3 ||
/^[A-Za-z]:$/.test(normalized) ||
normalized.startsWith('\\\\') ||
normalized.startsWith('//') ||
normalized.includes('\0')
) {
return undefined
}
return (normalized + sep).normalize('NFC')
}

isAutoMemPath() 函数被文件系统 permission 系统用于在无需明确用户授权的情况下授予对 memory 文件的写权限——这一豁免使 memory 写入无缝流畅,但需要严格的路径验证以防止滥用。