@ppagent/memory 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms.txt CHANGED
@@ -1,862 +1,908 @@
1
- # @ppagent/memory
2
-
3
- > `@ppagent/memory` is an independent TypeScript/Node.js long-term memory package for AI agents. It stores conversation memory in a pluggable vector store (LanceDB or SQLite/sqlite-vec, auto-detected per platform), extracts and searches entity relationships with Grafeo, supports user/chat/session scoped facts, compresses chat history into multi-resolution topics, and ingests Markdown documents as a searchable knowledge base.
4
-
5
- This file is written for AI coding agents and assistants. Use it as the primary package guide when generating code that depends on `@ppagent/memory`.
6
-
7
- ## Package Identity
8
-
9
- - Package name: `@ppagent/memory`
10
- - Module format: ESM
11
- - Main entry: `dist/index.js`
12
- - Type entry: `dist/index.d.ts`
13
- - Source entry during local workspace development: `src/index.ts`
14
- - Runtime target: Node.js with global `fetch`
15
- - Storage engines:
16
- - Vector store (dual backend, auto-detected): LanceDB or SQLite (better-sqlite3 + sqlite-vec + FTS5 with jieba Chinese tokenization) for messages, topics, facts, sessions, documents, and chunks. LanceDB requires native prebuilt binaries (unavailable on Intel macOS since 0.23.0); SQLite works everywhere and is the automatic fallback. RRF hybrid-search fusion runs in the shared domain layer, so search behavior is identical across backends.
17
- - Grafeo for entity/relation graph storage and graph search
18
- - Configuration principle: the package NEVER reads environment variables, config files, or any implicit global state. Every parameter enters through the `MemoryConfig` object passed to `MemoryManager`/`MemoryStore`. Mapping env vars to config fields (as shown in the examples below) is entirely the host application's responsibility.
19
- - LLM APIs: OpenAI-compatible `/chat/completions`
20
- - Embedding APIs: OpenAI-compatible `/embeddings`
21
-
22
- ## When To Use This Package
23
-
24
- Use `@ppagent/memory` when an AI agent needs:
25
-
26
- - Long-term conversation memory across sessions.
27
- - Recent message storage plus compressed historical context windows.
28
- - User-level or chat-level facts such as preferences, profile information, project decisions, or instructions that should persist.
29
- - Hybrid semantic/full-text search over remembered topics and messages.
30
- - Optional graph search for entity-relationship questions.
31
- - Markdown knowledge-base ingestion with chunking, vector search, document-level coarse recall, and optional knowledge graph construction.
32
- - Session management APIs for agent dashboards or memory administration pages.
33
- - A standalone memory layer that does not depend on the larger PPAgent runtime.
34
-
35
- Do not use it as a full agent framework by itself. It does not send chat messages, call tools for users, route platform webhooks, or run a frontend. It is a memory and retrieval module.
36
-
37
- ## Install
38
-
39
- ```bash
40
- pnpm add @ppagent/memory
41
- ```
42
-
43
- ```bash
44
- npm install @ppagent/memory
45
- ```
46
-
47
- ## Imports
48
-
49
- ```ts
50
- import {
51
- MemoryManager,
52
- MemoryStore,
53
- DEFAULT_NODE_TYPES,
54
- DEFAULT_RELATION_TYPES,
55
- KIND_CONVERSATION,
56
- KIND_KNOWLEDGE,
57
- } from "@ppagent/memory";
58
-
59
- import type {
60
- MemoryConfig,
61
- RawMessage,
62
- SearchOptions,
63
- SearchResult,
64
- AddDocumentOptions,
65
- KnowledgeSearchOptions,
1
+ # @ppagent/memory
2
+
3
+ > `@ppagent/memory` is an independent TypeScript/Node.js long-term memory package for AI agents. It stores complete raw conversation messages in a pluggable vector store (LanceDB or SQLite/sqlite-vec, auto-detected per platform), builds importance-aware Topic summaries, returns fixed-budget Topics plus recent uncompressed messages as one context window, extracts and searches entity relationships with Grafeo, supports cached user/chat facts, and ingests Markdown documents as a searchable knowledge base.
4
+
5
+ This file is written for AI coding agents and assistants. Use it as the primary package guide when generating code that depends on `@ppagent/memory`.
6
+
7
+ ## Package Identity
8
+
9
+ - Package name: `@ppagent/memory`
10
+ - Module format: ESM
11
+ - Main entry: `dist/index.js`
12
+ - Type entry: `dist/index.d.ts`
13
+ - Source entry during local workspace development: `src/index.ts`
14
+ - Runtime target: Node.js with global `fetch`
15
+ - Storage engines:
16
+ - Vector store (dual backend, auto-detected): LanceDB or SQLite (better-sqlite3 + sqlite-vec + FTS5 with jieba Chinese tokenization) for messages, topics, facts, sessions, documents, and chunks. LanceDB requires native prebuilt binaries (unavailable on Intel macOS since 0.23.0); SQLite works everywhere and is the automatic fallback. RRF hybrid-search fusion runs in the shared domain layer, so search behavior is identical across backends.
17
+ - Grafeo for entity/relation graph storage and graph search
18
+ - Runtime configuration principle: `MemoryManager`/`MemoryStore` never read environment variables, config files, or implicit global state; every parameter enters through `MemoryConfig`. The separate manual migration CLI reads only the file explicitly supplied with `--config`.
19
+ - LLM APIs: OpenAI-compatible `/chat/completions`
20
+ - Embedding APIs: OpenAI-compatible `/embeddings`
21
+
22
+ ## When To Use This Package
23
+
24
+ Use `@ppagent/memory` when an AI agent needs:
25
+
26
+ - Long-term conversation memory across sessions.
27
+ - Recent message storage plus compressed historical context windows.
28
+ - User-level or chat-level facts such as preferences, profile information, project decisions, or instructions that should persist.
29
+ - Hybrid semantic/full-text search over remembered topics and messages.
30
+ - Optional graph search for entity-relationship questions.
31
+ - Markdown knowledge-base ingestion with chunking, vector search, document-level coarse recall, and optional knowledge graph construction.
32
+ - Session management APIs for agent dashboards or memory administration pages.
33
+ - A standalone memory layer that does not depend on the larger PPAgent runtime.
34
+
35
+ Do not use it as a full agent framework by itself. It does not send chat messages, call tools for users, route platform webhooks, or run a frontend. It is a memory and retrieval module.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pnpm add @ppagent/memory
41
+ ```
42
+
43
+ ```bash
44
+ npm install @ppagent/memory
45
+ ```
46
+
47
+ ## Imports
48
+
49
+ ```ts
50
+ import {
51
+ MemoryManager,
52
+ MemoryStore,
53
+ DEFAULT_NODE_TYPES,
54
+ DEFAULT_RELATION_TYPES,
55
+ KIND_CONVERSATION,
56
+ KIND_KNOWLEDGE,
57
+ } from "@ppagent/memory";
58
+
59
+ import type {
60
+ MemoryConfig,
61
+ RawMessage,
62
+ SearchOptions,
63
+ SearchResult,
64
+ AddDocumentOptions,
65
+ KnowledgeSearchOptions,
66
66
  KnowledgeSearchResult,
67
+ MemoryContextWindow,
67
68
  } from "@ppagent/memory";
68
- ```
69
-
70
- Most consumers should only instantiate `MemoryManager`. `MemoryStore` (plus the `VectorStoreProvider` / `Filter` types) is exported for advanced storage tests or custom infrastructure work. Breaking change in 0.2.0: `LanceService` was replaced by `MemoryStore` + provider abstraction; string SQL filters were replaced by structured `Filter` objects.
71
-
72
- ## Minimal Example
73
-
74
- ```ts
75
- import { MemoryManager } from "@ppagent/memory";
76
-
77
- const memory = new MemoryManager({
78
- lancedbPath: "./data/memory/lance",
79
- grafeoPath: "./data/memory/grafeo",
80
-
81
- llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
82
- llmApiKey: process.env.LLM_API_KEY ?? "",
83
- llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
84
-
85
- embeddingBaseUrl: process.env.EMBED_BASE_URL ?? process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
86
- embeddingApiKey: process.env.EMBED_API_KEY ?? process.env.LLM_API_KEY ?? "",
87
- embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
88
- embeddingDimension: Number(process.env.EMBED_DIMENSION ?? 1536),
89
- });
90
-
91
- await memory.init();
92
-
93
- await memory.updateChat(
94
- [
95
- { talkerId: "user", content: "我叫 Bob,正在做一个 Rust 项目。" },
96
- { talkerId: "assistant", content: "好的,我会记住你在做 Rust 项目。" },
97
- ],
98
- {
99
- userId: "user-bob",
100
- chatId: "agent-dev",
101
- sessionId: "session-001",
102
- sessionTitle: "Bob 的开发对话",
103
- }
104
- );
105
-
106
- const results = await memory.search({
107
- query: "Bob 正在做什么项目?",
108
- scope: "user",
109
- scopeId: "user-bob",
110
- mode: "auto",
111
- });
112
-
113
- console.log(results);
114
-
115
- const answer = await memory.ask({
116
- query: "Bob 正在做什么项目?",
117
- scope: "user",
118
- scopeId: "user-bob",
119
- maxChars: 120,
120
- });
121
-
122
- console.log(answer);
123
-
124
- memory.destroy();
125
- ```
126
-
127
- ## Complete Example
128
-
129
- ```ts
130
- import { MemoryManager } from "@ppagent/memory";
131
- import type { Entity, Relation } from "@ppagent/memory";
132
-
133
- const memory = new MemoryManager({
134
- lancedbPath: "./data/memory/lance",
135
- grafeoPath: "./data/memory/grafeo",
136
-
137
- llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
138
- llmApiKey: process.env.LLM_API_KEY ?? "",
139
- llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
140
-
141
- embeddingBaseUrl: process.env.EMBED_BASE_URL ?? "",
142
- embeddingApiKey: process.env.EMBED_API_KEY ?? "",
143
- embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
144
- embeddingDimension: 1536,
145
-
146
- httpTimeoutMs: 60_000,
147
- httpMaxRetries: 2,
148
- embeddingBatchSize: 20,
149
- embeddingConcurrency: 2,
150
-
151
- sessionTokenLimit: 16_386,
152
- historyWindowTokenLimit: 10_240,
153
- topicRatio: [1, 5, 20],
154
- detailMaxTokens: 2048,
155
- summaryMaxTokens: 512,
156
- conciseMaxTokens: 128,
69
+ ```
70
+
71
+ Most consumers should only instantiate `MemoryManager`. `MemoryStore` (plus the `VectorStoreProvider` / `Filter` types) is exported for advanced storage tests or custom infrastructure work. Breaking change in 0.2.0: `LanceService` was replaced by `MemoryStore` + provider abstraction; string SQL filters were replaced by structured `Filter` objects.
72
+
73
+ ## Minimal Example
74
+
75
+ ```ts
76
+ import { MemoryManager } from "@ppagent/memory";
77
+
78
+ const memory = new MemoryManager({
79
+ lancedbPath: "./data/memory/lance",
80
+ grafeoPath: "./data/memory/grafeo",
81
+
82
+ llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
83
+ llmApiKey: process.env.LLM_API_KEY ?? "",
84
+ llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
85
+
86
+ embeddingBaseUrl: process.env.EMBED_BASE_URL ?? process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
87
+ embeddingApiKey: process.env.EMBED_API_KEY ?? process.env.LLM_API_KEY ?? "",
88
+ embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
89
+ embeddingDimension: Number(process.env.EMBED_DIMENSION ?? 1536),
90
+ });
91
+
92
+ await memory.init();
93
+
94
+ await memory.updateChat(
95
+ [
96
+ { talkerId: "user", content: "我叫 Bob,正在做一个 Rust 项目。" },
97
+ { talkerId: "assistant", content: "好的,我会记住你在做 Rust 项目。" },
98
+ ],
99
+ {
100
+ userId: "user-bob",
101
+ chatId: "agent-dev",
102
+ sessionId: "session-001",
103
+ sessionTitle: "Bob 的开发对话",
104
+ }
105
+ );
106
+
107
+ const results = await memory.search({
108
+ query: "Bob 正在做什么项目?",
109
+ scope: "user",
110
+ scopeId: "user-bob",
111
+ mode: "auto",
112
+ });
113
+
114
+ console.log(results);
115
+
116
+ const answer = await memory.ask({
117
+ query: "Bob 正在做什么项目?",
118
+ scope: "user",
119
+ scopeId: "user-bob",
120
+ maxChars: 120,
121
+ });
122
+
123
+ console.log(answer);
124
+
125
+ await memory.destroy();
126
+ ```
127
+
128
+ ## Complete Example
129
+
130
+ ```ts
131
+ import { MemoryManager } from "@ppagent/memory";
132
+ import type { Entity, Relation } from "@ppagent/memory";
133
+
134
+ const memory = new MemoryManager({
135
+ lancedbPath: "./data/memory/lance",
136
+ grafeoPath: "./data/memory/grafeo",
137
+
138
+ llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
139
+ llmApiKey: process.env.LLM_API_KEY ?? "",
140
+ llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
141
+
142
+ embeddingBaseUrl: process.env.EMBED_BASE_URL ?? "",
143
+ embeddingApiKey: process.env.EMBED_API_KEY ?? "",
144
+ embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
145
+ embeddingDimension: 1536,
146
+
147
+ httpTimeoutMs: 60_000,
148
+ httpMaxRetries: 2,
149
+ embeddingBatchSize: 20,
150
+ embeddingConcurrency: 2,
151
+
152
+ compressedContextTokenLimit: 16 * 1024,
153
+ contextUsageRatio: 0.75,
154
+ precompressionRatio: 0.75,
155
+ compressionBatchRatio: 0.5,
156
+ compressionBatchTokenLimit: 0,
157
+ topicSummaryMaxTokens: 2048,
158
+ defaultModelContextTokens: 256 * 1024,
159
+ maxHistoryAgeMs: 0,
160
+ sessionIdleTtlMs: 30 * 60_000,
161
+ sessionSweepIntervalMs: 60_000,
157
162
  maxConcurrentCompressions: 3,
158
- entitySimilarityThreshold: 0.92,
159
- defaultSearchLimit: 10,
160
- recallBoostMs: 3_600_000,
161
-
162
- chunkStrategy: "markdown-heading",
163
- chunkMaxTokens: 800,
164
- chunkOverlap: 0,
165
- knowledgeTopK: 8,
166
- docCoarseTopK: 5,
167
- buildGraphDefault: "auto",
168
- chunkRedundantIds: true,
169
- knowledgeGraphTriggerScore: 0.78,
170
- knowledgeGraphEntityTopK: 10,
171
- knowledgeGraphAnchorTopK: 3,
172
- knowledgeGraphHopLimit: 10,
173
- graphExtractConcurrency: 2,
174
- graphBuildTimeoutMs: 120_000,
175
- });
176
-
177
- await memory.init();
178
-
179
- const userId = "u-001";
180
- const chatId = "agent-work";
181
- const sessionId = "s-2026-06-12";
182
-
183
- await memory.updateChat(
184
- [
185
- {
186
- talkerId: "user",
187
- content: "我叫 Alice,在 ChatMe 项目中负责后端架构。",
188
- metadata: { source: "chat" },
189
- },
190
- {
191
- talkerId: "assistant",
192
- content: "明白,我会记住你负责 ChatMe 后端架构。",
193
- },
194
- ],
195
- {
196
- userId,
197
- chatId,
198
- sessionId,
199
- sessionTitle: "ChatMe 架构讨论",
200
- sessionMetadata: { product: "ChatMe" },
201
- }
202
- );
203
-
204
- await memory.updateFacts(
205
- "Alice 负责 ChatMe 项目的后端架构",
206
- "user",
207
- userId,
208
- chatId,
209
- sessionId
210
- );
211
-
212
- const entities: Entity[] = [
213
- { name: "Alice", type: "Person", meta: { role: "backend architect", userId, chatId, sessionId } },
214
- { name: "ChatMe", type: "Project", meta: { userId, chatId, sessionId } },
215
- { name: "TypeScript", type: "Technology", meta: { userId, chatId, sessionId } },
216
- ];
217
-
218
- const relations: Relation[] = [
219
- { from: "Alice", to: "ChatMe", type: "works_on", meta: { userId, chatId, sessionId } },
220
- { from: "ChatMe", to: "TypeScript", type: "built_with", meta: { userId, chatId, sessionId } },
221
- ];
222
-
223
- await memory.updateEntity(entities, relations, { userId, chatId, sessionId });
224
-
225
- await memory.flushChat(sessionId, { wait: true });
226
-
227
- const remembered = await memory.search({
228
- query: "Alice 在 ChatMe 中负责什么?",
229
- scope: "user",
230
- scopeId: userId,
231
- mode: "all",
232
- limit: 5,
233
- });
234
-
235
- console.log(remembered);
236
-
237
- const doc = await memory.addDocument({
238
- content: `# ChatMe 技术说明
239
-
240
- ChatMe 使用 TypeScript 构建,后端包含长期记忆系统。
241
-
242
- ## Memory
243
-
244
- 长期记忆系统使用向量存储(LanceDB 或 SQLite,自动探测)存储向量,使用 Grafeo 存储知识图谱。`,
245
- userId,
246
- chatId,
247
- sessionId,
248
- sourceName: "chatme-memory.md",
249
- metadata: { product: "ChatMe" },
250
- buildGraph: true,
251
- wait: true,
252
- waitGraph: true,
253
- });
254
-
255
- const knowledge = await memory.searchKnowledge({
256
- query: "ChatMe 的长期记忆系统用了什么存储?",
257
- scope: "chat",
258
- scopeId: chatId,
259
- mode: "auto",
260
- });
261
-
262
- console.log(doc.docId, knowledge);
263
-
264
- const answer = await memory.ask({
265
- query: "ChatMe 的长期记忆系统用了什么存储?",
266
- scope: "chat",
267
- scopeId: chatId,
268
- includeKnowledge: true,
269
- maxChars: 200,
270
- });
271
-
272
- console.log(answer);
273
-
274
- memory.destroy();
275
- ```
276
-
277
- ## Configuration Reference
278
-
279
- `MemoryManager` accepts a `MemoryConfig` object.
280
-
281
- Required storage fields:
282
-
283
- - `lancedbPath: string` - local LanceDB directory (used when the lancedb backend is active; its parent directory also hosts the backend marker file `.memory-provider`).
284
- - `grafeoPath: string` - Grafeo database path.
285
-
286
- Optional storage fields:
287
-
288
- - `provider?: "auto" | "lancedb" | "sqlite"` - vector store backend. Default `"auto"`: probes the @lancedb/lancedb native binding at init and falls back to sqlite when unavailable (e.g. Intel macOS, musl without prebuilds). The first resolved backend is persisted in a marker file; auto mode never silently switches an existing data directory to a different backend (it throws with migration guidance instead).
289
- - `sqlitePath?: string` - SQLite database file for the sqlite backend. Default: `path.join(path.dirname(lancedbPath), "memory.sqlite3")`, i.e. a `memory.sqlite3` file in the parent directory of `lancedbPath`. Parent directories are created automatically on init.
290
-
291
- Storage layout example with `lancedbPath: "./data/memory/lance"` and defaults:
292
-
293
- ```
294
- data/memory/
295
- ├── .memory-provider # backend marker written on first init ("lancedb" or "sqlite")
296
- ├── lance/ # LanceDB tables (lancedb backend only)
297
- └── memory.sqlite3 # SQLite database (sqlite backend only; plus -wal/-shm files while open)
298
- ```
299
-
300
- Backend data files are not interchangeable; switching backends requires an explicit export/import migration.
301
-
302
- Required LLM fields:
303
-
304
- - `llmBaseUrl: string` - OpenAI-compatible base URL, for example `https://api.openai.com/v1`.
305
- - `llmApiKey: string` - API key. Never hard-code real secrets in examples.
306
- - `llmModel: string` - chat model used for summaries, graph decisions, entity extraction, and final `ask` answers.
307
-
308
- Required embedding fields:
309
-
310
- - `embeddingBaseUrl: string` - OpenAI-compatible embedding base URL.
311
- - `embeddingApiKey: string` - embedding API key.
312
- - `embeddingModel: string` - embedding model name.
313
- - `embeddingDimension: number` - vector dimension. This must match the embedding model and the existing vector store schema.
314
-
315
- Embedding fallback behavior:
316
-
317
- - If `embeddingBaseUrl` is empty, it falls back to `llmBaseUrl` and logs a warning.
318
- - If `embeddingApiKey` is empty, it falls back to `llmApiKey` and logs a warning.
319
- - This is convenient when one provider serves both chat and embeddings.
320
-
321
- HTTP and retry fields:
322
-
323
- - `httpTimeoutMs?: number` - timeout per LLM or embedding HTTP attempt. Default: `60000`.
324
- - `httpMaxRetries?: number` - retry count after the first attempt for network errors, timeout/abort, HTTP `429`, and HTTP `5xx`. Default: `2`.
325
- - `embeddingBatchSize?: number` - max texts per embedding request. Default: `20`.
326
- - `embeddingConcurrency?: number` - concurrent embedding batches. Default: `2`.
327
-
163
+ entitySimilarityThreshold: 0.92,
164
+ defaultSearchLimit: 10,
165
+
166
+ chunkStrategy: "markdown-heading",
167
+ chunkMaxTokens: 800,
168
+ chunkOverlap: 0,
169
+ knowledgeTopK: 8,
170
+ docCoarseTopK: 5,
171
+ buildGraphDefault: "auto",
172
+ chunkRedundantIds: true,
173
+ knowledgeGraphTriggerScore: 0.78,
174
+ knowledgeGraphEntityTopK: 10,
175
+ knowledgeGraphAnchorTopK: 3,
176
+ knowledgeGraphHopLimit: 10,
177
+ graphExtractConcurrency: 2,
178
+ graphBuildTimeoutMs: 120_000,
179
+ });
180
+
181
+ await memory.init();
182
+
183
+ const userId = "u-001";
184
+ const chatId = "agent-work";
185
+ const sessionId = "s-2026-06-12";
186
+
187
+ await memory.updateChat(
188
+ [
189
+ {
190
+ talkerId: "user",
191
+ content: "我叫 Alice,在 ChatMe 项目中负责后端架构。",
192
+ metadata: { source: "chat" },
193
+ },
194
+ {
195
+ talkerId: "assistant",
196
+ content: "明白,我会记住你负责 ChatMe 后端架构。",
197
+ },
198
+ ],
199
+ {
200
+ userId,
201
+ chatId,
202
+ sessionId,
203
+ sessionTitle: "ChatMe 架构讨论",
204
+ sessionMetadata: { product: "ChatMe" },
205
+ }
206
+ );
207
+
208
+ await memory.updateFacts(
209
+ "Alice 负责 ChatMe 项目的后端架构",
210
+ "user",
211
+ userId,
212
+ chatId,
213
+ sessionId
214
+ );
215
+
216
+ const entities: Entity[] = [
217
+ { name: "Alice", type: "Person", meta: { role: "backend architect", userId, chatId, sessionId } },
218
+ { name: "ChatMe", type: "Project", meta: { userId, chatId, sessionId } },
219
+ { name: "TypeScript", type: "Technology", meta: { userId, chatId, sessionId } },
220
+ ];
221
+
222
+ const relations: Relation[] = [
223
+ { from: "Alice", to: "ChatMe", type: "works_on", meta: { userId, chatId, sessionId } },
224
+ { from: "ChatMe", to: "TypeScript", type: "built_with", meta: { userId, chatId, sessionId } },
225
+ ];
226
+
227
+ await memory.updateEntity(entities, relations, { userId, chatId, sessionId });
228
+
229
+ await memory.flushChat(sessionId, { wait: true });
230
+
231
+ const remembered = await memory.search({
232
+ query: "Alice ChatMe 中负责什么?",
233
+ scope: "user",
234
+ scopeId: userId,
235
+ mode: "all",
236
+ limit: 5,
237
+ });
238
+
239
+ console.log(remembered);
240
+
241
+ const doc = await memory.addDocument({
242
+ content: `# ChatMe 技术说明
243
+
244
+ ChatMe 使用 TypeScript 构建,后端包含长期记忆系统。
245
+
246
+ ## Memory
247
+
248
+ 长期记忆系统使用向量存储(LanceDB 或 SQLite,自动探测)存储向量,使用 Grafeo 存储知识图谱。`,
249
+ userId,
250
+ chatId,
251
+ sessionId,
252
+ sourceName: "chatme-memory.md",
253
+ metadata: { product: "ChatMe" },
254
+ buildGraph: true,
255
+ wait: true,
256
+ waitGraph: true,
257
+ });
258
+
259
+ const knowledge = await memory.searchKnowledge({
260
+ query: "ChatMe 的长期记忆系统用了什么存储?",
261
+ scope: "chat",
262
+ scopeId: chatId,
263
+ mode: "auto",
264
+ });
265
+
266
+ console.log(doc.docId, knowledge);
267
+
268
+ const answer = await memory.ask({
269
+ query: "ChatMe 的长期记忆系统用了什么存储?",
270
+ scope: "chat",
271
+ scopeId: chatId,
272
+ includeKnowledge: true,
273
+ maxChars: 200,
274
+ });
275
+
276
+ console.log(answer);
277
+
278
+ await memory.destroy();
279
+ ```
280
+
281
+ ## Configuration Reference
282
+
283
+ `MemoryManager` accepts a `MemoryConfig` object.
284
+
285
+ Required storage fields:
286
+
287
+ - `lancedbPath: string` - local LanceDB directory (used when the lancedb backend is active; its parent directory also hosts the backend marker file `.memory-provider`).
288
+ - `grafeoPath: string` - Grafeo database path.
289
+
290
+ Optional storage fields:
291
+
292
+ - `provider?: "auto" | "lancedb" | "sqlite"` - vector store backend. Default `"auto"`: probes the @lancedb/lancedb native binding at init and falls back to sqlite when unavailable (e.g. Intel macOS, musl without prebuilds). The first resolved backend is persisted in a marker file; auto mode never silently switches an existing data directory to a different backend (it throws with migration guidance instead).
293
+ - `sqlitePath?: string` - SQLite database file for the sqlite backend. Default: `path.join(path.dirname(lancedbPath), "memory.sqlite3")`, i.e. a `memory.sqlite3` file in the parent directory of `lancedbPath`. Parent directories are created automatically on init.
294
+
295
+ Storage layout example with `lancedbPath: "./data/memory/lance"` and defaults:
296
+
297
+ ```
298
+ data/memory/
299
+ ├── .memory-provider # backend marker written on first init ("lancedb" or "sqlite")
300
+ ├── lance/ # LanceDB tables (lancedb backend only)
301
+ └── memory.sqlite3 # SQLite database (sqlite backend only; plus -wal/-shm files while open)
302
+ ```
303
+
304
+ Backend data files are not interchangeable; switching backends requires an explicit export/import migration.
305
+
306
+ Required LLM fields:
307
+
308
+ - `llmBaseUrl: string` - OpenAI-compatible base URL, for example `https://api.openai.com/v1`.
309
+ - `llmApiKey: string` - API key. Never hard-code real secrets in examples.
310
+ - `llmModel: string` - chat model used for summaries, graph decisions, entity extraction, and final `ask` answers.
311
+
312
+ Required embedding fields:
313
+
314
+ - `embeddingBaseUrl: string` - OpenAI-compatible embedding base URL.
315
+ - `embeddingApiKey: string` - embedding API key.
316
+ - `embeddingModel: string` - embedding model name.
317
+ - `embeddingDimension: number` - vector dimension. This must match the embedding model and the existing vector store schema.
318
+
319
+ Embedding fallback behavior:
320
+
321
+ - If `embeddingBaseUrl` is empty, it falls back to `llmBaseUrl` and logs a warning.
322
+ - If `embeddingApiKey` is empty, it falls back to `llmApiKey` and logs a warning.
323
+ - This is convenient when one provider serves both chat and embeddings.
324
+
325
+ HTTP and retry fields:
326
+
327
+ - `httpTimeoutMs?: number` - timeout per LLM or embedding HTTP attempt. Default: `60000`.
328
+ - `httpMaxRetries?: number` - retry count after the first attempt for network errors, timeout/abort, HTTP `429`, and HTTP `5xx`. Default: `2`.
329
+ - `embeddingBatchSize?: number` - max texts per embedding request. Default: `20`.
330
+ - `embeddingConcurrency?: number` - concurrent embedding batches. Default: `2`.
331
+
328
332
  Conversation memory fields:
329
333
 
330
- - `sessionTokenLimit?: number` - when a session cache reaches this token count, compression is triggered. Default: `16386`.
331
- - `historyWindowTokenLimit?: number` - maximum token budget for the rebuilt compressed history window. Default: `10240`.
332
- - `topicRatio?: [number, number, number]` - number ratio for detail/summary/concise topics in the history window. Default: `[1, 5, 20]`.
333
- - `detailMaxTokens?: number` - max detailed summary tokens requested from the LLM. Default: `2048`.
334
- - `summaryMaxTokens?: number` - max medium summary tokens requested from the LLM. Default: `512`.
335
- - `conciseMaxTokens?: number` - max concise summary tokens requested from the LLM. Default: `128`.
334
+ - `compressedContextTokenLimit?: number` - fixed token budget for compressed Topics. Default: `16384`; startup logs a warning above `32768`.
335
+ - `contextUsageRatio?: number` - fraction of the current model context available to conversation history. Default: `0.75`.
336
+ - `precompressionRatio?: number` - start background raw-message compression when raw usage reaches this fraction of its dynamic budget. Default: `0.75`.
337
+ - `compressionBatchRatio?: number` - target fraction of the raw-message budget compressed in one batch. Default: `0.5`.
338
+ - `compressionBatchTokenLimit?: number` - optional hard limit for one compression batch. Default: `0` (ratio only).
339
+ - `topicSummaryMaxTokens?: number` - hard maximum for one Topic summary. The LLM uses less for low-value chat and more for facts, experience, decisions, preferences, constraints, and reusable knowledge. Default: `2048`.
340
+ - `defaultModelContextTokens?: number` - used when `getHistoryWindow` receives no model size; a warning is logged. Default: `262144`.
341
+ - `maxHistoryAgeMs?: number` - maximum Topic and raw-message age restored during lazy cold start. Default: `0` (all history).
342
+ - `sessionIdleTtlMs?: number` - idle time before a session cache is released. Persistent data is not deleted. Default: `1800000`.
343
+ - `sessionSweepIntervalMs?: number` - idle cache sweep interval. Default: `60000`.
336
344
  - `maxConcurrentCompressions?: number` - global semaphore limit for concurrent compression and knowledge ingestion tasks. Default: `3`.
