memo-grafter 0.2.2 → 0.2.4

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.
Files changed (78) hide show
  1. package/README.md +81 -73
  2. package/USER_GUIDE.md +306 -73
  3. package/dist/MemoGrafterAgent.d.ts +7 -3
  4. package/dist/MemoGrafterAgent.d.ts.map +1 -1
  5. package/dist/MemoGrafterAgent.js +64 -23
  6. package/dist/MemoGrafterAgent.js.map +1 -1
  7. package/dist/crawler/ConflictDetectionPass.d.ts +6 -0
  8. package/dist/crawler/ConflictDetectionPass.d.ts.map +1 -0
  9. package/dist/crawler/ConflictDetectionPass.js +46 -0
  10. package/dist/crawler/ConflictDetectionPass.js.map +1 -0
  11. package/dist/crawler/DecayScoringPass.d.ts +17 -0
  12. package/dist/crawler/DecayScoringPass.d.ts.map +1 -0
  13. package/dist/crawler/DecayScoringPass.js +73 -0
  14. package/dist/crawler/DecayScoringPass.js.map +1 -0
  15. package/dist/crawler/MemoGrafterCrawler.d.ts +14 -0
  16. package/dist/crawler/MemoGrafterCrawler.d.ts.map +1 -0
  17. package/dist/crawler/MemoGrafterCrawler.js +108 -0
  18. package/dist/crawler/MemoGrafterCrawler.js.map +1 -0
  19. package/dist/crawler/VersioningPass.d.ts +6 -0
  20. package/dist/crawler/VersioningPass.d.ts.map +1 -0
  21. package/dist/crawler/VersioningPass.js +43 -0
  22. package/dist/crawler/VersioningPass.js.map +1 -0
  23. package/dist/crawler/decayScoring.d.ts +7 -0
  24. package/dist/crawler/decayScoring.d.ts.map +1 -0
  25. package/dist/crawler/decayScoring.js +9 -0
  26. package/dist/crawler/decayScoring.js.map +1 -0
  27. package/dist/crawler/index.d.ts +7 -0
  28. package/dist/crawler/index.d.ts.map +1 -0
  29. package/dist/crawler/index.js +5 -0
  30. package/dist/crawler/index.js.map +1 -0
  31. package/dist/crawler/memoryMaintenance.d.ts +14 -0
  32. package/dist/crawler/memoryMaintenance.d.ts.map +1 -0
  33. package/dist/crawler/memoryMaintenance.js +40 -0
  34. package/dist/crawler/memoryMaintenance.js.map +1 -0
  35. package/dist/crawler/types.d.ts +63 -0
  36. package/dist/crawler/types.d.ts.map +1 -0
  37. package/dist/crawler/types.js +2 -0
  38. package/dist/crawler/types.js.map +1 -0
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/pipeline/GrafterPipeline.d.ts.map +1 -1
  44. package/dist/pipeline/GrafterPipeline.js +42 -1
  45. package/dist/pipeline/GrafterPipeline.js.map +1 -1
  46. package/dist/pipeline/IngestPipeline.d.ts +2 -0
  47. package/dist/pipeline/IngestPipeline.d.ts.map +1 -1
  48. package/dist/pipeline/IngestPipeline.js +93 -17
  49. package/dist/pipeline/IngestPipeline.js.map +1 -1
  50. package/dist/prompts/memoryInjectionPrompt.d.ts +6 -2
  51. package/dist/prompts/memoryInjectionPrompt.d.ts.map +1 -1
  52. package/dist/prompts/memoryInjectionPrompt.js +25 -2
  53. package/dist/prompts/memoryInjectionPrompt.js.map +1 -1
  54. package/dist/store/GraphStore.d.ts +20 -1
  55. package/dist/store/GraphStore.d.ts.map +1 -1
  56. package/dist/store/postgres-pgvector/GraphStore.d.ts +25 -1
  57. package/dist/store/postgres-pgvector/GraphStore.d.ts.map +1 -1
  58. package/dist/store/postgres-pgvector/GraphStore.js +334 -1
  59. package/dist/store/postgres-pgvector/GraphStore.js.map +1 -1
  60. package/dist/types.d.ts +29 -0
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/utils/drift/cosineSimilarity.test.d.ts +2 -0
  63. package/dist/utils/drift/cosineSimilarity.test.d.ts.map +1 -0
  64. package/dist/utils/drift/cosineSimilarity.test.js +25 -0
  65. package/dist/utils/drift/cosineSimilarity.test.js.map +1 -0
  66. package/dist/utils/drift/vectorAvg.test.d.ts +2 -0
  67. package/dist/utils/drift/vectorAvg.test.d.ts.map +1 -0
  68. package/dist/utils/drift/vectorAvg.test.js +36 -0
  69. package/dist/utils/drift/vectorAvg.test.js.map +1 -0
  70. package/dist/utils/text/normalizeText.test.d.ts +2 -0
  71. package/dist/utils/text/normalizeText.test.d.ts.map +1 -0
  72. package/dist/utils/text/normalizeText.test.js +23 -0
  73. package/dist/utils/text/normalizeText.test.js.map +1 -0
  74. package/dist/utils/vector/vectorLiteral.test.d.ts +2 -0
  75. package/dist/utils/vector/vectorLiteral.test.d.ts.map +1 -0
  76. package/dist/utils/vector/vectorLiteral.test.js +28 -0
  77. package/dist/utils/vector/vectorLiteral.test.js.map +1 -0
  78. package/package.json +1 -1
