本系列文章基于 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 提取时机判断
typescript
function shouldExtractMemory(messages: Message[]): boolean { ... }
function countToolCallsSince(messages: Message[], lastMessageId: string): number { ... }
系统根据工具调用次数和消息量判断是否需要提取记忆。这种基于活动量的触发机制避免了过于频繁的提取(浪费 API 调用)和过于稀疏的提取(丢失重要信息)。
1.2 并发控制
typescript
function markExtractionStarted(): void { ... }
function markExtractionCompleted(): void { ... }
async function waitForSessionMemoryExtraction(): Promise<void> { ... }
记忆提取过程有锁机制,防止并发提取。waitForSessionMemoryExtraction 允许其他操作等待提取完成后再继续,确保后续操作能看到最新的记忆状态。
1.3 记忆模板与 Prompt
typescript
async function loadSessionMemoryTemplate(): Promise<string> { ... }
async function loadSessionMemoryPrompt(): Promise<string> { ... }
function getDefaultUpdatePrompt(): string { ... }
记忆提取使用专门的 prompt 模板,指导模型从对话中提取结构化的知识。
1.4 记忆大小分析
typescript
function analyzeSectionSizes(content: string): Record<string, number> { ... }
function generateSectionReminders(sizes: Record<string, number>): string[] { ... }
系统会分析记忆各部分的大小,当某个部分过大时生成优化建议。这防止了记忆文件无限膨胀。
1.5 位置追踪
typescript
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 类型维护独立的记忆目录:
typescript
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 实现了记忆的快照与同步机制:
typescript
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 实现了基于会话记忆的压缩策略:
typescript
function setSessionMemoryCompactConfig(config: SessionMemoryCompactConfig): void { ... }
function getSessionMemoryCompactConfig(): SessionMemoryCompactConfig { ... }
async function initSessionMemoryCompactConfig(): Promise<void> { ... }
当上下文需要压缩时,系统优先尝试会话记忆压缩------将对话中的关键信息提取为记忆,然后用记忆替代原始对话内容。这比简单的截断保留了更多有价值的信息。
4.2 压缩后的记忆重置
在自动压缩完成后,会话记忆的位置标记会被重置:
typescript
// autoCompact.ts 中
setLastSummarizedMessageId(undefined)
因为压缩会替换消息列表,旧的消息 UUID 不再存在。
五、记忆在 Agent 派生中的传递
5.1 记忆加载事件
当 Agent 加载其记忆时,系统会记录遥测事件:
typescript
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:
typescript
export const EXPLORE_AGENT: BuiltInAgentDefinition = {
omitClaudeMd: true, // 不加载项目记忆文件
}
Explore Agent 是一个快速的只读搜索代理,不需要了解项目的编码规范或架构决策。主 Agent 拥有完整的上下文,会在 Explore Agent 的结果基础上做出判断。
5.3 嵌套记忆去重
ToolUseContext 中维护了一个去重集合,防止同一个 CLAUDE.md 文件被重复注入:
typescript
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 记忆系统的团队,这套分层架构提供了一个清晰的参考框架。