1. 项目概述:Claude Code 的分层记忆体系
在构建AI助手时,记忆管理一直是个棘手的工程难题。想象一下,你正在和一个人类助手合作完成一个长期项目:每次会议后,助手都会忘记之前讨论的所有内容;每次遇到类似问题时,都需要重新解释背景知识;不同部门的助手之间无法共享经验——这样的工作效率可想而知。Claude Code团队通过三层记忆架构,系统性地解决了这些问题。
这套架构的核心价值在于:
- 会话内记忆:像即时贴一样记录当前对话的关键信息,防止上下文压缩导致知识丢失
- 跨会话记忆:类似项目wiki,通过CLAUDE.md文件积累项目级知识
- Agent级记忆:相当于个人笔记本,为不同专业领域的Agent维护专属知识库
2. 会话内记忆的工程实现
2.1 记忆提取的触发机制
会话内记忆的核心挑战在于平衡提取频率和资源消耗。Claude Code采用了基于活动量的动态触发策略:
typescript复制function shouldExtractMemory(messages: Message[]): boolean {
const lastExtractedId = getLastSummarizedMessageId()
const newMessages = lastExtractedId
? messages.slice(messages.findIndex(m => m.id === lastExtractedId) + 1)
: messages
return countToolCallsSince(newMessages, lastExtractedId) >= 3
|| newMessages.length >= 10
}
这个设计考虑了两种触发条件:
- 工具调用次数:当累计3次工具调用后触发,因为工具调用通常表示重要操作
- 消息数量阈值:对话超过10条新消息时触发,防止长时间闲聊导致信息遗漏
实际工程中,我们发现将阈值设为动态值(如基于对话复杂度的自适应调整)效果更好,但会增加系统复杂度。Claude Code选择了更稳定的静态阈值方案。
2.2 并发控制与状态管理
记忆提取可能涉及API调用和文件IO,必须处理好并发问题。源码中的锁机制设计值得借鉴:
typescript复制let extractionInProgress = false
async function waitForSessionMemoryExtraction(): Promise<void> {
while (extractionInProgress) {
await new Promise(resolve => setTimeout(resolve, 100))
}
}
function markExtractionStarted(): void {
if (extractionInProgress) {
throw new Error('Memory extraction already in progress')
}
extractionInProgress = true
}
这种简单的自旋锁(spin lock)实现:
- 避免了复杂的锁管理开销
- 通过100ms的轮询间隔平衡了响应速度和CPU占用
- 在单进程环境下完全够用
2.3 记忆模板与Prompt工程
记忆提取的质量很大程度上取决于prompt设计。Claude Code采用了分层prompt结构:
- 模板层:定义记忆的标准化结构
typescript复制async function loadSessionMemoryTemplate(): Promise<string> {
return `# 会话记忆总结
## 关键决策
{decisions}
## 待办事项
{todos}
## 重要细节
{details}`
}
- 指令层:指导模型如何填充模板
typescript复制async function loadSessionMemoryPrompt(): Promise<string> {
return `请从对话中提取以下信息:
1. 关键决策:用户确认的最终方案
2. 待办事项:明确需要后续执行的任务
3. 重要细节:可能被反复提及的技术参数
避免记录:
- 未达成一致的讨论
- 已经完成的一次性操作`
}
这种分离设计使得:
- 非技术人员可以安全修改模板而不影响提取逻辑
- 可以根据不同Agent类型加载不同的prompt组合
- 便于进行A/B测试优化提取效果
3. 跨会话记忆的持久化设计
3.1 CLAUDE.md 文件规范
CLAUDE.md是项目级的记忆中枢,其设计遵循几个原则:
- 可读性优先:采用标准Markdown格式,方便人类查阅
- 机器可解析:通过固定章节结构支持自动化处理
- 增量更新:新内容追加到相应章节,而非覆盖
典型的文件结构示例:
markdown复制# 项目知识库
## 开发规范
- 代码缩进:2个空格
- 分支命名:feature/xxx, fix/yyy
## 常用命令
```bash
# 启动开发服务器
npm run dev -- --port 3000
架构决策
2023-11-01: 选择TypeScript而非Flow,因为...
code复制
### 3.2 记忆合并算法
当多个会话同时更新CLAUDE.md时,系统采用基于时间戳的合并策略:
1. 读取现有文件内容
2. 解析出各章节内容
3. 将新记忆按类型归类到对应章节
4. 对冲突内容(如相同主题的不同描述)添加时间戳标记
5. 按时间倒序重新组织内容
这种设计避免了:
- 完全覆盖导致的记忆丢失
- 纯追加模式造成的重复冗余
- 需要复杂锁机制带来的性能损耗
## 4. [Agent](https://taotoken.net?utm_source=ai)级记忆的架构实现
### 4.1 记忆目录的安全管理
Agent记忆需要处理路径安全问题,源码中的处理方式很实用:
```typescript
function sanitizeAgentTypeForPath(agentType: string): string {
return agentType
.replace(/[^a-z0-9]/gi, '_')
.substring(0, 64)
.toLowerCase()
}
这个函数实现了:
- 特殊字符替换:防止路径注入攻击
- 长度限制:避免文件系统路径长度问题
- 大小写统一:确保跨平台兼容性
4.2 记忆快照的同步机制
快照系统支持三种同步模式:
- 本地快照:
AgentMemoryScope.Local- 存储在项目目录中
- 仅对当前项目可见
- 用户级快照:
AgentMemoryScope.User- 存储在用户配置目录
- 对该用户的所有项目可见
- 全局快照:
AgentMemoryScope.Global- 存储在中央仓库
- 需要权限验证
同步过程采用写时复制(Copy-on-Write)策略:
typescript复制async function copySnapshotToLocal(agentType: string): Promise<void> {
const remotePath = getSnapshotJsonPath(agentType)
const localPath = getSyncedJsonPath(agentType, AgentMemoryScope.Local)
if (!await fileExists(localPath) ||
(await getFileHash(remotePath)) !== (await getFileHash(localPath))) {
await fs.copy(remotePath, localPath)
}
}
5. 记忆系统与其他模块的协同
5.1 与压缩系统的集成
记忆压缩的配置参数很有参考价值:
typescript复制interface SessionMemoryCompactConfig {
minCompressionRatio: number // 0.7
maxContextTokensToSave: number // 2000
preservationRules: {
keepToolCalls: true
keepLastUserMessage: true
keepSystemMessages: true
}
}
这些参数决定了:
- 当压缩后能节省至少30%空间时才启用记忆压缩
- 最多保留2000个token的原始上下文
- 总是保留工具调用、最后用户消息和系统消息
5.2 与Agent派生机制的配合
记忆加载的遥测设计值得注意:
typescript复制logEvent('tengu_agent_memory_loaded', {
agent_type: selectedAgent.agentType,
scope: selectedAgent.memory,
source: 'subagent',
load_time_ms: Date.now() - startTime,
memory_size_kb: Math.round(memoryBuffer.length / 1024)
})
收集的指标包括:
- Agent类型和记忆作用域
- 加载来源(主Agent/子Agent)
- 加载耗时和记忆大小
- 用于监控性能问题和优化内存使用
6. 实践经验与优化建议
在实际应用中,我们发现几个关键优化点:
-
记忆碎片整理:
- 定期运行记忆压缩脚本
- 合并相似主题的记忆条目
- 删除过时内容(如"昨天的问题")
-
跨Agent记忆共享:
typescript复制function shareMemory(sourceAgent: string, targetAgent: string, memoryKeys: string[]) { const memories = getMemories(sourceAgent, memoryKeys) setMemories(targetAgent, memories, { ttl: 24 * 60 * 60 }) // 24小时有效期 } -
记忆新鲜度衰减:
- 为记忆条目添加时间戳
- 根据时间衰减记忆权重
- 自动归档超过30天未引用的记忆
-
敏感信息过滤:
typescript复制function filterSensitiveContent(content: string): string { return content.replace(/(api|access)_?key=(\w+)/gi, '$1_key=***') }
这套分层记忆架构在实践中表现出色,特别适合:
- 长期协作的项目开发
- 需要持续学习的复杂任务
- 多专家Agent协同的场景
对于想要实现类似系统的团队,建议从会话内记忆开始逐步扩展,重点关注记忆提取的质量和与其他系统的集成点。