Claude Code 源码分析(五):分层记忆体系 —— AI Agent 的知识持久化架构

AI2小时前发布 beixibaobao
1 0 0

本系列文章基于 Claude Code 2.1.88 版本的 TypeScript 源码进行分析。源码版权归 Anthropic 所有,本文仅用于技术研究。

引言

AI Agent 的记忆管理是一个尚未被充分解决的工程问题。单次对话中的上下文会随着压缩而丢失,跨会话的知识无法自动积累,不同 Agent 之间的经验无法共享。Claude Code 实现了一套三层记忆架构——会话内记忆、跨会话记忆、Agent 级记忆——在不同时间尺度上解决知识持久化问题。

涉及的核心源码文件:

  • src/services/SessionMemory/ —— 会话内记忆(3 个文件)
  • src/services/extractMemories/ —— 跨会话记忆提取
  • src/tools/AgentTool/agentMemory.ts —— Agent 级记忆目录
  • src/tools/AgentTool/agentMemorySnapshot.ts —— 记忆快照与同步

一、会话内记忆(Session Memory)

src/services/SessionMemory/ 负责在单次会话内提取和管理记忆。

1.1 提取时机判断

function shouldExtractMemory(messages: Message[]): boolean { ... }
function countToolCallsSince(messages: Message[], lastMessageId: string): number { ... }

系统根据工具调用次数和消息量判断是否需要提取记忆。这种基于活动量的触发机制避免了过于频繁的提取(浪费 API 调用)和过于稀疏的提取(丢失重要信息)。

1.2 并发控制

function markExtractionStarted(): void { ... }
function markExtractionCompleted(): void { ... }
async function waitForSessionMemoryExtraction(): Promise<void> { ... }

记忆提取过程有锁机制,防止并发提取。waitForSessionMemoryExtraction 允许其他操作等待提取完成后再继续,确保后续操作能看到最新的记忆状态。

1.3 记忆模板与 Prompt

async function loadSessionMemoryTemplate(): Promise<string> { ... }
async function loadSessionMemoryPrompt(): Promise<string> { ... }
function getDefaultUpdatePrompt(): string { ... }

记忆提取使用专门的 prompt 模板,指导模型从对话中提取结构化的知识。

1.4 记忆大小分析

function analyzeSectionSizes(content: string): Record<string, number> { ... }
function generateSectionReminders(sizes: Record<string, number>): string[] { ... }

系统会分析记忆各部分的大小,当某个部分过大时生成优化建议。这防止了记忆文件无限膨胀。

1.5 位置追踪

function getLastSummarizedMessageId(): string | undefined { ... }
function setLastSummarizedMessageId(id: string | undefined): void { ... }

系统追踪"上次总结到哪条消息",确保增量提取不会重复处理已总结的内容。当对话被压缩时,这个标记会被重置(因为旧的消息 UUID 不再存在)。


二、跨会话记忆(Extract Memories)

src/services/extractMemories/ 负责从对话中提取可跨会话复用的知识,持久化到 CLAUDE.md 等记忆文件中。

CLAUDE.md 是 Claude Code 的项目级记忆文件,类似于 .editorconfig.eslintrc,但存储的是 AI 助手需要了解的项目知识:编码规范、架构决策、常用命令等。

跨会话记忆的提取发生在会话结束时或用户显式触发时,提取的知识被追加到 CLAUDE.md 中,供后续会话使用。


三、Agent 级记忆

3.1 记忆目录管理

agentMemory.ts 为每个 Agent 类型维护独立的记忆目录:

function sanitizeAgentTypeForPath(agentType: string): string { ... }
function getLocalAgentMemoryDir(dirName: string): string { ... }
function getAgentMemoryDir(agentType: string, scope: AgentMemoryScope): string { ... }
function isAgentMemoryPath(absolutePath: string): boolean { ... }
function getAgentMemoryEntrypoint(agentType: string, scope: AgentMemoryScope): string { ... }