package/USER_GUIDE.md CHANGED
@@ -8,6 +8,8 @@ The project is intentionally focused. MemoGrafter is a chatbot memory framework,
8
8
 
9
9
  The most important idea is memory grafting. A chatbot can build useful memory during one conversation, and another chatbot can absorb only the relevant parts.
10
10
 
11
+ MemoGrafter ingests conversation memory incrementally. New turns append topic nodes, memory nodes, and graph edges to the existing session graph instead of clearing and rebuilding the graph on every response. This keeps grafted memory and future external graph enrichment durable across normal chatbot turns.
12
+
11
13
  ## Requirements
12
14
 
13
15
  - Node.js 18 or newer.
@@ -85,6 +87,8 @@ Current v1 tables:
85
87
  - `mg_memory_edges`
86
88
  - `mg_fleets`
87
89
  - `mg_fleet_agents`
90
+ - `mg_session_ingest_state`
91
+ - `mg_graft_registry`
88
92
 
89
93
  ## Quick Start
90
94
 
@@ -140,7 +144,9 @@ export interface Message {
140
144
  }
141
145
  ```
142
146
 
143
- `MemoGrafterAgent` keeps an in-memory user/assistant history for the current session and stores messages in PostgreSQL during ingestion. System messages can be added to LLM calls internally when memory recall is pinned into an overflowing context window.
147
+ `MemoGrafterAgent` keeps an in-memory user/assistant history for the current session and stores messages in PostgreSQL during ingestion. When a session already has memory graph content, `invoke()` can add a system message with recalled facts before the LLM call.
148
+
149
+ Ingestion tracks the last message index that has been processed for each session. Re-running ingestion with the same history is a no-op for graph creation, while later turns append new graph state.
144
150
 
145
151
  ### Segments
146
152
 
@@ -191,10 +197,13 @@ Important fields:
191
197
  - `confidence`: confidence score from `0` to `1`.
192
198
  - `topicNodeId`: parent topic node ID.
193
199
  - `decayed`: whether the memory is stale.
200
+ - `hasConflict`: whether crawler maintenance found a conflicting active fact.
194
201
  - `supersededBy`: newer memory ID when this memory has been replaced.
195
202
 
196
203
  Memory nodes are stored in `mg_memory_nodes`.
197
204
 
205
+ `decayed`, `hasConflict`, and `supersededBy` are maintenance fields. Normal ingestion creates active memories. Optional crawler passes can later annotate existing memory rows, but they do not delete rows or rewrite topic summaries.
206
+
198
207
  ### Graph Edges
199
208
 
200
209
  Edges connect related topic nodes. They can represent temporal, semantic, grafted, or reentry relationships.
@@ -206,15 +215,32 @@ Edges connect related topic nodes. They can represent temporal, semantic, grafte
206
215
 
207
216
  Edges are stored in `mg_topic_edges`.
208
217
 
218
+ Memory edges are stored separately in `mg_memory_edges`. They can represent:
219
+
220
+ - `semantic`: two memory facts are similar.
221
+ - `conflicts`: two active memory facts disagree.
222
+ - `updates`: a newer memory supersedes an older memory.
223
+ - `related`: reserved for broader memory relationships.
224
+
225
+ For version edges, MemoGrafter uses this direction:
226
+
227
+ ```text
228
+ newer_memory --updates--> older_memory
229
+ ```
230
+
209
231
  ### Grafting
210
232
 
211
- Grafting is the process of selecting memory nodes and turning them into useful context for another prompt or another chatbot.
233
+ Grafting is the process of selecting topic nodes and turning them into useful context for another prompt or another chatbot.
212
234
 
213
235
  There are two common forms:
214
236
 
215
237
  - Preview memory with `graft()`.
216
238
  - Copy memory into another chatbot with `absorbFromAgent()` or `ingestGraftedNodes()`.
217
239
 
240
+ When topic nodes are absorbed into another session, MemoGrafter also copies their active memory nodes so targeted recall can find the transferred facts. Copied memory rows get fresh IDs, keep their existing embeddings, and are copied as active memories only when the source row is not decayed or superseded.
241
+
242
+ Absorbed topic nodes are also registered in `mg_graft_registry`. The registry records the destination session, copied node ID, source session ID, source node ID, and graft timestamp so applications can inspect provenance or remove a graft later.
243
+
218
244
  ## Using MemoGrafterAgent
219
245
 
220
246
  `MemoGrafterAgent` is the easiest API to start with.
@@ -251,17 +277,31 @@ await agent.close();
251
277
 
252
278
  On every call, `invoke()`:
253
279
 
254
- 1. Adds the user message to local history.
255
- 2. Builds the message list for the LLM.
256
- 3. If history is under the configured budget, sends the raw local history.
257
- 4. If history crosses the overflow threshold, calls targeted recall using recent conversation context, prepends the recall result as one pinned system message, and keeps only the recent raw message window.
258
- 5. Adds the assistant response to history.
259
- 6. Queues ingestion of the updated conversation into the memory graph.
280
+ 1. Checks whether the current session has topic nodes in the memory graph.
281
+ 2. If graph content exists, calls targeted recall using the current user message.
282
+ 3. Prepends the recalled memory prompt as a system message when recall returns matching facts.
283
+ 4. Builds the LLM message list from the optional memory system message, the recent raw history window, and the current user message.
284
+ 5. Adds the user message and assistant response to local history after the LLM response.
285
+ 6. Queues ingestion of only the newly added conversation turns into the memory graph.
286
+
287
+ Recall is skipped entirely when the session has no topic nodes yet. This avoids an unnecessary embed and vector search on the first turn or while async ingestion has not produced graph content.
260
288
 
261
- The overflow threshold is 80% of `inject.tokenBudget`. Recall failures are logged as warnings and fall back to the recent raw message window, so a retrieval or embedder problem should not crash the foreground chatbot turn.
289
+ Recall failures are logged as warnings and fall back to raw history only, so a retrieval or embedder problem should not crash the foreground chatbot turn.
262
290
 
263
291
  On the first turn there may be no memory to recall. Later turns can use memory created from earlier turns once ingestion has completed.
264
292
 
293
+ Normal ingestion does not call `clearSession()`. Existing topic nodes, grafted nodes, memory rows, and graph edges are preserved unless you explicitly clear the session.
294
+
295
+ ### Clearing A Session
296
+
297
+ Use `clearSession()` when you intentionally want to reset an agent:
298
+
299
+ ```ts
300
+ await agent.clearSession();
301
+ ```
302
+
303
+ This waits for pending ingestion, clears the agent's local in-memory history, removes stored messages, topic nodes, memory nodes, graph edges, segments, and resets the session ingest cursor. It is a destructive operation and is not part of normal `invoke()` processing.
304
+
265
305
  ### Targeted Recall
266
306
 
267
307
  Use `recall()` when you want to retrieve structured memory by meaning without asking the LLM to produce an answer.
@@ -298,38 +338,46 @@ Options:
298
338
 
299
339
  `recall()` is side-effect free. It does not call `invoke()`, does not trigger a new LLM completion, and does not mutate local history. Your application can call it directly to display memories, add `result.systemPrompt` to a model call, or ignore the result.
300
340
 
301
- `MemoGrafterAgent.invoke()` also calls `recall()` internally when local history overflows the configured history budget. In that automatic path, the returned `systemPrompt` is pinned as a single system message before the recent raw chat window.
302
-
303
- If you call `recall()` immediately after `invoke()`, it only sees memory that has already been ingested into storage. In queue mode, wait for your background worker to finish before expecting newly created memories to appear.
304
-
305
- ## Inspecting Memory
306
-
307
- Read a complete session graph snapshot:
308
-
309
- ```ts
310
- const snapshot = await agent.getGraphSnapshot();
311
-
312
- console.log(snapshot.sessionId);
313
- console.log(snapshot.nodes);
314
- console.log(snapshot.edges);
315
- console.log(snapshot.memories);
316
- console.log(snapshot.capturedAt);
317
- ```
318
-
319
- `getGraphSnapshot()` returns a `GraphSnapshot`:
320
-
321
- - `sessionId`: current agent session ID.
322
- - `nodes`: active topic nodes for the session.
323
- - `edges`: topic edges where either endpoint belongs to a session topic node.
324
- - `memories`: all memory nodes for the session, including decayed or superseded rows.
325
- - `capturedAt`: ISO timestamp for when the snapshot was produced.
326
-
327
- This method is read-only. It does not include raw `mg_message_buffer` content and does not add rendering, layout, or color decisions. Like `getActiveNodes()` and `getActiveSegments()`, it waits for the agent's pending ingest work before reading. If called immediately after `invoke()` in queue mode, it waits for the current ingest job to settle before returning.
328
-
329
- Read active topic nodes:
330
-
331
- ```ts
332
- const nodes = await agent.getActiveNodes();
341
+ `MemoGrafterAgent.invoke()` also calls `recall()` internally before answering when the session has topic nodes. In that automatic path, the returned `systemPrompt` is pinned as a single system message before the recent raw chat window. Automatic recall uses `inject.recallLimit` and `inject.recallMinSimilarity`, defaulting to `6` and `0.55`.
342
+
343
+ If you call `recall()` immediately after `invoke()`, it only sees memory that has already been ingested into storage. In queue mode, wait for your background worker to finish before expecting newly created memories to appear.
344
+
345
+ ## Inspecting Memory
346
+
347
+ Read a complete session graph snapshot:
348
+
349
+ ```ts
350
+ const snapshot = await agent.getGraphSnapshot();
351
+
352
+ console.log(snapshot.sessionId);
353
+ console.log(snapshot.nodes);
354
+ console.log(snapshot.snapshotNodes);
355
+ console.log(snapshot.edges);
356
+ console.log(snapshot.memories);
357
+ console.log(snapshot.memoryEdges);
358
+ console.log(snapshot.capturedAt);
359
+ ```
360
+
361
+ `getGraphSnapshot()` returns a `GraphSnapshot`:
362
+
363
+ - `sessionId`: current agent session ID.
364
+ - `nodes`: active topic nodes for the session.
365
+ - `snapshotNodes`: active topic nodes wrapped with provenance metadata when a node came from a graft.
366
+ - `edges`: topic edges where either endpoint belongs to a session topic node.
367
+ - `memories`: all memory nodes for the session, including decayed or superseded rows.
368
+ - `memoryEdges`: memory-level edges such as `semantic`, `conflicts`, `updates`, and `related`.
369
+ - `capturedAt`: ISO timestamp for when the snapshot was produced.
370
+
371
+ Each `snapshotNodes` entry contains the topic `node` and an optional `graftOrigin` with `sourceSessionId`, `sourceNodeId`, and `graftedAt`. The plain `nodes` array remains available for callers that only need the topic nodes.
372
+
373
+ This method is read-only. It does not include raw `mg_message_buffer` content and does not add rendering, layout, or color decisions. Like `getActiveNodes()` and `getActiveSegments()`, it waits for the agent's pending ingest work before reading. If called immediately after `invoke()` in queue mode, it waits for the current ingest job to settle before returning.
374
+
375
+ Graph snapshots intentionally include stale and maintenance metadata so visualizers can show memory lifecycle state. For example, a UI can fade `decayed` memories, show `hasConflict` badges, draw `conflicts` edges between contradictory facts, and draw `updates` edges from the current fact to the older fact it replaced.
376
+
377
+ Read active topic nodes:
378
+
379
+ ```ts
380
+ const nodes = await agent.getActiveNodes();
333
381
 
334
382
  for (const node of nodes) {
335
383
  console.log({
@@ -389,6 +437,112 @@ const graft = await agent.graft([nodes[0]!.id]);
389
437
  - `nodes`: selected topic nodes.
390
438
  - `tokenCount`: estimated token count.
391
439
 
440
+ Graft prompts include topic summaries because they preserve useful conversation context. Topic summaries are historical: if an older topic summary says "the user lives in Delhi" and a later memory says "the user lives in Bangalore", MemoGrafter does not rewrite the old summary. Instead, when crawler maintenance has marked a contradiction or supersession, graft prompt assembly adds deterministic maintenance notes and active memory facts:
441
+
442
+ ```text
443
+ Memory maintenance notes:
444
+ - The fact "user location: Delhi" was superseded by "Bangalore".
445
+ - Prefer active memory facts over contradictory historical summary details.
446
+ Active memory facts:
447
+ - user location: Bangalore
448
+ ```
449
+
450
+ This keeps history intact while telling the downstream model which fact is current.
451
+
452
+ ## Maintaining Memory With The Crawler
453
+
454
+ `MemoGrafterCrawler` is an optional graph maintenance worker. It can run once on demand or on a simple in-process interval. It does not use Redis, BullMQ, OpenAI, embeddings, or LLMs for the built-in conflict/versioning/decay passes.
455
+
456
+ Typical usage with the real store:
457
+
458
+ ```ts
459
+ import {
460
+ ConflictDetectionPass,
461
+ DecayScoringPass,
462
+ MemoGrafter,
463
+ MemoGrafterCrawler,
464
+ VersioningPass,
465
+ } from "memo-grafter";
466
+
467
+ const memo = new MemoGrafter(config);
468
+ await memo.initialize();
469
+
470
+ const crawler = new MemoGrafterCrawler({
471
+ store: memo.store,
472
+ intervalMs: 60_000,
473
+ passes: [
474
+ new ConflictDetectionPass(),
475
+ new VersioningPass(),
476
+ new DecayScoringPass({
477
+ halfLifeDays: 90,
478
+ minScore: 0.25,
479
+ }),
480
+ ],
481
+ });
482
+
483
+ const report = await crawler.runOnce();
484
+ console.log(report);
485
+ ```
486
+
487
+ `runOnce()` executes the configured passes exactly one time and returns a `CrawlerReport`. `intervalMs` does not affect `runOnce()`.
488
+
489
+ To run the crawler in-process on a schedule:
490
+
491
+ ```ts
492
+ crawler.start();
493
+
494
+ // Later, during shutdown:
495
+ crawler.stop();
496
+ await memo.close();
497
+ ```
498
+
499
+ `start()` is safe to call more than once; it does not create duplicate intervals. If a scheduled tick fires while the previous run is still executing, that tick is skipped.
500
+
501
+ The built-in conflict/versioning passes use deterministic matching:
502
+
503
+ - they group active memories by session, normalized `subject`, and normalized `predicate`;
504
+ - a group conflicts when it has different normalized `value` strings;
505
+ - decayed memories are skipped;
506
+ - already superseded memories are skipped;
507
+ - the newest conflicting fact wins by `createdAt`;
508
+ - if timestamps tie, deterministic ID ordering is used.
509
+
510
+ `DecayScoringPass` uses confidence-weighted exponential recency decay:
511
+
512
+ ```text
513
+ recency_factor = exp(-(ln(2) / half_life_days) * age_days)
514
+ decay_score = confidence * recency_factor
515
+ ```
516
+
517
+ If `decay_score < minScore`, the memory is marked `decayed: true`. Superseded memories and already decayed memories are skipped. Conservative defaults are used when options are omitted:
518
+
519
+ ```ts
520
+ new DecayScoringPass({
521
+ halfLifeDays: 90,
522
+ minScore: 0.25,
523
+ });
524
+ ```
525
+
526
+ By default the pass does not change stored `confidence`; confidence is treated as extraction confidence, while decay score is temporal freshness. If you explicitly want the score written back as confidence, pass `updateConfidence: true`.
527
+
528
+ When conflicts are found:
529
+
530
+ - both active facts get `hasConflict: true`;
531
+ - a `conflicts` memory edge is created;
532
+ - the older fact gets `supersededBy` pointing to the newer fact;
533
+ - an `updates` edge is created as `newer_memory --updates--> older_memory`.
534
+ - stale active memories can be marked `decayed: true` by the decay pass.
535
+
536
+ Crawler maintenance is non-destructive. It annotates existing memory rows and creates memory edges. It does not delete nodes, does not rebuild topics, and does not rewrite topic summaries.
537
+
538
+ Do not put crawler behavior inside `clearSession()`. `clearSession()` is a destructive reset. The crawler is a non-destructive maintenance worker. If you intentionally rebuild a session graph, use this order:
539
+
540
+ ```ts
541
+ await agent.clearSession();
542
+ await memo.ingestNow(messages, sessionId);
543
+ await crawler.runOnce();
544
+ ```
545
+
392
546
  ## Absorbing Memory Into Another Chatbot
393
547
 
394
548
  Use `absorbFromAgent()` to copy selected memory from one chatbot into another.
@@ -419,6 +573,38 @@ const response = await writingBot.invoke(
419
573
  console.log(response);
420
574
  ```
