Sandbox
@Danialsamadi/synapse

SQLite memory server for agent tools

Synapse gives agents durable memory on local SQLite, with typed writes, hybrid retrieval, conflict handling, decay, and a session digest for core facts. It plugs into agents through MCP, plus a CLI, HTTP API, and SDK so builders can write, query, export, purge, and re-embed memories.

63 stars11 forksTypeScriptUpdated 17d ago
Who it's for

Builders who want their agent to remember facts, update them cleanly, and recall them in later sessions.

What it delivers

You can give your agent a local memory layer that stays under your control and works across sessions.

What it does

Typed memory writes

Stores episodic, semantic, and procedural memories with Zod validation and entity-key supersession for current-value facts.

Hybrid retrieval

Combines vector search, FTS5 keyword search, importance, recency, decay, and conflict signals to rank memories.

Always-on digest

Returns a capped block of core facts that the agent should know at session start.

Feedback loop

Lets agents mark memories as helpful, stale, or wrong so confidence and visibility can change over time.

MCP and CLI access

Exposes the same memory store through an MCP server and commands like remember, query, export, decay, backup, and restore.

Privacy controls

Keeps storage local by default, blocks credential-like content, and supports export, purge, and bearer auth.

How to get it

  1. 1Small /tmp, no native toolchain, or no GPU runtime? This is the supported path
    npm install -g synapse-os        # no onnxruntime, no model downloads
    # if install scripts are blocked or better-sqlite3 has no prebuilt binary for your platform:
    npm install -g synapse-os --ignore-scripts && (cd "$(npm root -g)/synapse-os" && npm rebuild better-sqlite3)
  2. 2Claude Code
    claude mcp add --scope user synapse -- npx -y synapse-os
    
    # or from a source checkout:
    claude mcp add --scope user synapse -- pnpm --dir /path/to/synapse mcp
  3. 3OpenCode
    opencode mcp add synapse -- pnpm --dir /path/to/memory-os mcp

README

Synapse

Synapse — Personal AI Memory OS

CI npm License: GPL v3 TypeScript

Chat history is a log. Synapse is a brain.

Synapse is a local-first personal memory operating system that gives AI agents durable, typed long-term memory. It extracts semantic facts from episodic conversations, anchors single-current-value facts to entity keys so new values supersede old ones, detects and resolves conflicts, decays stale information, and retrieves with a hybrid scoring pipeline that attaches trust qualifiers and learns from agent feedback — all on local SQLite with full user control over export and purge. It refuses to store credentials, and an always-on digest covers what retrieval can't: the facts an agent should just know at session start.

Architecture

flowchart TB
    subgraph Agents["Agents & Surfaces"]
        MCP["MCP clients<br/>Claude Code · Claude Desktop · Cursor · OpenCode"]
        SDK["SDK adapters<br/>Anthropic · OpenAI-compatible routers"]
        CLI["CLI<br/>synapse remember / query / export / decay"]
        UI["Inspector UI"]
    end

    subgraph Tools["Memory tools (Zod-validated, importance-capped)"]
        W["memory_write<br/>+ entityKey supersession"]
        R["memory_retrieve<br/>+ trust qualifiers"]
        D["memory_digest<br/>always-on core memory"]
        F["memory_feedback<br/>helpful / stale / wrong"]
    end

    API["HTTP API (Hono)<br/>/v1/memories · /v1/jobs · /v1/conflicts · /v1/export · /v1/purge"]

    subgraph Store["Store (better-sqlite3 · WAL)"]
        REPO["MemoryRepository<br/>CRUD · links · quarantine · audit · jobs"]
        RET["RetrievalService<br/>vector + BM25 keyword + importance + recency − decay − conflict"]
        JOBS["Jobs<br/>consolidate · conflict · decay · purge"]
    end

    subgraph Providers["Providers (env-configured factories)"]
        EMB["Embeddings<br/>hash (offline) · OpenAI-compatible"]
        LLM["LLM<br/>OpenAI-compatible · FakeLlm for tests"]
    end

    MCP --> W & R & D & F
    SDK --> W & R
    W & R --> API
    CLI --> Store
    UI --> API
    API --> Store
    RET --> EMB
    JOBS --> LLM
    JOBS --> EMB

The MCP server talks to the store directly (like the CLI); the SDK adapters route through the HTTP API. All write paths share the same guards.

Monorepo layout

