@ppagent/memory 0.1.4 → 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,6 +1,6 @@
1
1
  # @ppagent/memory
2
2
 
3
- > `@ppagent/memory` is an independent TypeScript/Node.js long-term memory package for AI agents. It stores conversation memory in LanceDB, 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.
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
4
 
5
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
6
 
@@ -13,8 +13,9 @@ This file is written for AI coding agents and assistants. Use it as the primary
13
13
  - Source entry during local workspace development: `src/index.ts`
14
14
  - Runtime target: Node.js with global `fetch`
15
15
  - Storage engines:
16
- - LanceDB for messages, topics, facts, sessions, documents, and chunks
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
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`.
18
19
  - LLM APIs: OpenAI-compatible `/chat/completions`
19
20
  - Embedding APIs: OpenAI-compatible `/embeddings`
20
21
 
@@ -48,7 +49,7 @@ npm install @ppagent/memory
48
49
  ```ts
49
50
  import {
50
51
  MemoryManager,
51
- LanceService,
52
+ MemoryStore,
52
53
  DEFAULT_NODE_TYPES,
53
54
  DEFAULT_RELATION_TYPES,
54
55
  KIND_CONVERSATION,
@@ -62,11 +63,12 @@ import type {
62
63
  SearchResult,
63
64
  AddDocumentOptions,
64
65
  KnowledgeSearchOptions,
65
- KnowledgeSearchResult,
66
- } from "@ppagent/memory";
66
+ KnowledgeSearchResult,
67
+ MemoryContextWindow,
68
+ } from "@ppagent/memory";
67
69
  ```
68
70
 
69
- Most consumers should only instantiate `MemoryManager`. `LanceService` is exported for advanced storage tests or custom infrastructure work.
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.
70
72
 
71
73
  ## Minimal Example
72
74
 
@@ -120,7 +122,7 @@ const answer = await memory.ask({
120
122
 
121
123
  console.log(answer);
122
124
 
123
- memory.destroy();
125
+ await memory.destroy();
124
126
  ```
125
127
 
126
128
  ## Complete Example