421
575
 
576
+ Absorbing copies selected topic nodes into the target session and creates `grafted` edges back to their source nodes. It also copies active memory facts attached to those topic nodes into the target session so future `recall()` and `invoke()` calls can surface the transferred context. Decayed or superseded source memories are not copied.
577
+
578
+ Each copied topic node is recorded in the graft registry. Registry entries let you answer where a graft came from and which copied destination node owns it.
579
+
580
+ Known limitation: if a source topic node has no associated memory rows in `mg_memory_nodes`, there are no facts to copy. The topic node can still be grafted, but targeted recall will not return facts for that node unless memory extraction produced them.
581
+
582
+ ### Inspect Graft Registry
583
+
584
+ ```ts
585
+ const registry = await writingBot.getGraftRegistry();
586
+
587
+ for (const entry of registry) {
588
+ console.log({
589
+ nodeId: entry.nodeId,
590
+ sourceSessionId: entry.sourceSessionId,
591
+ sourceNodeId: entry.sourceNodeId,
592
+ graftedAt: entry.graftedAt,
593
+ });
594
+ }
595
+ ```
596
+
597
+ Use this when your app needs to display transferred memory provenance or decide which graft to remove.
598
+
599
+ ### Remove A Graft
600
+
601
+ ```ts
602
+ const registry = await writingBot.getGraftRegistry();
603
+ await writingBot.removeGraft(registry[0]!.nodeId);
604
+ ```
605
+
606
+ `removeGraft()` removes the copied graft node from the current session. The registry row is removed with it. The method is scoped to the current agent session and throws if the node is not a registered graft for that session.
607
+
422
608
  ### Absorb By Semantic Prompt