sanitizeAgentTypeForPath 将 Agent 类型名转换为安全的文件系统路径。isAgentMemoryPath 用于判断一个路径是否属于 Agent 记忆目录,这在权限检查中很重要——Agent 对自己的记忆目录有特殊的读写权限。

3.2 记忆快照

agentMemorySnapshot.ts 实现了记忆的快照与同步机制:

function getSnapshotDirForAgent(agentType: string): string { ... }
function getSnapshotJsonPath(agentType: string): string { ... }
function getSyncedJsonPath(agentType: string, scope: AgentMemoryScope): string { ... }
async function readJsonFile<T>(path: string): Promise<T | null> { ... }
async function copySnapshotToLocal(agentType: string): Promise<void> { ... }

快照机制支持记忆的版本管理和跨作用域同步。copySnapshotToLocal 将远程或共享的记忆快照复制到本地,确保 Agent 在离线环境中也能访问其记忆。


四、记忆与压缩的协同

记忆系统与上下文压缩系统之间有紧密的协同关系。

4.1 会话记忆压缩

src/services/compact/sessionMemoryCompact.ts 实现了基于会话记忆的压缩策略:

function setSessionMemoryCompactConfig(config: SessionMemoryCompactConfig): void { ... }
function getSessionMemoryCompactConfig(): SessionMemoryCompactConfig { ... }
async function initSessionMemoryCompactConfig(): Promise<void> { ... }

当上下文需要压缩时,系统优先尝试会话记忆压缩——将对话中的关键信息提取为记忆,然后用记忆替代原始对话内容。这比简单的截断保留了更多有价值的信息。

4.2 压缩后的记忆重置

在自动压缩完成后,会话记忆的位置标记会被重置:

// autoCompact.ts 中
setLastSummarizedMessageId(undefined)

因为压缩会替换消息列表,旧的消息 UUID 不再存在。


五、记忆在 Agent 派生中的传递

5.1 记忆加载事件

当 Agent 加载其记忆时,系统会记录遥测事件:

if (selectedAgent.memory) {
  logEvent('tengu_agent_memory_loaded', {
    agent_type: selectedAgent.agentType,
    scope: selectedAgent.memory,
    source: 'subagent',
  })
}

5.2 CLAUDE.md 的选择性加载

不同类型的 Agent 对 CLAUDE.md 的需求不同。例如 Explore Agent 设置了 omitClaudeMd: true

export const EXPLORE_AGENT: BuiltInAgentDefinition = {
  omitClaudeMd: true,  // 不加载项目记忆文件
}

Explore Agent 是一个快速的只读搜索代理,不需要了解项目的编码规范或架构决策。主 Agent 拥有完整的上下文,会在 Explore Agent 的结果基础上做出判断。

5.3 嵌套记忆去重

ToolUseContext 中维护了一个去重集合,防止同一个 CLAUDE.md 文件被重复注入:

loadedNestedMemoryPaths?: Set<string>
// 源码注释:readFileState is an LRU that evicts entries in busy 
// sessions, so its .has() check alone can re-inject the same 
// CLAUDE.md dozens of times.

在繁忙的会话中,LRU 缓存会驱逐条目,导致 readFileState.has() 检查失效,同一个 CLAUDE.md 可能被注入数十次。独立的去重集合解决了这个问题。


六、总结

Claude Code 的分层记忆体系在三个时间尺度上解决了知识持久化问题:

其一,会话内记忆通过增量提取保留对话中的关键信息,与压缩系统协同工作,确保上下文压缩不会丢失重要知识。

其二,跨会话记忆通过 CLAUDE.md 等文件实现项目级知识的积累,使得 AI 助手能够在多次会话中逐步"了解"一个项目。

其三,Agent 级记忆为每个 Agent 类型维护独立的知识库,支持快照和同步,使得专业化的 Agent 能够积累领域经验。

三层记忆之间的协同设计(压缩时的记忆重置、Agent 派生时的选择性加载、嵌套记忆去重)确保了整个系统的一致性。对于正在构建 AI Agent 记忆系统的团队,这套分层架构提供了一个清晰的参考框架。

© 版权声明

相关文章