337
345
  - `entitySimilarityThreshold?: number` - graph entity similarity threshold. Default: `0.92`.
338
346
  - `defaultSearchLimit?: number` - default `search` result limit. Default: `10`.
339
- - `recallBoostMs?: number` - each topic recall count acts like this many milliseconds of freshness boost. Default: `3_600_000`.
340
-
341
- Knowledge-base fields:
342
-
343
- - `chunkStrategy?: "markdown-heading"` - current chunking strategy. Default: `"markdown-heading"`.
344
- - `chunkMaxTokens?: number` - max tokens per chunk; long heading sections are split again. Default: `800`.
345
- - `chunkOverlap?: number` - overlap tokens between chunks. Default: `0`.
346
- - `knowledgeTopK?: number` - default `searchKnowledge` chunk return count. Default: `8`.
347
- - `docCoarseTopK?: number` - document-level coarse recall count before chunk search. Set `0` to disable coarse recall. Default: `5`.
348
- - `buildGraphDefault?: boolean | "auto"` - default graph build behavior for `addDocument`. Default: `"auto"`.
349
- - `chunkRedundantIds?: boolean` - whether chunks redundantly store `userId`, `chatId`, and `sessionId` for direct filtering. Default: `true`.
350
- - `knowledgeGraphTriggerScore?: number` - in `mode: "auto"`, graph expansion only starts when top chunk cosine score reaches this threshold. Default: `0.78`.
351
- - `knowledgeGraphEntityTopK?: number` - entity vector search candidate count for knowledge graph expansion. Default: `10`.
352
- - `knowledgeGraphAnchorTopK?: number` - top entity anchors used for one-hop expansion. Default: `3`.
353
- - `knowledgeGraphHopLimit?: number` - max one-hop relations per anchor. Default: `10`.
354
- - `graphExtractConcurrency?: number` - concurrent chunk-level entity extraction tasks during document graph construction. Default: `2`.
355
- - `graphBuildTimeoutMs?: number` - timeout used only when a caller explicitly waits for graph construction with `waitGraph: true`. Default: `120000`.
356
-
357
- ## Critical Async And Wait Semantics
358
-
359
- The package intentionally separates durable base writes from expensive graph construction.
360
347
 