423
609
 
424
610
  ```ts
@@ -480,6 +666,8 @@ const agent = new MemoGrafterAgent({
480
666
  bufferSize: 4,
481
667
  tokenBudget: 1500,
482
668
  recentWindowSize: 20,
669
+ recallLimit: 6,
670
+ recallMinSimilarity: 0.55,
483
671
  },
484
672
  cache: {
485
673
  connectionString: process.env.REDIS_URL!,
@@ -511,14 +699,16 @@ const store: GraphStore = new PostgresGraphStore(process.env.DATABASE_URL!);
511
699
 
512
700
  `GraphStore` is the public storage interface. `PostgresGraphStore` is the default PostgreSQL and pgvector implementation.
513
701
 
514
- Useful store inspection methods include:
515
-
516
- - `getNodesBySession(sessionId)`: read topic nodes for a session.
517
- - `getTopicNode(topicNodeId, sessionId?)`: read one topic node by ID.
518
- - `getSegmentsBySession(sessionId)`: read topic segments for a session.
519
- - `getEdgesByType(sessionId, type)`: inspect graph edges such as `"reentry"`, `"semantic"`, `"temporal"`, or `"grafted"`.
520
- - `getEdgesBySession(sessionId)`: read all topic edges where either endpoint belongs to the session's topic nodes.
521
- - `getMemoriesBySession(sessionId)`: read all memory nodes for a session, including decayed and superseded rows.
702
+ Useful store inspection methods include:
703
+
704
+ - `getNodesBySession(sessionId)`: read topic nodes for a session.
705
+ - `getTopicNode(topicNodeId, sessionId?)`: read one topic node by ID.
706
+ - `getSegmentsBySession(sessionId)`: read topic segments for a session.
707
+ - `getEdgesByType(sessionId, type)`: inspect graph edges such as `"reentry"`, `"semantic"`, `"temporal"`, or `"grafted"`.
708
+ - `getEdgesBySession(sessionId)`: read all topic edges where either endpoint belongs to the session's topic nodes.
709
+ - `getMemoriesBySession(sessionId)`: read all memory nodes for a session, including decayed and superseded rows.
710
+ - `getMemoryEdgesBySession(sessionId)`: read memory-level edges such as `conflicts` and `updates`.
711
+ - `getGraftRegistry(sessionId)`: read graft provenance entries for a session.
522
712
 
523
713
  ### `llm`
524
714
 
@@ -607,7 +797,7 @@ Without reentry detection, the later database discussion is just another topic n
607
797
 
608
798
  This helps graph traversal and memory injection recover earlier related context. A later question about connection pooling can still be connected to the original PostgreSQL/ACID discussion.
609
799
 
610
- Reentry edges are written between the current rebuilt topic nodes. They do not point at deleted nodes from previous ingestion passes.
800
+ Reentry edges are written between newly ingested topic nodes, and can also point from a new node back to an existing durable topic node when the detector recognizes a return to earlier context.
611
801
 
612
802
  ### `graph`
613
803
 
@@ -630,16 +820,20 @@ inject: {
630
820
  bufferSize: 4,
631
821
  tokenBudget: 1500,
632
822
  recentWindowSize: 20,
823
+ recallLimit: 6,
824
+ recallMinSimilarity: 0.55,
633
825
  }
634
826
  ```
