mcp-super-memory 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -25
- package/dist/autokey.d.ts +39 -0
- package/dist/autokey.d.ts.map +1 -0
- package/dist/autokey.js +79 -0
- package/dist/autokey.js.map +1 -0
- package/dist/memoryGraph.d.ts +15 -0
- package/dist/memoryGraph.d.ts.map +1 -1
- package/dist/memoryGraph.js +495 -68
- package/dist/memoryGraph.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +89 -21
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -46,9 +46,9 @@ Key Space (concepts) Value Space (memories)
|
|
|
46
46
|
[strawberry]────┘
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
`recall("Newton")` returns matching key clusters such as `[Newton]` and `[apple]`, not memory content. The agent then navigates explicitly: `read_key(apple)` → select the Newton memory → `read_memory(...)` → discover its `[fruit]` key → `read_key(fruit)` → select the strawberry memory.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
The default MCP flow is therefore **Key → Memory → Key**. Full memory content enters the model context only when the agent deliberately calls `read_memory()`.
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -57,6 +57,7 @@ Search `"Newton"` → matches `[Newton]`, `[apple]` keys (1-hop) → follows sha
|
|
|
57
57
|
| Feature | Super Memory | A-MEM | Mem0 | MemGPT |
|
|
58
58
|
|---------|-------------|-------|------|--------|
|
|
59
59
|
| Key/Value separation | ✅ N:M | ❌ | ❌ | ❌ |
|
|
60
|
+
| Agent-driven Key → Memory navigation | ✅ built-in | ❌ | ❌ | ❌ |
|
|
60
61
|
| Associative multi-hop | ✅ built-in | ❌ | ❌ | ❌ |
|
|
61
62
|
| Depth system | ✅ | ❌ | ❌ | partial |
|
|
62
63
|
| Memory versioning | ✅ supersede | overwrites | overwrites | ❌ |
|
|
@@ -113,26 +114,37 @@ Add key "파이썬" → finds existing "Python" (similarity 0.87 > threshold 0.
|
|
|
113
114
|
|
|
114
115
|
Prevents key space fragmentation. Same concept across languages or phrasing stays unified.
|
|
115
116
|
|
|
116
|
-
###
|
|
117
|
+
### Agent-driven Retrieval (default)
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
The default MCP API keeps Key Space and Value Space separate:
|
|
120
|
+
|
|
121
|
+
1. `recall(query)` searches canonical keys and aliases. It returns key IDs, concept labels, match scores, linked-memory counts, hub status, and specificity — never memory content.
|
|
122
|
+
2. `read_key(key_id)` returns ranked memory IDs and metadata, never content. Hub keys are paginated with `limit`/`offset` so broad concepts cannot flood context.
|
|
123
|
+
3. `read_memory(memory_id, via_key_id)` returns the full memory plus every connected key cluster. Only this full read increases memory depth/access count; only the traversed `via_key_id` edge receives Hebbian reinforcement.
|
|
124
|
+
4. The agent follows any returned key with another `read_key()` call, producing an explicit **Key → Memory → Key** graph walk.
|
|
125
|
+
|
|
126
|
+
Semantically merged keys are preserved as aliases on one canonical key cluster (for example `Python` + `파이썬`). A key linked to at least three active memories is surfaced as a hub with `is_hub`, `memory_count`, and `specificity` metadata rather than being hidden by IDF. Override the threshold with `SUPER_MEMORY_KEY_HUB_MIN_LINKS`.
|
|
127
|
+
|
|
128
|
+
### Direct Hybrid Retrieval (optional compatibility mode)
|
|
129
|
+
|
|
130
|
+
Set `SUPER_MEMORY_DIRECT_RECALL=true` to expose `recall_memories()`, the previous one-call memory retrieval path. Three signals run in parallel and are fused with **Reciprocal Rank Fusion** (`RRF_K = 60`):
|
|
119
131
|
|
|
120
132
|
- **BM25 (sparse):** lexical full-text search over memory content (MiniSearch, fuzzy + prefix). Catches exact terms, names, and rare tokens that embeddings blur.
|
|
121
133
|
- **Dense Path A (key matching):** query embedding → match keys → follow links → memories. Score = `keySim × IDF × linkWeight`, summed across all matching keys.
|
|
122
134
|
- **Dense Path B (content matching):** query embedding → directly compare against memory content embeddings. Finds memories even when they weren't tagged with the right keys.
|
|
123
135
|
|
|
124
|
-
Sparse and dense rank lists are merged by RRF, then modulated by depth and time before
|
|
136
|
+
Sparse and dense rank lists are merged by RRF, then modulated by depth and time before configurable multi-hop expansion (`hops=1–5`, default `2`). This compatibility tool is hidden by default so agents use explicit key navigation instead of collapsing the graph into one search call.
|
|
125
137
|
|
|
126
138
|
### Hebbian Link Learning
|
|
127
139
|
|
|
128
|
-
|
|
140
|
+
Reading a full memory is a **write**, not just a read. In the default flow, `recall()` and `read_key()` are read-only; `read_memory(memory_id, via_key_id)` reshapes the selected path:
|
|
129
141
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
142
|
+
- The traversed `via_key_id → memory_id` link is **reinforced** (`+0.1`, capped at `3.0`).
|
|
143
|
+
- Memory depth and access count increase only after the agent reads the full memory.
|
|
132
144
|
|
|
133
|
-
Reinforcement is scoped to the
|
|
145
|
+
Reinforcement is scoped to the key the agent actually traversed — not every key attached to the memory. This is the literal Hebbian rule ("fire together, wire together") and prevents unrelated associations from growing when the memory is reached through a different concept. Weights are clamped to `[0.1, 3.0]`.
|
|
134
146
|
|
|
135
|
-
Link weights feed
|
|
147
|
+
Link weights feed back into `read_key()` ranking, so repeatedly selected paths become easier to reach. Optional `recall_memories()` retains the previous matched-link reinforcement and explored-link decay behavior.
|
|
136
148
|
|
|
137
149
|
---
|
|
138
150
|
|
|
@@ -155,7 +167,15 @@ Link weights feed directly back into scoring (`keySim × IDF × linkWeight`), so
|
|
|
155
167
|
└─────────────────────────────────────────────────────────┘
|
|
156
168
|
```
|
|
157
169
|
|
|
158
|
-
**
|
|
170
|
+
**Default MCP navigation:**
|
|
171
|
+
|
|
172
|
+
1. Embed the query and match canonical key concepts plus exact aliases.
|
|
173
|
+
2. Return key clusters with `memory_count`, `is_hub`, and `specificity`; do not return memory content.
|
|
174
|
+
3. Rank a selected key's memory handles by link weight, depth, and time decay in `read_key()`.
|
|
175
|
+
4. Return full content and adjacent key clusters from `read_memory()`; reinforce only the traversed edge.
|
|
176
|
+
5. Repeat `read_key(next_key_id)` to walk the graph deliberately.
|
|
177
|
+
|
|
178
|
+
**Optional `recall_memories()` algorithm (hybrid, configurable 1–5 hops; default 2):**
|
|
159
179
|
|
|
160
180
|
Three retrieval signals run in parallel, then get fused and expanded:
|
|
161
181
|
|
|
@@ -166,7 +186,7 @@ Three retrieval signals run in parallel, then get fused and expanded:
|
|
|
166
186
|
5. **Depth & time modulation:** `score × (0.9 + depth × 0.1) × timeFactor`, where `timeFactor` is a depth-weighted 30-day half-life decay (deep memories decay slower).
|
|
167
187
|
6. **Associative expansion (`hops`, default 2):** breadth-first from the directly-matched set — each round follows shared keys (`× HOP_DECAY(0.3) × IDF × linkWeight`) and explicit `related_to` links (bidirectional, `× HOP_DECAY`) to the next frontier. `hops=N` walks up to N steps, so a memory's `hop` is its shortest chain distance. Score decays by `HOP_DECAY` per hop.
|
|
168
188
|
7. **Hebbian update:** reinforce matched-key links of returned memories (`+0.1`), decay explored-but-unreturned links (`−0.005`).
|
|
169
|
-
8. Return ranked results with `hop` field (`1` = direct, `2
|
|
189
|
+
8. Return ranked results with `hop` field (`1` = direct, `2+` = associative distance).
|
|
170
190
|
|
|
171
191
|
### Similarity thresholds (calibrated per embedding model)
|
|
172
192
|
|
|
@@ -194,19 +214,23 @@ SUPER_MEMORY_MEMORY_DEDUP=0.99
|
|
|
194
214
|
|
|
195
215
|
| Env var | Default (profile) | Description |
|
|
196
216
|
| --- | --- | --- |
|
|
197
|
-
| `SUPER_MEMORY_MIN_SCORE` | per-model (e.g. `0.55` for bge-m3) | Absolute cosine floor for
|
|
198
|
-
| `SUPER_MEMORY_GATE_Z` |
|
|
199
|
-
| `SUPER_MEMORY_CONTRADICTION` | per-model (e.g. `0.80` for bge-m3) | Contradiction-band lower bound. Memory pairs whose cosine similarity falls in `[contradiction, memoryDedup)` are flagged as contradictions. `
|
|
217
|
+
| `SUPER_MEMORY_MIN_SCORE` | per-model (e.g. `0.55` for bge-m3) | Absolute cosine floor for optional `recall_memories()`. Set to `0` to disable. |
|
|
218
|
+
| `SUPER_MEMORY_GATE_Z` | `0` by default | Opt-in distribution gate for `recall_memories()` (robust-z, median/MAD). Values around 2–5 are typical; `0` disables it. |
|
|
219
|
+
| `SUPER_MEMORY_CONTRADICTION` | per-model (e.g. `0.80` for bge-m3) | Contradiction-band lower bound. Memory pairs whose cosine similarity falls in `[contradiction, memoryDedup)` are flagged as contradictions. `read_memory()`, `related()`, and optional `recall_memories()` expose conflicting IDs. |
|
|
220
|
+
| `SUPER_MEMORY_AUTOKEY` | `true` | Auto-key self-healing: learn missing search terms from real usage. Set `false` to disable. |
|
|
221
|
+
| `SUPER_MEMORY_AUTOKEY_PROMOTE_N` | `3` | Weak-confirmed reads of a `(key, query)` pair before the query is folded into the key space. |
|
|
222
|
+
| `SUPER_MEMORY_AUTOKEY_MAX_ALIASES` | `8` | Max learned aliases promoted per key. |
|
|
223
|
+
| `SUPER_MEMORY_AUTOKEY_PRUNE_AGE` | `2592000` | Seconds before a never-hit learned alias is pruned by `cleanup_expired` (30 days). |
|
|
200
224
|
|
|
201
|
-
**Why e5
|
|
225
|
+
**Why e5 gates are opt-in:** multilingual-e5's narrow cosine band (~0.86–0.99) makes a static floor unreliable, while held-out tests showed distribution and key-proximity gates can also overfit. Both are disabled by default to avoid hiding real memories. Use bge-m3 for reliable not-found behavior, or calibrate e5 gates on your own corpus.
|
|
202
226
|
|
|
203
227
|
Distribution gate parameters:
|
|
204
|
-
- **`gateZ`**
|
|
228
|
+
- **`gateZ`** — set via `SUPER_MEMORY_GATE_Z` or the `min_z` parameter of optional `recall_memories()`.
|
|
205
229
|
- **`0` disables** the gate (default for bge-m3, bge, openai, minilm — where `min_score` already works).
|
|
206
230
|
- Both gates **compose (AND)**: a result must clear both `min_score` and `gateZ` to be returned.
|
|
207
231
|
- A **literal name/proper-noun key match** (e.g. querying a stored `name`-typed key exactly) is always a definite anchor and bypasses the distribution gate.
|
|
208
232
|
- **`GATE_MIN_POPULATION = 8`**: the gate is skipped when fewer than 8 memories exist (too few samples for a reliable distribution), so early-session recall is unaffected. The gate's background population is **namespace-filtered** and excludes superseded/expired memories, so recall scoped to a sparse namespace may fall below this threshold and skip the gate entirely.
|
|
209
|
-
- **Known e5 limitation:** the gate keys off `maxContentSim` (content cosine only). A
|
|
233
|
+
- **Known e5 limitation:** the optional gate keys off `maxContentSim` (content cosine only). A relevant fuzzy-key hit with a flat content distribution may be rejected; literal key matches bypass the gate.
|
|
210
234
|
|
|
211
235
|
An uncalibrated `LOCAL_EMBEDDING_MODEL` falls back to the BGE profile **and logs a warning** so the miscalibration is never silent.
|
|
212
236
|
|
|
@@ -216,11 +240,13 @@ An uncalibrated `LOCAL_EMBEDDING_MODEL` falls back to the BGE profile **and logs
|
|
|
216
240
|
|
|
217
241
|
## MCP Tools
|
|
218
242
|
|
|
219
|
-
The memory system exposes
|
|
243
|
+
The memory system exposes 12 tools by default:
|
|
220
244
|
|
|
221
245
|
| Tool | Description |
|
|
222
246
|
| --- | --- |
|
|
223
|
-
| `recall(query, top_k
|
|
247
|
+
| `recall(query, top_k?, namespace?)` | Search Key Space only. Returns canonical keys, aliases, scores, and hub metadata; never memory content. |
|
|
248
|
+
| `read_key(key_id, namespace?, limit?, offset?)` | List ranked memory IDs and metadata connected to one key. Never returns content; supports pagination for hubs. |
|
|
249
|
+
| `read_memory(memory_id, via_key_id?, namespace?)` | Read full memory content and connected keys. Increases depth/access and reinforces the traversed edge. |
|
|
224
250
|
| `remember(content, keys, key_types?, namespace?, ttl_seconds?, related_to?)` | Save memory with key concepts and optional type annotations |
|
|
225
251
|
| `correct(memory_id, content, keys?, key_types?, related_to?)` | Versioned update — old memory preserved but weakened |
|
|
226
252
|
| `related(memory_id)` | Find memories sharing keys (associative exploration) |
|
|
@@ -231,6 +257,8 @@ The memory system exposes 10 tools via MCP:
|
|
|
231
257
|
| `cleanup_expired()` | Delete memories whose TTL has expired |
|
|
232
258
|
| `memory_stats()` | Get current key/memory/link counts |
|
|
233
259
|
|
|
260
|
+
Set `SUPER_MEMORY_DIRECT_RECALL=true` to expose a thirteenth compatibility tool, `recall_memories(...)`, with the previous BM25+dense+RRF multi-hop behavior.
|
|
261
|
+
|
|
234
262
|
A system prompt template is also available via `memory_system_prompt` MCP prompt — include it to instruct the agent to recall silently, use diverse keys, and never mention the memory system to users.
|
|
235
263
|
|
|
236
264
|
---
|
|
@@ -316,18 +344,29 @@ LOCAL_EMBEDDING_MODEL=bge-m3
|
|
|
316
344
|
|
|
317
345
|
> First run downloads ~570MB once, then reuses the cache. If `LOCAL_EMBEDDING_MODEL_PATH` already holds the model it is used **as-is with no download** (a partial dir is self-healed — only missing files are fetched). Online-API backends (OpenAI) and fastembed built-ins are unaffected.
|
|
318
346
|
|
|
319
|
-
**Cross-encoder reranking (optional):** set `SUPER_MEMORY_RERANK=true` to re-score
|
|
347
|
+
**Cross-encoder reranking (optional direct mode):** set `SUPER_MEMORY_DIRECT_RECALL=true` and `SUPER_MEMORY_RERANK=true` to re-score `recall_memories()` candidates with `bge-reranker-v2-m3`. The default key-navigation flow does not load the reranker. The model (~570MB, quantized) auto-downloads on first use and caches under `~/.super-memory/models/reranker`.
|
|
320
348
|
|
|
321
349
|
```
|
|
322
350
|
SUPER_MEMORY_RERANK=true
|
|
351
|
+
SUPER_MEMORY_DIRECT_RECALL=true
|
|
323
352
|
# optional: SUPER_MEMORY_RERANK_MODEL_PATH=/dir SUPER_MEMORY_RERANK_POOL=30 (candidates re-scored)
|
|
324
353
|
```
|
|
325
354
|
|
|
326
|
-
> Off by default. If the model
|
|
355
|
+
> Off by default. If the model cannot load, `recall_memories()` falls back to fused ranking. Query decomposition remains the caller's responsibility.
|
|
356
|
+
|
|
357
|
+
**Reranker not-found gate (`SUPER_MEMORY_RERANK_MIN_SCORE`):** in direct compatibility mode, reject the complete `recall_memories()` result when the top cross-encoder logit is below this floor.
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
SUPER_MEMORY_RERANK=true
|
|
361
|
+
SUPER_MEMORY_DIRECT_RECALL=true
|
|
362
|
+
SUPER_MEMORY_RERANK_MIN_SCORE=0 # reject when top rerank logit < 0 (bge-reranker-v2-m3 scale)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
> ⚠️ **Caveats.** (1) Unset by default — no gate. (2) The logit scale is **model-dependent**; `0` (≈ sigmoid 0.5) suits `bge-reranker-v2-m3` (measured: same-language found ≈ +3.9, not-found ≈ −5 to −6) — recalibrate for other rerankers. (3) **Trusted for SAME-LANGUAGE only.** Cross-lingual relevance logits run low even when relevant (KR query ↔ EN memory ≈ −5.4), so the gate auto-**bypasses on a script mismatch** (KR↔Latin) to avoid false-rejecting cross-lingual hits — which means **cross-lingual content must be reachable via bilingual keys** (`["Jiwoo","지우"]`), and cross-lingual *not-found* precision is a known limitation. Leave this off if you can't tag bilingual keys.
|
|
327
366
|
|
|
328
367
|
> **Prefix behavior:** BGE-M3 does **not** use `passage:`/`query:` prefixes — embeddings are passed through as-is. All other local models (e5, BGE-en, MiniLM) continue to use prefixes unchanged.
|
|
329
368
|
|
|
330
|
-
> **Recommended for multilingual / cross-lingual use: `bge-m3`.**
|
|
369
|
+
> **Recommended for multilingual / cross-lingual use: `bge-m3`.** It separates unrelated queries more reliably and performs substantially better than e5 on the project's Korean↔English fixtures. In optional direct mode, bge-m3's absolute `min_score` gate reaches ≈96% on the gate fixture; e5 requires corpus-specific tuning.
|
|
331
370
|
|
|
332
371
|
If `OPENAI_API_KEY` is not set and `EMBEDDING_BACKEND` is unset, the server automatically uses the local `fastembed` backend.
|
|
333
372
|
For English-only use or lower local resource usage, set `LOCAL_EMBEDDING_MODEL=fast-bge-base-en-v1.5` or `fast-bge-small-en-v1.5`.
|
|
@@ -356,7 +395,7 @@ All data is local. No external database required.
|
|
|
356
395
|
|
|
357
396
|
```
|
|
358
397
|
~/.super-memory/
|
|
359
|
-
├── graph.json # keys, memories, links
|
|
398
|
+
├── graph.json # canonical keys, aliases, memories, weighted links
|
|
360
399
|
└── conversations/
|
|
361
400
|
└── {session_id}.jsonl # original conversation turns
|
|
362
401
|
```
|
|
@@ -368,7 +407,8 @@ Set `SUPER_MEMORY_DATA_DIR` to use a different storage directory.
|
|
|
368
407
|
## Limitations
|
|
369
408
|
|
|
370
409
|
- **Linear scan** — suitable for personal use (~10k memories). FAISS/ChromaDB integration planned for larger scale.
|
|
371
|
-
- **
|
|
410
|
+
- **Agentic round trips** — the default `recall → read_key → read_memory` flow is more controllable and context-efficient, but needs more tool calls than one-shot retrieval.
|
|
411
|
+
- **Hub breadth** — broad keys can connect many memories. `read_key()` paginates hubs; the agent must choose whether to continue paging or follow a more specific adjacent key.
|
|
372
412
|
- **Agent quality matters** — key selection on `remember` affects retrieval quality. System prompt tuning is important.
|
|
373
413
|
- **Cross-lingual content bias** — with multilingual e5, raw content similarity favors same-language memories regardless of meaning. Tag memories with multilingual keys so the key graph (not biased content cosine) carries cross-lingual recall.
|
|
374
414
|
- **Threshold calibration** — thresholds are tuned per embedding model. A new/uncalibrated model falls back to the BGE profile (with a warning); recalibrate via the `SUPER_MEMORY_*` env overrides.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface RecallBufferEntry {
|
|
2
|
+
queryText: string;
|
|
3
|
+
weakKeyScores: Map<string, number>;
|
|
4
|
+
ts: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class RecallBuffer {
|
|
7
|
+
private _entries;
|
|
8
|
+
private readonly _capacity;
|
|
9
|
+
private readonly _ttl;
|
|
10
|
+
private readonly _now;
|
|
11
|
+
constructor(opts?: {
|
|
12
|
+
capacity?: number;
|
|
13
|
+
ttlSeconds?: number;
|
|
14
|
+
now?: () => number;
|
|
15
|
+
});
|
|
16
|
+
push(entry: {
|
|
17
|
+
queryText: string;
|
|
18
|
+
weakKeyScores: Map<string, number>;
|
|
19
|
+
}): void;
|
|
20
|
+
consumeWeakMatch(keyId: string): RecallBufferEntry | null;
|
|
21
|
+
size(): number;
|
|
22
|
+
}
|
|
23
|
+
export declare const AUTOKEY_ENABLED: boolean;
|
|
24
|
+
export declare const AUTOKEY_PROMOTE_N: number;
|
|
25
|
+
export declare const AUTOKEY_MAX_ALIASES: number;
|
|
26
|
+
export declare const AUTOKEY_BUFFER_CAPACITY = 32;
|
|
27
|
+
export declare const AUTOKEY_BUFFER_TTL_SECONDS = 300;
|
|
28
|
+
export declare const AUTOKEY_PRUNE_AGE_SECONDS: number;
|
|
29
|
+
export declare function decidePromotion(args: {
|
|
30
|
+
count: number;
|
|
31
|
+
query: string;
|
|
32
|
+
cosine: number;
|
|
33
|
+
learnedAliasCount: number;
|
|
34
|
+
aliasThreshold: number;
|
|
35
|
+
newKeyThreshold: number;
|
|
36
|
+
promoteN: number;
|
|
37
|
+
maxAliases: number;
|
|
38
|
+
}): "alias" | "newKey" | "none";
|
|
39
|
+
//# sourceMappingURL=autokey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autokey.d.ts","sourceRoot":"","sources":["../src/autokey.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,EAAE,EAAE,MAAM,CAAC;CACZ;AAKD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;gBAExB,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAMrF,IAAI,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI;IAa5E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAkBzD,IAAI,IAAI,MAAM;CAGf;AAWD,eAAO,MAAM,eAAe,SAA+C,CAAC;AAC5E,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,mBAAmB,QAAmD,CAAC;AACpF,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAKF,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAQ9B"}
|
package/dist/autokey.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isShortConcept } from "./embedding.js";
|
|
2
|
+
// Runtime-only ring buffer of recent recalls that matched one or more keys only
|
|
3
|
+
// *weakly* (semantic, not literal). Never persisted. Bounded by capacity + TTL so
|
|
4
|
+
// it can never grow without bound or attribute a confirmation to a stale query.
|
|
5
|
+
export class RecallBuffer {
|
|
6
|
+
_entries = [];
|
|
7
|
+
_capacity;
|
|
8
|
+
_ttl;
|
|
9
|
+
_now;
|
|
10
|
+
constructor(opts = {}) {
|
|
11
|
+
this._capacity = Math.max(1, Math.floor(opts.capacity ?? 32));
|
|
12
|
+
this._ttl = Math.max(1, opts.ttlSeconds ?? 300);
|
|
13
|
+
this._now = opts.now ?? (() => Date.now() / 1000);
|
|
14
|
+
}
|
|
15
|
+
push(entry) {
|
|
16
|
+
this._entries.push({
|
|
17
|
+
queryText: entry.queryText,
|
|
18
|
+
weakKeyScores: new Map(entry.weakKeyScores),
|
|
19
|
+
ts: this._now(),
|
|
20
|
+
});
|
|
21
|
+
if (this._entries.length > this._capacity) {
|
|
22
|
+
this._entries.splice(0, this._entries.length - this._capacity);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Most-recent fresh entry that weakly matched keyId; removes keyId from that
|
|
26
|
+
// entry so a single recall confirms a given key at most once.
|
|
27
|
+
consumeWeakMatch(keyId) {
|
|
28
|
+
const cutoff = this._now() - this._ttl;
|
|
29
|
+
for (let i = this._entries.length - 1; i >= 0; i--) {
|
|
30
|
+
const e = this._entries[i];
|
|
31
|
+
if (e.ts < cutoff)
|
|
32
|
+
continue;
|
|
33
|
+
if (e.weakKeyScores.has(keyId)) {
|
|
34
|
+
const result = {
|
|
35
|
+
queryText: e.queryText,
|
|
36
|
+
weakKeyScores: new Map(e.weakKeyScores),
|
|
37
|
+
ts: e.ts,
|
|
38
|
+
};
|
|
39
|
+
e.weakKeyScores.delete(keyId);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
size() {
|
|
46
|
+
return this._entries.length;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function envInt(name, fallback, min) {
|
|
50
|
+
const raw = process.env[name];
|
|
51
|
+
if (raw === undefined || raw.trim() === "")
|
|
52
|
+
return fallback;
|
|
53
|
+
const n = Number(raw);
|
|
54
|
+
return Number.isFinite(n) && n >= min ? Math.floor(n) : fallback;
|
|
55
|
+
}
|
|
56
|
+
// Feature flag. Default ON; set SUPER_MEMORY_AUTOKEY=false to disable (mirrors
|
|
57
|
+
// SUPER_MEMORY_AUTO_MIGRATE). Read once at import.
|
|
58
|
+
export const AUTOKEY_ENABLED = process.env.SUPER_MEMORY_AUTOKEY !== "false";
|
|
59
|
+
export const AUTOKEY_PROMOTE_N = envInt("SUPER_MEMORY_AUTOKEY_PROMOTE_N", 3, 1);
|
|
60
|
+
export const AUTOKEY_MAX_ALIASES = envInt("SUPER_MEMORY_AUTOKEY_MAX_ALIASES", 8, 0);
|
|
61
|
+
export const AUTOKEY_BUFFER_CAPACITY = 32;
|
|
62
|
+
export const AUTOKEY_BUFFER_TTL_SECONDS = 300;
|
|
63
|
+
export const AUTOKEY_PRUNE_AGE_SECONDS = envInt("SUPER_MEMORY_AUTOKEY_PRUNE_AGE", 30 * 24 * 3600, 0);
|
|
64
|
+
// Pure policy: given accumulated heat and the recall-time query↔key cosine, decide
|
|
65
|
+
// whether to fold the query into the key space and how. Short-concept gate keeps
|
|
66
|
+
// natural-language queries out of the alias set; the content path already serves those.
|
|
67
|
+
export function decidePromotion(args) {
|
|
68
|
+
if (args.count < args.promoteN)
|
|
69
|
+
return "none";
|
|
70
|
+
if (!isShortConcept(args.query))
|
|
71
|
+
return "none";
|
|
72
|
+
if (args.cosine >= args.aliasThreshold) {
|
|
73
|
+
return args.learnedAliasCount < args.maxAliases ? "alias" : "none";
|
|
74
|
+
}
|
|
75
|
+
if (args.cosine >= args.newKeyThreshold)
|
|
76
|
+
return "newKey";
|
|
77
|
+
return "none";
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=autokey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autokey.js","sourceRoot":"","sources":["../src/autokey.ts"],"names":[],"mappings":"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"}
|
package/dist/memoryGraph.d.ts
CHANGED
|
@@ -12,8 +12,11 @@ export declare class MemoryGraph {
|
|
|
12
12
|
private _storedDim;
|
|
13
13
|
private _storedFingerprint;
|
|
14
14
|
private _lock;
|
|
15
|
+
private _saveLock;
|
|
16
|
+
private _saveSeq;
|
|
15
17
|
private _dirty;
|
|
16
18
|
private _bm25;
|
|
19
|
+
private _recallBuffer;
|
|
17
20
|
constructor();
|
|
18
21
|
static readonly HOP_DECAY = 0.3;
|
|
19
22
|
static readonly TIME_HALF_LIFE: number;
|
|
@@ -33,6 +36,9 @@ export declare class MemoryGraph {
|
|
|
33
36
|
private _isExpired;
|
|
34
37
|
private _timeFactor;
|
|
35
38
|
private _keyIdf;
|
|
39
|
+
private _recordKeyAlias;
|
|
40
|
+
private _activeMemoryIdsForKey;
|
|
41
|
+
private _keyView;
|
|
36
42
|
private _findDuplicate;
|
|
37
43
|
private _findContradiction;
|
|
38
44
|
private _autoLinkKeys;
|
|
@@ -57,6 +63,15 @@ export declare class MemoryGraph {
|
|
|
57
63
|
namespace?: string | null;
|
|
58
64
|
relatedTo?: string[] | null;
|
|
59
65
|
}): Promise<string>;
|
|
66
|
+
searchKeys(query: string, topK?: number, namespace?: string | null): Promise<object[]>;
|
|
67
|
+
readKey(keyId: string, options?: {
|
|
68
|
+
namespace?: string | null;
|
|
69
|
+
limit?: number;
|
|
70
|
+
offset?: number;
|
|
71
|
+
query?: string | null;
|
|
72
|
+
}): Promise<object>;
|
|
73
|
+
private _maybeLearnAlias;
|
|
74
|
+
readMemory(memoryId: string, viaKeyId?: string | null, namespace?: string | null): Promise<object>;
|
|
60
75
|
recall(query: string, topK?: number, namespace?: string | null, expand?: boolean, maxHops?: number, minRelScore?: number, minScore?: number, minZ?: number, minKeyGate?: number, minDepth?: number): Promise<object[]>;
|
|
61
76
|
getRelated(memoryId: string): object[];
|
|
62
77
|
delete(memoryId: string): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memoryGraph.d.ts","sourceRoot":"","sources":["../src/memoryGraph.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAa,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"memoryGraph.d.ts","sourceRoot":"","sources":["../src/memoryGraph.ts"],"names":[],"mappings":"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"}
|