apps/
  api/             HTTP API (Hono) + inspector page
  demo-agent/      tool-calling demo agent
  mcp-server/      stdio MCP server (Claude Code/Desktop/Cursor)
packages/
  core/            Zod schemas, scoring helpers, ID generation
  store/           SQLite repository, retrieval, jobs
  embeddings/      provider interface + hash/OpenAI embeddings
  sdk/             SynapseClient + tool definitions + provider adapters
  evals/           32 golden cases + lifecycle test
  cli/             synapse CLI
scripts/
  demo.sh          north-star demo script

Retrieval scoring

Hybrid score per candidate memory:

\begin{aligned}
\text{score} ={} & 0.40 \cdot \text{vector} + 0.20 \cdot \text{keyword} + 0.15 \cdot \text{importance} + 0.10 \cdot \text{confidence} \\
& + 0.10 \cdot \text{recency} - 0.10 \cdot \text{decay} - 0.05 \cdot \text{conflict}
\end{aligned}

Weights defined in DEFAULT_RANK_WEIGHTS (packages/core/src/scoring.ts). Retrieval is 100% non-LLM by default; the LLM is used only in consolidation and conflict detection. An opt-in rerank: true flag lets an LLM reorder the final hit list (for benchmark headroom) — any parse failure or LLM error falls back silently to hybrid order, and the MCP path never uses it.

The keyword component is real full-text search, not substring matching: an SQLite FTS5 index (porter unicode61 tokenizer — English stemming, other scripts match exactly) scores hits with BM25, and every query token prefix-matches ("roas" finds "roast"). Candidacy is the union of FTS keyword hits and the vector top-K — memories matching neither signal are noise for that query and are never scored (always-know facts are the digest's job, below). If the FTS index is ever broken, retrieval degrades to legacy substring scoring instead of failing, and audits the fallback.

Retrieval also closes the loop instead of being a one-way pipe:

  • Trust qualifiers — each result may carry a qualifier string ("stored 8 months ago — may be outdated; disputed by a conflicting memory; low confidence") so the consuming LLM can hedge instead of confidently asserting stale facts.
  • Touch-on-retrieve — returned memories get their lastAccessedAt bumped, so memories that keep proving relevant rank higher over time via the recency term.
  • Abstention — pass minScore and Synapse returns nothing rather than weakly-related noise; an empty result is a signal, not a failure.
  • Rank-based group boost — the tag group whose best member scored highest gets a small additive boost for all its members (ordinal, not distance-based), so weak siblings of a strong hit outrank unrelated stragglers.
  • 1-hop link expansion — a hit on a memory pulls in its part_of / related_to neighbors at half score when there's room, so a hit on a chapter brings its book along.
  • Semantic dedup at write time — a new memory near-identical to an existing one is absorbed instead of stored twice, keeping retrieval results from filling with duplicates.

Memory tools (MCP)

ToolWhat it does
memory_writeStore a typed memory (episodic / semantic / procedural). Pass entityKey (e.g. user.employer) for single-current-value facts — a new value automatically supersedes the old one instead of coexisting with it. Content that looks like a credential is rejected (see Secret detection).
memory_retrieveHybrid-scored recall with trust qualifiers on each result.
memory_digestAlways-on core memory: pinned + most important facts as one capped block. Call once at session start — the "agent should just know this" layer that pure retrieval misses.
memory_feedbackReport a retrieved memory as helpful, stale, or wrong. Helpful raises confidence (and re-activates a disputed memory); stale/wrong lowers it and marks the memory disputed, hiding it from default retrieval.

Entity anchoring fixes the classic staleness bug ("I work at Acme Corp" retrieved three months after you switched jobs): facts with an entityKey behave like a current-value slot, not an append-only log. The consolidation job emits entity keys too, so facts extracted from conversation get the same treatment. Superseded values stay in history (status: superseded, linked via supersedes) — nothing is silently lost.

Quick start

pnpm install
pnpm test                              # all tests
pnpm eval                              # eval harness (32 cases)
pnpm dev:api                           # http://localhost:8787

# CLI
pnpm --filter @synapse/cli start remember semantic "User prefers TypeScript"
pnpm --filter @synapse/cli start query "TypeScript preference"
pnpm --filter @synapse/cli start export

# Inspector — browse/edit memories, link graph, analytics charts, audit trail (light/dark)
open http://localhost:8787/inspector

# North-star demo (requires API running)
./scripts/demo.sh

The synapse-os package: CLI + MCP in one

The published npm package is both the stdio MCP server and a full CLI sharing the same store, env vars, and write guards:

npx -y synapse-os                      # no args → stdio MCP server (existing configs keep working)
npx -y synapse-os mcp                  # explicit MCP mode
npx -y synapse-os remember semantic "User prefers TypeScript"
npx -y synapse-os query "typescript preference"
npx -y synapse-os list / get / delete / import / export
npx -y synapse-os reembed              # after switching embedding provider/model
npx -y synapse-os decay                # TTL/decay sweep — for cron, no live server needed
npx -y synapse-os backup [dest] / restore <src> --force

What's in the package: the stdio MCP server + CLI, on better-sqlite3. The HTTP/SSE API and Inspector UI live in this monorepo (apps/api) and are not part of the synapse-os package — "works with any MCP client" means stdio. Heavy ML is opt-in: installing synapse-os does not pull transformers.js/ONNX; SYNAPSE_EMBED_PROVIDER=local requires a separate npm i @huggingface/transformers.

To inspect the DB while an MCP host holds it (WAL mode allows concurrent readers): npx -y synapse-os list, or read-only SQL: sqlite3 "file:$HOME/.synapse/synapse.db?mode=ro" "SELECT type, status, content FROM memories".

Constrained environments (containers, gateways, Hermes)

Small /tmp, no native toolchain, or no GPU runtime? This is the supported path:

npm install -g synapse-os        # no onnxruntime, no model downloads
# if install scripts are blocked or better-sqlite3 has no prebuilt binary for your platform:
npm install -g synapse-os --ignore-scripts && (cd "$(npm root -g)/synapse-os" && npm rebuild better-sqlite3)
  • Default embeddings are hash (non-semantic, loud warning at startup): retrieval runs on FTS5 + importance/recency — about 80% useful for structured/cron-log recall, blind to paraphrase.
  • For real semantic recall without heavy npm installs, use Ollama (see provider matrix) and run synapse-os reembed after switching.
  • Hermes MCP config — env vars go in an env: object, not --env CLI args:
mcpServers:
  synapse:
    command: npx
    args: ["-y", "synapse-os"]
    env:
      SYNAPSE_DB: /root/.synapse/synapse.db
      SYNAPSE_EMBED_PROVIDER: hash

Full Hermes guide, including cron-run episodic logging patterns: integrations/hermes/.

Use from any AI agent

Synapse exposes memory_write, memory_retrieve, memory_digest, and memory_feedback over MCP. Any MCP-capable agent can use it — verified live with Claude Code (write in one session, recall in a fresh one).

Claude Code:

claude mcp add --scope user synapse -- npx -y synapse-os

# or from a source checkout:
claude mcp add --scope user synapse -- pnpm --dir /path/to/synapse mcp

Then verify inside a new session with /mcp — synapse must show as connected. Tool calls appear as permission prompts named synapse - memory_write / synapse - memory_retrieve.

Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows), then restart the app:

{
  "mcpServers": {
    "synapse": {
      "command": "pnpm",
      "args": ["--dir", "/path/to/memory-os", "mcp"]
    }
  }
}

Cursor — Settings → MCP → Add server, or add the same mcpServers block to ~/.cursor/mcp.json.

OpenCode:

opencode mcp add synapse -- pnpm --dir /path/to/memory-os mcp

Forced recall (optional): MCP tool calls are ultimately the model's choice. For deterministic recall, copy integrations/opencode/synapse-recall.ts to ~/.config/opencode/plugin/ and set SYNAPSE_REPO to your checkout (or edit the REPO constant). It hooks chat.message: when a prompt contains a trigger phrase ("use synapse", "deep memory", "recall", "what do you know about …"), it queries the DB directly and injects the results into the prompt before the model runs — no reliance on the model calling the tool.

Any other MCP client — it's a standard stdio server: command pnpm, args ["--dir", "/path/to/memory-os", "mcp"].

The server stores to ~/.synapse/synapse.db by default; set SYNAPSE_DB in the server's env to share one database with the API/CLI/Inspector.

Testing tips (learned the hard way):

  • Confirm the tool is actually connected in the session before judging results (/mcp in Claude Code, opencode mcp list).
  • Hosts with their own memory feature (Claude Code) may prefer it for passive "remember X" phrasing — name the tool ("use the synapse memory_write tool") or add Use the synapse MCP tools for storing and recalling user memories. to your CLAUDE.md / agent rules.
  • Independent proof a write landed: sqlite3 ~/.synapse/synapse.db "SELECT type, content FROM memories WHERE status='active'".

