0%

Claude Code 源码解析 (13):AI 记忆存储与检索机制

Claude Code 源码解析 (13):AI 记忆存储与检索机制

导读: 这是 Claude Code 20 个功能特性源码解析系列的第 13 篇,深入分析记忆系统的架构设计。


📋 目录

  1. [问题引入:为什么 AI 需要记忆?](#问题引入为什么 ai-需要记忆)
  2. 技术原理:记忆系统架构
  3. 设计思想:为什么这样设计
  4. 解决方案:完整实现详解
  5. OpenClaw 最佳实践
  6. 总结

问题引入:为什么 AI 需要记忆?

痛点场景

场景 1:重复说明相同信息

1
2
3
4
5
6
7
8
Day 1:
用户:"我偏好使用 TypeScript"
AI:"好的,记住了"

Day 2:
用户:"帮我创建一个新项目"
AI:"好的,用 Python 还是 JavaScript?"
用户:"...我说过用 TypeScript 的"

场景 2:不了解用户习惯

1
2
3
4
5
6
用户第 10 次让 AI 写代码:
- 每次都需说明代码风格
- 每次都需说明测试框架
- 每次都需说明部署方式

→ 没有累积学习

场景 3:长周期项目断档

1
2
3
4
5
6
7
8
项目第 1 周:
- 确定技术栈
- 设计架构
- 分配任务

项目第 4 周:
AI:"这个项目的技术栈是什么?"
→ 早期的决策遗忘了

核心问题

设计 AI 助手的记忆系统时,面临以下挑战:

  1. 存储问题

    • 如何高效存储记忆?
    • 如何组织记忆结构?
  2. 检索问题

    • 如何快速找到相关记忆?
    • 如何判断记忆相关性?
  3. 更新问题

    • 如何更新过时记忆?
    • 如何合并冲突记忆?
  4. 隐私问题

    • 哪些信息应该记住?
    • 哪些信息应该遗忘?

Claude Code 用向量数据库 + 智能检索解决了这些问题。


技术原理:记忆系统架构

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────┐
│ 对话/交互 │
│ (产生新的记忆) │
└─────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Memory Extractor (记忆提取器) │
│ - 从对话中提取记忆 │
│ - 分类 (用户偏好/项目上下文/技术决策) │
│ - 评分 (重要性/时效性) │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Memory Store (记忆存储) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 向量数据库 (Vector DB) │ │
│ │ - 记忆内容 → 向量嵌入 │ │
│ │ - 相似度检索 │ │
│ │ - 支持:LanceDB, Pinecone, Chroma │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 元数据存储 │ │
│ │ - 类型、重要性、创建时间 │ │
│ │ - 过期时间、访问计数 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Memory Retriever (记忆检索器) │
│ - 语义检索 (向量相似度) │
│ - 关键词检索 (全文搜索) │
│ - 混合检索 (向量 + 关键词) │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Memory Injector (记忆注入器) │
│ - 选择相关记忆 │
│ - 格式化注入 │
│ - 上下文整合 │
└─────────────────────────────────────────────────────────────┘

记忆定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
interface Memory {
// 基本信息
id: string;
content: string;

// 分类
type: MemoryType;
category?: string;

// 元数据
importance: number; // 1-5
confidence: number; // 0-1
source?: string; // 来源会话 ID

// 时间
createdAt: Date;
updatedAt: Date;
expiresAt?: Date; // 过期时间
lastAccessedAt?: Date;

// 使用统计
accessCount: number;

// 向量嵌入 (用于检索)
embedding?: number[];

// 关联
relatedMemories?: string[]; // 相关记忆 ID
tags?: string[];
}

type MemoryType =
| 'user_preference' // 用户偏好
| 'project_context' // 项目上下文
| 'technical_decision' // 技术决策
| 'workflow' // 工作流程
| 'fact' // 事实信息
| 'skill' // 技能/能力
| 'relationship' // 关系信息
| 'other'; // 其他

记忆提取器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class MemoryExtractor {
private llm: LLMClient;
private config: MemoryExtractorConfig;

constructor(llm: LLMClient, config: MemoryExtractorConfig) {
this.llm = llm;
this.config = config;
}

// 从对话中提取记忆
async extract(messages: Message[]): Promise<Memory[]> {
// 1. 构建提取 Prompt
const prompt = this.buildExtractionPrompt(messages);

// 2. 调用 LLM 提取
const response = await this.llm.generate(prompt, {
maxTokens: 2000,
temperature: 0.3,
});

// 3. 解析结果
const memories = this.parseMemories(response.content);

// 4. 过滤和评分
const filtered = await this.filterAndScore(memories);

// 5. 生成向量嵌入
const embedded = await this.embed(filtered);

return embedded;
}

private buildExtractionPrompt(messages: Message[]): string {
const conversationText = messages
.map(m => `${m.role}: ${m.content}`)
.join('\n');

return `
从以下对话中提取值得长期记住的信息:

${conversationText}

提取标准:
1. 用户明确表达的偏好 (如"我喜欢用 TypeScript")
2. 项目相关的重要信息 (如技术栈、架构决策)
3. 工作流程和习惯 (如"每天早上 9 点开会")
4. 事实性信息 (如"公司在上海")
5. 技能和能力 (如"熟悉 Python 和 Go")

不要提取:
- 临时性的信息 (如"今天的待办")
- 对话中的闲聊
- 已经过时的信息

返回 JSON 数组格式:
[
{
"content": "记忆内容",
"type": "user_preference",
"category": "programming",
"importance": 4,
"confidence": 0.9,
"tags": ["typescript", "language"]
}
]
`.trim();
}

private parseMemories(content: string): Memory[] {
try {
const parsed = JSON.parse(content);

return parsed.map((item: any) => ({
id: generateMemoryId(),
content: item.content,
type: item.type as MemoryType,
category: item.category,
importance: item.importance || 3,
confidence: item.confidence || 0.8,
source: undefined,
createdAt: new Date(),
updatedAt: new Date(),
expiresAt: this.calculateExpiry(item.type),
lastAccessedAt: undefined,
accessCount: 0,
tags: item.tags || [],
}));

} catch (error) {
console.error('Failed to parse memories:', error);
return [];
}
}

private async filterAndScore(memories: Memory[]): Promise<Memory[]> {
const filtered: Memory[] = [];

for (const memory of memories) {
// 1. 置信度过滤
if (memory.confidence < this.config.minConfidence) {
continue;
}

// 2. 重要性过滤
if (memory.importance < this.config.minImportance) {
continue;
}

// 3. 去重 (检查是否已有相似记忆)
const existing = await this.findSimilar(memory);
if (existing) {
// 更新现有记忆
await this.mergeMemories(existing, memory);
continue;
}

// 4. 综合评分
memory.importance = this.calculateImportance(memory);

filtered.push(memory);
}

return filtered;
}

private async embed(memories: Memory[]): Promise<Memory[]> {
// 使用 embedding 模型生成向量
const embeddings = await this.llm.embed(
memories.map(m => m.content)
);

for (let i = 0; i < memories.length; i++) {
memories[i].embedding = embeddings[i];
}

return memories;
}

private calculateExpiry(type: MemoryType): Date | undefined {
// 不同类型记忆有不同的过期时间
const expiryDays: Record<MemoryType, number> = {
user_preference: 365 * 10, // 10 年 (几乎永久)
project_context: 365, // 1 年
technical_decision: 365, // 1 年
workflow: 90, // 90 天
fact: 365, // 1 年
skill: 365 * 5, // 5 年
relationship: 365 * 2, // 2 年
other: 90, // 90 天
};

const days = expiryDays[type] || 90;
return new Date(Date.now() + days * 86400000);
}

private calculateImportance(memory: Memory): number {
let score = memory.importance;

// 用户明确表达的偏好加分
if (memory.content.includes('我喜欢') ||
memory.content.includes('我偏好') ||
memory.content.includes('always') ||
memory.content.includes('never')) {
score += 0.5;
}

// 高频关键词加分
const importantKeywords = ['必须', '一定', 'always', 'never', 'important'];
if (importantKeywords.some(kw => memory.content.includes(kw))) {
score += 0.3;
}

return Math.min(5, score);
}

private async findSimilar(memory: Memory): Promise<Memory | null> {
// 向量相似度搜索
const similar = await memoryStore.search({
query: memory.content,
filter: { type: memory.type },
limit: 1,
threshold: 0.85, // 相似度阈值
});

return similar.length > 0 ? similar[0] : null;
}

private async mergeMemories(existing: Memory, newMem: Memory): Promise<void> {
// 更新现有记忆
existing.content = this.mergeContent(existing.content, newMem.content);
existing.updatedAt = new Date();
existing.confidence = Math.max(existing.confidence, newMem.confidence);
existing.importance = Math.max(existing.importance, newMem.importance);

await memoryStore.update(existing);
}

private mergeContent(oldContent: string, newContent: string): string {
// 简单合并策略:保留更多信息
if (newContent.length > oldContent.length) {
return newContent;
}
return oldContent;
}
}

向量记忆存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
class MemoryStore {
private vectorDb: VectorDatabase;
private metadataDb: Database;

constructor(config: MemoryStoreConfig) {
// 初始化向量数据库 (如 LanceDB)
this.vectorDb = new LanceDB({
path: config.dbPath,
embeddingDimension: 1536, // 根据 embedding 模型
});

// 初始化元数据数据库
this.metadataDb = new Database(config.metadataPath);
this.initMetadataSchema();
}

private initMetadataSchema(): void {
this.metadataDb.exec(`
CREATE TABLE IF NOT EXISTS memories (
id TEXT PRIMARY KEY,
content TEXT,
type TEXT,
category TEXT,
importance INTEGER,
confidence REAL,
source TEXT,
created_at TEXT,
updated_at TEXT,
expires_at TEXT,
last_accessed_at TEXT,
access_count INTEGER,
tags TEXT
)
`);

this.metadataDb.exec(`
CREATE INDEX IF NOT EXISTS idx_memories_type
ON memories(type)
`);

this.metadataDb.exec(`
CREATE INDEX IF NOT EXISTS idx_memories_importance
ON memories(importance DESC)
`);
}

// 添加记忆
async add(memory: Memory): Promise<void> {
// 1. 存储到向量数据库
if (memory.embedding) {
await this.vectorDb.insert({
id: memory.id,
vector: memory.embedding,
metadata: {
content: memory.content,
type: memory.type,
},
});
}

// 2. 存储元数据
await this.metadataDb.run(`
INSERT INTO memories (
id, content, type, category, importance, confidence,
source, created_at, updated_at, expires_at,
access_count, tags
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
memory.id,
memory.content,
memory.type,
memory.category,
memory.importance,
memory.confidence,
memory.source,
memory.createdAt.toISOString(),
memory.updatedAt.toISOString(),
memory.expiresAt?.toISOString(),
memory.accessCount,
JSON.stringify(memory.tags),
]);
}

// 向量相似度搜索
async search(options: SearchOptions): Promise<Memory[]> {
// 1. 生成查询向量
const queryEmbedding = await this.llm.embed(options.query);

// 2. 向量搜索
const vectorResults = await this.vectorDb.search({
query: queryEmbedding,
limit: options.limit || 10,
filter: options.filter,
threshold: options.threshold || 0.7,
});

// 3. 加载完整记忆信息
const memories: Memory[] = [];

for (const result of vectorResults) {
const memory = await this.get(result.id);
if (memory) {
memory.similarity = result.similarity;
memories.push(memory);
}
}

// 4. 排序 (相似度 + 重要性)
memories.sort((a, b) => {
const scoreA = (a.similarity || 0) * 0.7 + (a.importance / 5) * 0.3;
const scoreB = (b.similarity || 0) * 0.7 + (b.importance / 5) * 0.3;
return scoreB - scoreA;
});

return memories;
}

// 获取单个记忆
async get(id: string): Promise<Memory | null> {
const row = await this.metadataDb.get(
'SELECT * FROM memories WHERE id = ?',
[id]
);

if (!row) {
return null;
}

return {
id: row.id,
content: row.content,
type: row.type,
category: row.category,
importance: row.importance,
confidence: row.confidence,
source: row.source,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
accessCount: row.access_count,
tags: JSON.parse(row.tags || '[]'),
};
}

// 更新记忆
async update(memory: Memory): Promise<void> {
await this.metadataDb.run(`
UPDATE memories SET
content = ?,
type = ?,
category = ?,
importance = ?,
confidence = ?,
updated_at = ?,
access_count = ?
WHERE id = ?
`, [
memory.content,
memory.type,
memory.category,
memory.importance,
memory.confidence,
memory.updatedAt.toISOString(),
memory.accessCount + 1,
memory.id,
]);
}

// 删除记忆
async delete(id: string): Promise<void> {
await this.vectorDb.delete(id);
await this.metadataDb.run('DELETE FROM memories WHERE id = ?', [id]);
}

// 清理过期记忆
async cleanup(): Promise<number> {
const now = new Date();
const rows = await this.metadataDb.all(
'SELECT id FROM memories WHERE expires_at < ?',
[now.toISOString()]
);

let deleted = 0;
for (const row of rows) {
await this.delete(row.id);
deleted++;
}

return deleted;
}

// 批量导入
async import(memories: Memory[]): Promise<void> {
const tx = await this.metadataDb.transaction();

try {
for (const memory of memories) {
await this.add(memory);
}
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
}

// 导出记忆
async export(filter?: MemoryFilter): Promise<Memory[]> {
let query = 'SELECT * FROM memories';
const params: any[] = [];

if (filter?.type) {
query += ' WHERE type = ?';
params.push(filter.type);
}

const rows = await this.metadataDb.all(query, params);

return rows.map(row => this.rowToMemory(row));
}

private rowToMemory(row: any): Memory {
return {
id: row.id,
content: row.content,
type: row.type,
category: row.category,
importance: row.importance,
confidence: row.confidence,
source: row.source,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
accessCount: row.access_count,
tags: JSON.parse(row.tags || '[]'),
};
}
}

interface SearchOptions {
query: string;
limit?: number;
filter?: Record<string, any>;
threshold?: number;
}

interface MemoryFilter {
type?: MemoryType;
minImportance?: number;
tags?: string[];
}

记忆注入器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
class MemoryInjector {
private memoryStore: MemoryStore;

constructor(memoryStore: MemoryStore) {
this.memoryStore = memoryStore;
}

// 注入记忆到上下文
async inject(
context: Message[],
budget: ContextBudget
): Promise<Message[]> {
// 1. 从当前上下文提取关键词
const keywords = this.extractKeywords(context);

// 2. 搜索相关记忆
const memories = await this.searchRelevantMemories(keywords, budget);

if (memories.length === 0) {
return context;
}

// 3. 格式化记忆
const memoryContext = this.formatMemories(memories);

// 4. 创建记忆消息
const memoryMessage: Message = {
id: generateId(),
role: 'system',
content: memoryContext,
timestamp: new Date(),
metadata: {
type: 'memory_injection',
memoryCount: memories.length,
},
};

// 5. 插入到上下文开头
return [memoryMessage, ...context];
}

private async searchRelevantMemories(
keywords: string[],
budget: ContextBudget
): Promise<Memory[]> {
// 计算记忆预算 (占上下文预算的 10%)
const memoryBudget = Math.round(budget.conversation * 0.1);

// 搜索记忆
const memories = await this.memoryStore.search({
query: keywords.join(' '),
limit: 20,
threshold: 0.6,
});

// 过滤过期记忆
const validMemories = memories.filter(m =>
!m.expiresAt || m.expiresAt > new Date()
);

// 限制 Token 使用
let memoryTokens = 0;
const selected: Memory[] = [];

for (const memory of validMemories) {
const tokens = this.tokenCounter.count(memory.content);
if (memoryTokens + tokens <= memoryBudget) {
selected.push(memory);
memoryTokens += tokens;
}
}

return selected;
}

private formatMemories(memories: Memory[]): string {
const sections: Record<string, Memory[]> = {};

// 按类型分组
for (const memory of memories) {
if (!sections[memory.type]) {
sections[memory.type] = [];
}
sections[memory.type].push(memory);
}

// 格式化输出
let output = '<相关记忆>\n\n';

const typeLabels: Record<MemoryType, string> = {
user_preference: '用户偏好',
project_context: '项目上下文',
technical_decision: '技术决策',
workflow: '工作流程',
fact: '事实信息',
skill: '技能',
relationship: '关系',
other: '其他',
};

for (const [type, typeMemories] of Object.entries(sections)) {
output += `**${typeLabels[type as MemoryType]}**\n`;

for (const memory of typeMemories) {
output += `- ${memory.content}\n`;
}

output += '\n';
}

output += '</相关记忆>';

return output;
}

private extractKeywords(context: Message[]): string[] {
const recentMessages = context.slice(-10);
const text = recentMessages.map(m => m.content).join(' ');

// 提取名词和专有名词
const nouns = text.match(/\b[A-Z][a-zA-Z]+\b/g) || []; // 专有名词
const keywords = text.match(/\b[a-z]{4,}\b/g) || []; // 普通单词

// 统计词频
const freq: Record<string, number> = {};
for (const word of [...nouns, ...keywords]) {
const lower = word.toLowerCase();
freq[lower] = (freq[lower] || 0) + 1;
}

// 返回高频词
return Object.entries(freq)
.sort((a, b) => b[1] - a[1])
.slice(0, 15)
.map(([word]) => word);
}
}

设计思想:为什么这样设计

思想 1:向量检索 + 元数据过滤

问题: 纯关键词检索不够智能。

解决: 向量相似度 + 元数据过滤。

1
2
3
4
5
6
7
// 传统关键词检索
SELECT * FROM memories WHERE content LIKE '%TypeScript%'
→ 只能匹配字面

// 向量检索
search(query: "我喜欢用 TypeScript 写代码")
→ 匹配 "偏好 TypeScript""前端开发首选 TS"等语义相关内容

设计智慧:

向量检索理解语义,元数据过滤保证精度。

思想 2:记忆分级

问题: 所有记忆同等重要会淹没关键信息。

解决: 重要性分级。

重要性 说明 保留时间 检索权重
5 用户明确偏好 永久
4 关键技术决策 1 年
3 项目上下文 6 个月
2 工作流程 3 个月
1 临时信息 1 个月 很低

思想 3:记忆合并

问题: 相同信息重复存储浪费空间。

解决: 智能合并。

1
2
3
4
记忆 1: "用户偏好 TypeScript" (Day 1)
记忆 2: "喜欢用 TS 写前端" (Day 30)
↓ 合并
记忆: "用户偏好使用 TypeScript 进行前端开发" (更新)

思想 4:记忆遗忘

问题: 记忆无限增长影响检索性能。

解决: 自动遗忘。

1
2
3
4
5
6
7
8
// 过期策略
- 用户偏好:10 年 (几乎永久)
- 技术决策:1
- 工作流程:90
- 临时信息:30

// 定期清理
await memoryStore.cleanup(); // 删除过期记忆

思想 5:记忆可解释

问题: AI 使用记忆但用户不知道。

解决: 记忆来源可追溯。

1
2
3
4
5
6
7
8
9
interface Memory {
source?: string; // 来源会话 ID
createdAt: Date; // 创建时间
confidence: number; // 置信度
}

// 用户可以查询
"这个记忆是什么时候创建的?"
"这条记忆来自哪次对话?"

解决方案:完整实现详解

MemoryTool 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
export class MemoryTool extends Tool {
name = 'memory';
description = '管理 AI 记忆 (查看/添加/删除/搜索)';

inputSchema = {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['list', 'add', 'delete', 'search', 'export', 'import'],
description: '操作类型',
},
content: {
type: 'string',
description: '记忆内容',
},
type: {
type: 'string',
enum: ['user_preference', 'project_context', 'technical_decision', 'other'],
description: '记忆类型',
},
query: {
type: 'string',
description: '搜索关键词',
},
},
required: ['action'],
};

private extractor: MemoryExtractor;
private store: MemoryStore;
private injector: MemoryInjector;

async execute(input: MemoryInput, context: ToolContext): Promise<ToolResult> {
switch (input.action) {
case 'list':
return this.list(input.type);

case 'add':
return this.add(input.content!, input.type);

case 'delete':
return this.delete(input.id!);

case 'search':
return this.search(input.query!);

case 'export':
return this.export();

case 'import':
return this.import(input.data!);

default:
return {
success: false,
error: `Unknown action: ${input.action}`,
};
}
}

private async list(type?: MemoryType): Promise<ToolResult> {
const memories = await this.store.export(
type ? { type } : undefined
);

return {
success: true,
memories: memories.map(m => ({
id: m.id,
content: m.content,
type: m.type,
importance: m.importance,
createdAt: m.createdAt,
})),
total: memories.length,
};
}

private async add(content: string, type?: MemoryType): Promise<ToolResult> {
const memory: Memory = {
id: generateMemoryId(),
content,
type: type || 'other',
importance: 3,
confidence: 1.0,
createdAt: new Date(),
updatedAt: new Date(),
accessCount: 0,
tags: [],
};

await this.store.add(memory);

return {
success: true,
message: '记忆已添加',
memory: {
id: memory.id,
content: memory.content,
type: memory.type,
},
};
}

private async search(query: string): Promise<ToolResult> {
const memories = await this.store.search({
query,
limit: 20,
threshold: 0.6,
});

return {
success: true,
memories: memories.map(m => ({
id: m.id,
content: m.content,
type: m.type,
similarity: m.similarity,
importance: m.importance,
})),
total: memories.length,
};
}
}

配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# ~/.openclaw/config/memory.yaml

# 存储配置
storage:
# 向量数据库路径
vector_db_path: ~/.openclaw/data/memories-vector

# 元数据数据库路径
metadata_db_path: ~/.openclaw/data/memories.db

# Embedding 模型
embedding_model: text-embedding-3-small
embedding_dimension: 1536

# 提取配置
extraction:
# 启用自动提取
auto_extract: true

# 最小置信度
min_confidence: 0.7

# 最小重要性
min_importance: 2

# 提取触发条件
trigger:
# 每 N 条消息提取一次
message_interval: 10

# 会话结束时提取
on_session_end: true

# 检索配置
retrieval:
# 启用记忆注入
inject_on_query: true

# 最大记忆数量
max_memories: 10

# 相似度阈值
similarity_threshold: 0.6

# 记忆预算 (占上下文预算的比例)
budget_ratio: 0.1

# 清理配置
cleanup:
# 自动清理
auto_cleanup: true

# 清理间隔 (小时)
interval: 24

# 保留过期记忆的天数 (用于审计)
retention_days: 30

OpenClaw 最佳实践

实践 1:查看记忆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 列出所有记忆
openclaw run memory --action list

# 输出:
已存储记忆 (共 25 条):

┌───────────────────────────────────────────┐
│ ID │ 内容 │ 类型 │ 重要性 │
├───────────────────────────────────────────┤
│ mem-001 │ 偏好 TypeScript │ 用户偏好│ ⭐⭐⭐⭐⭐│
│ mem-002 │ Monorepo 结构 │ 项目 │ ⭐⭐⭐⭐ │
│ mem-003 │ 使用 PostgreSQL │ 技术 │ ⭐⭐⭐⭐ │
└───────────────────────────────────────────┘

# 按类型查看
openclaw run memory --action list --type user_preference

实践 2:搜索记忆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 搜索相关记忆
openclaw run memory --action search --query "TypeScript"

# 输出:
搜索结果 (3 条):

1. [用户偏好] 偏好使用 TypeScript 进行前端开发
相似度:0.92 重要性:⭐⭐⭐⭐⭐

2. [技术决策] 项目使用 TypeScript 4.x 版本
相似度:0.85 重要性:⭐⭐⭐⭐

3. [技能] 熟悉 TypeScript 高级特性
相似度:0.78 重要性:⭐⭐⭐

实践 3:添加记忆

1
2
3
4
5
6
7
8
9
# 手动添加记忆
openclaw run memory --action add \
--content "用户偏好使用 ESLint + Prettier 进行代码格式化" \
--type user_preference

# 输出:
记忆已添加
ID: mem-026
类型:user_preference

实践 4:删除记忆

1
2
3
4
5
6
7
8
# 删除记忆
openclaw run memory --action delete --id mem-001

# 批量删除过期记忆
openclaw run memory --action cleanup

# 输出:
已清理 5 条过期记忆

实践 5:导出/导入记忆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导出记忆
openclaw run memory --action export --output memories.json

# 导入记忆
openclaw run memory --action import --file memories.json

# 查看记忆统计
openclaw run memory --action stats

# 输出:
记忆统计:
─────────────────────────────────────
总记忆数:25
- 用户偏好:8
- 项目上下文:7
- 技术决策:6
- 其他:4

平均重要性:3.6
平均置信度:0.85
最早记忆:2026-01-15
最近记忆:2026-04-03

总结

核心要点

  1. 向量检索 - 理解语义,不只是关键词匹配
  2. 记忆分级 - 重要性决定保留时间和检索权重
  3. 智能合并 - 避免重复,保持记忆更新
  4. 自动遗忘 - 过期记忆自动清理
  5. 可解释性 - 记忆来源可追溯

设计智慧

好的记忆系统让 AI”越用越懂你”。

Claude Code 的记忆系统设计告诉我们:

  • 向量数据库实现智能检索
  • 分级管理保证关键信息优先
  • 自动清理避免无限增长
  • 可解释性建立用户信任

记忆类型与保留时间

类型 保留时间 示例
用户偏好 10 年 “偏好 TypeScript”
技术决策 1 年 “选择 PostgreSQL”
项目上下文 6 个月 “Monorepo 结构”
工作流程 3 个月 “每天站会”
临时信息 1 个月 “今天的待办”

下一步

  • 配置向量数据库
  • 启用自动提取
  • 设置记忆注入
  • 定期清理过期记忆

系列文章:

  • [1] Bash 命令执行的安全艺术 (已发布)
  • [2] 差异编辑的设计艺术 (已发布)
  • [3] 文件搜索的底层原理 (已发布)
  • [4] 多 Agent 协作的架构设计 (已发布)
  • [5] 技能系统的设计哲学 (已发布)
  • [6] MCP 协议集成的完整指南 (已发布)
  • [7] 后台任务管理的完整方案 (已发布)
  • [8] Web 抓取的 SSRF 防护设计 (已发布)
  • [9] 多层权限决策引擎设计 (已发布)
  • [10] 插件生命周期的设计智慧 (已发布)
  • [11] 会话持久化的架构设计 (已发布)
  • [12] 上下文压缩与恢复技术 (已发布)
  • [13] AI 记忆存储与检索机制 (本文)
  • [14+] 更多高级功能 (继续中…)

进度:13/N

上一篇: Claude Code 源码解析 (12):上下文压缩与恢复技术


关于作者: John,OpenClaw 平台开发者,专注 AI 助手架构设计与实现。