348
+ `sessionTokenLimit`, `historyWindowTokenLimit`, `topicRatio`, `detailMaxTokens`, and `conciseMaxTokens` remain only as deprecated 0.3.x configuration/read compatibility fields. New code should not use them.
349
+
350
+ Knowledge-base fields:
351
+
352
+ - `chunkStrategy?: "markdown-heading"` - current chunking strategy. Default: `"markdown-heading"`.
353
+ - `chunkMaxTokens?: number` - max tokens per chunk; long heading sections are split again. Default: `800`.
354
+ - `chunkOverlap?: number` - overlap tokens between chunks. Default: `0`.
355
+ - `knowledgeTopK?: number` - default `searchKnowledge` chunk return count. Default: `8`.
356
+ - `docCoarseTopK?: number` - document-level coarse recall count before chunk search. Set `0` to disable coarse recall. Default: `5`.
357
+ - `buildGraphDefault?: boolean | "auto"` - default graph build behavior for `addDocument`. Default: `"auto"`.
358
+ - `chunkRedundantIds?: boolean` - whether chunks redundantly store `userId`, `chatId`, and `sessionId` for direct filtering. Default: `true`.
359
+ - `knowledgeGraphTriggerScore?: number` - in `mode: "auto"`, graph expansion only starts when top chunk cosine score reaches this threshold. Default: `0.78`.
360
+ - `knowledgeGraphEntityTopK?: number` - entity vector search candidate count for knowledge graph expansion. Default: `10`.
361
+ - `knowledgeGraphAnchorTopK?: number` - top entity anchors used for one-hop expansion. Default: `3`.
362
+ - `knowledgeGraphHopLimit?: number` - max one-hop relations per anchor. Default: `10`.
363
+ - `graphExtractConcurrency?: number` - concurrent chunk-level entity extraction tasks during document graph construction. Default: `2`.
364
+ - `graphBuildTimeoutMs?: number` - timeout used only when a caller explicitly waits for graph construction with `waitGraph: true`. Default: `120000`.
365
+
366
+ ## Critical Async And Wait Semantics
367
+
368
+ The package intentionally separates durable base writes from expensive graph construction.
369
+
361
370
  `updateChat(messages, opts)`:
362
371
 
363
- - Embeds and writes raw messages to the vector store before resolving.
372
+ - Registers its write synchronously, so a caller may intentionally fire-and-forget it and a following `getHistoryWindow` will still wait for that write.
373
+ - Calculates tokens over `content`, `parts`, `payload`, and `metadata`, embeds the searchable text, and upserts raw messages by stable `messageId` before resolving.
364
374
  - Creates or updates the session record.
365
375
  - Updates the in-memory session cache.
366
- - If `sessionTokenLimit` is exceeded, compression is triggered in the background.
367
- - Background compression may create topics/history and graph entities later.
368
-
376
+ - Once `getHistoryWindow` has supplied the current model size, reaching the precompression threshold starts background compression.
377
+ - Topic storage and cache replacement finish before conversation graph extraction; graph work remains asynchronous.
378
+
369
379
  `flushChat(sessionId?, opts?)`:
370
-
371
- - Forces compression for the current cached messages even if `sessionTokenLimit` was not reached.
372
- - `wait: true` waits for topic creation, cache clearing, and history-window rebuild.
373
- - `wait: true` does not wait for graph extraction/persistence.
374
- - `waitGraph: true` waits for graph extraction/persistence too.
380
+
381
+ - Forces compression for the current cached raw messages even if the dynamic precompression threshold was not reached.
382
+ - `wait: true` waits for topic creation, cache clearing, and history-window rebuild.
383
+ - `wait: true` does not wait for graph extraction/persistence.
384
+ - `waitGraph: true` waits for graph extraction/persistence too.
375
385
  - When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
376
386
 
377
- `addDocument(opts)`:
378
-
379
- - Writes the document row first.
380
- - Chunk embedding and chunk insertion run through the ingestion pipeline.
381
- - Graph construction, when enabled, runs after chunks are ready.
382
- - With neither `wait` nor `waitGraph`, the method returns after document storage; chunks and graph are background work.
383
- - `wait: true` waits until chunks are inserted and searchable.
384
- - `wait: true` does not wait for graph construction.
385
- - `waitGraph: true` implies waiting for chunks and then waits for graph construction.
386
- - When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
387
-
388
- Use `wait: true` when the next line of code must immediately call `searchKnowledge` and find chunks. Use `waitGraph: true` only when `hasGraph` or graph hits must be ready immediately.
389
-
390
- ## Data Model
391
-
392
- `RawMessage` is input to `updateChat`:
393
-
394
- - `messageId?: string` - generated with UUID if omitted.
395
- - `talkerId?: string` - speaker id. Default: `"user"`.
396
- - `chatId?: string` - conversation/chat/agent id.
397
- - `userId?: string` - owner user id.
398
- - `sessionId?: string` - session id.
399
- - `type?: "text" | "image" | "file"` - default: `"text"`.
400
- - `content: string` - required plain text used for embedding and retrieval.
387
+ `getHistoryWindow(sessionId, modelContextTokens?)`:
388
+
389
+ - Lazily hydrates the session from persistent Topics plus raw messages after the newest Topic boundary.
390
+ - Waits for registered message writes. If the hard raw budget is exceeded, it also waits for or starts compression until the returned context fits.
391
+ - Returns `{ compressedContext, recentMessages, usage }`. `recentMessages` preserves the `RawMessage` input shape, including `messageId`, `parts`, `payload`, `metadata`, and `createdAt`.
392
+ - Topic budget is fixed by manager configuration; the raw-message budget is recalculated from the supplied model context size.
393
+ - If accumulated Topics exceed their fixed budget, the oldest approximately half are summarized into one Topic. This Topic-to-Topic rollup does not rebuild graph data.
394
+
395
+ `addDocument(opts)`:
396
+
397
+ - Writes the document row first.
398
+ - Chunk embedding and chunk insertion run through the ingestion pipeline.
399
+ - Graph construction, when enabled, runs after chunks are ready.
400
+ - With neither `wait` nor `waitGraph`, the method returns after document storage; chunks and graph are background work.
401
+ - `wait: true` waits until chunks are inserted and searchable.
402
+ - `wait: true` does not wait for graph construction.
403
+ - `waitGraph: true` implies waiting for chunks and then waits for graph construction.
404
+ - When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
405
+
406
+ Use `wait: true` when the next line of code must immediately call `searchKnowledge` and find chunks. Use `waitGraph: true` only when `hasGraph` or graph hits must be ready immediately.
407
+
408
+ ## Data Model
409
+
410
+ `RawMessage` is input to `updateChat`:
411
+
412
+ - `messageId?: string` - generated with UUID if omitted.
413
+ - `talkerId?: string` - speaker id. Default: `"user"`.
414
+ - `chatId?: string` - conversation/chat/agent id.
415
+ - `userId?: string` - owner user id.
416
+ - `sessionId?: string` - session id.
417
+ - `type?: "text" | "image" | "file"` - default: `"text"`.
418
+ - `content: string` - required plain text used for embedding and retrieval.
401
419
  - `parts?: ContentPart[]` - optional multimodal content parts; stored serialized.
420
+ - `payload?: unknown` - host-framework message payload stored and returned without interpretation.
402
421
  - `usage?: number` - token count; estimated with `tiktoken` if omitted.