Anthropic / OpenAI / any OpenAI-compatible router (OpenRouter, Groq, Ollama, Mistral):

import { toAnthropicTools, toOpenAiTools, parseToolCall, executeMemoryTool, SynapseClient } from "@synapse/sdk";

const client = new SynapseClient({ baseUrl: "http://localhost:8787" });
// pass toAnthropicTools() to the Messages API, or toOpenAiTools() to Chat Completions
// on any tool call the model returns:
const { name, args } = parseToolCall(providerToolCall);
const result = await executeMemoryTool(client, name, args);

Guaranteed recall: to make the model query memory before answering, pass tool_choice on the first request of a turn — anthropicForceTool() (Messages API) or openAiForceTool() (Chat Completions) — then drop it on the follow-up call that carries the tool result, or the model is forced into an infinite tool loop. Note: Anthropic rejects forced tool use combined with extended thinking.

Every provider path shares the same guards: Zod validation on all inputs and agent-write importance capped at 0.8 (prompt-injection protection).

Secret detection

Synapse refuses to store credentials. Every write path — MCP, HTTP API, CLI, the consolidation job — runs the content through a high-precision pattern gate before it touches disk: AWS access keys, sk-… API keys, GitHub/Slack/Google tokens, JWTs, PEM private keys, and password:/token=-style assignments.

  • Rejected writes never reach the database, the embedder, exports, or the digest.
  • The agent gets a readable reason ("Content appears to contain a credential (aws-access-key)… use a password manager") — the HTTP API returns 422, the CLI exits non-zero.
  • The audit log records only the credential kind, never the matched text.
  • Deliberately storing secrets is a human's call, not an agent's: set SYNAPSE_ALLOW_SECRETS=1 in the server environment to disable the gate entirely. There is no per-write override an agent could reach.

Evals

MetricValue
Golden cases32
Precision@50.969
Stale-fact rate0.000
Pass rate0.969

Golden-case numbers are measured in hash mode (no semantic vectors — FTS5 + importance/recency/confidence only), i.e. the worst-case constrained-environment configuration. Real embedding providers add paraphrase recall on top. Scale caveat: these are personal-scale figures (hundreds to ~1K memories, linear vector scan); 10K+ retrieval is unbenchmarked — treat performance claims beyond that as unproven.

On the external engram-v3 benchmark (LongMemEval-format, 50 multi-session QA questions, LLM-judged): 96.0% (48/50) — ingest the haystack sessions, retrieve, answer from retrieved memories only. Full results, metric caveats, and reproduction commands: BENCHMARKS.md.

Lifecycle tests prove the full pipeline: episode → extraction → conflict detection (or entityKey supersession) → retrieval excludes the stale fact.

Behavioral evals go beyond retrieval ranking and test outcomes — the failure modes most memory systems never measure: does an entity update actually replace the old fact at retrieval time, does stale feedback demote a memory out of default retrieval and out of the digest, do disputed results carry a warning qualifier, does repeated feedback converge confidence instead of oscillating.