635
827
 
636
- Controls memory prompt sizing and the raw history window kept when chat history overflows.
828
+ Controls memory prompt sizing, invoke-time recall, and the raw history window sent to the LLM.
637
829
 
638
830
  - `bufferSize`: nearby raw messages to include.
639
- - `tokenBudget`: approximate token budget used for memory prompts and agent history overflow checks. `MemoGrafterAgent` starts overflow handling at 80% of this value.
640
- - `recentWindowSize`: number of newest raw chat messages to keep after the pinned recall block during overflow. Defaults to `20`.
831
+ - `tokenBudget`: approximate token budget used for graft prompt assembly.
832
+ - `recentWindowSize`: number of newest raw chat messages to send after the optional invoke-time memory system message. Defaults to `20`.
833
+ - `recallLimit`: max memory facts to fetch for automatic invoke-time recall. Defaults to `6`.
834
+ - `recallMinSimilarity`: similarity floor for automatic invoke-time recall. Defaults to `0.55`.
641
835
 
642
- When `MemoGrafterAgent` detects overflow, it builds a recall query from the last six message contents, calls recall with `{ limit: 5, minSimilarity: 0.65 }`, prepends the returned memory prompt as a system message, and keeps the last `recentWindowSize` raw messages. If recall fails, it uses only that recent raw window.
836
+ When `MemoGrafterAgent.invoke()` sees existing topic nodes for the session, it calls recall with the current user message, prepends the returned memory prompt as a system message when facts are found, and keeps the last `recentWindowSize` raw messages. If recall fails or returns no facts, it uses only that recent raw window plus the current user message.
643
837
 