403
- - `metadata?: Record<string, unknown>` - custom metadata stored as JSON.
404
- - `createdAt?: number` - Unix milliseconds; default is current time.
405
-
406
- `ContentPart` aligns with common multimodal content shapes:
407
-
408
- - Text: `{ type: "text", text: "..." }`
409
- - Image: `{ type: "image_url", image_url: { url: "https://..." } }`
410
- - File: `{ type: "file_url", file_url: { url: "https://...", name: "file.pdf" } }`
411
-
412
- `StoredMessage` is persisted message output with required ids, serialized `parts`, serialized `metadata`, generated vector, and `createdAt`.
413
-
422
+ - `metadata?: Record<string, unknown>` - custom metadata stored as JSON.
423
+ - `createdAt?: number` - Unix milliseconds; default is current time.
424
+
425
+ `ContentPart` aligns with common multimodal content shapes:
426
+
427
+ - Text: `{ type: "text", text: "..." }`
428
+ - Image: `{ type: "image_url", image_url: { url: "https://..." } }`
429
+ - File: `{ type: "file_url", file_url: { url: "https://...", name: "file.pdf" } }`
430
+
431
+ `StoredMessage` is persisted message output with required ids, serialized `parts`, serialized `metadata`, generated vector, and `createdAt`.
432
+
414
433
  `Topic` is a compressed memory item:
415
434
 
416
435
  - `title` - short topic title.
417
- - `detail` - rich summary.
418
- - `summary` - medium summary.
419
- - `concise` - short summary.
436
+ - `summary` - the only compressed body; its actual length is importance-aware and bounded by `topicSummaryMaxTokens`.
437
+ - `tokens` - summary token count stored when the Topic is created.
438
+ - `startMessageId` / `endMessageId` - exact raw-message boundaries, in addition to timestamps.
420
439
  - `startTime` and `endTime` - covered message time range.
421
- - `recallCount` - affects freshness ranking through `recallBoostMs`.
422
-
440
+ - `recallCount` - incremented best-effort when search recalls the Topic; it is not used to reorder the continuous history window.
441
+
423
442
  `Fact` is a user/chat scoped manual memory:
424
-
425
- - `level: "user" | "chat"`
426
- - `userId`, `chatId`, optional `sessionId`
443
+
444
+ - `level: "user" | "chat"`
445
+ - `userId`, `chatId`, optional `sessionId`
427
446
  - `content`
428
- - `createdAt`
429
-
430
- `Entity` and `Relation` are graph records:
431
-
432
- - Entity: `{ name, type, meta }`
433
- - Relation: `{ from, to, type, happenedAt?, meta }`
434
- - `meta` should usually include `userId`, `chatId`, `sessionId`, and source details.
435
-
436
- `SessionView` is the in-memory session representation:
437
-
438
- - `sessionId`
439
- - `chatId`
440
- - `userId`
441
- - `title`
442
- - `metadata: Record<string, unknown>`
447
+ - optional stable `key` for immediate cache + database upsert
443
448
  - `createdAt`
444
449
  - `updatedAt`
445
-
446
- `Document` is a knowledge-base document row:
447
-
448
- - `docId`
449
- - `userId`, `chatId`, `sessionId`
450
- - `title`, `sourceName`
451
- - `fullContent`
452
- - `contentHash`
453
- - `summary`
454
- - `summaryVector`
455
- - `chunkCount`
456
- - `hasGraph`
457
- - `metadata`
458
- - `createdAt`, `updatedAt`
459
-
460
- `Chunk` is a knowledge-base chunk row:
461
-
462
- - `chunkId`
463
- - `docId`
464
- - `content`
465
- - `headingPath`
466
- - `ordinal`
467
- - `tokens`
468
- - `vector`
469
- - optional redundant domain ids depending on `chunkRedundantIds`
470
-
471
- ## Scope And Search Modes
472
-
473
- Scopes:
474
-
475
- - `scope: "session"` - filter by `sessionId`; pass `scopeId`.
476
- - `scope: "chat"` - filter by `chatId`; pass `scopeId`.
477
- - `scope: "user"` - filter by `userId`; pass `scopeId`.
478
- - `scope: "all"` - no domain filter; `scopeId` is ignored.
479
-
450
+
451
+ `Entity` and `Relation` are graph records:
452
+
453
+ - Entity: `{ name, type, meta }`
454
+ - Relation: `{ from, to, type, happenedAt?, meta }`
455
+ - `meta` should usually include `userId`, `chatId`, `sessionId`, and source details.
456
+
457
+ `SessionView` is the in-memory session representation:
458
+
459
+ - `sessionId`
460
+ - `chatId`
461
+ - `userId`
462
+ - `title`
463
+ - `metadata: Record<string, unknown>`
464
+ - `createdAt`
465
+ - `updatedAt`
466
+
467
+ `Document` is a knowledge-base document row:
468
+
469
+ - `docId`
470
+ - `userId`, `chatId`, `sessionId`
471
+ - `title`, `sourceName`
472
+ - `fullContent`
473
+ - `contentHash`
474
+ - `summary`
475
+ - `summaryVector`
476
+ - `chunkCount`
477
+ - `hasGraph`
478
+ - `metadata`
479
+ - `createdAt`, `updatedAt`
480
+
481
+ `Chunk` is a knowledge-base chunk row:
482
+
483
+ - `chunkId`
484
+ - `docId`
485
+ - `content`
486
+ - `headingPath`
487
+ - `ordinal`
488
+ - `tokens`
489
+ - `vector`
490
+ - optional redundant domain ids depending on `chunkRedundantIds`
491
+
492
+ ## Scope And Search Modes
493
+
494
+ Scopes:
495
+
496
+ - `scope: "session"` - filter by `sessionId`; pass `scopeId`.
497
+ - `scope: "chat"` - filter by `chatId`; pass `scopeId`.
498
+ - `scope: "user"` - filter by `userId`; pass `scopeId`.
499
+ - `scope: "all"` - no domain filter; `scopeId` is ignored.
500
+
480
501
  Search modes:
481
-
482
- - `mode: "fast"` - only vector-store hybrid/vector retrieval. No graph search.
483
- - `mode: "auto"` - conversation search asks the LLM whether graph search is needed; knowledge search triggers graph expansion only when top chunk score is high enough.
502
+
503
+ - `mode: "fast"` - only vector-store hybrid/vector retrieval. No graph search.
504
+ - `mode: "auto"` - conversation search asks the LLM whether graph search is needed; knowledge search triggers graph expansion only when top chunk score is high enough.
484
505
  - `mode: "all"` - include graph search and merge graph results with vector/full-text results.
485
506
 
486
- For deterministic low-latency calls, use `fast`. For richer entity relationship answers, use `auto` or `all`.
487
-
488
- ## API Reference
489
-
490
- ### `new MemoryManager(config)`
491
-
492
- Creates the manager and resolves defaults. It does not connect to storage until `init()`.
493
-
507
+ Conversation search always searches both compressed Topics and raw messages, including raw rows already represented by a Topic. For deterministic low-latency calls, use `fast`. For richer entity relationship answers, use `auto` or `all`.
508
+
509
+ ## API Reference
510
+
511
+ ### `new MemoryManager(config)`
512
+
513
+ Creates the manager and resolves defaults. It does not connect to storage until `init()`.
514
+
494
515
  ### `init(): Promise<void>`
495
516
 