@@ -147,16 +149,19 @@ const memory = new MemoryManager({
147
149
  embeddingBatchSize: 20,
148
150
  embeddingConcurrency: 2,
149
151
 
150
- sessionTokenLimit: 16_386,
151
- historyWindowTokenLimit: 10_240,
152
- topicRatio: [1, 5, 20],
153
- detailMaxTokens: 2048,
154
- summaryMaxTokens: 512,
155
- conciseMaxTokens: 128,
156
- maxConcurrentCompressions: 3,
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,
162
+ maxConcurrentCompressions: 3,
157
163
  entitySimilarityThreshold: 0.92,
158
164
  defaultSearchLimit: 10,
159
- recallBoostMs: 3_600_000,
160
165
 
161
166
  chunkStrategy: "markdown-heading",
162
167
  chunkMaxTokens: 800,
@@ -240,7 +245,7 @@ ChatMe 使用 TypeScript 构建,后端包含长期记忆系统。
240
245
 
241
246
  ## Memory
242
247
 
243
- 长期记忆系统使用 LanceDB 存储向量,使用 Grafeo 存储知识图谱。`,
248
+ 长期记忆系统使用向量存储(LanceDB SQLite,自动探测)存储向量,使用 Grafeo 存储知识图谱。`,
244
249
  userId,
245
250
  chatId,
246
251
  sessionId,
@@ -270,7 +275,7 @@ const answer = await memory.ask({
270
275
 
271
276
  console.log(answer);
272
277
 
273
- memory.destroy();
278
+ await memory.destroy();
274
279
  ```
275
280
 
276
281
  ## Configuration Reference
@@ -279,9 +284,25 @@ memory.destroy();
279
284
 
280
285
  Required storage fields:
281
286
 
282
- - `lancedbPath: string` - local LanceDB directory.
287
+ - `lancedbPath: string` - local LanceDB directory (used when the lancedb backend is active; its parent directory also hosts the backend marker file `.memory-provider`).
283
288
  - `grafeoPath: string` - Grafeo database path.
284
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
+
285
306
  Required LLM fields:
286
307
 
287
308
  - `llmBaseUrl: string` - OpenAI-compatible base URL, for example `https://api.openai.com/v1`.
@@ -293,7 +314,7 @@ Required embedding fields:
293
314
  - `embeddingBaseUrl: string` - OpenAI-compatible embedding base URL.
294
315
  - `embeddingApiKey: string` - embedding API key.
295
316
  - `embeddingModel: string` - embedding model name.
296
- - `embeddingDimension: number` - vector dimension. This must match the embedding model and existing LanceDB schema.
317
+ - `embeddingDimension: number` - vector dimension. This must match the embedding model and the existing vector store schema.
297
318
 
298
319
  Embedding fallback behavior:
299
320
 
@@ -308,18 +329,23 @@ HTTP and retry fields:
308
329
  - `embeddingBatchSize?: number` - max texts per embedding request. Default: `20`.
309
330
  - `embeddingConcurrency?: number` - concurrent embedding batches. Default: `2`.
310
331
 
311
- Conversation memory fields:
312
-
313
- - `sessionTokenLimit?: number` - when a session cache reaches this token count, compression is triggered. Default: `16386`.
314
- - `historyWindowTokenLimit?: number` - maximum token budget for the rebuilt compressed history window. Default: `10240`.
315
- - `topicRatio?: [number, number, number]` - number ratio for detail/summary/concise topics in the history window. Default: `[1, 5, 20]`.
316
- - `detailMaxTokens?: number` - max detailed summary tokens requested from the LLM. Default: `2048`.
317
- - `summaryMaxTokens?: number` - max medium summary tokens requested from the LLM. Default: `512`.
318
- - `conciseMaxTokens?: number` - max concise summary tokens requested from the LLM. Default: `128`.
319
- - `maxConcurrentCompressions?: number` - global semaphore limit for concurrent compression and knowledge ingestion tasks. Default: `3`.
320
- - `entitySimilarityThreshold?: number` - graph entity similarity threshold. Default: `0.92`.
321
- - `defaultSearchLimit?: number` - default `search` result limit. Default: `10`.
322
- - `recallBoostMs?: number` - each topic recall count acts like this many milliseconds of freshness boost. Default: `3_600_000`.
332
+ Conversation memory fields:
333
+
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`.
344
+ - `maxConcurrentCompressions?: number` - global semaphore limit for concurrent compression and knowledge ingestion tasks. Default: `3`.
345
+ - `entitySimilarityThreshold?: number` - graph entity similarity threshold. Default: `0.92`.
346
+ - `defaultSearchLimit?: number` - default `search` result limit. Default: `10`.
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.
323
349
 
324
350
  Knowledge-base fields:
325
351
 
@@ -341,21 +367,30 @@ Knowledge-base fields:
341
367
 
342
368
  The package intentionally separates durable base writes from expensive graph construction.
343
369
 
344
- `updateChat(messages, opts)`:
345
-
346
- - Embeds and writes raw messages to LanceDB before resolving.
347
- - Creates or updates the session record.
348
- - Updates the in-memory session cache.
349
- - If `sessionTokenLimit` is exceeded, compression is triggered in the background.
350
- - Background compression may create topics/history and graph entities later.
370
+ `updateChat(messages, opts)`:
371
+
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.
374
+ - Creates or updates the session record.
375
+ - Updates the in-memory session cache.
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.
351
378
 
352
- `flushChat(sessionId?, opts?)`:
379
+ `flushChat(sessionId?, opts?)`:
353
380
 
354
- - Forces compression for the current cached messages even if `sessionTokenLimit` was not reached.
381
+ - Forces compression for the current cached raw messages even if the dynamic precompression threshold was not reached.
355
382
  - `wait: true` waits for topic creation, cache clearing, and history-window rebuild.
356
383
  - `wait: true` does not wait for graph extraction/persistence.
357
384
  - `waitGraph: true` waits for graph extraction/persistence too.
358
- - When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
385
+ - When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
386
+
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.
359
394
 
360
395
  `addDocument(opts)`:
361
396
 
@@ -381,8 +416,9 @@ Use `wait: true` when the next line of code must immediately call `searchKnowled
381
416
  - `sessionId?: string` - session id.
382
417
  - `type?: "text" | "image" | "file"` - default: `"text"`.
383
418
  - `content: string` - required plain text used for embedding and retrieval.
384
- - `parts?: ContentPart[]` - optional multimodal content parts; stored serialized.
385
- - `usage?: number` - token count; estimated with `tiktoken` if omitted.
419
+ - `parts?: ContentPart[]` - optional multimodal content parts; stored serialized.
420
+ - `payload?: unknown` - host-framework message payload stored and returned without interpretation.
421
+ - `usage?: number` - token count; estimated with `tiktoken` if omitted.
386
422
  - `metadata?: Record<string, unknown>` - custom metadata stored as JSON.
387
423
  - `createdAt?: number` - Unix milliseconds; default is current time.
388
424
 
@@ -394,21 +430,23 @@ Use `wait: true` when the next line of code must immediately call `searchKnowled
394
430
 
395
431
  `StoredMessage` is persisted message output with required ids, serialized `parts`, serialized `metadata`, generated vector, and `createdAt`.
396
432
 
397
- `Topic` is a compressed memory item:
398
-
399
- - `title` - short topic title.
400
- - `detail` - rich summary.
401
- - `summary` - medium summary.
402
- - `concise` - short summary.
403
- - `startTime` and `endTime` - covered message time range.
404
- - `recallCount` - affects freshness ranking through `recallBoostMs`.
433
+ `Topic` is a compressed memory item:
434
+
435
+ - `title` - short topic title.
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.
439
+ - `startTime` and `endTime` - covered message time range.
440
+ - `recallCount` - incremented best-effort when search recalls the Topic; it is not used to reorder the continuous history window.
405
441
 
406
- `Fact` is a user/chat scoped manual memory:
442
+ `Fact` is a user/chat scoped manual memory:
407
443
 
408
444
  - `level: "user" | "chat"`
409
445
  - `userId`, `chatId`, optional `sessionId`
410
- - `content`
411
- - `createdAt`
446
+ - `content`
447
+ - optional stable `key` for immediate cache + database upsert
448
+ - `createdAt`
449
+ - `updatedAt`
412
450
 
413
451
  `Entity` and `Relation` are graph records:
414
452
 
@@ -460,13 +498,13 @@ Scopes:
460
498
  - `scope: "user"` - filter by `userId`; pass `scopeId`.
461
499
  - `scope: "all"` - no domain filter; `scopeId` is ignored.
462
500
 
463
- Search modes:
501
+ Search modes:
464
502
 
465
- - `mode: "fast"` - only LanceDB hybrid/vector retrieval. No graph search.
503
+ - `mode: "fast"` - only vector-store hybrid/vector retrieval. No graph search.
466
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.
467
- - `mode: "all"` - include graph search and merge graph results with vector/full-text results.
468
-
469
- For deterministic low-latency calls, use `fast`. For richer entity relationship answers, use `auto` or `all`.
505
+ - `mode: "all"` - include graph search and merge graph results with vector/full-text results.
506
+
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`.
470
508
 
471
509
  ## API Reference
472
510
 
@@ -474,9 +512,9 @@ For deterministic low-latency calls, use `fast`. For richer entity relationship
474
512
 
475
513
  Creates the manager and resolves defaults. It does not connect to storage until `init()`.
476
514
 
477
- ### `init(): Promise<void>`
478
-
479
- Initializes tiktoken, LanceDB, Grafeo, facts cache, sessions cache, and restored history windows. Always call before using read/write APIs.
515
+ ### `init(): Promise<void>`
516
+
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.
480
518
 
481
519
  ### `updateChat(messages, opts?): Promise<void>`
482
520
 
@@ -569,13 +607,19 @@ Context:
569
607
  - `chatId?: string`
570
608
  - `userId?: string`
571
609
 
572
- ### `getHistoryWindow(sessionId): string`
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.
573
619
 
574
- Returns the compressed history window for a session. This is prompt-ready text built from recent `Topic` records using `topicRatio` and `historyWindowTokenLimit`.
620
+ ### `getRecentMessages(sessionId, limit): Promise<StoredMessage[]>`
575
621
 
576
- ### `getRecentMessages(sessionId, limit): Promise<StoredMessage[]>`
577
-
578
- 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.
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.
579
623
 
580
624
  ### Session APIs
581
625
 
@@ -590,7 +634,8 @@ Returns the globally latest `limit` messages for a session in chronological orde
590
634
  Session update options:
591
635
 
592
636
  - `title?: string`
593
- - `metadata?: Record<string, unknown>`
637
+ - `metadata?: Record<string, unknown>`
638
+ - `scope?: "session" | "chat" | "user" | "all"` - content-hash deduplication boundary; default is `session`.
594
639
 
595
640
  Session search options:
596
641
 
@@ -628,7 +673,7 @@ Options:
628
673
 
629
674
  Deduplication:
630
675
 
631
- - Documents are deduplicated by `sha256(content)` within the same `userId + chatId + sessionId` domain.
676
+ - Documents are deduplicated by `sha256(content)` within the requested scope (`session` by default, or `chat` / `user` / `all`).
632
677
  - If a duplicate exists, `addDocument` returns the existing `docId`.
633
678
 
634
679
  Graph behavior:
@@ -673,11 +718,22 @@ Lists documents, optionally filtered by:
673
718
  - `chatId`
674
719
  - `sessionId`
675
720
 
676
- ### `destroy(): void`
677
-
678
- Closes the Grafeo connection. It does not delete LanceDB or Grafeo data.
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.
679
724
 
680
- ## Common Recipes
725
+ ## Common Recipes
726
+
727
+ ### Migrate A 0.3.x Store
728
+
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
734
+ ```
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.
681
737
 
682
738
  ### Store User And Assistant Messages
683
739
 
@@ -691,14 +747,20 @@ await memory.updateChat(
691
747
  );
692
748
  ```
693
749
 
694
- ### Force A Prompt-Ready History Window
695
-
696
- ```ts
697
- await memory.flushChat("s1", { wait: true });
698
- const history = memory.getHistoryWindow("s1");
699
-
700
- const systemPrompt = `你是助手。以下是长期记忆摘要:\n${history}`;
701
- ```
750
+ ### Force A Prompt-Ready History Window
751
+
752
+ ```ts
753
+ await memory.flushChat("s1", { wait: true });
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
+ ];
763
+ ```
702
764
 
703
765
  ### Wait For Conversation Graph Data
704
766
 
@@ -806,35 +868,36 @@ Conversation graph search only targets conversation graph data. Knowledge graph
806
868
  ## Best Practices
807
869
 
808
870
  - Always call `await memory.init()` before using the manager.
809
- - Always call `memory.destroy()` during shutdown to close Grafeo resources.
871
+ - Always call `await memory.destroy()` during shutdown to drain background work and close resources.
810
872
  - Use stable `userId`, `chatId`, and `sessionId` values. They define memory isolation and retrieval scope.
811
873
  - Use `scope: "user"` for personal memory, `scope: "chat"` for one agent/conversation channel, and `scope: "session"` for one short-lived conversation.
812
- - Use `getFactsForContext` for stable facts and `getHistoryWindow` for compressed session history.
874
+ - Use `getFactsForContext` for stable facts and `getHistoryWindow` for compressed Topics plus recent raw session history.
813
875
  - Use `updateFacts` for explicit facts that should not depend on LLM extraction.
814
876
  - Use `updateEntity` when you already know structured entities and relations.
815
877
  - Use `flushChat(..., { wait: true })` before reading `getHistoryWindow` in tests or immediate workflows.
816
878
  - Use `addDocument(..., { wait: true })` before immediate knowledge search.
817
879
  - Use `waitGraph: true` sparingly because graph extraction depends on LLM calls and can be slower.
818
- - Keep `embeddingDimension` unchanged for an existing LanceDB directory unless you rebuild the database.
880
+ - Keep `embeddingDimension` unchanged for an existing vector store unless you rebuild the database.
819
881
  - Keep examples free of real API keys, tokens, personal data, and proprietary customer content.
820
882
 
821
883
  ## Common Pitfalls
822
884
 
823
885
  - Forgetting `init()` before `updateChat` or search.
824
- - Expecting `updateChat` to finish compression or graph extraction. It only waits for raw message storage.
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.
825
888
  - Expecting `flushChat(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
826
889
  - Expecting `addDocument(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
827
890
  - Setting `waitGraph: true` without allowing enough `graphBuildTimeoutMs` for large documents.
828
891
  - Using `scope: "session"` with a `chatId`, or `scope: "chat"` with a `userId`. `scopeId` must match the selected scope.
829
892
  - Searching immediately after `addDocument` without `wait: true`; chunks may still be ingesting.
830
- - Mixing embedding models with different vector dimensions in the same existing LanceDB path.
893
+ - Mixing embedding models with different vector dimensions in the same existing vector store.
831
894
  - Treating `metadata` as indexed arbitrary JSON. Store important filter dimensions in stable top-level ids where possible.
832
895
  - Using `mode: "all"` for every query. It is richer but can be slower than `fast`.
833
896
 
834
897
  ## Security And Privacy
835
898
 
836
899
  - Never hard-code real `llmApiKey` or `embeddingApiKey`.
837
- - Treat LanceDB and Grafeo files as sensitive data stores.
900
+ - Treat vector store (LanceDB/SQLite) and Grafeo files as sensitive data stores.
838
901
  - `content`, `metadata`, facts, and graph `meta` may contain private information.
839
902
  - `destroy()` closes resources; it does not scrub data.
840
903
  - Implement your own retention/deletion policy around `deleteSession`, `deleteDocument`, `deleteFact`, and storage directory cleanup.
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@ppagent/memory",
3
- "version": "0.1.4",
4
- "description": "独立记忆系统模块,基于 LanceDB + Grafeo 实现向量存储与知识图谱",
3
+ "version": "0.4.0",
4
+ "description": "独立记忆系统模块,向量存储支持 LanceDB / SQLite(sqlite-vec) 双后端自动切换 + Grafeo 知识图谱",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "ppagent-memory": "dist/cli.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "ppagent-source": "./src/index.ts",
@@ -25,29 +28,38 @@
25
28
  "memory",
26
29
  "rag",
27
30
  "lancedb",
31
+ "sqlite",
32
+ "sqlite-vec",
28
33
  "grafeo",
29
34
  "vector",
30
- "graph"
35
+ "graph",
36
+ "hybrid-search"
31
37
  ],
32
38
  "author": "ppagent",
33
39
  "license": "Apache-2.0",
34
40
  "dependencies": {
35
41
  "@dqbd/tiktoken": "^1.0.22",
36
42
  "@grafeo-db/js": "^0.5.42",
37
- "@lancedb/lancedb": "^0.30.0",
38
- "apache-arrow": "^18.1.0",
43
+ "better-sqlite3": "^11.8.1",
44
+ "jieba-wasm": "^2.4.0",
45
+ "sqlite-vec": "^0.1.9",
39
46
  "uuid": "^10.0.0"
40
47
  },
41
48
  "devDependencies": {
49
+ "@types/better-sqlite3": "^7.6.13",
42
50
  "@types/node": "^22.0.0",
43
51
  "@types/uuid": "^10.0.0",
44
52
  "tsup": "^8.3.5",
45
53
  "typescript": "^5.8.3",
46
54
  "vitest": "^2.0.5"
47
55
  },
56
+ "optionalDependencies": {
57
+ "@lancedb/lancedb": "^0.30.0",
58
+ "apache-arrow": "^18.1.0"
59
+ },
48
60
  "scripts": {
49
- "build": "tsup src/index.ts --format esm --dts --out-dir dist --clean",
50
- "dev": "tsup src/index.ts --format esm --dts --out-dir dist --watch",
61
+ "build": "tsup src/index.ts --format esm --dts --out-dir dist --clean --external @lancedb/lancedb --external apache-arrow && tsup src/cli.ts --format esm --out-dir dist --external @lancedb/lancedb --external apache-arrow",
62
+ "dev": "tsup src/index.ts --format esm --dts --out-dir dist --watch --external @lancedb/lancedb --external apache-arrow",
51
63
  "test": "vitest"
52
64
  }
53
65
  }