Privacy

  • Export: GET /v1/export — full JSON dump of all non-deleted memories
  • Purge: POST /v1/purge — hard-deletes rows, embeddings, and links
  • Auth: set SYNAPSE_TOKEN env var to require Bearer auth on all /v1/* routes
  • Audit: export and purge actions are logged to the audit table
  • Network: the API binds 127.0.0.1 by default. To expose it beyond loopback set SYNAPSE_HOST=0.0.0.0 — and set SYNAPSE_TOKEN when you do, or /v1/export and /v1/purge are reachable unauthenticated.

Where your data goes

Storage and retrieval are local — SQLite on your disk. "Local-first" here means the store and ranking never need a network; some optional features do talk to endpoints you configure:

  • Consolidation (POST /v1/jobs/consolidate): new episodic memories are sent to the LLM at SYNAPSE_LLM_BASE_URL (default https://api.openai.com/v1) to extract semantic facts. Point it at a local model (e.g. Ollama) to keep everything on-device.
  • Embeddings: the default hash provider makes no network calls (and no semantic vectors — see the provider matrix below). openai sends memory text to whatever endpoint you configure (localhost for Ollama; a third party for hosted APIs). local (transformers.js) downloads the model once from HuggingFace, then runs fully on-device.

With the defaults (hash, no consolidation), no memory content ever leaves your machine and no network request is made. With local embeddings, the only network traffic is the one-time model download.

Choosing your embedding provider (plug and play)

Everything is switchable by env var — no code changes. Honest expectations per provider:

ProviderEnvSemantic?Install weightNetwork
hash (default)SYNAPSE_EMBED_PROVIDER=hashNo — deterministic char-frequency vectorszero extranone
openai — any OpenAI-compatible endpoint, incl. OllamaSYNAPSE_EMBED_PROVIDER=openai SYNAPSE_EMBED_BASE_URL=... SYNAPSE_EMBED_MODEL=... (SYNAPSE_EMBED_API_KEY if the endpoint needs one)Yeszero extraper-request to the endpoint (localhost for Ollama)
local — transformers.jsSYNAPSE_EMBED_PROVIDER=local (+ optional SYNAPSE_EMBED_MODEL=Xenova/bge-small-en-v1.5)Yesheavy, opt-in: npm i @huggingface/transformers pulls the ONNX runtime (~100MB+ native binaries); model (~25MB) downloads once from HuggingFaceone-time model download, then fully on-device

Default selection: an explicit SYNAPSE_EMBED_PROVIDER always wins. Otherwise, if SYNAPSE_EMBED_API_KEY or SYNAPSE_EMBED_BASE_URL is set, Synapse assumes openai. Otherwise it runs hash and prints a loud startup warning: in hash mode the vector term is disabled entirely (no noise in the ranking) and retrieval works on FTS5 keyword search + importance/recency/confidence — genuinely useful for structured/keyword-ish recall, blind to paraphrase. Semantic dedup/absorb at write time is also disabled in hash mode, so near-duplicate wording is stored rather than mis-merged.

Recommended path for constrained environments (containers, no native toolchain, small disks): Ollama — real embeddings, no npm-install weight: ollama pull nomic-embed-text, then the openai row above with SYNAPSE_EMBED_BASE_URL=http://localhost:11434/v1.

After switching provider or model, run npx -y synapse-os reembed — it re-embeds every stored memory with the new model. Without it, old memories keep vectors the new model can't compare against and silently fall back to keyword-only scoring.

The LLM for consolidation/conflict jobs is equally pluggable: SYNAPSE_LLM_BASE_URL + SYNAPSE_LLM_MODEL + SYNAPSE_LLM_API_KEY accept any OpenAI-compatible endpoint (Ollama, LM Studio, OpenRouter, ...). Library users can go further and inject any EmbeddingProvider implementation directly into RetrievalService/writeMemory.

2-minute reviewer script

pnpm install && pnpm test && pnpm eval
pnpm dev:api &
sleep 1

# Write and retrieve
curl -s -X POST http://localhost:8787/v1/memories \
  -H 'Content-Type: application/json' \
  -d '{"type":"semantic","content":"User lives in Vancouver","tags":["location"]}'

curl -s -X POST http://localhost:8787/v1/memories/retrieve \
  -H 'Content-Type: application/json' \
  -d '{"query":"Where do I live?","limit":3}' | python3 -m json.tool

# Inspector
open http://localhost:8787/inspector

# Export
curl -s http://localhost:8787/v1/export | python3 -m json.tool

License

GPL-3.0-only — see LICENSE.

Files in the repo

Repository payload17 top-level entries
  • .github
  • apps
  • integrations
  • packages
  • scripts
  • .env.example
  • .gitignore
  • BENCHMARKS.md
  • CHANGELOG.md
  • LICENSE
  • package.json
  • pnpm-lock.yaml
  • pnpm-workspace.yaml
  • README.md
  • SECURITY.md
  • Synapse.png
  • tsconfig.base.json

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More connectors

Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface

86k

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

43k

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

14k
okf-memory/
okf-agent-memory

Git-native persistent memory for AI coding agents. Implements Google OKF v0.2 with sub-300µs in-memory BM25 search, embedded MCP server, and progressive disclosure. Slashes token bloat by 80% with zero external databases or dependencies. Built in pure Go.

547
tirth8205/
code-review-graph

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.

31k
2akouwu/
reverify

Stop your AI from making things up — it proposes, deterministic tools decide, every claim checked against ground truth with evidence. Grounded facts and context survive resets. Reverse engineering is the proving ground. MCP server + CLI.

1.1k