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.
- package/README.md +81 -73
- package/USER_GUIDE.md +306 -73
- package/dist/MemoGrafterAgent.d.ts +7 -3
- package/dist/MemoGrafterAgent.d.ts.map +1 -1
- package/dist/MemoGrafterAgent.js +64 -23
- package/dist/MemoGrafterAgent.js.map +1 -1
- package/dist/crawler/ConflictDetectionPass.d.ts +6 -0
- package/dist/crawler/ConflictDetectionPass.d.ts.map +1 -0
- package/dist/crawler/ConflictDetectionPass.js +46 -0
- package/dist/crawler/ConflictDetectionPass.js.map +1 -0
- package/dist/crawler/DecayScoringPass.d.ts +17 -0
- package/dist/crawler/DecayScoringPass.d.ts.map +1 -0
- package/dist/crawler/DecayScoringPass.js +73 -0
- package/dist/crawler/DecayScoringPass.js.map +1 -0
- package/dist/crawler/MemoGrafterCrawler.d.ts +14 -0
- package/dist/crawler/MemoGrafterCrawler.d.ts.map +1 -0
- package/dist/crawler/MemoGrafterCrawler.js +108 -0
- package/dist/crawler/MemoGrafterCrawler.js.map +1 -0
- package/dist/crawler/VersioningPass.d.ts +6 -0
- package/dist/crawler/VersioningPass.d.ts.map +1 -0
- package/dist/crawler/VersioningPass.js +43 -0
- package/dist/crawler/VersioningPass.js.map +1 -0
- package/dist/crawler/decayScoring.d.ts +7 -0
- package/dist/crawler/decayScoring.d.ts.map +1 -0
- package/dist/crawler/decayScoring.js +9 -0
- package/dist/crawler/decayScoring.js.map +1 -0
- package/dist/crawler/index.d.ts +7 -0
- package/dist/crawler/index.d.ts.map +1 -0
- package/dist/crawler/index.js +5 -0
- package/dist/crawler/index.js.map +1 -0
- package/dist/crawler/memoryMaintenance.d.ts +14 -0
- package/dist/crawler/memoryMaintenance.d.ts.map +1 -0
- package/dist/crawler/memoryMaintenance.js +40 -0
- package/dist/crawler/memoryMaintenance.js.map +1 -0
- package/dist/crawler/types.d.ts +63 -0
- package/dist/crawler/types.d.ts.map +1 -0
- package/dist/crawler/types.js +2 -0
- package/dist/crawler/types.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/GrafterPipeline.d.ts.map +1 -1
- package/dist/pipeline/GrafterPipeline.js +42 -1
- package/dist/pipeline/GrafterPipeline.js.map +1 -1
- package/dist/pipeline/IngestPipeline.d.ts +2 -0
- package/dist/pipeline/IngestPipeline.d.ts.map +1 -1
- package/dist/pipeline/IngestPipeline.js +93 -17
- package/dist/pipeline/IngestPipeline.js.map +1 -1
- package/dist/prompts/memoryInjectionPrompt.d.ts +6 -2
- package/dist/prompts/memoryInjectionPrompt.d.ts.map +1 -1
- package/dist/prompts/memoryInjectionPrompt.js +25 -2
- package/dist/prompts/memoryInjectionPrompt.js.map +1 -1
- package/dist/store/GraphStore.d.ts +20 -1
- package/dist/store/GraphStore.d.ts.map +1 -1
- package/dist/store/postgres-pgvector/GraphStore.d.ts +25 -1
- package/dist/store/postgres-pgvector/GraphStore.d.ts.map +1 -1
- package/dist/store/postgres-pgvector/GraphStore.js +334 -1
- package/dist/store/postgres-pgvector/GraphStore.js.map +1 -1
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/drift/cosineSimilarity.test.d.ts +2 -0
- package/dist/utils/drift/cosineSimilarity.test.d.ts.map +1 -0
- package/dist/utils/drift/cosineSimilarity.test.js +25 -0
- package/dist/utils/drift/cosineSimilarity.test.js.map +1 -0
- package/dist/utils/drift/vectorAvg.test.d.ts +2 -0
- package/dist/utils/drift/vectorAvg.test.d.ts.map +1 -0
- package/dist/utils/drift/vectorAvg.test.js +36 -0
- package/dist/utils/drift/vectorAvg.test.js.map +1 -0
- package/dist/utils/text/normalizeText.test.d.ts +2 -0
- package/dist/utils/text/normalizeText.test.d.ts.map +1 -0
- package/dist/utils/text/normalizeText.test.js +23 -0
- package/dist/utils/text/normalizeText.test.js.map +1 -0
- package/dist/utils/vector/vectorLiteral.test.d.ts +2 -0
- package/dist/utils/vector/vectorLiteral.test.d.ts.map +1 -0
- package/dist/utils/vector/vectorLiteral.test.js +28 -0
- package/dist/utils/vector/vectorLiteral.test.js.map +1 -0
- 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.
|
|
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
|
|
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.
|
|
255
|
-
2.
|
|
256
|
-
3.
|
|
257
|
-
4.
|
|
258
|
-
5. Adds the assistant response to history.
|
|
259
|
-
6. Queues ingestion of the
|
|
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
|
-
|
|
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
|
|
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.
|
|
315
|
-
console.log(snapshot.
|
|
316
|
-
console.log(snapshot.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- `
|
|
324
|
-
- `
|
|
325
|
-
- `
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
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
|
|
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
|
|
640
|
-
- `recentWindowSize`: number of newest raw chat messages to
|
|
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`
|
|
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
|
|
1059
|
+
## Example Projects
|
|
864
1060
|
|
|
865
|
-
This repository includes
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
- `
|
|
1045
|
-
- `
|
|
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
|
|
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":"
|
|
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"}
|