Install
$ agentstack add mcp-skt329-semantic-recall ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
🧠 semantic-recall
Give your AI a brain that remembers.
Persistent semantic memory for LLM apps — zero config, zero API keys, two methods.
📦 View on npm · ⭐ Star on GitHub · 🐛 Report a Bug · 💡 Request a Feature
Every LLM chatbot forgets everything between sessions. Users repeat themselves. Context is lost. semantic-recall fixes this — in two lines of code.
import { Memory } from 'semantic-recall'
const memory = new Memory({ userId: 'user_123' })
memory.remember("User is vegetarian and allergic to nuts")
const facts = await memory.recall("What should I recommend for dinner?")
// → ["User is vegetarian and allergic to nuts"]
No vector database. No API keys. No Docker containers. Just npm install and go.
Why semantic-recall?
Most memory solutions require you to set up infrastructure, manage API keys, or lock into a paid platform. semantic-recall is different:
| | semantic-recall | Mem0 | Zep | LangChain Memory | |---|:---:|:---:|:---:|:---:| | npm install & go | ✅ | ❌ Requires API key or self-host setup | ❌ Requires server (Docker) | ✅ | | Works offline | ✅ Local embeddings | ❌ Cloud API calls | ❌ Server required | ❌ No built-in embeddings | | Persistent across sessions | ✅ SQLite on disk | ✅ Cloud-managed | ✅ Server-managed | ❌ In-memory by default | | Semantic search | ✅ Cosine similarity | ✅ | ✅ Knowledge graph | ❌ Keyword/buffer only | | Auto deduplication | ✅ Configurable threshold | ✅ | ❌ | ❌ | | Crash recovery | ✅ Persistent queue | ❌ | ❌ | ❌ | | Worker thread isolation | ✅ CPU never blocks | ❌ | N/A (separate server) | ❌ | | TTL / auto-expiry | ✅ "7d", "1h" | ❌ | ❌ | ❌ | | Multi-tenant | ✅ userId + namespace | ✅ user/session/agent | ✅ Sessions | ❌ | | Bundle size | ~67 KB | Cloud SDK | Cloud SDK | Large framework | | Free & open-source | ✅ MIT, forever | Freemium (paid tiers) | Freemium (credit-based) | ✅ MIT | | Self-contained | ✅ Single package | ❌ Platform dependency | ❌ Server + Redis + Postgres | ❌ Framework dependency |
> TL;DR — semantic-recall is the only solution that gives you persistent, semantic, crash-safe memory with zero infrastructure and zero API keys out of the box.
Installation
npm install semantic-recall
> First-run note: The initial call downloads a ~25 MB embedding model to a local cache. After that, everything runs offline with zero network calls.
Works Great With
- OpenAI Node SDK — inject recalled facts directly into your
messages[]array - Vercel AI SDK — wrap
recall()as a tool call for streaming chat apps - LangChain JS — use as a persistent, semantic drop-in memory module
- Turso — serverless edge storage adapter built-in
- Supabase — Postgres storage adapter built-in
- Transformers.js — powers the local offline embeddings under the hood
Quick Start
The Basics — remember() and recall()
import { Memory } from 'semantic-recall'
const memory = new Memory({ userId: 'user_123' })
// Store memories (fire-and-forget — returns instantly, never throws)
memory.remember("User prefers dark mode")
memory.remember("User is a senior TypeScript developer")
memory.remember("User lives in San Francisco")
// Retrieve relevant context for your LLM prompt
const context = await memory.recall("What IDE theme should I suggest?")
// → ["User prefers dark mode"]
// Inject into your system prompt
const systemPrompt = `You are a helpful assistant.
Known facts about the user:
${context.map(f => `- ${f}`).join('\n')}`
Synchronous Confirmation
const result = await memory.rememberAndWait("User is vegetarian")
console.log(result) // → { saved: true, duplicate: false }
const result2 = await memory.rememberAndWait("User is vegetarian")
console.log(result2) // → { saved: false, duplicate: true }
Namespaces — Organize by Topic
const memory = new Memory({ userId: 'user_123', namespace: 'health' })
memory.remember("User is allergic to peanuts")
// Only searches the 'health' namespace
const health = await memory.recall("allergies")
// Cross-namespace query
const work = await memory.recall("allergies", { namespace: 'work' }) // → []
TTL — Auto-Expiring Memories
// Memory expires after 7 days
memory.remember("User is in Paris for a conference", { ttl: "7d" })
// Supported formats: '500ms', '60s', '30m', '12h', '7d'
memory.remember("Session preference: compact view", { ttl: "1h" })
LLM Auto-Extraction
Automatically extract memorable facts from conversations:
const memory = new Memory({
userId: 'user_123',
llmProvider: 'openai',
llmApiKey: process.env.OPENAI_API_KEY,
})
await memory.extractAndRemember([
{ role: 'user', content: "I just moved to Tokyo from London" },
{ role: 'assistant', content: "Welcome to Tokyo! How exciting..." },
{ role: 'user', content: "Yeah, I'm starting a new job as a ML engineer at Google" },
])
// Automatically extracts and stores:
// → "User lives in Tokyo"
// → "User previously lived in London"
// → "User works as a ML engineer at Google"
Supported providers: 'openai' · 'gemini' · 'claude' · or any custom LLMFunction.
How It Works
remember("user is vegetarian")
│
▼
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Enqueue │────▶│ Embed Text │────▶│ Dedup Check │
│ (persistent │ │ (worker │ │ (cosine sim │
│ queue) │ │ thread) │ │ ≥ 0.92?) │
└─────────────┘ └──────────────┘ └────────┬────────┘
│
┌────────┴────────┐
│ │
Unique Duplicate
│ │
▼ ▼
┌────────────┐ ┌────────────┐
│ INSERT │ │ Skip │
│ + emit │ │ (mark done)│
│ memory:saved│ │ │
└────────────┘ └────────────┘
Reliability — Built Like Infrastructure
Every call to remember() is crash-safe. Memories are first written to a persistent pending_memories queue, then processed asynchronously. If your process crashes mid-pipeline:
PENDING ──▶ PROCESSING ──▶ DONE
│
▼
FAILED ──(exponential backoff)──▶ PENDING
│
▼ (after max attempts)
DEAD ──(manual retry)──▶ PENDING
- Stale recovery: On startup, stuck
PROCESSINGjobs are automatically reset toPENDING - Exponential backoff: Failed jobs retry with 2^n second delays (2s → 4s → 8s)
- Dead letter queue: After max attempts, jobs move to
DEADfor manual inspection - Never throws:
remember()swallows all errors — your app never crashes because of memory storage
Observability
Real-time events for monitoring and debugging:
memory.on('memory:saved', ({ content, jobId }) => {
console.log(`✓ Saved: "${content}" (id: ${jobId})`)
})
memory.on('memory:duplicate', ({ content }) => {
console.log(`⊘ Duplicate skipped: "${content}"`)
})
memory.on('memory:retry', ({ content, error, attempts }) => {
console.warn(`↻ Retry #${attempts}: "${content}" — ${error}`)
})
memory.on('memory:dead', ({ content, error }) => {
console.error(`☠ Dead: "${content}" — ${error}`)
})
Storage Adapters
SQLite (Default) — Zero Config
Works everywhere with a filesystem. WAL mode enabled for concurrent reads.
const memory = new Memory({
userId: 'user_123',
dbPath: './my-memories.db', // default: './semantic-recall.db'
})
Turso — Serverless Edge
For serverless and edge deployments with Turso:
npm install @libsql/client
import { Memory } from 'semantic-recall'
import { createTursoAdapter } from 'semantic-recall/adapters/storage/turso'
const memory = new Memory({
userId: 'user_123',
storage: createTursoAdapter({
url: 'libsql://your-db.turso.io',
authToken: 'your-token',
}),
})
Supabase — Postgres Scale
For production Postgres deployments with Supabase:
npm install @supabase/supabase-js
import { Memory } from 'semantic-recall'
import { createSupabaseAdapter } from 'semantic-recall/adapters/storage/supabase'
const memory = new Memory({
userId: 'user_123',
storage: createSupabaseAdapter({
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key',
dimensions: 384,
}),
})
Custom Adapter
Implement the StorageAdapter interface for any backend:
import { Memory, type StorageAdapter } from 'semantic-recall'
const myAdapter: StorageAdapter = {
async init() { /* create tables */ },
async insertMemory(params) { /* insert */ },
async searchMemories(params) { /* return all rows */ },
async deleteMemory(id) { /* delete by id */ },
async deleteAllMemories(userId, namespace) { /* bulk delete */ },
async listMemories(userId, namespace, limit) { /* list */ },
async pruneExpired(userId) { /* remove expired */ },
async enqueue(job) { /* queue job, return id */ },
async markProcessing(jobId) { /* update status */ },
async markDone(jobId) { /* update status */ },
async markFailed(jobId, error) { /* update status + backoff */ },
async getRetryable() { /* return pending/failed jobs */ },
async getDeadJobs(userId) { /* return dead jobs */ },
async resetStaleProcessing() { /* crash recovery */ },
async cleanupDoneJobs(olderThanMs) { /* prune */ },
async retryDeadJob(jobId) { /* reset dead → pending */ },
close() { /* cleanup */ },
}
const memory = new Memory({ userId: 'user_123', storage: myAdapter })
Embedder Adapters
Local (Default) — No API Keys
Uses Transformers.js in an isolated worker thread. The main thread is never blocked.
const memory = new Memory({
userId: 'user_123',
embedder: 'local',
embeddingModel: 'Xenova/all-MiniLM-L6-v2', // 384 dims, ~25 MB
})
OpenAI
const memory = new Memory({
userId: 'user_123',
embedder: 'openai',
openaiApiKey: process.env.OPENAI_API_KEY,
embeddingModel: 'text-embedding-3-small',
})
Custom Embedder
const memory = new Memory({
userId: 'user_123',
embedder: async (text: string): Promise => {
const res = await fetch('https://my-api.com/embed', {
method: 'POST',
body: JSON.stringify({ text }),
})
return res.json()
},
})
Full Configuration
const memory = new Memory({
// ─── Required ──────────────────────────────────
userId: 'user_123',
// ─── Storage ───────────────────────────────────
storage: 'sqlite', // 'sqlite' | StorageAdapter
dbPath: './semantic-recall.db',
// ─── Embedder ──────────────────────────────────
embedder: 'local', // 'local' | 'openai' | EmbedderFunction
embeddingModel: 'Xenova/all-MiniLM-L6-v2',
openaiApiKey: '...', // Required if embedder: 'openai'
// ─── Behavior ──────────────────────────────────
namespace: 'default',
dedupThreshold: 0.92, // Cosine sim threshold for dedup (0–1)
recallThreshold: 0.70, // Min similarity to return (0–1)
topK: 5, // Max results per recall()
// ─── Reliability ───────────────────────────────
maxAttempts: 3, // Retries before marking dead
retryIntervalMs: 30_000, // Retry scheduler interval
// ─── LLM Auto-Extraction ──────────────────────
llmProvider: 'openai', // 'openai' | 'gemini' | 'claude' | LLMFunction
llmApiKey: '...',
llmModel: 'gpt-4o-mini',
})
API Reference
| Method | Returns | Description | |---|---|---| | memory.remember(text, opts?) | void | Store a memory. Fire-and-forget, never throws. | | memory.rememberAndWait(text, opts?) | Promise | Store and wait. Returns { saved, duplicate }. | | memory.recall(query, opts?) | Promise | Semantic search. Returns content strings. | | memory.recallDetailed(query, opts?) | Promise | Like recall but with similarity scores + metadata. | | memory.extractAndRemember(messages, opts?) | Promise | LLM-powered fact extraction from conversations. | | memory.forget(memoryId) | Promise | Delete a specific memory. | | memory.forgetAll(opts?) | Promise | Delete all memories for user+namespace. | | memory.list(opts?) | Promise | List all stored memories (no search). | | memory.getDeadJobs() | Promise | Inspect failed jobs. | | memory.retryDead(jobId) | Promise | Retry a dead job. | | memory.cleanup(opts?) | Promise | Prune old done jobs from queue. | | memory.destroy() | void | Stop scheduler, close DB. |
Events
| Event | Payload | When | |---|---|---| | memory:saved | { jobId, content, replayed?, retried? } | Memory stored successfully | | memory:retry | { jobId, content, error, attempts } | Job failed, will retry | | memory:dead | { jobId, content, error, attempts } | Job exhausted all retries |
Types
All types are exported for TypeScript consumers:
import type {
MemoryOptions,
RememberOptions,
RecallOptions,
MemoryResult,
RememberResult,
MemoryJob,
StorageAdapter,
EmbedderFunction,
ConversationMessage,
LLMFunction,
MemorySavedEvent,
MemoryRetryEvent,
MemoryDeadEvent,
} from 'semantic-recall'
Real-World Patterns
Inject Context Into Any LLM
import OpenAI from 'openai'
import { Memory } from 'semantic-recall'
const memory = new Memory({ userId: 'user_123' })
const openai = new OpenAI()
async function chat(userMessage: string) {
// Recall relevant memories
const context = await memory.recall(userMessage)
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: `You are a helpful assistant.
Known facts about the user:
${context.map(f => `- ${f}`).join('\n')}`,
},
{ role: 'user', content: userMessage },
],
})
const reply = response.choices[0].message.content!
// Auto-extract facts from this exchange
await memory.extractAndRemember([
{ role: 'user', content: userMessage },
{ role: 'assistant', content: reply },
])
return reply
}
Graceful Shutdown
process.on('SIGTERM', () => {
memory.destroy() // Stops retry scheduler, closes DB
process.exit(0)
})
Dead Job Monitoring
// In a health check endpoint
app.get('/health/memory', async (req, res) => {
const dead = await memory.getDeadJobs()
res.json({
status: dead.length === 0 ? 'healthy' : 'degraded',
deadJobs: dead.length,
})
})
Comparison Deep Dive
vs Mem0
Mem0 is a managed memory platform (cloud-hosted or self-hosted). It's a great product if you want a managed service — but it requires API keys for the cloud version and Docker + Redis for self-hosting. semantic-recall runs entirely locally with npm install and zero infrastructure.
vs Zep
[Zep](https://github.com/getz
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Skt329
- Source: Skt329/semantic-recall
- License: MIT
- Homepage: https://www.npmjs.com/package/semantic-recall
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.