memo-grafter 0.1.1 → 0.2.1
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/.env.example +1 -0
- package/README.md +19 -41
- package/USER_GUIDE.md +329 -21
- package/dist/MemoGrafter.d.ts +3 -1
- package/dist/MemoGrafter.d.ts.map +1 -1
- package/dist/MemoGrafter.js +31 -5
- package/dist/MemoGrafter.js.map +1 -1
- package/dist/MemoGrafterAgent.d.ts +9 -1
- package/dist/MemoGrafterAgent.d.ts.map +1 -1
- package/dist/MemoGrafterAgent.js +62 -8
- package/dist/MemoGrafterAgent.js.map +1 -1
- package/dist/adapters/AnthropicAdapter.d.ts.map +1 -1
- package/dist/adapters/AnthropicAdapter.js +8 -2
- package/dist/adapters/AnthropicAdapter.js.map +1 -1
- package/dist/adapters/GeminiAdapter.d.ts +15 -0
- package/dist/adapters/GeminiAdapter.d.ts.map +1 -0
- package/dist/adapters/GeminiAdapter.js +48 -0
- package/dist/adapters/GeminiAdapter.js.map +1 -0
- package/dist/fleet/FleetStore.d.ts +1 -1
- package/dist/fleet/FleetStore.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/GrafterPipeline.d.ts +8 -3
- package/dist/pipeline/GrafterPipeline.d.ts.map +1 -1
- package/dist/pipeline/GrafterPipeline.js +8 -6
- package/dist/pipeline/GrafterPipeline.js.map +1 -1
- package/dist/pipeline/IngestPipeline.d.ts +19 -4
- package/dist/pipeline/IngestPipeline.d.ts.map +1 -1
- package/dist/pipeline/IngestPipeline.js +40 -6
- package/dist/pipeline/IngestPipeline.js.map +1 -1
- package/dist/pipeline/RetrieverPipeline.d.ts +24 -0
- package/dist/pipeline/RetrieverPipeline.d.ts.map +1 -0
- package/dist/pipeline/RetrieverPipeline.js +106 -0
- package/dist/pipeline/RetrieverPipeline.js.map +1 -0
- package/dist/pipeline/SegmentProcessor.d.ts +3 -2
- package/dist/pipeline/SegmentProcessor.d.ts.map +1 -1
- package/dist/pipeline/SegmentProcessor.js +50 -18
- package/dist/pipeline/SegmentProcessor.js.map +1 -1
- package/dist/pipeline/TopicDriftDetector.d.ts +21 -3
- package/dist/pipeline/TopicDriftDetector.d.ts.map +1 -1
- package/dist/pipeline/TopicDriftDetector.js +143 -17
- package/dist/pipeline/TopicDriftDetector.js.map +1 -1
- package/dist/prompts/factRetrievalPrompt.d.ts +4 -0
- package/dist/prompts/factRetrievalPrompt.d.ts.map +1 -0
- package/dist/prompts/factRetrievalPrompt.js +25 -0
- package/dist/prompts/factRetrievalPrompt.js.map +1 -0
- package/dist/prompts/historyCompressionPrompt.d.ts +3 -0
- package/dist/prompts/historyCompressionPrompt.d.ts.map +1 -0
- package/dist/prompts/historyCompressionPrompt.js +4 -0
- package/dist/prompts/historyCompressionPrompt.js.map +1 -0
- package/dist/prompts/intentShiftPrompt.d.ts +3 -0
- package/dist/prompts/intentShiftPrompt.d.ts.map +1 -0
- package/dist/prompts/intentShiftPrompt.js +11 -0
- package/dist/prompts/intentShiftPrompt.js.map +1 -0
- package/dist/prompts/segmentExtractionPrompt.d.ts.map +1 -1
- package/dist/prompts/segmentExtractionPrompt.js +68 -12
- package/dist/prompts/segmentExtractionPrompt.js.map +1 -1
- package/dist/store/GraphStore.d.ts +16 -19
- package/dist/store/GraphStore.d.ts.map +1 -1
- package/dist/store/GraphStore.js +1 -576
- package/dist/store/GraphStore.js.map +1 -1
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/postgres-pgvector/GraphStore.d.ts +75 -0
- package/dist/store/postgres-pgvector/GraphStore.d.ts.map +1 -0
- package/dist/store/postgres-pgvector/GraphStore.js +782 -0
- package/dist/store/postgres-pgvector/GraphStore.js.map +1 -0
- package/dist/types.d.ts +79 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/drift/driftMarkers.d.ts +4 -0
- package/dist/utils/drift/driftMarkers.d.ts.map +1 -0
- package/dist/utils/drift/driftMarkers.js +31 -0
- package/dist/utils/drift/driftMarkers.js.map +1 -0
- package/dist/utils/drift/driftScore.d.ts +3 -0
- package/dist/utils/drift/driftScore.d.ts.map +1 -0
- package/dist/utils/drift/driftScore.js +12 -0
- package/dist/utils/drift/driftScore.js.map +1 -0
- package/dist/utils/drift/driftThreshold.d.ts +7 -0
- package/dist/utils/drift/driftThreshold.d.ts.map +1 -0
- package/dist/utils/drift/driftThreshold.js +21 -0
- package/dist/utils/drift/driftThreshold.js.map +1 -0
- package/dist/utils/drift/reentryMatch.d.ts +3 -0
- package/dist/utils/drift/reentryMatch.d.ts.map +1 -0
- package/dist/utils/drift/reentryMatch.js +10 -0
- package/dist/utils/drift/reentryMatch.js.map +1 -0
- package/dist/utils/extraction/segmentExtraction.d.ts +5 -0
- package/dist/utils/extraction/segmentExtraction.d.ts.map +1 -0
- package/dist/utils/extraction/segmentExtraction.js +82 -0
- package/dist/utils/extraction/segmentExtraction.js.map +1 -0
- package/dist/utils/reentry/reentryCues.d.ts +3 -0
- package/dist/utils/reentry/reentryCues.d.ts.map +1 -0
- package/dist/utils/reentry/reentryCues.js +14 -0
- package/dist/utils/reentry/reentryCues.js.map +1 -0
- package/dist/utils/reentry/reentryEdges.d.ts +16 -0
- package/dist/utils/reentry/reentryEdges.d.ts.map +1 -0
- package/dist/utils/reentry/reentryEdges.js +80 -0
- package/dist/utils/reentry/reentryEdges.js.map +1 -0
- package/dist/utils/reentry/reentrySimilarity.d.ts +5 -0
- package/dist/utils/reentry/reentrySimilarity.d.ts.map +1 -0
- package/dist/utils/reentry/reentrySimilarity.js +26 -0
- package/dist/utils/reentry/reentrySimilarity.js.map +1 -0
- package/dist/utils/reentry/reentryText.d.ts +6 -0
- package/dist/utils/reentry/reentryText.d.ts.map +1 -0
- package/dist/utils/reentry/reentryText.js +29 -0
- package/dist/utils/reentry/reentryText.js.map +1 -0
- package/dist/utils/reentry/types.d.ts +6 -0
- package/dist/utils/reentry/types.d.ts.map +1 -0
- package/dist/utils/reentry/types.js +2 -0
- package/dist/utils/reentry/types.js.map +1 -0
- package/dist/utils/text/normalizeText.d.ts +2 -0
- package/dist/utils/text/normalizeText.d.ts.map +1 -0
- package/dist/utils/text/normalizeText.js +5 -0
- package/dist/utils/text/normalizeText.js.map +1 -0
- package/dist/utils/text/terms.d.ts +3 -0
- package/dist/utils/text/terms.d.ts.map +1 -0
- package/dist/utils/text/terms.js +51 -0
- package/dist/utils/text/terms.js.map +1 -0
- package/dist/utils/text/tokenCount.d.ts +3 -0
- package/dist/utils/text/tokenCount.d.ts.map +1 -0
- package/dist/utils/text/tokenCount.js +7 -0
- package/dist/utils/text/tokenCount.js.map +1 -0
- package/dist/utils/vector/vectorLiteral.d.ts +3 -0
- package/dist/utils/vector/vectorLiteral.d.ts.map +1 -0
- package/dist/utils/vector/vectorLiteral.js +19 -0
- package/dist/utils/vector/vectorLiteral.js.map +1 -0
- package/package.json +11 -3
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -3,32 +3,28 @@
|
|
|
3
3
|
|
|
4
4
|
Structured memory for TypeScript chatbots.
|
|
5
5
|
|
|
6
|
-
MemoGrafter helps chatbot applications remember conversations without stuffing every old message back into the prompt. It turns
|
|
6
|
+
MemoGrafter helps chatbot applications remember conversations without stuffing every old message back into the prompt. It turns conversation history into topic-based memory, recalls relevant details later, and can copy useful memory from one chatbot or session into another.
|
|
7
7
|
|
|
8
|
-
It is a memory framework, not an autonomous agent runtime. It does not run tools, schedule
|
|
8
|
+
It is a memory framework, not an autonomous agent runtime. It does not run tools, schedule work, or decide goals for an agent.
|
|
9
9
|
|
|
10
|
-
## What
|
|
10
|
+
## What It Is For
|
|
11
11
|
|
|
12
|
-
- Chatbots that
|
|
13
|
-
-
|
|
14
|
-
- Multi-chatbot
|
|
15
|
-
-
|
|
16
|
-
- Server-side TypeScript apps that need reusable LLM memory primitives.
|
|
12
|
+
- Chatbots that need long-running memory.
|
|
13
|
+
- Assistants that should recall user preferences, prior context, and open questions.
|
|
14
|
+
- Multi-chatbot or multi-session flows where selected memory can be grafted into another conversation.
|
|
15
|
+
- TypeScript apps that need reusable memory, retrieval, and graph-backed conversation primitives.
|
|
17
16
|
|
|
18
17
|
## How It Works
|
|
19
18
|
|
|
20
|
-
At a high level:
|
|
21
|
-
|
|
22
19
|
```text
|
|
23
20
|
chat messages
|
|
24
|
-
-> topic
|
|
25
|
-
-> memory nodes
|
|
21
|
+
-> topic-based memory
|
|
26
22
|
-> graph links
|
|
27
|
-
-> relevant
|
|
23
|
+
-> relevant recall
|
|
28
24
|
-> optional memory grafting
|
|
29
25
|
```
|
|
30
26
|
|
|
31
|
-
MemoGrafter stores conversation turns, detects topic
|
|
27
|
+
MemoGrafter stores conversation turns, detects topic changes, summarizes useful context, links related memories, and retrieves or grafts memory when needed.
|
|
32
28
|
|
|
33
29
|
## Install
|
|
34
30
|
|
|
@@ -36,12 +32,7 @@ MemoGrafter stores conversation turns, detects topic shifts, summarizes segments
|
|
|
36
32
|
npm install memo-grafter
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
MemoGrafter runs server-side on Node.js
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
DATABASE_URL=postgres://postgres:postgres@localhost:5432/memo_grafter
|
|
43
|
-
OPENAI_API_KEY=sk-...
|
|
44
|
-
```
|
|
35
|
+
MemoGrafter runs server-side on Node.js. The built-in storage backend uses PostgreSQL with `pgvector`.
|
|
45
36
|
|
|
46
37
|
## Minimal Example
|
|
47
38
|
|
|
@@ -62,33 +53,20 @@ const agent = new MemoGrafterAgent({
|
|
|
62
53
|
|
|
63
54
|
await agent.initialize();
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log(await agent.invoke("What do you remember about my travel preferences?"));
|
|
68
|
-
|
|
69
|
-
await agent.close();
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Memory Grafting
|
|
56
|
+
await agent.invoke("I am planning a Japan trip.");
|
|
57
|
+
await agent.invoke("I like quiet towns, bookstores, and local cafes.");
|
|
73
58
|
|
|
74
|
-
|
|
59
|
+
const recall = await agent.recall("travel preferences");
|
|
60
|
+
console.log(recall.facts);
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
await writingBot.absorbFromAgent(travelBot, {
|
|
78
|
-
prompt: "Japan travel preferences",
|
|
79
|
-
limit: 3,
|
|
80
|
-
});
|
|
62
|
+
await agent.close();
|
|
81
63
|
```
|
|
82
64
|
|
|
83
65
|
## Learn More
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```text
|
|
90
|
-
examples/chatbot-memory-demo
|
|
91
|
-
```
|
|
67
|
+
- [USER_GUIDE.md](https://github.com/mayhemking007/memo-grafter/blob/main/USER_GUIDE.md) covers setup, configuration, adapters, queue mode, fleet APIs, examples, and troubleshooting.
|
|
68
|
+
- [ARCHITECTURE.md](https://github.com/mayhemking007/memo-grafter/blob/main/ARCHITECTURE.md) explains the current high-level implementation.
|
|
69
|
+
- `examples` contains runnable demo.
|
|
92
70
|
|
|
93
71
|
## License
|
|
94
72
|
|
package/USER_GUIDE.md
CHANGED
|
@@ -12,12 +12,13 @@ The most important idea is memory grafting. A chatbot can build useful memory du
|
|
|
12
12
|
|
|
13
13
|
- Node.js 18 or newer.
|
|
14
14
|
- TypeScript or modern JavaScript using ES modules.
|
|
15
|
-
- PostgreSQL with the `pgvector` extension enabled
|
|
15
|
+
- PostgreSQL with the `pgvector` extension enabled for the built-in `PostgresGraphStore`.
|
|
16
16
|
- An LLM adapter.
|
|
17
17
|
- An embedding adapter.
|
|
18
18
|
- An OpenAI API key only if using the included OpenAI adapters.
|
|
19
19
|
- An Anthropic API key only if using the included Anthropic LLM adapter.
|
|
20
|
-
-
|
|
20
|
+
- A Gemini API key only if using the included Gemini adapters.
|
|
21
|
+
- Redis only if enabling queue mode or the optional recall cache.
|
|
21
22
|
|
|
22
23
|
MemoGrafter is server-side only. Do not run it in browser code.
|
|
23
24
|
|
|
@@ -51,17 +52,20 @@ Create a `.env` file in your app:
|
|
|
51
52
|
```bash
|
|
52
53
|
DATABASE_URL=postgres://postgres:postgres@localhost:5432/memo_grafter
|
|
53
54
|
ANTHROPIC_API_KEY=sk-ant-...
|
|
55
|
+
GEMINI_API_KEY=...
|
|
54
56
|
OPENAI_API_KEY=sk-...
|
|
55
57
|
REDIS_URL=redis://localhost:6379
|
|
56
58
|
```
|
|
57
59
|
|
|
58
|
-
`DATABASE_URL` is required.
|
|
60
|
+
`DATABASE_URL` is required when using the built-in PostgreSQL storage.
|
|
59
61
|
|
|
60
62
|
`OPENAI_API_KEY` is required only when using `OpenAILLMAdapter` or `OpenAIEmbedAdapter`.
|
|
61
63
|
|
|
62
64
|
`ANTHROPIC_API_KEY` is required only when using `AnthropicLLMAdapter`.
|
|
63
65
|
|
|
64
|
-
`
|
|
66
|
+
`GEMINI_API_KEY` is required only when using `GeminiLLMAdapter` or `GeminiEmbedAdapter`.
|
|
67
|
+
|
|
68
|
+
`REDIS_URL` is optional and only needed when you pass `queue` or `cache` config.
|
|
65
69
|
|
|
66
70
|
Enable `pgvector` in PostgreSQL:
|
|
67
71
|
|
|
@@ -69,7 +73,7 @@ Enable `pgvector` in PostgreSQL:
|
|
|
69
73
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
70
74
|
```
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
The built-in `PostgresGraphStore` creates its own tables during `initialize()`.
|
|
73
77
|
|
|
74
78
|
Current v1 tables:
|
|
75
79
|
|
|
@@ -77,6 +81,8 @@ Current v1 tables:
|
|
|
77
81
|
- `mg_segments`
|
|
78
82
|
- `mg_topic_nodes`
|
|
79
83
|
- `mg_topic_edges`
|
|
84
|
+
- `mg_memory_nodes`
|
|
85
|
+
- `mg_memory_edges`
|
|
80
86
|
- `mg_fleets`
|
|
81
87
|
- `mg_fleet_agents`
|
|
82
88
|
|
|
@@ -129,17 +135,25 @@ A message is one user or assistant turn:
|
|
|
129
135
|
|
|
130
136
|
```ts
|
|
131
137
|
export interface Message {
|
|
132
|
-
role: "user" | "assistant";
|
|
138
|
+
role: "system" | "user" | "assistant";
|
|
133
139
|
content: string;
|
|
134
140
|
}
|
|
135
141
|
```
|
|
136
142
|
|
|
137
|
-
`MemoGrafterAgent` keeps an in-memory history for the current session and stores messages in PostgreSQL during ingestion.
|
|
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.
|
|
138
144
|
|
|
139
145
|
### Segments
|
|
140
146
|
|
|
141
147
|
A segment is a range of messages that belong to the same topic. MemoGrafter uses drift detection to decide where topic boundaries are.
|
|
142
148
|
|
|
149
|
+
Drift detection combines several signals:
|
|
150
|
+
|
|
151
|
+
- how far the current message is from the current topic embedding,
|
|
152
|
+
- whether the current message is a sharp pivot from the previous user message,
|
|
153
|
+
- whether the message contains structural phrases such as "by the way", "different topic", or "going back to",
|
|
154
|
+
- short-message dampening so filler like "okay" or "got it" is less likely to create false boundaries,
|
|
155
|
+
- optional LLM classification for ambiguous topic-shift scores.
|
|
156
|
+
|
|
143
157
|
Example:
|
|
144
158
|
|
|
145
159
|
```text
|
|
@@ -166,9 +180,29 @@ Important fields:
|
|
|
166
180
|
|
|
167
181
|
Topic nodes are stored in `mg_topic_nodes`.
|
|
168
182
|
|
|
183
|
+
### Memory Nodes
|
|
184
|
+
|
|
185
|
+
Memory nodes are typed atomic memories attached to topic nodes. They are the units used by targeted recall.
|
|
186
|
+
|
|
187
|
+
Important fields:
|
|
188
|
+
|
|
189
|
+
- `memoryType`: `"fact"`, `"insight"`, `"question"`, `"task"`, or `"reference"`.
|
|
190
|
+
- `subject`, `predicate`, `value`: the structured memory triple.
|
|
191
|
+
- `confidence`: confidence score from `0` to `1`.
|
|
192
|
+
- `topicNodeId`: parent topic node ID.
|
|
193
|
+
- `decayed`: whether the memory is stale.
|
|
194
|
+
- `supersededBy`: newer memory ID when this memory has been replaced.
|
|
195
|
+
|
|
196
|
+
Memory nodes are stored in `mg_memory_nodes`.
|
|
197
|
+
|
|
169
198
|
### Graph Edges
|
|
170
199
|
|
|
171
|
-
Edges connect related topic nodes. They can represent temporal, semantic, or
|
|
200
|
+
Edges connect related topic nodes. They can represent temporal, semantic, grafted, or reentry relationships.
|
|
201
|
+
|
|
202
|
+
- `temporal`: one topic followed another in the conversation.
|
|
203
|
+
- `semantic`: two topics are similar by embedding search.
|
|
204
|
+
- `grafted`: a topic was copied from another session or chatbot.
|
|
205
|
+
- `reentry`: the conversation returned to an earlier topic after discussing something else.
|
|
172
206
|
|
|
173
207
|
Edges are stored in `mg_topic_edges`.
|
|
174
208
|
|
|
@@ -218,13 +252,55 @@ await agent.close();
|
|
|
218
252
|
On every call, `invoke()`:
|
|
219
253
|
|
|
220
254
|
1. Adds the user message to local history.
|
|
221
|
-
2.
|
|
222
|
-
3.
|
|
223
|
-
4.
|
|
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.
|
|
224
258
|
5. Adds the assistant response to history.
|
|
225
|
-
6.
|
|
259
|
+
6. Queues ingestion of the updated conversation into the memory graph.
|
|
260
|
+
|
|
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.
|
|
262
|
+
|
|
263
|
+
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
|
+
|
|
265
|
+
### Targeted Recall
|
|
266
|
+
|
|
267
|
+
Use `recall()` when you want to retrieve structured memory by meaning without asking the LLM to produce an answer.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
const result = await agent.recall("deployment config", {
|
|
271
|
+
limit: 8,
|
|
272
|
+
minSimilarity: 0.55,
|
|
273
|
+
tokenBudget: 1000,
|
|
274
|
+
cache: {
|
|
275
|
+
ttlSeconds: 90,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
console.log(result.facts);
|
|
280
|
+
console.log(result.nodes);
|
|
281
|
+
console.log(result.systemPrompt);
|
|
282
|
+
console.log(result.tokenCount);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`recall()` returns a `RetrievalResult`:
|
|
286
|
+
|
|
287
|
+
- `facts`: matching memory nodes with a `similarity` score.
|
|
288
|
+
- `nodes`: parent topic nodes for the included facts.
|
|
289
|
+
- `systemPrompt`: a formatted memory block that can be passed to an LLM if you choose.
|
|
290
|
+
- `tokenCount`: approximate token count for the included fact blocks.
|
|
291
|
+
|
|
292
|
+
Options:
|
|
293
|
+
|
|
294
|
+
- `limit`: max memory nodes to fetch before filtering. Defaults to `10`.
|
|
295
|
+
- `minSimilarity`: cosine similarity floor. Defaults to `0.6`.
|
|
296
|
+
- `tokenBudget`: max approximate tokens for included fact blocks. Defaults to `1200`.
|
|
297
|
+
- `cache.ttlSeconds`: per-call recall cache TTL override when `MemoGrafterConfig.cache` is enabled. Values are clamped to 60-120 seconds.
|
|
298
|
+
|
|
299
|
+
`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
|
+
|
|
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.
|
|
226
302
|
|
|
227
|
-
|
|
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.
|
|
228
304
|
|
|
229
305
|
## Inspecting Memory
|
|
230
306
|
|
|
@@ -368,8 +444,11 @@ const agent = new MemoGrafterAgent({
|
|
|
368
444
|
drift: {
|
|
369
445
|
mode: "intent",
|
|
370
446
|
windowSize: 5,
|
|
371
|
-
|
|
447
|
+
driftSensitivity: "medium",
|
|
372
448
|
minSegmentMessages: 3,
|
|
449
|
+
llmAmbiguityDetection: false,
|
|
450
|
+
reentryDetection: true,
|
|
451
|
+
reentryThreshold: 0.85,
|
|
373
452
|
},
|
|
374
453
|
graph: {
|
|
375
454
|
topK: 5,
|
|
@@ -378,6 +457,11 @@ const agent = new MemoGrafterAgent({
|
|
|
378
457
|
inject: {
|
|
379
458
|
bufferSize: 4,
|
|
380
459
|
tokenBudget: 1500,
|
|
460
|
+
recentWindowSize: 20,
|
|
461
|
+
},
|
|
462
|
+
cache: {
|
|
463
|
+
connectionString: process.env.REDIS_URL!,
|
|
464
|
+
ttlSeconds: 90,
|
|
381
465
|
},
|
|
382
466
|
});
|
|
383
467
|
```
|
|
@@ -390,7 +474,27 @@ db: {
|
|
|
390
474
|
}
|
|
391
475
|
```
|
|
392
476
|
|
|
393
|
-
PostgreSQL connection string
|
|
477
|
+
PostgreSQL connection string used by the built-in `PostgresGraphStore`.
|
|
478
|
+
|
|
479
|
+
MemoGrafter currently constructs `PostgresGraphStore` from this config internally. Advanced users can also import the storage contract directly:
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import {
|
|
483
|
+
PostgresGraphStore,
|
|
484
|
+
type GraphStore,
|
|
485
|
+
} from "memo-grafter";
|
|
486
|
+
|
|
487
|
+
const store: GraphStore = new PostgresGraphStore(process.env.DATABASE_URL!);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`GraphStore` is the public storage interface. `PostgresGraphStore` is the default PostgreSQL and pgvector implementation.
|
|
491
|
+
|
|
492
|
+
Useful store inspection methods include:
|
|
493
|
+
|
|
494
|
+
- `getNodesBySession(sessionId)`: read topic nodes for a session.
|
|
495
|
+
- `getTopicNode(topicNodeId, sessionId?)`: read one topic node by ID.
|
|
496
|
+
- `getSegmentsBySession(sessionId)`: read topic segments for a session.
|
|
497
|
+
- `getEdgesByType(sessionId, type)`: inspect graph edges such as `"reentry"`, `"semantic"`, `"temporal"`, or `"grafted"`.
|
|
394
498
|
|
|
395
499
|
### `llm`
|
|
396
500
|
|
|
@@ -406,6 +510,12 @@ Anthropic models can be used with the included Anthropic adapter:
|
|
|
406
510
|
llm: new AnthropicLLMAdapter("claude-sonnet-4-5")
|
|
407
511
|
```
|
|
408
512
|
|
|
513
|
+
Gemini models can be used with the included Gemini adapter:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
llm: new GeminiLLMAdapter("gemini-2.5-flash")
|
|
517
|
+
```
|
|
518
|
+
|
|
409
519
|
### `embedder`
|
|
410
520
|
|
|
411
521
|
```ts
|
|
@@ -416,14 +526,25 @@ Adapter used to create vectors for semantic search.
|
|
|
416
526
|
|
|
417
527
|
Anthropic does not provide a native embedding API. Pair `AnthropicLLMAdapter` with `OpenAIEmbedAdapter` or another custom `EmbedAdapter`.
|
|
418
528
|
|
|
529
|
+
Gemini embeddings can be used with the included Gemini embedder:
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
embedder: new GeminiEmbedAdapter("gemini-embedding-001")
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
`GeminiEmbedAdapter` requests 1536-dimensional embeddings by default to match MemoGrafter's current `vector(1536)` database schema. If you change the schema dimension, pass the matching value as the second constructor argument.
|
|
536
|
+
|
|
419
537
|
### `drift`
|
|
420
538
|
|
|
421
539
|
```ts
|
|
422
540
|
drift: {
|
|
423
541
|
mode: "intent",
|
|
424
542
|
windowSize: 5,
|
|
425
|
-
|
|
543
|
+
driftSensitivity: "medium",
|
|
426
544
|
minSegmentMessages: 3,
|
|
545
|
+
llmAmbiguityDetection: false,
|
|
546
|
+
reentryDetection: true,
|
|
547
|
+
reentryThreshold: 0.85,
|
|
427
548
|
}
|
|
428
549
|
```
|
|
429
550
|
|
|
@@ -431,11 +552,39 @@ Controls topic boundary detection.
|
|
|
431
552
|
|
|
432
553
|
- `mode`: `"intent"` or `"window"`.
|
|
433
554
|
- `windowSize`: message window size for window mode.
|
|
434
|
-
- `
|
|
555
|
+
- `driftSensitivity`: preferred sensitivity preset, one of `"low"`, `"medium"`, or `"high"`.
|
|
556
|
+
- `threshold`: deprecated numeric threshold. It still works when `driftSensitivity` is not set, but MemoGrafter logs a one-time warning.
|
|
435
557
|
- `minSegmentMessages`: minimum messages before a boundary.
|
|
558
|
+
- `llmAmbiguityDetection`: optional LLM check for borderline topic shifts. Defaults to `false`.
|
|
559
|
+
- `reentryDetection`: whether to link later topic returns back to earlier topic nodes. Defaults to `true`.
|
|
560
|
+
- `reentryThreshold`: embedding similarity threshold for reentry detection. Defaults to `0.85`.
|
|
436
561
|
|
|
437
562
|
Use `"intent"` for most chatbot memory demos. In intent mode, user messages drive topic shifts.
|
|
438
563
|
|
|
564
|
+
Sensitivity presets resolve internally to numeric thresholds:
|
|
565
|
+
|
|
566
|
+
- `"low"`: `0.25`
|
|
567
|
+
- `"medium"`: `0.35`
|
|
568
|
+
- `"high"`: `0.50`
|
|
569
|
+
|
|
570
|
+
Use `"medium"` first. Boundaries are cut when a drift score exceeds the resolved threshold, so lower numeric thresholds split more readily and higher numeric thresholds require stronger evidence.
|
|
571
|
+
|
|
572
|
+
If both `driftSensitivity` and `threshold` are provided, `driftSensitivity` wins.
|
|
573
|
+
|
|
574
|
+
#### Reentry Detection
|
|
575
|
+
|
|
576
|
+
Reentry detection handles conversations that leave a topic and later return to it:
|
|
577
|
+
|
|
578
|
+
```text
|
|
579
|
+
database choice -> authentication flow -> database connection pooling
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Without reentry detection, the later database discussion is just another topic node. With reentry detection, MemoGrafter creates a `reentry` edge from the later database node back to the earlier database node.
|
|
583
|
+
|
|
584
|
+
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.
|
|
585
|
+
|
|
586
|
+
Reentry edges are written between the current rebuilt topic nodes. They do not point at deleted nodes from previous ingestion passes.
|
|
587
|
+
|
|
439
588
|
### `graph`
|
|
440
589
|
|
|
441
590
|
```ts
|
|
@@ -456,13 +605,33 @@ Controls graph retrieval and traversal.
|
|
|
456
605
|
inject: {
|
|
457
606
|
bufferSize: 4,
|
|
458
607
|
tokenBudget: 1500,
|
|
608
|
+
recentWindowSize: 20,
|
|
459
609
|
}
|
|
460
610
|
```
|
|
461
611
|
|
|
462
|
-
Controls
|
|
612
|
+
Controls memory prompt sizing and the raw history window kept when chat history overflows.
|
|
463
613
|
|
|
464
614
|
- `bufferSize`: nearby raw messages to include.
|
|
465
|
-
- `tokenBudget`: approximate memory
|
|
615
|
+
- `tokenBudget`: approximate token budget used for memory prompts and agent history overflow checks. `MemoGrafterAgent` starts overflow handling at 80% of this value.
|
|
616
|
+
- `recentWindowSize`: number of newest raw chat messages to keep after the pinned recall block during overflow. Defaults to `20`.
|
|
617
|
+
|
|
618
|
+
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.
|
|
619
|
+
|
|
620
|
+
### `cache`
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
cache: {
|
|
624
|
+
connectionString: process.env.REDIS_URL!,
|
|
625
|
+
ttlSeconds: 90,
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Enables an opt-in Redis cache for targeted recall. MemoGrafter creates one shared Redis client and uses it to cache only the raw `searchMemories()` result. It does not cache final prompts, filtered blocks, or `RetrievalResult`, so different `tokenBudget` values still assemble fresh output.
|
|
630
|
+
|
|
631
|
+
- `connectionString`: Redis URL.
|
|
632
|
+
- `ttlSeconds`: cache TTL in seconds. Defaults to `90` and is clamped between `60` and `120`.
|
|
633
|
+
|
|
634
|
+
Recall cache keys include the session ID, `limit`, `minSimilarity`, and a deterministic hash of the query embedding. Redis failures are logged as warnings and recall falls back to PostgreSQL search. The cache is disabled unless this section is present.
|
|
466
635
|
|
|
467
636
|
## Queue Mode
|
|
468
637
|
|
|
@@ -531,6 +700,88 @@ const agent = new MemoGrafterAgent({
|
|
|
531
700
|
|
|
532
701
|
Your embedding vector dimension must match the vector dimension expected by the database schema.
|
|
533
702
|
|
|
703
|
+
## Storage Backends
|
|
704
|
+
|
|
705
|
+
MemoGrafter exposes storage through the `GraphStore` interface. The built-in implementation is `PostgresGraphStore`, which stores relational data, graph edges, and vector search data in PostgreSQL with `pgvector`.
|
|
706
|
+
|
|
707
|
+
Most users do not need to instantiate a store directly. `MemoGrafter` and `MemoGrafterAgent` use `PostgresGraphStore` from the `db.connectionString` config:
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
const agent = new MemoGrafterAgent({
|
|
711
|
+
db: {
|
|
712
|
+
connectionString: process.env.DATABASE_URL!,
|
|
713
|
+
},
|
|
714
|
+
llm,
|
|
715
|
+
embedder,
|
|
716
|
+
});
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
If you are extending MemoGrafter, use `GraphStore` as the contract and keep concrete storage details behind an implementation:
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import type { GraphStore } from "memo-grafter";
|
|
723
|
+
|
|
724
|
+
class MyGraphStore implements GraphStore {
|
|
725
|
+
// Implement the full GraphStore contract.
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Future storage implementations can use the same interface without changing pipeline or fleet code. For example, a SQLite plus vector-database implementation would implement `GraphStore` while preserving the same behavior expected by ingestion, grafting, and fleet APIs.
|
|
730
|
+
|
|
731
|
+
## Using Pipelines Directly
|
|
732
|
+
|
|
733
|
+
`MemoGrafterAgent` is the recommended starting point, but the underlying
|
|
734
|
+
pipeline classes are also exported for developers who want to build custom
|
|
735
|
+
agent loops or integrate MemoGrafter memory primitives into an existing
|
|
736
|
+
orchestration framework.
|
|
737
|
+
|
|
738
|
+
Pipeline classes are exported for composability. Their constructors and
|
|
739
|
+
internal behavior are not covered by semver stability guarantees until v1.0.
|
|
740
|
+
Breaking changes to pipeline internals may occur in minor versions.
|
|
741
|
+
|
|
742
|
+
Available pipeline exports:
|
|
743
|
+
|
|
744
|
+
- `IngestPipeline`: segments messages, builds topic nodes, extracts memory nodes, and writes graph edges.
|
|
745
|
+
- `RetrieverPipeline`: embeds a query, searches memory nodes, and returns a structured `RetrievalResult`.
|
|
746
|
+
- `GrafterPipeline`: traverses the topic graph and assembles a token-budget-fitted system prompt.
|
|
747
|
+
|
|
748
|
+
Example using `RetrieverPipeline` directly:
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
import {
|
|
752
|
+
PostgresGraphStore,
|
|
753
|
+
RetrieverPipeline,
|
|
754
|
+
OpenAIEmbedAdapter,
|
|
755
|
+
} from "memo-grafter";
|
|
756
|
+
|
|
757
|
+
const store = new PostgresGraphStore(process.env.DATABASE_URL!);
|
|
758
|
+
await store.initialize();
|
|
759
|
+
|
|
760
|
+
const embedder = new OpenAIEmbedAdapter("text-embedding-3-small");
|
|
761
|
+
|
|
762
|
+
const retriever = new RetrieverPipeline(store, embedder, {
|
|
763
|
+
limit: 8,
|
|
764
|
+
minSimilarity: 0.55,
|
|
765
|
+
tokenBudget: 1000,
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const result = await retriever.run(
|
|
769
|
+
"deployment config and Kubernetes namespace",
|
|
770
|
+
sessionId,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
console.log(result.facts);
|
|
774
|
+
console.log(result.systemPrompt);
|
|
775
|
+
|
|
776
|
+
await store.close();
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
When using pipelines directly you are responsible for managing the store
|
|
780
|
+
connection lifecycle. Call `store.close()` during graceful shutdown.
|
|
781
|
+
|
|
782
|
+
`MemoGrafterAgent` remains the batteries-included default. Existing code
|
|
783
|
+
that uses `MemoGrafterAgent` does not need to change.
|
|
784
|
+
|
|
534
785
|
## Fleet API
|
|
535
786
|
|
|
536
787
|
Fleets let you group color-scoped worker chatbots and use a conductor to graft memory across workers.
|
|
@@ -641,11 +892,31 @@ For small demos, try:
|
|
|
641
892
|
```ts
|
|
642
893
|
drift: {
|
|
643
894
|
mode: "intent",
|
|
644
|
-
|
|
895
|
+
driftSensitivity: "medium",
|
|
645
896
|
minSegmentMessages: 3,
|
|
646
897
|
}
|
|
647
898
|
```
|
|
648
899
|
|
|
900
|
+
If segments are too coarse, try a lower resolved threshold:
|
|
901
|
+
|
|
902
|
+
```ts
|
|
903
|
+
drift: {
|
|
904
|
+
mode: "intent",
|
|
905
|
+
driftSensitivity: "low",
|
|
906
|
+
minSegmentMessages: 2,
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
If segments are too fragmented, try a higher resolved threshold:
|
|
911
|
+
|
|
912
|
+
```ts
|
|
913
|
+
drift: {
|
|
914
|
+
mode: "intent",
|
|
915
|
+
driftSensitivity: "high",
|
|
916
|
+
minSegmentMessages: 4,
|
|
917
|
+
}
|
|
918
|
+
```
|
|
919
|
+
|
|
649
920
|
### Absorb Copies Zero Nodes
|
|
650
921
|
|
|
651
922
|
Inspect the source memory:
|
|
@@ -664,9 +935,27 @@ await targetAgent.absorbFromAgent(sourceAgent, {
|
|
|
664
935
|
});
|
|
665
936
|
```
|
|
666
937
|
|
|
938
|
+
### Recall Returns Zero Facts
|
|
939
|
+
|
|
940
|
+
`recall()` searches atomic memory nodes, not raw chat messages. If it returns no facts:
|
|
941
|
+
|
|
942
|
+
- Make sure ingestion has completed.
|
|
943
|
+
- Confirm memory nodes exist for the session.
|
|
944
|
+
- Try a lower `minSimilarity`.
|
|
945
|
+
- Try a more specific query that uses the same vocabulary as the original conversation.
|
|
946
|
+
|
|
947
|
+
Example:
|
|
948
|
+
|
|
949
|
+
```ts
|
|
950
|
+
const result = await agent.recall("Japan travel preferences", {
|
|
951
|
+
minSimilarity: 0.3,
|
|
952
|
+
limit: 5,
|
|
953
|
+
});
|
|
954
|
+
```
|
|
955
|
+
|
|
667
956
|
### Redis Warnings
|
|
668
957
|
|
|
669
|
-
Redis is only required when you pass `queue` config. If you do not need background ingestion, remove
|
|
958
|
+
Redis is only required when you pass `queue` or `cache` config. If you do not need background ingestion or recall caching, remove those sections.
|
|
670
959
|
|
|
671
960
|
### Browser Runtime Error
|
|
672
961
|
|
|
@@ -682,6 +971,7 @@ Practical notes:
|
|
|
682
971
|
- Use PostgreSQL with `pgvector` enabled.
|
|
683
972
|
- Tune `tokenBudget` to control prompt size and cost.
|
|
684
973
|
- Use queue mode if ingestion becomes slow.
|
|
974
|
+
- Use the optional recall cache for long sessions with repeated or automatic overflow recall.
|
|
685
975
|
- Store your own user/session mapping outside MemoGrafter.
|
|
686
976
|
- Call `close()` during graceful shutdown.
|
|
687
977
|
- Do not expose database credentials or OpenAI keys to browser code.
|
|
@@ -697,10 +987,27 @@ Main exports:
|
|
|
697
987
|
- `WorkerAgent`
|
|
698
988
|
- `ConductorAgent`
|
|
699
989
|
- `AnthropicLLMAdapter`
|
|
990
|
+
- `GeminiLLMAdapter`
|
|
991
|
+
- `GeminiEmbedAdapter`
|
|
700
992
|
- `OpenAILLMAdapter`
|
|
701
993
|
- `OpenAIEmbedAdapter`
|
|
994
|
+
- `PostgresGraphStore`
|
|
995
|
+
- `GrafterPipeline`
|
|
996
|
+
- `IngestPipeline`
|
|
997
|
+
- `RetrieverPipeline`
|
|
998
|
+
- `GraphStore`
|
|
999
|
+
- `FleetAgentRecord`
|
|
1000
|
+
- `RetrievalResult`
|
|
1001
|
+
- `RetrieverConfig`
|
|
702
1002
|
- public shared and fleet types
|
|
703
1003
|
|
|
1004
|
+
Useful `GraphStore` inspection methods:
|
|
1005
|
+
|
|
1006
|
+
- `getTopicNode(topicNodeId, sessionId?)`
|
|
1007
|
+
- `getNodesBySession(sessionId)`
|
|
1008
|
+
- `getSegmentsBySession(sessionId)`
|
|
1009
|
+
- `getEdgesByType(sessionId, type)`
|
|
1010
|
+
|
|
704
1011
|
Common `MemoGrafterAgent` methods:
|
|
705
1012
|
|
|
706
1013
|
- `initialize()`: initialize storage.
|
|
@@ -709,6 +1016,7 @@ Common `MemoGrafterAgent` methods:
|
|
|
709
1016
|
- `getSessionId()`: read the current session ID.
|
|
710
1017
|
- `getActiveNodes()`: inspect topic nodes.
|
|
711
1018
|
- `getActiveSegments()`: inspect topic segments.
|
|
1019
|
+
- `recall(query, options?)`: retrieve structured memory by semantic query.
|
|
712
1020
|
- `graft(topicIds?)`: preview memory injection.
|
|
713
1021
|
- `ingestGraftedNodes(nodes)`: copy provided nodes into this agent.
|
|
714
1022
|
- `absorbFromAgent(sourceAgent, options)`: select and copy memory from another agent.
|
package/dist/MemoGrafter.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Redis } from "ioredis";
|
|
2
2
|
import { MemoGrafterFleet } from "./fleet/MemoGrafterFleet.js";
|
|
3
3
|
import type { MemoGrafterFleetOptions } from "./fleet/types.js";
|
|
4
|
+
import type { GraphStore } from "./store/index.js";
|
|
4
5
|
import type { AbsorbFromAgentOptions, EmbedAdapter, InjectionResult, LLMAdapter, MemoGrafterConfig, Message, TopicNode, TopicSegment } from "./types.js";
|
|
5
6
|
export declare class MemoGrafter {
|
|
6
7
|
readonly llm: LLMAdapter;
|
|
7
8
|
readonly embedder: EmbedAdapter;
|
|
8
9
|
readonly store: GraphStore;
|
|
10
|
+
readonly recallCache: Redis | null;
|
|
9
11
|
private readonly ingestPipeline;
|
|
10
12
|
private readonly grafterPipeline;
|
|
11
13
|
private readonly ingestQueue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemoGrafter.d.ts","sourceRoot":"","sources":["../src/MemoGrafter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MemoGrafter.d.ts","sourceRoot":"","sources":["../src/MemoGrafter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAKhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACZ,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,OAAO,EACP,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,qBAAa,WAAW;IACtB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAErC,MAAM,EAAE,iBAAiB;IAmDrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAQpE,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAIjE,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAM7F,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IAIjE,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAMrF,oBAAoB,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAmBpG,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAMpF,WAAW,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB;IAI9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B,OAAO,CAAC,uBAAuB;CAUhC"}
|