644
838
  ### `cache`
645
839
 
@@ -680,6 +874,8 @@ const agent = new MemoGrafterAgent({
680
874
 
681
875
  Queue mode is useful when ingestion becomes too slow to run inline. Redis connection problems are logged as warnings and should not throw from normal chatbot invocation.
682
876
 
877
+ Whether ingestion runs inline or through the queue, MemoGrafter uses the stored ingest cursor to skip message ranges that have already been processed. Queue retries should therefore avoid creating duplicate topic nodes for the same message range.
878
+
683
879
  ## Custom Adapters
684
880
 
685
881
  You can use any model provider if you implement the public adapter interfaces.
@@ -765,7 +961,7 @@ Breaking changes to pipeline internals may occur in minor versions.
765
961
 
766
962
  Available pipeline exports:
767
963
 
768
- - `IngestPipeline`: segments messages, builds topic nodes, extracts memory nodes, and writes graph edges.
964
+ - `IngestPipeline`: incrementally segments new messages, builds topic nodes, extracts memory nodes, and writes graph edges.
769
965
  - `RetrieverPipeline`: embeds a query, searches memory nodes, and returns a structured `RetrievalResult`.
770
966
  - `GrafterPipeline`: traverses the topic graph and assembles a token-budget-fitted system prompt.
771
967
 
@@ -860,15 +1056,29 @@ await conductor.graftByPrompt("invoice credit policy", technical, {
860
1056
  });
861
1057
  ```
862
1058
 
863
- ## Example Project
1059
+ ## Example Projects
864
1060
 
865
- This repository includes a runnable example:
1061
+ This repository includes two runnable examples:
866
1062
 
867
1063
  ```text
1064
+ examples/basic-chat-memory
868
1065
  examples/chatbot-memory-demo
869
1066
  ```
870
1067
 
871
- Run it:
1068
+ Run the single-agent memory demo:
1069
+
1070
+ ```bash
1071
+ cd D:/cohort/projects/project-memoGrafter
1072
+ npm install
1073
+ npm run build
1074
+
1075
+ cd examples/basic-chat-memory
1076
+ npm install
1077
+ cp .env.example .env
1078
+ npm run dev
1079
+ ```
1080
+
1081
+ Run the two-agent grafting demo:
872
1082
 
873
1083
  ```bash
874
1084
  cd D:/cohort/projects/project-memoGrafter
@@ -977,6 +1187,18 @@ const result = await agent.recall("Japan travel preferences", {
977
1187
  });
978
1188
  ```
979
1189
 
1190
+ For grafted sessions, also confirm that the source topic had active memory nodes before it was absorbed. Absorbing copies active memory rows, but it cannot create facts for a topic if extraction produced only a topic summary and no `mg_memory_nodes` rows.
1191
+
1192
+ ### Duplicate Or Unexpected Topic Nodes
1193
+
1194
+ MemoGrafter processes only messages after the stored ingest cursor. If you see duplicate topic ranges:
1195
+
1196
+ - Confirm your database has the `mg_session_ingest_state` table.
1197
+ - Confirm the same session ID is being reused for the same agent.
1198
+ - Check whether `clearSession()` was called, which resets the cursor and removes stored session data.
1199
+
1200
+ Incremental ingest can still create semantically similar topic nodes over time. That is expected; node merge and graph compaction are separate maintenance features.
1201
+
980
1202
  ### Redis Warnings
981
1203
 
982
1204
  Redis is only required when you pass `queue` or `cache` config. If you do not need background ingestion or recall caching, remove those sections.
@@ -995,7 +1217,8 @@ Practical notes:
995
1217
  - Use PostgreSQL with `pgvector` enabled.
996
1218
  - Tune `tokenBudget` to control prompt size and cost.
997
1219
  - Use queue mode if ingestion becomes slow.
998
- - Use the optional recall cache for long sessions with repeated or automatic overflow recall.
1220
+ - Use `clearSession()` only for intentional resets; normal ingestion preserves the graph incrementally.
1221
+ - Use the optional recall cache for long sessions with repeated direct or invoke-time recall.
999
1222
  - Store your own user/session mapping outside MemoGrafter.
1000
1223
  - Call `close()` during graceful shutdown.
1001
1224
  - Do not expose database credentials or OpenAI keys to browser code.
@@ -1016,6 +1239,10 @@ Main exports:
1016
1239
  - `OpenAILLMAdapter`
1017
1240
  - `OpenAIEmbedAdapter`
1018
1241
  - `PostgresGraphStore`
1242
+ - `MemoGrafterCrawler`
1243
+ - `ConflictDetectionPass`
1244
+ - `DecayScoringPass`
1245
+ - `VersioningPass`
1019
1246
  - `GrafterPipeline`
1020
1247
  - `IngestPipeline`
1021
1248
  - `RetrieverPipeline`
@@ -1025,26 +1252,32 @@ Main exports:
1025
1252
  - `RetrieverConfig`
1026
1253
  - public shared and fleet types
1027
1254
 
1028
- Useful `GraphStore` inspection methods:
1029
-
1030
- - `getTopicNode(topicNodeId, sessionId?)`
1031
- - `getNodesBySession(sessionId)`
1032
- - `getSegmentsBySession(sessionId)`
1033
- - `getEdgesByType(sessionId, type)`
1034
- - `getEdgesBySession(sessionId)`
1035
- - `getMemoriesBySession(sessionId)`
1036
-
1037
- Common `MemoGrafterAgent` methods:
1255
+ Useful `GraphStore` inspection methods:
1256
+
1257
+ - `getTopicNode(topicNodeId, sessionId?)`
1258
+ - `getNodesBySession(sessionId)`
1259
+ - `getSegmentsBySession(sessionId)`
1260
+ - `getEdgesByType(sessionId, type)`
1261
+ - `getEdgesBySession(sessionId)`
1262
+ - `getMemoriesBySession(sessionId)`
1263
+ - `getMemoryEdgesBySession(sessionId)`
1264
+ - `getSessionNodeCount(sessionId)`
1265
+ - `getSessionIngestState(sessionId)`
1266
+
1267
+ Common `MemoGrafterAgent` methods:
1038
1268
 
1039
1269
  - `initialize()`: initialize storage.
1040
1270
  - `invoke(message)`: send a user message and receive an assistant response.
1041
1271
  - `getHistory()`: read local chat history.
1042
- - `getSessionId()`: read the current session ID.
1043
- - `getGraphSnapshot()`: read nodes, edges, memories, session ID, and capture timestamp for visualization or inspection.
1044
- - `getActiveNodes()`: inspect topic nodes.
1045
- - `getActiveSegments()`: inspect topic segments.
1272
+ - `getSessionId()`: read the current session ID.
1273
+ - `getGraphSnapshot()`: read nodes, edges, memories, session ID, and capture timestamp for visualization or inspection.
1274
+ - `getGraftRegistry()`: inspect provenance for grafted nodes in the current session.
1275
+ - `getActiveNodes()`: inspect topic nodes.
1276
+ - `getActiveSegments()`: inspect topic segments.
1277
+ - `clearSession()`: explicitly clear local history and stored session memory.
1046
1278
  - `recall(query, options?)`: retrieve structured memory by semantic query.
1047
1279
  - `graft(topicIds?)`: preview memory injection.
1048
1280
  - `ingestGraftedNodes(nodes)`: copy provided nodes into this agent.
1049
1281
  - `absorbFromAgent(sourceAgent, options)`: select and copy memory from another agent.
1282
+ - `removeGraft(nodeId)`: remove a registered graft node from the current session.
1050
1283
  - `close()`: close database and queue resources.
@@ -1,11 +1,12 @@
1
- import type { AbsorbFromAgentOptions, GraphSnapshot, InjectionResult, MemoGrafterConfig, Message, RetrievalResult, RetrieverConfig, TopicNode, TopicSegment } from "./types.js";
1
+ import type { AbsorbFromAgentOptions, GraftRegistryEntry, GraphSnapshot, InjectionResult, MemoGrafterConfig, Message, RetrievalResult, RetrieverConfig, TopicNode, TopicSegment } from "./types.js";
2
2
  export declare class MemoGrafterAgent {
3
3
  private readonly core;
4
4
  private readonly sessionId;
5
5
  private readonly history;
6
6
  private readonly baseSystemPrompt;
7
- private readonly historyTokenBudget;
8
7
  private readonly recentWindowSize;
8
+ private readonly recallLimit;
9
+ private readonly recallMinSimilarity;
9
10
  private readonly cacheConfig;
10
11
  private pendingIngest;
11
12
  constructor(config: MemoGrafterConfig);
@@ -16,12 +17,15 @@ export declare class MemoGrafterAgent {
16
17
  getActiveNodes(): Promise<TopicNode[]>;
17
18
  getActiveSegments(): Promise<TopicSegment[]>;
18
19
  getGraphSnapshot(): Promise<GraphSnapshot>;
20
+ getGraftRegistry(): Promise<GraftRegistryEntry[]>;
21
+ removeGraft(nodeId: string): Promise<void>;
22
+ clearSession(): Promise<void>;
19
23
  graft(topicIds?: string[]): Promise<InjectionResult>;
20
24
  ingestGraftedNodes(nodes: TopicNode[]): Promise<TopicNode[]>;
21
25
  recall(query: string, options?: RetrieverConfig): Promise<RetrievalResult>;
22
26
  absorbFromAgent(sourceAgent: MemoGrafterAgent, options?: AbsorbFromAgentOptions): Promise<TopicNode[]>;
23
27
  private enqueueBackgroundIngest;
24
- private buildHistory;
28
+ private _buildMemoryContext;
25
29
  close(): Promise<void>;
26
30
  }
27
31
  //# sourceMappingURL=MemoGrafterAgent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MemoGrafterAgent.d.ts","sourceRoot":"","sources":["../src/MemoGrafterAgent.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,eAAe,EACf,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,aAAa,CAAoC;gBAE7C,MAAM,EAAE,iBAAiB;IAQrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYlD,UAAU,IAAI,OAAO,EAAE;IAIvB,YAAY,IAAI,MAAM;IAIhB,cAAc,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAMtC,iBAAiB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAM5C,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC;IAe1C,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IAO1D,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAItD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB9E,eAAe,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKhH,OAAO,CAAC,uBAAuB;YAUjB,YAAY;IA4B1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB"}
1
+ {"version":3,"file":"MemoGrafterAgent.d.ts","sourceRoot":"","sources":["../src/MemoGrafterAgent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,eAAe,EACf,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,aAAa,CAAoC;gBAE7C,MAAM,EAAE,iBAAiB;IASrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBlD,UAAU,IAAI,OAAO,EAAE;IAIvB,YAAY,IAAI,MAAM;IAIhB,cAAc,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAMtC,iBAAiB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAM5C,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC;IAmC1C,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAKjD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7B,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IAO1D,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAItD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB9E,eAAe,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKhH,OAAO,CAAC,uBAAuB;YAUjB,mBAAmB;IAsBjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB"}