@jeremiaheth/neolata-mem 0.8.1 → 0.8.2
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/LICENSE +89 -21
- package/README.md +56 -12
- package/docs/guide.md +177 -4
- package/docs/implementation-notes.md +261 -0
- package/docs/runtime-helpers.md +161 -0
- package/package.json +63 -61
- package/src/graph.mjs +2724 -2691
- package/src/index.mjs +19 -18
- package/src/runtime.mjs +309 -0
- package/src/storage.mjs +90 -90
- package/src/supabase-storage.mjs +514 -520
- package/docs/PRD-v0.6.md +0 -2106
- package/docs/PRD-v0.7.md +0 -2294
- package/docs/PRD-v0.8.1.md +0 -155
- package/docs/PRD-v0.8.md +0 -1739
- package/docs/analysis.md +0 -81
- package/docs/deep-research-report.md +0 -199
- package/docs/prompt-1.md +0 -120
- package/docs/prompt-2.md +0 -124
- package/docs/prompt-3-4.md +0 -176
- package/docs/prompt-5-6.md +0 -298
- package/docs/prompt-7-8.md +0 -203
package/LICENSE
CHANGED
|
@@ -1,21 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Elastic License 2.0 (ELv2)
|
|
2
|
+
|
|
3
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
12
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
13
|
+
available, and prepare derivative works of the software, in each case subject
|
|
14
|
+
to the limitations and conditions below.
|
|
15
|
+
|
|
16
|
+
## Limitations
|
|
17
|
+
|
|
18
|
+
You may not provide the software to third parties as a hosted or managed
|
|
19
|
+
service, where the service provides users with access to any substantial set
|
|
20
|
+
of the features or functionality of the software.
|
|
21
|
+
|
|
22
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
23
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
24
|
+
software that is protected by the license key.
|
|
25
|
+
|
|
26
|
+
You may not alter, remove, or obscure any licensing, copyright, or other
|
|
27
|
+
notices of the licensor in the software. Any use of the licensor's trademarks
|
|
28
|
+
is subject to applicable law.
|
|
29
|
+
|
|
30
|
+
## Patents
|
|
31
|
+
|
|
32
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
33
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
34
|
+
sale, import and have imported the software, in each case subject to the
|
|
35
|
+
limitations and conditions in this license. This license does not cover any
|
|
36
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
37
|
+
the software. If you or your company make any written claim that the software
|
|
38
|
+
infringes or contributes to infringement of any patent, your patent license
|
|
39
|
+
for the software granted under these terms ends immediately. If your company
|
|
40
|
+
makes such a claim, your patent license ends immediately for work on behalf
|
|
41
|
+
of your company.
|
|
42
|
+
|
|
43
|
+
## Notices
|
|
44
|
+
|
|
45
|
+
You must ensure that anyone who gets a copy of any part of the software from
|
|
46
|
+
you also gets a copy of these terms.
|
|
47
|
+
|
|
48
|
+
If you modify the software, you must include in any modified copies of the
|
|
49
|
+
software prominent notices stating that you have modified the software.
|
|
50
|
+
|
|
51
|
+
## No Other Rights
|
|
52
|
+
|
|
53
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
54
|
+
these terms.
|
|
55
|
+
|
|
56
|
+
## Termination
|
|
57
|
+
|
|
58
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
59
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
60
|
+
with a notice of your violation, and you cease all violation of this license
|
|
61
|
+
no later than 30 days after you receive that notice, your licenses will be
|
|
62
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
63
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
64
|
+
to terminate automatically and permanently.
|
|
65
|
+
|
|
66
|
+
## No Liability
|
|
67
|
+
|
|
68
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
69
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
70
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
71
|
+
legal claim.*
|
|
72
|
+
|
|
73
|
+
## Definitions
|
|
74
|
+
|
|
75
|
+
The **licensor** is the entity offering these terms, and the **software** is
|
|
76
|
+
the software the licensor makes available under these terms, including any
|
|
77
|
+
portion of it.
|
|
78
|
+
|
|
79
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
80
|
+
|
|
81
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of
|
|
82
|
+
organization that you work for, plus all organizations that have control over,
|
|
83
|
+
are under the control of, or are under common control with that organization.
|
|
84
|
+
**Control** means ownership of substantially all the assets of an entity, or
|
|
85
|
+
the power to direct the management and policies of an entity.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
Copyright 2025-2026 Jeremiaheth (Jeremiah Ojo)
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Graph-native memory engine for AI agents.** Zettelkasten-inspired linking, biological decay, conflict resolution.
|
|
4
4
|
|
|
5
|
-
[](LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -37,6 +37,7 @@ const mem = createMemory({
|
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// Agent IDs like 'kuro' and 'maki' below are just examples — use any string to identify your agents.
|
|
40
41
|
await mem.store('kuro', 'Found XSS vulnerability in login form', { category: 'finding', importance: 0.9 });
|
|
41
42
|
await mem.store('kuro', 'OWASP Top 10 audit completed', { category: 'event' });
|
|
42
43
|
|
|
@@ -68,7 +69,7 @@ embeddings: {
|
|
|
68
69
|
|
|
69
70
|
### 🔗 A-MEM Zettelkasten Linking
|
|
70
71
|
|
|
71
|
-
Every memory automatically links to related memories
|
|
72
|
+
Every memory automatically links to related memories - bidirectionally. When you store "Redis runs on port 6379", it finds existing memories about Redis, ports, or databases and creates links in both directions.
|
|
72
73
|
|
|
73
74
|
```javascript
|
|
74
75
|
await mem.store('a', 'Redis runs on port 6379');
|
|
@@ -110,14 +111,14 @@ await mem.evolve('a', 'Server now runs on port 8080');
|
|
|
110
111
|
// Old version archived with evolution history
|
|
111
112
|
```
|
|
112
113
|
|
|
113
|
-
**Quarantine lane**
|
|
114
|
+
**Quarantine lane** - low-trust or structurally conflicting memories are quarantined instead of auto-superseding:
|
|
114
115
|
|
|
115
116
|
```javascript
|
|
116
117
|
// Store with claim metadata and provenance
|
|
117
118
|
await mem.store('a', 'Server runs on port 443', {
|
|
118
119
|
claim: { subject: 'server', predicate: 'port', value: '443' },
|
|
119
120
|
provenance: { source: 'user_explicit', trust: 1.0 },
|
|
120
|
-
onConflict: 'quarantine', // default
|
|
121
|
+
onConflict: 'quarantine', // default - quarantine low-trust conflicts
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
// Review quarantined memories
|
|
@@ -207,7 +208,7 @@ const mem = createMemory({
|
|
|
207
208
|
dir: './my-data', // Custom directory for JSON storage
|
|
208
209
|
},
|
|
209
210
|
|
|
210
|
-
// Embeddings (optional
|
|
211
|
+
// Embeddings (optional - keyword search works without)
|
|
211
212
|
embeddings: {
|
|
212
213
|
type: 'openai', // 'openai' (any compatible API) | 'noop' (keyword only)
|
|
213
214
|
apiKey: '...',
|
|
@@ -216,7 +217,7 @@ const mem = createMemory({
|
|
|
216
217
|
extraBody: {}, // Extra params (e.g. { input_type: 'passage' } for NIM)
|
|
217
218
|
},
|
|
218
219
|
|
|
219
|
-
// Fact extraction (optional
|
|
220
|
+
// Fact extraction (optional - enables ingest())
|
|
220
221
|
extraction: {
|
|
221
222
|
type: 'llm', // 'llm' | 'passthrough'
|
|
222
223
|
apiKey: '...',
|
|
@@ -224,7 +225,7 @@ const mem = createMemory({
|
|
|
224
225
|
baseUrl: 'https://api.openai.com/v1',
|
|
225
226
|
},
|
|
226
227
|
|
|
227
|
-
// LLM for conflict resolution (optional
|
|
228
|
+
// LLM for conflict resolution (optional - enables evolve())
|
|
228
229
|
llm: {
|
|
229
230
|
type: 'openai',
|
|
230
231
|
apiKey: '...',
|
|
@@ -323,7 +324,7 @@ Set `OPENAI_API_KEY` or `NVIDIA_API_KEY` for embedding support. See `npx @jeremi
|
|
|
323
324
|
|
|
324
325
|
### `createMemory(opts?) → MemoryGraph`
|
|
325
326
|
|
|
326
|
-
Factory function. All options are optional
|
|
327
|
+
Factory function. All options are optional - zero-config returns a working instance with JSON storage and keyword search.
|
|
327
328
|
|
|
328
329
|
### Core Methods
|
|
329
330
|
|
|
@@ -419,6 +420,48 @@ Factory function. All options are optional — zero-config returns a working ins
|
|
|
419
420
|
| `pendingConflicts()` | List unresolved structural conflicts |
|
|
420
421
|
| `resolveConflict(conflictId, opts)` | Resolve a pending conflict |
|
|
421
422
|
|
|
423
|
+
### Runtime Helpers
|
|
424
|
+
|
|
425
|
+
Convenience functions for agent workflows — heartbeat auto-store, contextual recall, and pre-compaction dumps.
|
|
426
|
+
|
|
427
|
+
| Function | Description |
|
|
428
|
+
|----------|-------------|
|
|
429
|
+
| `detectKeyMoments(text, opts?)` | Extract decisions, preferences, commitments, and blockers from text |
|
|
430
|
+
| `extractTopicSlug(text, opts?)` | Derive a topic slug from text (with optional synonym mapping) |
|
|
431
|
+
| `heartbeatStore(mem, agent, turns, config?)` | Auto-store key moments from conversation turns on a heartbeat interval |
|
|
432
|
+
| `contextualRecall(mem, agent, seedText, config?)` | Budget-aware recall: merges recent + semantic + high-importance memories by topic |
|
|
433
|
+
| `preCompactionDump(mem, agent, turns, config?)` | Extract and persist takeaways before context window compaction |
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
import { createMemory, heartbeatStore, contextualRecall, preCompactionDump } from '@jeremiaheth/neolata-mem';
|
|
437
|
+
|
|
438
|
+
const mem = createMemory({ /* ... */ });
|
|
439
|
+
|
|
440
|
+
// Heartbeat: auto-store key moments from recent turns
|
|
441
|
+
const result = await heartbeatStore(mem, 'kuro', conversationTurns, {
|
|
442
|
+
sessionId: 'sess-123',
|
|
443
|
+
topicSlug: 'deployment',
|
|
444
|
+
minNewTurns: 3, // skip if fewer than 3 new turns (default: 3)
|
|
445
|
+
lastStoredIndex: -1, // track position across calls
|
|
446
|
+
});
|
|
447
|
+
// → { stored: 2, ids: [...], lastIndex: 14, moments: [...] }
|
|
448
|
+
|
|
449
|
+
// Contextual recall: topic-aware, budget-capped context retrieval
|
|
450
|
+
const context = await contextualRecall(mem, 'kuro', 'How did we fix the RLS issue?', {
|
|
451
|
+
maxTokens: 2000, // token budget (default: 2000)
|
|
452
|
+
semanticCount: 8, // semantic search results (default: 8)
|
|
453
|
+
importanceThreshold: 0.8,
|
|
454
|
+
});
|
|
455
|
+
// → { topicSlug: 'rls', memories: [...], totalTokens: 1823, excluded: 3 }
|
|
456
|
+
|
|
457
|
+
// Pre-compaction dump: persist session takeaways before context reset
|
|
458
|
+
const dump = await preCompactionDump(mem, 'kuro', conversationTurns, {
|
|
459
|
+
sessionId: 'sess-123',
|
|
460
|
+
maxTakeaways: 10, // max individual moments to store (default: 10)
|
|
461
|
+
});
|
|
462
|
+
// → { takeaways: 4, snapshotId: '...', ids: [...] }
|
|
463
|
+
```
|
|
464
|
+
|
|
422
465
|
### Advanced: Bring Your Own Providers
|
|
423
466
|
|
|
424
467
|
```javascript
|
|
@@ -482,6 +525,7 @@ Decay Cycle:
|
|
|
482
525
|
| Conflict resolution | ✅ | ✅ | ❌ | ❌ |
|
|
483
526
|
| Quarantine lane | ✅ | ❌ | ❌ | ❌ |
|
|
484
527
|
| Predicate schemas | ✅ | ❌ | ❌ | ❌ |
|
|
528
|
+
| Runtime helpers (heartbeat/recall/dump) | ✅ | ❌ | ❌ | ❌ |
|
|
485
529
|
| Explainability API | ✅ | ❌ | ❌ | ❌ |
|
|
486
530
|
| Episodes & compression | ✅ | ❌ | ❌ | ❌ |
|
|
487
531
|
| Labeled clusters | ✅ | ❌ | ❌ | ❌ |
|
|
@@ -496,7 +540,7 @@ neolata-mem includes several hardening measures:
|
|
|
496
540
|
|
|
497
541
|
- **Input validation**: Agent names (alphanumeric, max 64 chars), memory text (max 10KB), bounded total memory count (default 50K)
|
|
498
542
|
- **Prompt injection mitigation**: All user content is XML-fenced in LLM prompts with explicit instruction boundaries. LLM output is structurally validated (type checks, index bounds, category whitelists)
|
|
499
|
-
- **SSRF protection**: All provider URLs validated via `validateBaseUrl()`
|
|
543
|
+
- **SSRF protection**: All provider URLs validated via `validateBaseUrl()` - blocks cloud metadata endpoints, private IP ranges (configurable), non-HTTP protocols
|
|
500
544
|
- **Supabase hardening**: UUID validation on all query params (prevents PostgREST injection), error text sanitized (strips tokens/keys), safe upsert-based save (no data loss on crash), automatic 429 retry with backoff
|
|
501
545
|
- **Atomic writes**: JSON storage uses write-to-temp + rename to prevent corruption from concurrent access
|
|
502
546
|
- **Path traversal guards**: Storage directories and write-through paths validated with `resolve()` + prefix checks
|
|
@@ -504,12 +548,12 @@ neolata-mem includes several hardening measures:
|
|
|
504
548
|
- **Retry bounds**: Embedding and Supabase API retries are capped at 3 with exponential backoff (no infinite loops)
|
|
505
549
|
- **Error surfacing**: Failed conflict detection returns `{ error }` instead of silently proceeding
|
|
506
550
|
|
|
507
|
-
**Trust model**: For JSON storage, neolata-mem trusts the filesystem
|
|
551
|
+
**Trust model**: For JSON storage, neolata-mem trusts the filesystem - protect your data directory. For Supabase, use Row Level Security (RLS) policies. Embedding vectors can approximate original text via inversion attacks - treat them as sensitive.
|
|
508
552
|
|
|
509
553
|
## Documentation
|
|
510
554
|
|
|
511
|
-
📖 **[Full User Guide](docs/guide.md)**
|
|
555
|
+
📖 **[Full User Guide](docs/guide.md)** - configuration deep dive, embedding providers, storage backends, recipes, troubleshooting, architecture.
|
|
512
556
|
|
|
513
557
|
## License
|
|
514
558
|
|
|
515
|
-
|
|
559
|
+
[Elastic License 2.0](LICENSE) — free to use, modify, and distribute. You just can't offer it as a hosted/managed service.
|
package/docs/guide.md
CHANGED
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
17. [CLI Reference](#cli-reference)
|
|
26
26
|
18. [OpenClaw Integration](#openclaw-integration)
|
|
27
27
|
19. [Recipes](#recipes)
|
|
28
|
-
20. [
|
|
29
|
-
21. [
|
|
28
|
+
20. [Runtime Helpers](#runtime-helpers)
|
|
29
|
+
21. [Troubleshooting](#troubleshooting)
|
|
30
|
+
22. [Architecture](#architecture)
|
|
30
31
|
|
|
31
32
|
---
|
|
32
33
|
|
|
@@ -260,10 +261,12 @@ First-class Supabase backend with incremental operations and server-side vector
|
|
|
260
261
|
storage: {
|
|
261
262
|
type: 'supabase',
|
|
262
263
|
url: process.env.SUPABASE_URL,
|
|
263
|
-
key: process.env.
|
|
264
|
+
key: process.env.SUPABASE_KEY, // Prefer anon key + RLS; service key bypasses row-level security
|
|
264
265
|
}
|
|
265
266
|
```
|
|
266
267
|
|
|
268
|
+
> ⚠️ **Security:** Prefer a Supabase anon/public key with Row-Level Security (RLS) policies. A service key grants full database access and should only be used for admin operations, never in client-facing agents.
|
|
269
|
+
|
|
267
270
|
**Setup:** Run `sql/schema.sql` in your Supabase Dashboard SQL Editor to create the required tables.
|
|
268
271
|
|
|
269
272
|
**Features:**
|
|
@@ -329,6 +332,8 @@ This lets you back neolata-mem with PostgreSQL, SQLite, Redis, S3 — anything.
|
|
|
329
332
|
|
|
330
333
|
### Storing
|
|
331
334
|
|
|
335
|
+
> **Note:** Agent IDs like `'kuro'` and `'maki'` used throughout this guide are just examples — use any string to identify your agents.
|
|
336
|
+
|
|
332
337
|
```javascript
|
|
333
338
|
const result = await mem.store('kuro', 'Found XSS in login form', {
|
|
334
339
|
category: 'finding', // finding | decision | fact | insight | task | event | preference
|
|
@@ -1125,7 +1130,7 @@ const mem = createMemory({
|
|
|
1125
1130
|
storage: {
|
|
1126
1131
|
type: 'supabase',
|
|
1127
1132
|
url: process.env.SUPABASE_URL,
|
|
1128
|
-
key: process.env.
|
|
1133
|
+
key: process.env.SUPABASE_KEY, // Prefer anon key + RLS
|
|
1129
1134
|
},
|
|
1130
1135
|
embeddings: {
|
|
1131
1136
|
type: 'openai',
|
|
@@ -1148,6 +1153,8 @@ const results = await mem.search('kuro', 'dark mode');
|
|
|
1148
1153
|
|
|
1149
1154
|
### Write-through to webhooks
|
|
1150
1155
|
|
|
1156
|
+
> ⚠️ **Security:** Webhook URLs are an explicit data exfiltration surface. Each store/decay event sends memory content to the configured endpoint. Only configure URLs you trust and control.
|
|
1157
|
+
|
|
1151
1158
|
```javascript
|
|
1152
1159
|
import { createMemory, webhookWritethrough } from '@jeremiaheth/neolata-mem';
|
|
1153
1160
|
|
|
@@ -1166,6 +1173,172 @@ detach();
|
|
|
1166
1173
|
|
|
1167
1174
|
---
|
|
1168
1175
|
|
|
1176
|
+
## Runtime Helpers
|
|
1177
|
+
|
|
1178
|
+
Standalone functions for common agent workflows. These work with any `MemoryGraph` instance and don't require special configuration.
|
|
1179
|
+
|
|
1180
|
+
```javascript
|
|
1181
|
+
import {
|
|
1182
|
+
detectKeyMoments, extractTopicSlug,
|
|
1183
|
+
heartbeatStore, contextualRecall, preCompactionDump,
|
|
1184
|
+
} from '@jeremiaheth/neolata-mem';
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
### detectKeyMoments(text, opts?)
|
|
1188
|
+
|
|
1189
|
+
Scans text for decisions, preferences, commitments, and blockers using pattern matching.
|
|
1190
|
+
|
|
1191
|
+
```javascript
|
|
1192
|
+
const moments = detectKeyMoments("Decision: we're going with Supabase. Blocked by RLS permissions.");
|
|
1193
|
+
// [
|
|
1194
|
+
// { type: 'decision', text: "Decision: we're going with Supabase", importance: 0.9 },
|
|
1195
|
+
// { type: 'blocker', text: "Blocked by RLS permissions", importance: 0.85 },
|
|
1196
|
+
// ]
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
**Moment types and importance:**
|
|
1200
|
+
|
|
1201
|
+
| Type | Importance | Trigger patterns |
|
|
1202
|
+
|------|-----------|-----------------|
|
|
1203
|
+
| `decision` | 0.9 | "Decision:", "We decided", "Going with", "Let's do", "Ship it" |
|
|
1204
|
+
| `commitment` | 0.8 | "I will", "We will", "TODO:", "Action item:" |
|
|
1205
|
+
| `blocker` | 0.85 | "Blocked by", "Blocker:", "Can't proceed", "Waiting on" |
|
|
1206
|
+
| `preference` | 0.7 | "I prefer", "I like", "I want", "Always use" |
|
|
1207
|
+
|
|
1208
|
+
### extractTopicSlug(text, opts?)
|
|
1209
|
+
|
|
1210
|
+
Derives a topic slug from text by finding the most frequent non-stop-word. Supports synonym mapping.
|
|
1211
|
+
|
|
1212
|
+
```javascript
|
|
1213
|
+
extractTopicSlug('How did we fix the RLS policy issue?');
|
|
1214
|
+
// → 'rls'
|
|
1215
|
+
|
|
1216
|
+
extractTopicSlug('Update the neolata package', {
|
|
1217
|
+
synonyms: { 'neolata-mem': ['neolata', 'memory', 'mem'] }
|
|
1218
|
+
});
|
|
1219
|
+
// → 'neolata-mem'
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
### heartbeatStore(mem, agent, turns, config?)
|
|
1223
|
+
|
|
1224
|
+
Auto-stores key moments from conversation turns. Designed to be called periodically (e.g., on a heartbeat timer). If no key moments are detected, stores a truncated session snapshot instead.
|
|
1225
|
+
|
|
1226
|
+
**Config:**
|
|
1227
|
+
|
|
1228
|
+
| Option | Type | Default | Description |
|
|
1229
|
+
|--------|------|---------|-------------|
|
|
1230
|
+
| `sessionId` | string | — | Tag stored memories with session ID |
|
|
1231
|
+
| `topicSlug` | string | — | Tag with topic |
|
|
1232
|
+
| `projectSlug` | string | — | Tag with project |
|
|
1233
|
+
| `minNewTurns` | number | 3 | Skip if fewer new turns since last call |
|
|
1234
|
+
| `lastStoredIndex` | number | -1 | Index of last processed turn (track across calls) |
|
|
1235
|
+
|
|
1236
|
+
**Returns:** `{ stored, ids, lastIndex, moments, skipped? }`
|
|
1237
|
+
|
|
1238
|
+
```javascript
|
|
1239
|
+
const turns = [
|
|
1240
|
+
{ role: 'user', content: 'Let\'s do the Supabase migration' },
|
|
1241
|
+
{ role: 'assistant', content: 'Decision: migrating to Supabase for production storage.' },
|
|
1242
|
+
{ role: 'user', content: 'Ship it' },
|
|
1243
|
+
{ role: 'assistant', content: 'TODO: run the migration script on OCI.' },
|
|
1244
|
+
];
|
|
1245
|
+
|
|
1246
|
+
let lastIndex = -1;
|
|
1247
|
+
const result = await heartbeatStore(mem, 'kuro', turns, {
|
|
1248
|
+
sessionId: 'sess-abc',
|
|
1249
|
+
topicSlug: 'supabase',
|
|
1250
|
+
lastStoredIndex: lastIndex,
|
|
1251
|
+
});
|
|
1252
|
+
// → { stored: 3, ids: [...], lastIndex: 3, moments: [...] }
|
|
1253
|
+
|
|
1254
|
+
lastIndex = result.lastIndex; // track for next heartbeat call
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
### contextualRecall(mem, agent, seedText, config?)
|
|
1258
|
+
|
|
1259
|
+
Budget-aware context retrieval that merges three recall strategies: recent memories, semantic search results, and high-importance memories filtered by topic. Results are deduplicated, sorted by importance, and capped to a token budget.
|
|
1260
|
+
|
|
1261
|
+
**Config:**
|
|
1262
|
+
|
|
1263
|
+
| Option | Type | Default | Description |
|
|
1264
|
+
|--------|------|---------|-------------|
|
|
1265
|
+
| `maxTokens` | number | 2000 | Token budget for returned memories |
|
|
1266
|
+
| `recentCount` | number | 5 | Number of recent memories to fetch |
|
|
1267
|
+
| `semanticCount` | number | 8 | Number of semantic search results |
|
|
1268
|
+
| `importantCount` | number | 10 | Candidates for importance filtering |
|
|
1269
|
+
| `importanceThreshold` | number | 0.8 | Minimum importance for the "important" lane |
|
|
1270
|
+
| `synonyms` | object | {} | Synonym map for topic extraction |
|
|
1271
|
+
|
|
1272
|
+
**Returns:** `{ topicSlug, memories, totalTokens, excluded }`
|
|
1273
|
+
|
|
1274
|
+
```javascript
|
|
1275
|
+
const ctx = await contextualRecall(mem, 'kuro', 'What was our RLS fix?', {
|
|
1276
|
+
maxTokens: 1500,
|
|
1277
|
+
importanceThreshold: 0.7,
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
console.log(ctx.topicSlug); // 'rls'
|
|
1281
|
+
console.log(ctx.memories); // [...] deduplicated, importance-sorted, budget-capped
|
|
1282
|
+
console.log(ctx.totalTokens); // 1342
|
|
1283
|
+
console.log(ctx.excluded); // 5 (didn't fit in budget)
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
### preCompactionDump(mem, agent, turns, config?)
|
|
1287
|
+
|
|
1288
|
+
Extracts key moments from a full conversation, deduplicates them, persists the top takeaways, and stores a structured session snapshot. Call this before context window compaction to preserve important information.
|
|
1289
|
+
|
|
1290
|
+
**Config:**
|
|
1291
|
+
|
|
1292
|
+
| Option | Type | Default | Description |
|
|
1293
|
+
|--------|------|---------|-------------|
|
|
1294
|
+
| `sessionId` | string | — | Tag stored memories with session ID |
|
|
1295
|
+
| `topicSlug` | string | — | Tag with topic |
|
|
1296
|
+
| `projectSlug` | string | — | Tag with project |
|
|
1297
|
+
| `maxTakeaways` | number | 10 | Maximum individual moments to persist |
|
|
1298
|
+
|
|
1299
|
+
**Returns:** `{ takeaways, snapshotId, ids }`
|
|
1300
|
+
|
|
1301
|
+
The session snapshot is a markdown-formatted summary grouped by type:
|
|
1302
|
+
|
|
1303
|
+
```
|
|
1304
|
+
## Session Snapshot
|
|
1305
|
+
**Decisions:** migrating to Supabase for production storage
|
|
1306
|
+
**Open threads:** Blocked by RLS permissions on link inserts
|
|
1307
|
+
**Commitments:** TODO: run the migration script on OCI
|
|
1308
|
+
**Preferences:** I prefer service key over anon key for writes
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
```javascript
|
|
1312
|
+
const dump = await preCompactionDump(mem, 'kuro', allTurns, {
|
|
1313
|
+
sessionId: 'sess-abc',
|
|
1314
|
+
maxTakeaways: 10,
|
|
1315
|
+
});
|
|
1316
|
+
// → { takeaways: 4, snapshotId: 'uuid-...', ids: [...] }
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
### Workflow: Combining All Three
|
|
1320
|
+
|
|
1321
|
+
A typical agent lifecycle using all three helpers:
|
|
1322
|
+
|
|
1323
|
+
```javascript
|
|
1324
|
+
// 1. On session start: recall context
|
|
1325
|
+
const ctx = await contextualRecall(mem, agent, userMessage);
|
|
1326
|
+
// → inject ctx.memories into system prompt
|
|
1327
|
+
|
|
1328
|
+
// 2. Periodically during conversation: heartbeat store
|
|
1329
|
+
let lastIdx = -1;
|
|
1330
|
+
setInterval(async () => {
|
|
1331
|
+
const r = await heartbeatStore(mem, agent, turns, { lastStoredIndex: lastIdx });
|
|
1332
|
+
lastIdx = r.lastIndex;
|
|
1333
|
+
}, 60_000);
|
|
1334
|
+
|
|
1335
|
+
// 3. Before compaction: dump takeaways
|
|
1336
|
+
const dump = await preCompactionDump(mem, agent, turns);
|
|
1337
|
+
// → key moments and snapshot persisted to graph
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
---
|
|
1341
|
+
|
|
1169
1342
|
## Troubleshooting
|
|
1170
1343
|
|
|
1171
1344
|
### "No results from search"
|