496
- Initializes tiktoken, the vector store (backend auto-detection happens here), Grafeo, facts cache, sessions cache, and restored history windows. Always call before using read/write APIs.
497
-
498
- ### `updateChat(messages, opts?): Promise<void>`
499
-
500
- Stores new chat messages.
501
-
502
- Options:
503
-
504
- - `userId?: string`
505
- - `chatId?: string`
506
- - `sessionId?: string`
507
- - `sessionTitle?: string`
508
- - `sessionMetadata?: Record<string, unknown>`
509
-
510
- Defaults for ids are `"default"`.
511
-
512
- Use for every new user/assistant message that should become retrievable memory.
513
-
514
- ### `flushChat(sessionId?, opts?): Promise<void>`
515
-
516
- Forces compression of a session cache.
517
-
518
- Options:
519
-
520
- - `wait?: boolean` - wait for topic/history work.
521
- - `waitGraph?: boolean` - also wait for graph extraction/persistence, bounded by `graphBuildTimeoutMs`.
522
-
523
- If `sessionId` is omitted, `"default"` is used.
524
-
525
- ### `search(opts): Promise<SearchResult[]>`
526
-
527
- Searches conversation memory.
528
-
529
- Options:
530
-
531
- - `query: string`
532
- - `scope?: "session" | "chat" | "user" | "all"`
533
- - `scopeId?: string`
534
- - `mode?: "fast" | "auto" | "all"`
535
- - `limit?: number`
536
-
537
- Returns results with:
538
-
539
- - `type: "message" | "topic" | "entity" | "relation"`
540
- - `content`
541
- - `score`
542
- - `meta`
543
-
544
- ### `ask(opts): Promise<string>`
545
-
546
- Searches memory and asks the configured LLM to summarize the hits into a natural-language answer.
547
-
548
- Options are `SearchOptions` plus:
549
-
550
- - `maxChars?: number`
551
- - `includeKnowledge?: boolean`
552
-
553
- When `includeKnowledge: true`, the method also calls `searchKnowledge` and merges knowledge chunks and graph hits into the answer context.
554
-
555
- ### `updateFacts(content, level, userId, chatId, sessionId?): Promise<void>`
556
-
557
- Adds a fact to persistent storage and in-memory cache.
558
-
559
- - `level` is `"user"` or `"chat"`.
560
- - User facts apply across chats for that user.
561
- - Chat facts apply to a specific chat/agent context.
562
-
563
- ### `getFacts(level, id): Promise<string>`
564
-
565
- Returns facts as newline-separated text in `ISO_TIME:content` format. Use `level: "user"` with `userId`, or `level: "chat"` with `chatId`.
566
-
567
- ### `getFactsForContext(userId, chatId): Promise<string>`
568
-
569
- Returns merged user-level facts for `userId` and chat-level facts for `chatId`, sorted by time. This is useful for injecting stable facts into an agent prompt.
570
-
571
- ### `addFact(content, level, userId, chatId, sessionId?): Promise<void>`
572
-
573
- Alias-style management API for adding a fact manually.
574
-
575
- ### `deleteFact(factId): Promise<boolean>`
576
-
577
- Deletes one fact and returns whether it was found.
578
-
579
- ### `updateEntity(entities, relations, context?): Promise<void>`
580
-
581
- Upserts graph entities and relations. The method embeds entity names and stores graph data in Grafeo.
582
-
583
- Context:
584
-
585
- - `sessionId?: string`
586
- - `chatId?: string`
587
- - `userId?: string`
588
-
589
- ### `getHistoryWindow(sessionId): string`
590
-
591
- Returns the compressed history window for a session. This is prompt-ready text built from recent `Topic` records using `topicRatio` and `historyWindowTokenLimit`.
592
-
593
- ### `getRecentMessages(sessionId, limit): Promise<StoredMessage[]>`
594
-
595
- Returns the globally latest `limit` messages for a session in chronological order (oldest to newest within the returned window). The storage query orders by `createdAt` and uses `messageId` as a deterministic same-timestamp tiebreak before applying the limit, so long sessions do not return an early candidate window.
596
-
597
- ### Session APIs
598
-
599
- - `getSession(sessionId): SessionView | null`
600
- - `getSessionsByUserId(userId, opts?): SessionView[]`
601
- - `getSessionsByChatId(chatId, opts?): SessionView[]`
602
- - `searchSessions(opts): SessionView[]`
603
- - `updateSession(sessionId, opts): Promise<SessionView | null>`
604
- - `deleteSession(sessionId): Promise<boolean>`
605
- - `listSessions(filter?): SessionView[]`
606
-
607
- Session update options:
608
-
609
- - `title?: string`
517
+ Initializes tiktoken, the vector store (backend auto-detection happens here), Grafeo, and the facts cache. Conversation sessions are hydrated lazily on first access and evicted after the configured idle timeout. Always call before using read/write APIs.
518
+
519
+ ### `updateChat(messages, opts?): Promise<void>`
520
+
521
+ Stores new chat messages.
522
+
523
+ Options:
524
+
525
+ - `userId?: string`
526
+ - `chatId?: string`
527
+ - `sessionId?: string`
528
+ - `sessionTitle?: string`
529
+ - `sessionMetadata?: Record<string, unknown>`
530
+
531
+ Defaults for ids are `"default"`.
532
+
533
+ Use for every new user/assistant message that should become retrievable memory.
534
+
535
+ ### `flushChat(sessionId?, opts?): Promise<void>`
536
+
537
+ Forces compression of a session cache.
538
+
539
+ Options:
540
+
541
+ - `wait?: boolean` - wait for topic/history work.
542
+ - `waitGraph?: boolean` - also wait for graph extraction/persistence, bounded by `graphBuildTimeoutMs`.
543
+
544
+ If `sessionId` is omitted, `"default"` is used.
545
+
546
+ ### `search(opts): Promise<SearchResult[]>`
547
+
548
+ Searches conversation memory.
549
+
550
+ Options:
551
+
552
+ - `query: string`
553
+ - `scope?: "session" | "chat" | "user" | "all"`
554
+ - `scopeId?: string`
555
+ - `mode?: "fast" | "auto" | "all"`
556
+ - `limit?: number`
557
+
558
+ Returns results with:
559
+
560
+ - `type: "message" | "topic" | "entity" | "relation"`
561
+ - `content`
562
+ - `score`
563
+ - `meta`
564
+
565
+ ### `ask(opts): Promise<string>`
566
+
567
+ Searches memory and asks the configured LLM to summarize the hits into a natural-language answer.
568
+
569
+ Options are `SearchOptions` plus:
570
+
571
+ - `maxChars?: number`
572
+ - `includeKnowledge?: boolean`
573
+
574
+ When `includeKnowledge: true`, the method also calls `searchKnowledge` and merges knowledge chunks and graph hits into the answer context.
575
+
576
+ ### `updateFacts(content, level, userId, chatId, sessionId?): Promise<void>`
577
+
578
+ Adds a fact to persistent storage and in-memory cache.
579
+
580
+ - `level` is `"user"` or `"chat"`.
581
+ - User facts apply across chats for that user.
582
+ - Chat facts apply to a specific chat/agent context.
583
+
584
+ ### `getFacts(level, id): Promise<string>`
585
+
586
+ Returns facts as newline-separated text in `ISO_TIME:content` format. Use `level: "user"` with `userId`, or `level: "chat"` with `chatId`.
587
+
588
+ ### `getFactsForContext(userId, chatId): Promise<string>`
589
+
590
+ Returns merged user-level facts for `userId` and chat-level facts for `chatId`, sorted by time. This is useful for injecting stable facts into an agent prompt.
591
+
592
+ ### `addFact(content, level, userId, chatId, sessionId?): Promise<void>`
593
+
594
+ Alias-style management API for adding a fact manually.
595
+
596
+ ### `deleteFact(factId): Promise<boolean>`
597
+
598
+ Deletes one fact and returns whether it was found.
599
+
600
+ ### `updateEntity(entities, relations, context?): Promise<void>`
601
+
602
+ Upserts graph entities and relations. The method embeds entity names and stores graph data in Grafeo.
603
+
604
+ Context:
605
+
606
+ - `sessionId?: string`
607
+ - `chatId?: string`
608
+ - `userId?: string`
609
+
610
+ ### `getHistoryWindow(sessionId, modelContextTokens?): Promise<MemoryContextWindow>`
611
+
612
+ Returns the complete model-history window:
613
+
614
+ - `compressedContext: string` - chronological Topic summaries within the fixed compressed budget.
615
+ - `recentMessages: MemoryRawMessage[]` - all currently uncompressed raw messages in chronological order, preserving the host payload and metadata.
616
+ - `usage` - model size, usable history size, compressed usage, and dynamic raw-message budget/usage.
617
+
618
+ Pass the active model's context size on every read. Omitting it uses `defaultModelContextTokens` (256K by default) and logs a warning.
619
+
620
+ ### `getRecentMessages(sessionId, limit): Promise<StoredMessage[]>`
621
+
622
+ Returns the globally latest `limit` messages for a session in chronological order (oldest to newest within the returned window). The storage query orders by `createdAt` and uses `messageId` as a deterministic same-timestamp tiebreak before applying the limit, so long sessions do not return an early candidate window.
623
+
624
+ ### Session APIs
625
+
626
+ - `getSession(sessionId): SessionView | null`
627
+ - `getSessionsByUserId(userId, opts?): SessionView[]`
628
+ - `getSessionsByChatId(chatId, opts?): SessionView[]`
629
+ - `searchSessions(opts): SessionView[]`
630
+ - `updateSession(sessionId, opts): Promise<SessionView | null>`
631
+ - `deleteSession(sessionId): Promise<boolean>`
632
+ - `listSessions(filter?): SessionView[]`
633
+
634
+ Session update options:
635
+
636
+ - `title?: string`
610
637
  - `metadata?: Record<string, unknown>`
611
-
612
- Session search options:
613
-
614
- - `title?: string`
615
- - `userId?: string`
616
- - `chatId?: string`
617
- - `limit?: number`
618
-
619
- ### Management APIs
620
-
621
- - `stats(): Promise<{ sessions; messages; topics; facts; entities; relations; documents; chunks }>` - total counts.
622
- - `trend(days?): Promise<Array<{ date; sessions; messages; facts }>>` - daily activity trend. `days` defaults to `30` and is clamped to `1..365`.
623
- - `listMessages(sessionId, limit?): Promise<StoredMessage[]>`
624
- - `listTopics(filter?): Promise<Topic[]>`
625
- - `listFacts(filter?): Fact[]`
626
- - `listEntities(): Promise<Entity[]>`
627
- - `listRelations(): Promise<Relation[]>`
628
-
629
- ### `addDocument(opts): Promise<{ docId: string }>`
630
-
631
- Ingests a Markdown document into the knowledge base.
632
-
633
- Options:
634
-
635
- - `content: string` - required Markdown content.
636
- - `userId?: string`
637
- - `chatId?: string`
638
- - `sessionId?: string`
639
- - `title?: string` - inferred from first Markdown heading or first non-empty line when omitted.
640
- - `sourceName?: string`
641
- - `metadata?: Record<string, unknown>`
642
- - `buildGraph?: boolean | "auto"` - default from `config.buildGraphDefault`.
643
- - `wait?: boolean` - wait for chunks to be embedded and inserted.
644
- - `waitGraph?: boolean` - wait for chunks and graph construction.
645
-
646
- Deduplication:
647
-
648
- - Documents are deduplicated by `sha256(content)` within the same `userId + chatId + sessionId` domain.
649
- - If a duplicate exists, `addDocument` returns the existing `docId`.
650
-
651
- Graph behavior:
652
-
653
- - `buildGraph: false` disables graph construction.
654
- - `buildGraph: true` builds a graph.
655
- - `buildGraph: "auto"` uses the LLM document summary call to decide whether the document has valuable entity relationships.
656
-
657
- ### `searchKnowledge(opts): Promise<KnowledgeSearchResult>`
658
-
659
- Searches Markdown knowledge-base chunks.
660
-
661
- Options:
662
-
663
- - `query: string`
664
- - `scope?: "session" | "chat" | "user" | "all"`
665
- - `scopeId?: string`
666
- - `mode?: "fast" | "auto" | "all"`
667
- - `limit?: number`
668
-
669
- Returns:
670
-
671
- - `chunks: Array<{ chunkId; docId; content; headingPath; score }>`
672
- - `documents: Array<{ docId; title; matchedChunkCount }>`
673
- - `graphHits: SearchResult[]`
674
-
675
- The method uses document-level coarse recall when `docCoarseTopK > 0`, then chunk-level search. In `auto` mode, knowledge graph expansion only happens when the top chunk score reaches `knowledgeGraphTriggerScore`.
676
-
677
- ### `getDocument(docId): Promise<Document | null>`
678
-
679
- Returns a document including `fullContent`.
680
-
681
- ### `deleteDocument(docId): Promise<boolean>`
682
-
683
- Deletes the document, its chunks, and best-effort Grafeo knowledge graph entries for that document.
684
-
685
- ### `listDocuments(filter?): Promise<Document[]>`
686
-
687
- Lists documents, optionally filtered by:
688
-
689
- - `userId`
690
- - `chatId`
691
- - `sessionId`
692
-
693
- ### `destroy(): void`
694
-
695
- Closes the Grafeo connection. It also closes the vector store connection. It does not delete vector store or Grafeo data.
696
-
638
+ - `scope?: "session" | "chat" | "user" | "all"` - content-hash deduplication boundary; default is `session`.
639
+
640
+ Session search options:
641
+
642
+ - `title?: string`
643
+ - `userId?: string`
644
+ - `chatId?: string`
645
+ - `limit?: number`
646
+
647
+ ### Management APIs
648
+
649
+ - `stats(): Promise<{ sessions; messages; topics; facts; entities; relations; documents; chunks }>` - total counts.
650
+ - `trend(days?): Promise<Array<{ date; sessions; messages; facts }>>` - daily activity trend. `days` defaults to `30` and is clamped to `1..365`.
651
+ - `listMessages(sessionId, limit?): Promise<StoredMessage[]>`
652
+ - `listTopics(filter?): Promise<Topic[]>`
653
+ - `listFacts(filter?): Fact[]`
654
+ - `listEntities(): Promise<Entity[]>`
655
+ - `listRelations(): Promise<Relation[]>`
656
+
657
+ ### `addDocument(opts): Promise<{ docId: string }>`
658
+
659
+ Ingests a Markdown document into the knowledge base.
660
+
661
+ Options:
662
+
663
+ - `content: string` - required Markdown content.
664
+ - `userId?: string`
665
+ - `chatId?: string`
666
+ - `sessionId?: string`
667
+ - `title?: string` - inferred from first Markdown heading or first non-empty line when omitted.
668
+ - `sourceName?: string`
669
+ - `metadata?: Record<string, unknown>`
670
+ - `buildGraph?: boolean | "auto"` - default from `config.buildGraphDefault`.
671
+ - `wait?: boolean` - wait for chunks to be embedded and inserted.
672
+ - `waitGraph?: boolean` - wait for chunks and graph construction.
673
+
674
+ Deduplication:
675
+
676
+ - Documents are deduplicated by `sha256(content)` within the requested scope (`session` by default, or `chat` / `user` / `all`).
677
+ - If a duplicate exists, `addDocument` returns the existing `docId`.
678
+
679
+ Graph behavior:
680
+
681
+ - `buildGraph: false` disables graph construction.
682
+ - `buildGraph: true` builds a graph.
683
+ - `buildGraph: "auto"` uses the LLM document summary call to decide whether the document has valuable entity relationships.
684
+
685
+ ### `searchKnowledge(opts): Promise<KnowledgeSearchResult>`
686
+
687
+ Searches Markdown knowledge-base chunks.
688
+
689
+ Options:
690
+
691
+ - `query: string`
692
+ - `scope?: "session" | "chat" | "user" | "all"`
693
+ - `scopeId?: string`
694
+ - `mode?: "fast" | "auto" | "all"`
695
+ - `limit?: number`
696
+
697
+ Returns:
698
+
699
+ - `chunks: Array<{ chunkId; docId; content; headingPath; score }>`
700
+ - `documents: Array<{ docId; title; matchedChunkCount }>`
701
+ - `graphHits: SearchResult[]`
702
+
703
+ The method uses document-level coarse recall when `docCoarseTopK > 0`, then chunk-level search. In `auto` mode, knowledge graph expansion only happens when the top chunk score reaches `knowledgeGraphTriggerScore`.
704
+
705
+ ### `getDocument(docId): Promise<Document | null>`
706
+
707
+ Returns a document including `fullContent`.
708
+
709
+ ### `deleteDocument(docId): Promise<boolean>`
710
+
711
+ Deletes the document, its chunks, and best-effort Grafeo knowledge graph entries for that document.
712
+
713
+ ### `listDocuments(filter?): Promise<Document[]>`
714
+
715
+ Lists documents, optionally filtered by:
716
+
717
+ - `userId`
718
+ - `chatId`
719
+ - `sessionId`
720
+
721
+ ### `destroy(): Promise<void>`
722
+
723
+ Waits for registered writes, compression, graph work, and storage maintenance, then closes Grafeo and the vector store. It does not delete data.
724
+
697
725
  ## Common Recipes
