@memstack/core 0.2.0 → 0.5.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 +321 -69
- package/dist/index.cjs +2480 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +537 -27
- package/dist/index.d.ts +537 -27
- package/dist/index.js +2470 -267
- package/dist/index.js.map +1 -1
- package/package.json +27 -24
- package/src/adapters/embedding/cohere.ts +0 -77
- package/src/adapters/embedding/openai.ts +0 -65
- package/src/adapters/llm/anthropic.ts +0 -69
- package/src/adapters/llm/groq.ts +0 -185
- package/src/adapters/llm/ollama.ts +0 -85
- package/src/adapters/llm/openai.ts +0 -178
- package/src/adapters/storage/disk.ts +0 -334
- package/src/adapters/storage/memory.ts +0 -147
- package/src/adapters/storage/postgres.ts +0 -310
- package/src/adapters/storage/redis.ts +0 -274
- package/src/client.ts +0 -236
- package/src/errors.ts +0 -47
- package/src/index.ts +0 -52
- package/src/interfaces.ts +0 -154
- package/src/memory/ContextCompiler.ts +0 -158
- package/src/memory/MemoryStore.ts +0 -266
- package/src/memory/Pruner.ts +0 -66
- package/src/memory/Summarizer.ts +0 -46
- package/src/types.ts +0 -50
package/README.md
CHANGED
|
@@ -82,14 +82,74 @@ Think of it as the open-source alternative to [Mem0](https://mem0.ai/) — plugg
|
|
|
82
82
|
|
|
83
83
|
## Quick Start
|
|
84
84
|
|
|
85
|
+
```bash
|
|
86
|
+
npm install @memstack/core
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### OpenAI
|
|
90
|
+
|
|
85
91
|
```typescript
|
|
86
|
-
import { MemStack, OpenAILLMAdapter, OpenAIEmbeddingAdapter } from "@memstack/core";
|
|
92
|
+
import { MemStack, OpenAILLMAdapter, OpenAIEmbeddingAdapter, InMemoryStorageAdapter } from "@memstack/core";
|
|
93
|
+
|
|
94
|
+
const llm = new OpenAILLMAdapter({ apiKey: process.env.OPENAI_API_KEY! });
|
|
87
95
|
|
|
88
96
|
const memstack = new MemStack({
|
|
89
|
-
llm
|
|
97
|
+
llm,
|
|
90
98
|
embedding: new OpenAIEmbeddingAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
99
|
+
storage: new InMemoryStorageAdapter(),
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### DeepSeek (no embeddings)
|
|
104
|
+
|
|
105
|
+
DeepSeek provides chat completions but has no embedding API. Use the OpenAI-compatible LLM adapter with `baseURL` and omit the embedding adapter — retrieval falls back to keyword + recency + importance ranking. You still get the full pipeline: store, summarize, prune, and compileContext.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { MemStack, OpenAILLMAdapter, InMemoryStorageAdapter } from "@memstack/core";
|
|
109
|
+
|
|
110
|
+
const llm = new OpenAILLMAdapter({
|
|
111
|
+
apiKey: process.env.DEEPSEEK_API_KEY!,
|
|
112
|
+
baseURL: "https://api.deepseek.com/v1",
|
|
113
|
+
defaultModel: "deepseek-chat",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const memstack = new MemStack({
|
|
117
|
+
llm,
|
|
118
|
+
storage: new InMemoryStorageAdapter(),
|
|
119
|
+
// No embedding adapter — retrieval uses keyword matching
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### OpenRouter / Together AI / any OpenAI-compatible API
|
|
124
|
+
|
|
125
|
+
Same pattern — change `baseURL` and `defaultModel`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// OpenRouter
|
|
129
|
+
const llm = new OpenAILLMAdapter({
|
|
130
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
131
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
132
|
+
defaultModel: "openai/gpt-4o-mini",
|
|
91
133
|
});
|
|
92
134
|
|
|
135
|
+
// Together AI
|
|
136
|
+
const llm = new OpenAILLMAdapter({
|
|
137
|
+
apiKey: process.env.TOGETHER_API_KEY!,
|
|
138
|
+
baseURL: "https://api.together.xyz/v1",
|
|
139
|
+
defaultModel: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Gemini (OpenAI-compatible endpoint)
|
|
143
|
+
const llm = new OpenAILLMAdapter({
|
|
144
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
145
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
146
|
+
defaultModel: "gemini-2.0-flash",
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Store and retrieve
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
93
153
|
// 1. Store what happened
|
|
94
154
|
await memstack.memory.store({
|
|
95
155
|
actorId: "support-bot-42",
|
|
@@ -105,18 +165,21 @@ const memories = await memstack.memory.retrieve({
|
|
|
105
165
|
strategy: "hybrid",
|
|
106
166
|
});
|
|
107
167
|
|
|
108
|
-
// 3.
|
|
168
|
+
// 3. Assemble an LLM-ready context
|
|
109
169
|
const ctx = await memstack.memory.compileContext({
|
|
110
170
|
actorId: "support-bot-42",
|
|
111
171
|
maxTokens: 2000,
|
|
112
172
|
});
|
|
113
173
|
|
|
114
|
-
const
|
|
174
|
+
const response = await llm.complete({
|
|
115
175
|
system: `You are a support bot. Here is what you remember:\n${ctx.systemPrompt}`,
|
|
116
176
|
user: "The user is back and still can't log in. What do you do?",
|
|
117
177
|
});
|
|
118
178
|
|
|
119
|
-
|
|
179
|
+
console.log(response.text);
|
|
180
|
+
// "Based on our history, the user has been experiencing 503 errors on Chrome 125..."
|
|
181
|
+
|
|
182
|
+
// 4. Every 100 interactions, summarization triggers automatically.
|
|
120
183
|
// Old interactions are compressed into a paragraph. Token costs stay flat.
|
|
121
184
|
```
|
|
122
185
|
|
|
@@ -134,7 +197,7 @@ Every agent interaction becomes a `Memory` with metadata that controls how it's
|
|
|
134
197
|
interface Memory {
|
|
135
198
|
id: string;
|
|
136
199
|
actorId: string; // Who this memory belongs to (user ID, agent ID, session ID)
|
|
137
|
-
memoryType: MemoryType; // "interaction" | "summary" | "observation"
|
|
200
|
+
memoryType: MemoryType; // "interaction" | "summary" | "observation" | "fact" | "reflection"
|
|
138
201
|
content: string; // The actual text
|
|
139
202
|
importance: number; // 0-1 — higher = survives pruning, ranks higher in retrieval
|
|
140
203
|
emotionalValence: number; // -1 to 1 — for tone-aware retrieval
|
|
@@ -191,7 +254,7 @@ No embedding adapter? `semantic` and `hybrid` fall back to keyword matching + im
|
|
|
191
254
|
|
|
192
255
|
### 3. Compile Context
|
|
193
256
|
|
|
194
|
-
|
|
257
|
+
`compileContext()` takes retrieval results and assembles an LLM-ready system prompt — deduplicated, sorted by recency and importance, with a token estimate so you know the cost before calling the LLM.
|
|
195
258
|
|
|
196
259
|
```typescript
|
|
197
260
|
const ctx = await ms.memory.compileContext({
|
|
@@ -212,13 +275,16 @@ const ctx = await ms.memory.compileContext({
|
|
|
212
275
|
console.log(ctx.tokenEstimate); // ~280
|
|
213
276
|
|
|
214
277
|
// Inject into your LLM call
|
|
278
|
+
const currentMessage = "The user is asking about their refund status.";
|
|
215
279
|
const response = await llm.complete({
|
|
216
280
|
system: ctx.systemPrompt,
|
|
217
|
-
user:
|
|
281
|
+
user: currentMessage,
|
|
218
282
|
});
|
|
283
|
+
|
|
284
|
+
console.log(response.text);
|
|
219
285
|
```
|
|
220
286
|
|
|
221
|
-
`compileContext()`
|
|
287
|
+
`compileContext()` handles deduplication, token budgeting, and splits context into important-vs-recent sections. Without it, you'd be concatenating raw retrieval results and risking context-window overflow.
|
|
222
288
|
|
|
223
289
|
### 4. Summarize
|
|
224
290
|
|
|
@@ -308,13 +374,29 @@ const ms = new MemStack({
|
|
|
308
374
|
### Support Agent
|
|
309
375
|
|
|
310
376
|
```typescript
|
|
377
|
+
// detectUrgency and classifyIntent are your own business logic.
|
|
378
|
+
// They could be simple keyword matchers, regex, or an LLM call.
|
|
379
|
+
function detectUrgency(msg: string): number {
|
|
380
|
+
if (msg.match(/urgent|asap|immediately/i)) return 0.9;
|
|
381
|
+
if (msg.match(/error|fail|broken/i)) return 0.7;
|
|
382
|
+
return 0.5;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function classifyIntent(msg: string): string[] {
|
|
386
|
+
const tags: string[] = [];
|
|
387
|
+
if (msg.match(/bill|refund|charge|payment/i)) tags.push("billing");
|
|
388
|
+
if (msg.match(/error|bug|fail|crash/i)) tags.push("bug");
|
|
389
|
+
if (msg.match(/login|password|account/i)) tags.push("account");
|
|
390
|
+
return tags;
|
|
391
|
+
}
|
|
392
|
+
|
|
311
393
|
// Every customer message becomes a memory
|
|
312
394
|
async function handleMessage(customerId: string, message: string) {
|
|
313
395
|
await ms.memory.store({
|
|
314
396
|
actorId: `customer:${customerId}`,
|
|
315
397
|
content: message,
|
|
316
|
-
importance: detectUrgency(message),
|
|
317
|
-
tags: classifyIntent(message),
|
|
398
|
+
importance: detectUrgency(message),
|
|
399
|
+
tags: classifyIntent(message),
|
|
318
400
|
});
|
|
319
401
|
|
|
320
402
|
// Retrieve everything relevant to this customer's history
|
|
@@ -338,6 +420,12 @@ async function handleMessage(customerId: string, message: string) {
|
|
|
338
420
|
### RAG Pipeline
|
|
339
421
|
|
|
340
422
|
```typescript
|
|
423
|
+
// Suppose you have documents from your knowledge base
|
|
424
|
+
const documents = [
|
|
425
|
+
{ text: "Authentication uses JWT tokens with 15-minute expiry.", url: "/docs/auth", section: "security" },
|
|
426
|
+
{ text: "Refunds are processed within 5-10 business days.", url: "/docs/billing", section: "billing" },
|
|
427
|
+
];
|
|
428
|
+
|
|
341
429
|
// Index documents as observation memories
|
|
342
430
|
for (const doc of documents) {
|
|
343
431
|
await ms.memory.store({
|
|
@@ -403,7 +491,8 @@ const userCount = await ms.memory.count({ actorId: "user-42" });
|
|
|
403
491
|
| `interaction` | Default. Direct exchanges between agent and user/other agent. | "User asked about billing." |
|
|
404
492
|
| `summary` | Compressed collection of old interactions. Created by `summarize()`. | "Over 3 weeks, user reported 5 login failures..." |
|
|
405
493
|
| `observation` | Passive knowledge — facts, documents, things the agent knows but didn't interact with. | "Company refund policy is 30 days from purchase." |
|
|
406
|
-
| `
|
|
494
|
+
| `fact` | Verified knowledge — discrete truths the agent has confirmed. | "The user's subscription tier is Enterprise." |
|
|
495
|
+
| `reflection` | Self-generated insight — the agent thinking about its own experiences. | "I tend to over-explain billing policies — should be more concise." |
|
|
407
496
|
|
|
408
497
|
Types control retrieval behavior — `compileContext()` treats `interaction` and `summary` differently from `observation`. Use types to separate "what happened" from "what I know."
|
|
409
498
|
|
|
@@ -439,9 +528,61 @@ await ms.memory.retrieve({ actorId: "x", query: "login bug", strategy: "hybrid"
|
|
|
439
528
|
|
|
440
529
|
Embeddings power semantic search. They're optional — without them, retrieval uses keyword matching.
|
|
441
530
|
|
|
442
|
-
|
|
531
|
+
### With embeddings vs Without embeddings
|
|
443
532
|
|
|
444
|
-
**
|
|
533
|
+
**With embeddings** (`embedding` adapter configured):
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { MemStack, OpenAILLMAdapter, OpenAIEmbeddingAdapter, InMemoryStorageAdapter } from "@memstack/core";
|
|
537
|
+
|
|
538
|
+
const ms = new MemStack({
|
|
539
|
+
llm: new OpenAILLMAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
540
|
+
embedding: new OpenAIEmbeddingAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
541
|
+
storage: new InMemoryStorageAdapter(),
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// store() computes a 1536-dim vector automatically
|
|
545
|
+
await ms.memory.store({
|
|
546
|
+
actorId: "agent-7",
|
|
547
|
+
content: "Customer asked about refund policy for Q2 purchases.",
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// retrieve() with "semantic" or "hybrid" uses cosine similarity
|
|
551
|
+
// Query: "refund" finds the refund policy memory even though the word "refund"
|
|
552
|
+
// appears differently across stored memories.
|
|
553
|
+
const results = await ms.memory.retrieve({
|
|
554
|
+
actorId: "agent-7",
|
|
555
|
+
query: "how do I get my money back",
|
|
556
|
+
strategy: "semantic",
|
|
557
|
+
});
|
|
558
|
+
// Matches "Customer asked about refund policy" — semantic match, not keyword match.
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Without embeddings** (no `embedding` adapter):
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
const ms = new MemStack({
|
|
565
|
+
llm: new OpenAILLMAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
566
|
+
storage: new InMemoryStorageAdapter(),
|
|
567
|
+
// no embedding adapter
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// store() works identically, just no vector computed
|
|
571
|
+
await ms.memory.store({
|
|
572
|
+
actorId: "agent-7",
|
|
573
|
+
content: "Customer asked about refund policy for Q2 purchases.",
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// retrieve() with "semantic" or "hybrid" falls back to keyword matching
|
|
577
|
+
// plus importance/recency sorting. No API costs, no setup required.
|
|
578
|
+
const results = await ms.memory.retrieve({
|
|
579
|
+
actorId: "agent-7",
|
|
580
|
+
query: "refund",
|
|
581
|
+
strategy: "hybrid", // falls back to keyword + importance
|
|
582
|
+
});
|
|
583
|
+
// Still works — finds "refund" via substring match. Less precise for
|
|
584
|
+
// paraphrased queries ("money back" won't match "refund").
|
|
585
|
+
```
|
|
445
586
|
|
|
446
587
|
**Batch embedding:** `storeBatch()` sends all texts in one embedding API call, reducing cost and latency.
|
|
447
588
|
|
|
@@ -454,6 +595,28 @@ const ms = new MemStack({
|
|
|
454
595
|
});
|
|
455
596
|
```
|
|
456
597
|
|
|
598
|
+
### Vector dimensions and model compatibility
|
|
599
|
+
|
|
600
|
+
Different embedding models produce vectors of different lengths. Cosine similarity only works between vectors of the same dimension. If you change embedding models, existing vectors become incompatible — they can't be compared to new ones.
|
|
601
|
+
|
|
602
|
+
| Adapter | Default model | Dimensions |
|
|
603
|
+
|---------|--------------|------------|
|
|
604
|
+
| `OpenAIEmbeddingAdapter` | `text-embedding-3-small` | 1536 |
|
|
605
|
+
| `OpenAIEmbeddingAdapter` | `text-embedding-3-large` | 3072 |
|
|
606
|
+
| `CohereEmbeddingAdapter` | `embed-english-v3.0` | 1024 |
|
|
607
|
+
| `CohereEmbeddingAdapter` | `embed-english-light-v3.0` | 384 |
|
|
608
|
+
| `CohereEmbeddingAdapter` | `embed-english-v2.0` | 4096 |
|
|
609
|
+
| `CohereEmbeddingAdapter` | `embed-multilingual-v3.0` | 1024 |
|
|
610
|
+
|
|
611
|
+
**What happens if dimensions don't match:** If you store memories with one model (e.g., 1536 dims) then switch to another model (e.g., 1024 dims), the storage adapter receives query vectors and stored vectors of different lengths. Cosine similarity between vectors of different dimensions is undefined — results depend on the storage backend's behavior. Most will either error, return empty results, or produce meaningless scores.
|
|
612
|
+
|
|
613
|
+
**Recommendation:** Pick one embedding model per storage instance and stick with it. If you need to switch models, create a new storage instance and re-embed from scratch.
|
|
614
|
+
|
|
615
|
+
**DeepSeek users:** DeepSeek has no embeddings API. If you use DeepSeek as your LLM, you must either:
|
|
616
|
+
1. Omit the embedding adapter and use `"recent"` or `"important"` retrieval strategies (no API costs, less precise)
|
|
617
|
+
2. Pair DeepSeek with a separate embedding provider (e.g., OpenAI for embeddings, DeepSeek for chat)
|
|
618
|
+
|
|
619
|
+
|
|
457
620
|
---
|
|
458
621
|
|
|
459
622
|
## Adapters
|
|
@@ -462,16 +625,17 @@ MemStack is provider-agnostic. Every boundary is an interface — bring your own
|
|
|
462
625
|
|
|
463
626
|
### LLM Adapters
|
|
464
627
|
|
|
465
|
-
Used by `summarize()` and `compileContext()`. Ships with OpenAI and
|
|
628
|
+
Used by `summarize()` and `compileContext()`. Ships with OpenAI, Anthropic, Ollama, and Groq built-in — and via `baseURL`, the OpenAI adapter works with **any OpenAI-compatible API** (DeepSeek, Mistral, Gemini, Together AI, Perplexity, Fireworks, xAI, and dozens more).
|
|
466
629
|
|
|
467
630
|
```typescript
|
|
468
631
|
// OpenAI
|
|
469
632
|
import { OpenAILLMAdapter } from "@memstack/core";
|
|
470
|
-
const llm = new OpenAILLMAdapter({
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
});
|
|
633
|
+
const llm = new OpenAILLMAdapter({ apiKey: "..." });
|
|
634
|
+
|
|
635
|
+
// Any OpenAI-compatible API — just change baseURL
|
|
636
|
+
const deepseek = new OpenAILLMAdapter({ apiKey: "...", baseURL: "https://api.deepseek.com/v1" });
|
|
637
|
+
const mistral = new OpenAILLMAdapter({ apiKey: "...", baseURL: "https://api.mistral.ai/v1" });
|
|
638
|
+
const together = new OpenAILLMAdapter({ apiKey: "...", baseURL: "https://api.together.xyz/v1" });
|
|
475
639
|
|
|
476
640
|
// Anthropic
|
|
477
641
|
import { AnthropicLLMAdapter } from "@memstack/core";
|
|
@@ -480,62 +644,144 @@ const llm = new AnthropicLLMAdapter({
|
|
|
480
644
|
defaultModel: "claude-sonnet-4-5-20250929",
|
|
481
645
|
});
|
|
482
646
|
|
|
483
|
-
// Ollama (
|
|
484
|
-
import
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
method: "POST",
|
|
490
|
-
body: JSON.stringify({ model: req.model ?? "llama3.2", prompt: `${req.system}\n\n${req.user}`, stream: false }),
|
|
491
|
-
});
|
|
492
|
-
const data = await res.json() as { response: string };
|
|
493
|
-
return { text: data.response, tokens: { prompt: 0, completion: 0, total: 0 } };
|
|
494
|
-
}
|
|
495
|
-
}
|
|
647
|
+
// Ollama (built-in)
|
|
648
|
+
import { OllamaLLMAdapter } from "@memstack/core";
|
|
649
|
+
const llm = new OllamaLLMAdapter({
|
|
650
|
+
baseURL: "http://localhost:11434",
|
|
651
|
+
defaultModel: "llama3.2",
|
|
652
|
+
});
|
|
496
653
|
```
|
|
497
654
|
|
|
498
655
|
### Embedding Adapters
|
|
499
656
|
|
|
500
|
-
Used by semantic retrieval. Ships with OpenAI built-in.
|
|
657
|
+
Used by semantic retrieval. Ships with OpenAI and Cohere built-in — and via `baseURL`, the OpenAI adapter works with **any OpenAI-compatible embedding API** (Together AI, Voyage AI, Jina, Nomic, and more).
|
|
501
658
|
|
|
502
659
|
```typescript
|
|
503
|
-
import { OpenAIEmbeddingAdapter } from "@memstack/core";
|
|
504
|
-
const embedding = new OpenAIEmbeddingAdapter({
|
|
505
|
-
apiKey: process.env.OPENAI_API_KEY!,
|
|
506
|
-
model: "text-embedding-3-small", // 1536 dimensions (default)
|
|
507
|
-
// model: "text-embedding-3-large", // 3072 dimensions
|
|
508
|
-
});
|
|
509
|
-
```
|
|
660
|
+
import { OpenAIEmbeddingAdapter, CohereEmbeddingAdapter } from "@memstack/core";
|
|
510
661
|
|
|
511
|
-
|
|
662
|
+
// OpenAI
|
|
663
|
+
new OpenAIEmbeddingAdapter({ apiKey: "...", model: "text-embedding-3-small" }); // 1536 dims
|
|
512
664
|
|
|
513
|
-
|
|
665
|
+
// Cohere
|
|
666
|
+
new CohereEmbeddingAdapter({ apiKey: "..." }); // embed-english-v3.0, 1024 dims
|
|
514
667
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const storage = new InMemoryStorage();
|
|
668
|
+
// Any OpenAI-compatible embedding API
|
|
669
|
+
new OpenAIEmbeddingAdapter({ apiKey: "...", baseURL: "https://api.voyageai.com/v1", model: "voyage-3" });
|
|
518
670
|
```
|
|
519
671
|
|
|
520
|
-
|
|
672
|
+
### Storage Adapters
|
|
521
673
|
|
|
522
|
-
|
|
674
|
+
MemStack ships with **11 production-ready storage adapters (7 experimental)** — every major backend, zero peer dependencies, all client-injected.
|
|
675
|
+
|
|
676
|
+
### Production (e2e verified against real instances)
|
|
677
|
+
|
|
678
|
+
**Built-in (zero external deps):**
|
|
679
|
+
| Adapter | Backend | Use case |
|
|
680
|
+
|---|---|---|
|
|
681
|
+
| `InMemoryStorageAdapter` | In-memory Map | Testing, prototyping |
|
|
682
|
+
| `DiskStorageAdapter` | Local JSON files | Simple local persistence |
|
|
683
|
+
| `MarkdownStorageAdapter` | Append-only .md files | Human-readable, git-diffable, debug-friendly |
|
|
684
|
+
| `HybridStorageAdapter` | Compose any two StorageProviders | Cache + durable, edge + durable |
|
|
685
|
+
|
|
686
|
+
**Relational / SQL:**
|
|
687
|
+
| Adapter | Backend | Vector search |
|
|
688
|
+
|---|---|---|
|
|
689
|
+
| `PostgresStorageAdapter` | PostgreSQL + pgvector | HNSW native |
|
|
690
|
+
|
|
691
|
+
**Vector databases:**
|
|
692
|
+
| Adapter | Backend |
|
|
693
|
+
|---|---|
|
|
694
|
+
| `QdrantStorageAdapter` | Qdrant |
|
|
695
|
+
| `WeaviateStorageAdapter` | Weaviate |
|
|
696
|
+
| `LanceDBStorageAdapter` | LanceDB |
|
|
697
|
+
| `MongoDBStorageAdapter` | MongoDB Atlas Vector Search |
|
|
698
|
+
|
|
699
|
+
**Cache / KV:**
|
|
700
|
+
| Adapter | Backend |
|
|
701
|
+
|---|---|
|
|
702
|
+
| `RedisStorageAdapter` | Redis (ioredis) |
|
|
703
|
+
|
|
704
|
+
**Graph:**
|
|
705
|
+
| Adapter | Backend |
|
|
706
|
+
|---|---|
|
|
707
|
+
| `Neo4jStorageAdapter` | Neo4j |
|
|
708
|
+
|
|
709
|
+
### Experimental (mock-tested, blocked by cloud deps or platform constraints)
|
|
710
|
+
|
|
711
|
+
Available via direct source import. Not yet in the barrel export — uncomment in `src/index.ts` when e2e verified.
|
|
712
|
+
|
|
713
|
+
| Adapter | Backend | Blocker |
|
|
714
|
+
|---|---|---|
|
|
715
|
+
| `SQLiteStorageAdapter` | SQLite (better-sqlite3) | Native binary for Node 24 |
|
|
716
|
+
| `TursoStorageAdapter` | Turso (libsql) | Cloud-only (needs Turso account) |
|
|
717
|
+
| `ChromaStorageAdapter` | ChromaDB | Embedding function dependency |
|
|
718
|
+
| `PineconeStorageAdapter` | Pinecone | Cloud-only (needs API key) |
|
|
719
|
+
| `UpstashStorageAdapter` | Upstash Redis + Vector | Cloud-only (needs API key) |
|
|
720
|
+
| `Mem0StorageAdapter` | Mem0 OSS or Cloud | Cloud-only (needs API key) |
|
|
721
|
+
| `ZepStorageAdapter` | Zep Cloud or CE | Cloud-only (needs API key) |
|
|
722
|
+
|
|
723
|
+
> **Direct import:** `import { ChromaStorageAdapter } from "@memstack/core/src/adapters/storage/chroma.js"`
|
|
724
|
+
|
|
725
|
+
**Quick-start per backend:**
|
|
726
|
+
|
|
727
|
+
```ts
|
|
728
|
+
// Postgres
|
|
729
|
+
import { PostgresStorageAdapter } from "@memstack/core";
|
|
730
|
+
const storage = new PostgresStorageAdapter({ connectionString: "postgres://..." });
|
|
731
|
+
|
|
732
|
+
// Redis
|
|
733
|
+
import Redis from "ioredis";
|
|
734
|
+
import { RedisStorageAdapter } from "@memstack/core";
|
|
735
|
+
const storage = new RedisStorageAdapter({ redis: new Redis() });
|
|
736
|
+
|
|
737
|
+
// Markdown (append-only, human-readable)
|
|
738
|
+
import { MarkdownStorageAdapter } from "@memstack/core";
|
|
739
|
+
const storage = new MarkdownStorageAdapter({ dir: "./memories" });
|
|
740
|
+
|
|
741
|
+
// Hybrid (Redis cache + Postgres durable)
|
|
742
|
+
import { HybridStorageAdapter } from "@memstack/core";
|
|
743
|
+
const storage = new HybridStorageAdapter({
|
|
744
|
+
cache: new RedisStorageAdapter({ redis: new Redis() }),
|
|
745
|
+
durable: new PostgresStorageAdapter({ connectionString: "postgres://..." }),
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
**Custom storage:**
|
|
750
|
+
```ts
|
|
523
751
|
import type { StorageProvider, MemoryStoreInput } from "@memstack/core";
|
|
524
752
|
|
|
525
|
-
class
|
|
526
|
-
async store(input: MemoryStoreInput): Promise<Memory> { /*
|
|
527
|
-
async get(id: string): Promise<Memory | null> { /*
|
|
528
|
-
async retrieve(query: MemoryRetrieveQuery, embedding?: number[]): Promise<Memory[]> { /*
|
|
529
|
-
async count(filter?: MemoryCountFilter): Promise<number> { /*
|
|
530
|
-
async delete(id: string): Promise<void> { /*
|
|
531
|
-
async deleteMany(ids: string[]): Promise<number> { /*
|
|
532
|
-
async storeBatch(inputs: MemoryStoreInput[]): Promise<Memory[]> { /*
|
|
533
|
-
async initialize(): Promise<void> { /*
|
|
534
|
-
async close(): Promise<void> { /*
|
|
753
|
+
class MyStorage implements StorageProvider {
|
|
754
|
+
async store(input: MemoryStoreInput): Promise<Memory> { /* ... */ }
|
|
755
|
+
async get(id: string): Promise<Memory | null> { /* ... */ }
|
|
756
|
+
async retrieve(query: MemoryRetrieveQuery, embedding?: number[]): Promise<Memory[]> { /* ... */ }
|
|
757
|
+
async count(filter?: MemoryCountFilter): Promise<number> { /* ... */ }
|
|
758
|
+
async delete(id: string): Promise<void> { /* ... */ }
|
|
759
|
+
async deleteMany(ids: string[]): Promise<number> { /* ... */ }
|
|
760
|
+
async storeBatch(inputs: MemoryStoreInput[]): Promise<Memory[]> { /* ... */ }
|
|
761
|
+
async initialize(): Promise<void> { /* ... */ }
|
|
762
|
+
async close(): Promise<void> { /* ... */ }
|
|
535
763
|
}
|
|
536
764
|
```
|
|
537
765
|
|
|
538
|
-
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Backend Comparison
|
|
769
|
+
|
|
770
|
+
| Backend | Vector search | Touch | Status |
|
|
771
|
+
|---|---|---|---|
|
|
772
|
+
| InMemory | Cosine in-memory | Yes | ✅ Production |
|
|
773
|
+
| Disk (JSON) | Keyword + importance | Yes | ✅ Production |
|
|
774
|
+
| Markdown | Keyword + importance | No | ✅ Production |
|
|
775
|
+
| Postgres | pgvector HNSW | Yes | ✅ Production |
|
|
776
|
+
| Redis | RediSearch KNN (auto-detect) | Yes | ✅ Production |
|
|
777
|
+
| Qdrant | ANN native | No | ✅ Production |
|
|
778
|
+
| Weaviate | BM25 + vector hybrid | No | ✅ Production |
|
|
779
|
+
| LanceDB | DiskANN native | No | ✅ Production |
|
|
780
|
+
| MongoDB | Atlas Vector Search | No | ✅ Production |
|
|
781
|
+
| Neo4j | Neo4j vector index | No | ✅ Production |
|
|
782
|
+
| Hybrid | Delegates to cache/durable | If durable supports | ✅ Production |
|
|
783
|
+
| SQLite | Cosine in-memory | Yes | ✅ Production |
|
|
784
|
+
| Hybrid | Delegates to cache/durable | If durable supports | ✅ Production |
|
|
539
785
|
|
|
540
786
|
---
|
|
541
787
|
|
|
@@ -549,7 +795,7 @@ import { MemStack } from "@memstack/core";
|
|
|
549
795
|
const ms = new MemStack({
|
|
550
796
|
llm: LLMProvider, // Required — for summarization
|
|
551
797
|
embedding?: EmbeddingProvider, // Optional — for semantic search
|
|
552
|
-
storage?: StorageProvider, // Optional — defaults to
|
|
798
|
+
storage?: StorageProvider, // Optional — defaults to InMemoryStorageAdapter
|
|
553
799
|
defaults?: {
|
|
554
800
|
summarizationThreshold?: number, // Auto-summarize every N interactions. Default: 100
|
|
555
801
|
embedOnStore?: boolean, // Auto-embed on store(). Default: true
|
|
@@ -588,7 +834,11 @@ ms.memory.dryRunPrune(strategy: PruneStrategy): Promise<{ wouldPrune: string[];
|
|
|
588
834
|
ms.memory.count(filter?: MemoryCountFilter): Promise<number>
|
|
589
835
|
ms.memory.delete(id: string): Promise<void>
|
|
590
836
|
ms.memory.deleteMany(ids: string[]): Promise<number>
|
|
591
|
-
ms.memory.touch(id: string): Promise<void>
|
|
837
|
+
ms.memory.touch(id: string): Promise<void>
|
|
838
|
+
ms.memory.purgeActor(actorId: string): Promise<number>
|
|
839
|
+
ms.memory.merge(ids: string[]): Promise<Memory>
|
|
840
|
+
ms.memory.stats(actorId?: string): Promise<MemoryStats>
|
|
841
|
+
ms.memory.summarizeStream(options: SummarizeOptions): AsyncIterable<{ chunk: string; text: string }>
|
|
592
842
|
```
|
|
593
843
|
|
|
594
844
|
### Export / Import
|
|
@@ -596,6 +846,8 @@ ms.memory.touch(id: string): Promise<void> // bump recency without changing con
|
|
|
596
846
|
Snapshot and restore full state for persistence, backups, or migration:
|
|
597
847
|
|
|
598
848
|
```typescript
|
|
849
|
+
import * as fs from "node:fs";
|
|
850
|
+
|
|
599
851
|
// Save
|
|
600
852
|
const snapshot = await ms.export();
|
|
601
853
|
fs.writeFileSync("state.json", JSON.stringify(snapshot, null, 2));
|
|
@@ -696,7 +948,8 @@ git clone https://github.com/isiomaC/memstack.git
|
|
|
696
948
|
cd memstack
|
|
697
949
|
pnpm install
|
|
698
950
|
|
|
699
|
-
pnpm test #
|
|
951
|
+
pnpm test # 393 tests, no external services needed
|
|
952
|
+
pnpm test:e2e # 82 E2E tests (requires Docker)
|
|
700
953
|
pnpm test:watch # Watch mode
|
|
701
954
|
pnpm build # CJS + ESM + type declarations
|
|
702
955
|
pnpm check # TypeScript type-check only
|
|
@@ -723,7 +976,7 @@ const ms = new MemStack({
|
|
|
723
976
|
| `CONFIG_ERROR: LLM provider is required` | No LLM adapter | Pass any `LLMProvider` to config |
|
|
724
977
|
| Empty retrieval results | Wrong `actorId` or no memories stored | Check `await ms.memory.count({ actorId })` |
|
|
725
978
|
| Semantic search not working | No embedding adapter or `embedOnStore: false` | Add embedding adapter or use `strategy: "recent"` |
|
|
726
|
-
| High memory usage in production | Using
|
|
979
|
+
| High memory usage in production | Using InMemoryStorageAdapter | Implement `StorageProvider` for Postgres/Redis/etc |
|
|
727
980
|
| Poor summarization quality | Default prompt doesn't match your domain | Use `summarizationPrompt` in `defaults` config |
|
|
728
981
|
|
|
729
982
|
**Inspecting state at runtime:**
|
|
@@ -751,7 +1004,7 @@ npm login
|
|
|
751
1004
|
npm publish --access public
|
|
752
1005
|
```
|
|
753
1006
|
|
|
754
|
-
The `@memstack` scope requires `--access public
|
|
1007
|
+
The `@memstack` scope requires `--access public`.
|
|
755
1008
|
|
|
756
1009
|
---
|
|
757
1010
|
|
|
@@ -759,11 +1012,10 @@ The `@memstack` scope requires `--access public` on first publish.
|
|
|
759
1012
|
|
|
760
1013
|
Most needed contributions:
|
|
761
1014
|
|
|
762
|
-
- **
|
|
763
|
-
- **
|
|
764
|
-
- **
|
|
765
|
-
- **
|
|
766
|
-
- **Docs**: Architecture diagrams, tutorials
|
|
1015
|
+
- **LLM adapters**: Google Gemini (native), Amazon Bedrock, Vertex AI
|
|
1016
|
+
- **Embedding adapters**: local inference (transformers.js, ONNX)
|
|
1017
|
+
- **Benchmarks**: retrieval quality, latency, cost comparisons
|
|
1018
|
+
- **Python port**: `pip install memstack`
|
|
767
1019
|
|
|
768
1020
|
Open an issue or PR at [github.com/isiomaC/memstack](https://github.com/isiomaC/memstack).
|
|
769
1021
|
|