@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 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: new OpenAILLMAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
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. Inject into your LLM call
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 llmResponse = await llm.complete({
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
- // 4. Every 100 interactions, summarization kicks in automatically.
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
- The killer feature. `compileContext()` takes the 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.
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: userMessage,
281
+ user: currentMessage,
218
282
  });
283
+
284
+ console.log(response.text);
219
285
  ```
220
286
 
221
- `compileContext()` is the difference between "we have a vector DB" and "we have agent memory." It handles deduplication, token budgeting, and the recent-vs-important split that makes context useful.
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), // NLP heuristic or LLM call
317
- tags: classifyIntent(message), // "billing", "bug", "account", etc.
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
- | `gossip` | Information about third parties. For multi-agent systems. | "Agent-B told me the user is a power user." |
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
- **With embeddings** (configure an `EmbeddingProvider`): each `store()` computes a vector. `retrieve()` with `"semantic"` or `"hybrid"` uses cosine similarity ranking.
531
+ ### With embeddings vs Without embeddings
443
532
 
444
- **Without embeddings**: everything still works — retrieval falls back to importance + recency + keyword filters. No API costs, no setup.
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 Anthropic built-in.
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
- apiKey: process.env.OPENAI_API_KEY!,
472
- defaultModel: "gpt-4o-mini", // default
473
- baseURL: "https://api.openai.com/v1", // for proxies like LiteLLM
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 (custom — implement LLMProvider)
484
- import type { LLMProvider } from "@memstack/core";
485
- class OllamaAdapter implements LLMProvider {
486
- constructor(private baseURL = "http://localhost:11434") {}
487
- async complete(req: { system: string; user: string; model?: string }) {
488
- const res = await fetch(`${this.baseURL}/api/generate`, {
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
- ### Storage Adapters
662
+ // OpenAI
663
+ new OpenAIEmbeddingAdapter({ apiKey: "...", model: "text-embedding-3-small" }); // 1536 dims
512
664
 
513
- Ships with `InMemoryStorage` (zero setup, data lost on restart). For production, implement `StorageProvider` for your database.
665
+ // Cohere
666
+ new CohereEmbeddingAdapter({ apiKey: "..." }); // embed-english-v3.0, 1024 dims
514
667
 
515
- ```typescript
516
- import { InMemoryStorage } from "@memstack/core";
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
- **Custom storage** — implement `StorageProvider`:
672
+ ### Storage Adapters
521
673
 
522
- ```typescript
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 PostgresStorage implements StorageProvider {
526
- async store(input: MemoryStoreInput): Promise<Memory> { /* INSERT */ }
527
- async get(id: string): Promise<Memory | null> { /* SELECT */ }
528
- async retrieve(query: MemoryRetrieveQuery, embedding?: number[]): Promise<Memory[]> { /* SELECT + filters */ }
529
- async count(filter?: MemoryCountFilter): Promise<number> { /* SELECT COUNT */ }
530
- async delete(id: string): Promise<void> { /* DELETE */ }
531
- async deleteMany(ids: string[]): Promise<number> { /* DELETE batch */ }
532
- async storeBatch(inputs: MemoryStoreInput[]): Promise<Memory[]> { /* INSERT batch */ }
533
- async initialize(): Promise<void> { /* CREATE TABLE */ }
534
- async close(): Promise<void> { /* close pool */ }
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
- See `src/adapters/storage/memory.ts` for a complete reference implementation.
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 InMemoryStorage
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> // bump recency without changing content
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 # 56 tests, no external services needed
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 InMemoryStorage | Implement `StorageProvider` for Postgres/Redis/etc |
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` on first publish.
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
- - **Storage adapters**: Postgres, Redis, SQLite, filesystem
763
- - **LLM adapters**: Ollama, Groq, Together AI, Gemini
764
- - **Embedding adapters**: Cohere, Voyage AI, local transformers.js
765
- - **Tests**: Edge cases, concurrent access, large-scale benchmarks
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