698
726
 
699
- ### Store User And Assistant Messages
727
+ ### Migrate A 0.3.x Store
700
728
 
701
- ```ts
702
- await memory.updateChat(
703
- [
704
- { talkerId: "user", content: "我喜欢 TypeScript,不太喜欢写纯 JavaScript。" },
705
- { talkerId: "assistant", content: "记住了,你偏好 TypeScript。" },
706
- ],
707
- { userId: "u1", chatId: "assistant-main", sessionId: "s1" }
708
- );
729
+ Stop all processes that write the store, then run the manual, idempotent migration. It backfills single-field Topic summaries/tokens/boundaries, raw-message usage, and fact timestamps without deleting raw messages or rerunning the LLM.
730
+
731
+ ```bash
732
+ ppagent-memory migrate --config ./config.json --dry-run
733
+ ppagent-memory migrate --config ./config.json --backup ./memory-backup
709
734
  ```
710
735
 
736
+ The config loader accepts a direct `MemoryConfig`, `memory`, `app.memory.auto`, or `service.memory` object. `--dry-run` migrates a temporary copy. `--backup` copies both supported vector-store files/directories plus Grafeo data and writes rollback guidance before mutating the live store.
737
+
738
+ ### Store User And Assistant Messages
739
+
740
+ ```ts
741
+ await memory.updateChat(
742
+ [
743
+ { talkerId: "user", content: "我喜欢 TypeScript,不太喜欢写纯 JavaScript。" },
744
+ { talkerId: "assistant", content: "记住了,你偏好 TypeScript。" },
745
+ ],
746
+ { userId: "u1", chatId: "assistant-main", sessionId: "s1" }
747
+ );
748
+ ```
749
+
711
750
  ### Force A Prompt-Ready History Window
712
751
 
713
752
  ```ts
714
753
  await memory.flushChat("s1", { wait: true });
715
- const history = memory.getHistoryWindow("s1");
716
-
717
- const systemPrompt = `你是助手。以下是长期记忆摘要:\n${history}`;
718
- ```
719
-
720
- ### Wait For Conversation Graph Data
721
-
722
- ```ts
723
- await memory.flushChat("s1", {
724
- wait: true,
725
- waitGraph: true,
726
- });
727
- ```
728
-
729
- Use this only when graph data must be ready immediately. It can throw if graph construction exceeds `graphBuildTimeoutMs`.
730
-
731
- ### Store Stable Facts
732
-
733
- ```ts
734
- await memory.updateFacts("用户偏好 TypeScript", "user", "u1", "assistant-main", "s1");
735
-
736
- const facts = await memory.getFactsForContext("u1", "assistant-main");
737
- ```
738
-
739
- ### Search Fast Without Graph
740
-
741
- ```ts
742
- const hits = await memory.search({
743
- query: "用户偏好什么语言?",
744
- scope: "user",
745
- scopeId: "u1",
746
- mode: "fast",
747
- });
748
- ```
749
-
750
- ### Search With Graph
751
-
752
- ```ts
753
- const hits = await memory.search({
754
- query: "Alice 负责哪个项目,项目用了什么技术?",
755
- scope: "chat",
756
- scopeId: "work-agent",
757
- mode: "all",
758
- });
759
- ```
760
-
761
- ### Ingest Markdown And Search Immediately
762
-
763
- ```ts
764
- const { docId } = await memory.addDocument({
765
- content: "# Rust\n\nRust 的所有权系统保证内存安全。",
766
- userId: "u1",
767
- chatId: "dev-agent",
768
- sourceName: "rust.md",
769
- wait: true,
770
- });
771
-
772
- const result = await memory.searchKnowledge({
773
- query: "Rust 如何保证内存安全?",
774
- scope: "chat",
775
- scopeId: "dev-agent",
776
- mode: "fast",
777
- });
778
- ```
779
-
780
- ### Ingest Markdown And Wait For Knowledge Graph
781
-
782
- ```ts
783
- await memory.addDocument({
784
- content: markdown,
785
- userId: "u1",
786
- chatId: "work-agent",
787
- sourceName: "project.md",
788
- buildGraph: true,
789
- waitGraph: true,
790
- });
791
- ```
792
-
793
- ### Ask With Conversation Memory And Knowledge Base
794
-
795
- ```ts
796
- const answer = await memory.ask({
797
- query: "这个项目的技术栈是什么?结合我之前说过的内容回答。",
798
- scope: "chat",
799
- scopeId: "work-agent",
800
- mode: "auto",
801
- includeKnowledge: true,
802
- maxChars: 300,
803
- });
754
+ const history = await memory.getHistoryWindow("s1", 256 * 1024);
755
+
756
+ const promptMessages = [
757
+ { role: "system", content: `更早对话摘要:\n${history.compressedContext}` },
758
+ ...history.recentMessages.map((message) => ({
759
+ role: message.talkerId === "assistant" ? "assistant" : "user",
760
+ content: message.payload ?? message.content,
761
+ })),
762
+ ];
804
763
  ```
805
-
806
- ## Node And Relation Types
807
-
808
- Default node types:
809
-
810
- `Person`, `Group`, `Organization`, `Project`, `Task`, `Decision`, `Plan`, `Event`, `Product`, `Technology`, `Data`, `Document`, `Topic`, `Concept`, `Preference`, `Habit`, `Goal`, `Skill`, `Attribute`, `Value`, `Status`, `Time`, `Location`, `Resource`, `Relationship`.
811
-
812
- Default relation types:
813
-
814
- `is_a`, `part_of`, `belongs_to`, `contains`, `has_member`, `mentioned_in`, `refers_to`, `same_as`, `alias_of`, `related_to`, `associated_with`, `uses`, `creates`, `updates`, `buys`, `owns`, `consumes`, `works_on`, `prefers`, `likes`, `dislikes`, `interested_in`, `favorite`, `plans`, `decides`, `habit_of`, `tends_to`, `avoids`, `skilled_in`, `learning`, `knows`, `friends_with`, `married_to`, `parent_of`, `child_of`, `lives_with`, `depends_on`, `built_with`, `integrates_with`, `deployed_on`, `inputs`, `outputs`, `trained_on`, `predicts`, `happens_at`, `started_at`, `ended_at`, `affects`, `causes`, `leads_to`, `improves`, `reduces`, `assigned_to`, `executed_by`, `blocks`, `completes`, `describes`, `explains`, `references`.
815
-
816
- Graph kind constants:
817
-
818
- - `KIND_CONVERSATION = "conversation"`
819
- - `KIND_KNOWLEDGE = "knowledge"`
820
-
821
- Conversation graph search only targets conversation graph data. Knowledge graph search only targets knowledge graph data.
822
-
823
- ## Best Practices
824
-
825
- - Always call `await memory.init()` before using the manager.
826
- - Always call `memory.destroy()` during shutdown to close Grafeo resources.
827
- - Use stable `userId`, `chatId`, and `sessionId` values. They define memory isolation and retrieval scope.
828
- - Use `scope: "user"` for personal memory, `scope: "chat"` for one agent/conversation channel, and `scope: "session"` for one short-lived conversation.
829
- - Use `getFactsForContext` for stable facts and `getHistoryWindow` for compressed session history.
830
- - Use `updateFacts` for explicit facts that should not depend on LLM extraction.
831
- - Use `updateEntity` when you already know structured entities and relations.
832
- - Use `flushChat(..., { wait: true })` before reading `getHistoryWindow` in tests or immediate workflows.
833
- - Use `addDocument(..., { wait: true })` before immediate knowledge search.
834
- - Use `waitGraph: true` sparingly because graph extraction depends on LLM calls and can be slower.
835
- - Keep `embeddingDimension` unchanged for an existing vector store unless you rebuild the database.
836
- - Keep examples free of real API keys, tokens, personal data, and proprietary customer content.
837
-
838
- ## Common Pitfalls
839
-
840
- - Forgetting `init()` before `updateChat` or search.
841
- - Expecting `updateChat` to finish compression or graph extraction. It only waits for raw message storage.
842
- - Expecting `flushChat(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
843
- - Expecting `addDocument(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
844
- - Setting `waitGraph: true` without allowing enough `graphBuildTimeoutMs` for large documents.
845
- - Using `scope: "session"` with a `chatId`, or `scope: "chat"` with a `userId`. `scopeId` must match the selected scope.
846
- - Searching immediately after `addDocument` without `wait: true`; chunks may still be ingesting.
847
- - Mixing embedding models with different vector dimensions in the same existing vector store.
848
- - Treating `metadata` as indexed arbitrary JSON. Store important filter dimensions in stable top-level ids where possible.
849
- - Using `mode: "all"` for every query. It is richer but can be slower than `fast`.
850
-
851
- ## Security And Privacy
852
-
853
- - Never hard-code real `llmApiKey` or `embeddingApiKey`.
854
- - Treat vector store (LanceDB/SQLite) and Grafeo files as sensitive data stores.
855
- - `content`, `metadata`, facts, and graph `meta` may contain private information.
856
- - `destroy()` closes resources; it does not scrub data.
857
- - Implement your own retention/deletion policy around `deleteSession`, `deleteDocument`, `deleteFact`, and storage directory cleanup.
858
- - If exposing memory search through an API, enforce user authorization before passing `scope` and `scopeId`.
859
-
860
- ## Publication Notes
861
-
862
- This file is included in the npm package root through the `files` field in `packages/memory/package.json`. When public APIs, config defaults, wait behavior, search semantics, or examples change, update this `llms.txt` in the same change.
764
+
765
+ ### Wait For Conversation Graph Data
766
+
767
+ ```ts
768
+ await memory.flushChat("s1", {
769
+ wait: true,
770
+ waitGraph: true,
771
+ });
772
+ ```
773
+
774
+ Use this only when graph data must be ready immediately. It can throw if graph construction exceeds `graphBuildTimeoutMs`.
775
+
776
+ ### Store Stable Facts
777
+
778
+ ```ts
779
+ await memory.updateFacts("用户偏好 TypeScript", "user", "u1", "assistant-main", "s1");
780
+
781
+ const facts = await memory.getFactsForContext("u1", "assistant-main");
782
+ ```
783
+
784
+ ### Search Fast Without Graph
785
+
786
+ ```ts
787
+ const hits = await memory.search({
788
+ query: "用户偏好什么语言?",
789
+ scope: "user",
790
+ scopeId: "u1",
791
+ mode: "fast",
792
+ });
793
+ ```
794
+
795
+ ### Search With Graph
796
+
797
+ ```ts
798
+ const hits = await memory.search({
799
+ query: "Alice 负责哪个项目,项目用了什么技术?",
800
+ scope: "chat",
801
+ scopeId: "work-agent",
802
+ mode: "all",
803
+ });
804
+ ```
805
+
806
+ ### Ingest Markdown And Search Immediately
807
+
808
+ ```ts
809
+ const { docId } = await memory.addDocument({
810
+ content: "# Rust\n\nRust 的所有权系统保证内存安全。",
811
+ userId: "u1",
812
+ chatId: "dev-agent",
813
+ sourceName: "rust.md",
814
+ wait: true,
815
+ });
816
+
817
+ const result = await memory.searchKnowledge({
818
+ query: "Rust 如何保证内存安全?",
819
+ scope: "chat",
820
+ scopeId: "dev-agent",
821
+ mode: "fast",
822
+ });
823
+ ```
824
+
825
+ ### Ingest Markdown And Wait For Knowledge Graph
826
+
827
+ ```ts
828
+ await memory.addDocument({
829
+ content: markdown,
830
+ userId: "u1",
831
+ chatId: "work-agent",
832
+ sourceName: "project.md",
833
+ buildGraph: true,
834
+ waitGraph: true,
835
+ });
836
+ ```
837
+
838
+ ### Ask With Conversation Memory And Knowledge Base
839
+
840
+ ```ts
841
+ const answer = await memory.ask({
842
+ query: "这个项目的技术栈是什么?结合我之前说过的内容回答。",
843
+ scope: "chat",
844
+ scopeId: "work-agent",
845
+ mode: "auto",
846
+ includeKnowledge: true,
847
+ maxChars: 300,
848
+ });
849
+ ```
850
+
851
+ ## Node And Relation Types
852
+
853
+ Default node types:
854
+
855
+ `Person`, `Group`, `Organization`, `Project`, `Task`, `Decision`, `Plan`, `Event`, `Product`, `Technology`, `Data`, `Document`, `Topic`, `Concept`, `Preference`, `Habit`, `Goal`, `Skill`, `Attribute`, `Value`, `Status`, `Time`, `Location`, `Resource`, `Relationship`.
856
+
857
+ Default relation types:
858
+
859
+ `is_a`, `part_of`, `belongs_to`, `contains`, `has_member`, `mentioned_in`, `refers_to`, `same_as`, `alias_of`, `related_to`, `associated_with`, `uses`, `creates`, `updates`, `buys`, `owns`, `consumes`, `works_on`, `prefers`, `likes`, `dislikes`, `interested_in`, `favorite`, `plans`, `decides`, `habit_of`, `tends_to`, `avoids`, `skilled_in`, `learning`, `knows`, `friends_with`, `married_to`, `parent_of`, `child_of`, `lives_with`, `depends_on`, `built_with`, `integrates_with`, `deployed_on`, `inputs`, `outputs`, `trained_on`, `predicts`, `happens_at`, `started_at`, `ended_at`, `affects`, `causes`, `leads_to`, `improves`, `reduces`, `assigned_to`, `executed_by`, `blocks`, `completes`, `describes`, `explains`, `references`.
860
+
861
+ Graph kind constants:
862
+
863
+ - `KIND_CONVERSATION = "conversation"`
864
+ - `KIND_KNOWLEDGE = "knowledge"`
865
+
866
+ Conversation graph search only targets conversation graph data. Knowledge graph search only targets knowledge graph data.
867
+
868
+ ## Best Practices
869
+
870
+ - Always call `await memory.init()` before using the manager.
871
+ - Always call `await memory.destroy()` during shutdown to drain background work and close resources.
872
+ - Use stable `userId`, `chatId`, and `sessionId` values. They define memory isolation and retrieval scope.
873
+ - Use `scope: "user"` for personal memory, `scope: "chat"` for one agent/conversation channel, and `scope: "session"` for one short-lived conversation.
874
+ - Use `getFactsForContext` for stable facts and `getHistoryWindow` for compressed Topics plus recent raw session history.
875
+ - Use `updateFacts` for explicit facts that should not depend on LLM extraction.
876
+ - Use `updateEntity` when you already know structured entities and relations.
877
+ - Use `flushChat(..., { wait: true })` before reading `getHistoryWindow` in tests or immediate workflows.
878
+ - Use `addDocument(..., { wait: true })` before immediate knowledge search.
879
+ - Use `waitGraph: true` sparingly because graph extraction depends on LLM calls and can be slower.
880
+ - Keep `embeddingDimension` unchanged for an existing vector store unless you rebuild the database.
881
+ - Keep examples free of real API keys, tokens, personal data, and proprietary customer content.
882
+
883
+ ## Common Pitfalls
884
+
885
+ - Forgetting `init()` before `updateChat` or search.
886
+ - Treating `getHistoryWindow` as synchronous or string-only. Since 0.4 it is async and returns a structured complete window.
887
+ - Omitting stable `messageId` when the host can update an existing assistant message; without it, an update becomes another raw message.
888
+ - Expecting `flushChat(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
889
+ - Expecting `addDocument(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
890
+ - Setting `waitGraph: true` without allowing enough `graphBuildTimeoutMs` for large documents.
891
+ - Using `scope: "session"` with a `chatId`, or `scope: "chat"` with a `userId`. `scopeId` must match the selected scope.
892
+ - Searching immediately after `addDocument` without `wait: true`; chunks may still be ingesting.
893
+ - Mixing embedding models with different vector dimensions in the same existing vector store.
894
+ - Treating `metadata` as indexed arbitrary JSON. Store important filter dimensions in stable top-level ids where possible.
895
+ - Using `mode: "all"` for every query. It is richer but can be slower than `fast`.
896
+
897
+ ## Security And Privacy
898
+
899
+ - Never hard-code real `llmApiKey` or `embeddingApiKey`.
900
+ - Treat vector store (LanceDB/SQLite) and Grafeo files as sensitive data stores.
901
+ - `content`, `metadata`, facts, and graph `meta` may contain private information.
902
+ - `destroy()` closes resources; it does not scrub data.
903
+ - Implement your own retention/deletion policy around `deleteSession`, `deleteDocument`, `deleteFact`, and storage directory cleanup.
904
+ - If exposing memory search through an API, enforce user authorization before passing `scope` and `scopeId`.
905
+
906
+ ## Publication Notes
907
+
908
+ This file is included in the npm package root through the `files` field in `packages/memory/package.json`. When public APIs, config defaults, wait behavior, search semantics, or examples change, update this `llms.txt` in the same change.