Skip to main content
Glama

Memora

Persistent, semantic memory for AI coding agents — local-first, MCP-native.

License: MIT Python Status MCP

A local, persistent, semantically-aware knowledge graph for Claude Code (and any MCP-compatible AI coding agent). Auto-loads in every session in every project. Zero per-project setup, zero network calls at runtime, ~85–95% lower per-session context cost than naïve "load it all" memory.

git clone https://github.com/VnemAIDev/memora.git
cd memora
./install.sh --bootstrap

That's the full install. Open Claude Code in any directory; memory auto-loads. See QUICKSTART.md for prerequisites and troubleshooting.


At a glance

  • Install root: ~/.claude-memory/

  • Database: ~/.claude-memory/graph.db (SQLite, WAL mode)

  • Total disk: ~320 MB (venv 227 MB + ONNX model 90 MB + DB ~2 MB)

  • Registered scope: user-level MCP server (claude mcp listmemory)

  • Global protocol: ~/.claude/CLAUDE.md

  • Runtime network calls: zero (model is downloaded once at install time)


Related MCP server: rawthink

Quick start (for a fresh reader)

# Verify the server is registered and reachable
claude mcp list

# Check semantic coverage
~/.claude-memory/.venv/bin/python -c "
import sys; sys.path.insert(0,'$HOME/.claude-memory')
import embeddings; print(embeddings.status())"

# Inspect the DB directly
sqlite3 ~/.claude-memory/graph.db \
  "SELECT project, COUNT(*) FROM entities GROUP BY project;"

# Periodic maintenance (dedupe observations)
~/.claude-memory/.venv/bin/python ~/.claude-memory/compact.py --aggressive --semantic 0.92

File inventory

File

Purpose

server.py

FastMCP server, exposes 12 tools over stdio

embeddings.py

Lazy-loaded MiniLM-L6-v2 ONNX embedder (fastembed)

bootstrap_embeddings.py

One-shot: downloads model + embeds all observations

compact.py

Dedupe script (--aggressive, --semantic THRESHOLD)

run.sh

Venv-activating launcher (registered with Claude Code)

graph.db

SQLite knowledge graph

server.log

Server logs

models/

ONNX model cache (populated by bootstrap)


Build timeline — 4 phases

Phase 1 — Base infrastructure

Minimal MCP server matching the original spec.

  • Schema: 4 tables (entities, observations, relations, tags) + 7 indexes, WAL mode, foreign keys

  • Tools (8): recall_context, create_entity, add_observation, create_relation, search, get_entity, list_projects, forget

  • Project auto-detection: CWD basename → project name ($HOME and /"global")

  • Registration: claude mcp add --scope user memory -- ~/.claude-memory/run.sh

Phase 2 — First token-optimization pass (7 wins)

Targeted the biggest pain point: recall_context() returning ~8 KB of mostly-redundant JSON.

Win

What changed

Lean default JSON shape

Drop IDs/timestamps/indent; relations become [from, type, to] triples

max_chars budget

Hard cap with truncated: true flag and omitted count

summary column + set_summary tool

One-line gist replaces raw observations on long entities

archived tag auto-exclusion

Stale entities excluded by default

since_days filter

Only entities updated within last N days

FTS5 virtual table + triggers

Real ranked text search instead of LIKE %x%

compact.py script

Manual + --aggressive dedupe of redundant observations

New summarize_project tool

One-line digest per entity — cheapest possible "what's in here?"

Phase 3 — External research pass (Caveman / RTK / Supermemory)

Researched 3 token-optimization projects in parallel; ported the high-ROI ideas.

Win

Source

What it does

Type-tier ordering

RTK

Decisions/conventions/services kept first when budget trims

omitted_names + expand_with

RTK

Truncated response says exactly what to get_entity() for

Cross-project search via project="*"

Supermemory

One call hits all your projects

Type-tier grouping in summarize_project

Supermemory

Stable concepts above ephemeral work

Cross-entity dedup (@dup:<name> sentinel)

RTK

Repeated obs returned once, referenced thereafter

New flag_for_summary tool

Supermemory

Lists entities >N obs without a summary — actionable backlog

Phase 4 — Semantic layer (Supermemory's biggest idea)

Hybrid lexical + semantic search, all local.

Win

Implementation

embeddings.py module

Lazy-loaded MiniLM-L6-v2 via fastembed + ONNX, L2-normalized

observations.embedding BLOB column

384-dim float32 vector per observation (~1.5 KB each)

Hybrid search()

FTS5 BM25 + cosine top-K, fused via Reciprocal Rank Fusion (k=60)

Semantic dedup

add_observation(dedup_threshold=0.92) skips paraphrases

compact.py --semantic 0.92

Batch semantic dedup across whole DB

New embedding_status tool

Diagnostic — model availability + coverage %

bootstrap_embeddings.py

One-shot: download 90 MB model + embed all observations

Final coverage

620 / 620 observations embedded


The complete tool surface — 12 tools

recall_context       create_entity         add_observation
create_relation      search                get_entity
list_projects        forget                set_summary
summarize_project    embedding_status      flag_for_summary

Measured token savings (real project data)

Numbers from the actual smoke tests during the build, on the demo project project with 5 entities and 20 observations:

Call type

Bytes returned

vs. legacy verbose

recall_context(verbose=True) (legacy)

8,393

recall_context() lean default

3,378

−60%

recall_context() after set_summary on largest entity

2,715

−68%

summarize_project() triage

395

−95%

search("rebuild") FTS5

477

−94%

recall_context(max_chars=800)

464

−94% (hard cap honored)

At a glance: characters ÷ 4 ≈ tokens. Old startup recall cost ~2,100 tokens; the new ritual (summarize_project → selective get_entity) costs ~100-400 tokens depending on what's relevant. 5-20× reduction per session start.


Operational benefits — behavioral wins that compound

Before

After

Memory file rewritten end-to-end every session via /memory

Persistent SQLite — only deltas written, never the whole file

Per-project memory configured manually

CWD basename auto-detects project; works in every dir without setup

Memory loaded only when Claude noticed MEMORY.md

Auto-loaded via user-scope MCP + CLAUDE.md protocol nudge

Naïve recall returned full payload regardless of project size

Type-tier ordering keeps decisions/conventions; budget caps the rest

Searches missed concept-level queries ("auth" missing login_handler)

Hybrid lexical + semantic — finds entities by meaning

Repeated observations bloated context

Cross-entity dedup + semantic dedup at write-time

No way to know what's in memory without paying full cost

summarize_project() (~400 chars) + flag_for_summary() triage cheaply

Cross-project knowledge invisible from another project

search(query, project="*") finds it in one call


Continuous wins

  1. No per-project setup cost. Every new project already has full memory. Zero friction.

  2. Cross-session continuity. State that previously lived in fragile MEMORY.md files now lives in a queryable DB.

  3. Type-aware retrieval. Asking "what conventions apply here?" returns conventions first.

  4. Semantic recall. Don't have to remember exact words. "The thing about animation" finds entities tagged #hero even if "animation" isn't in any observation.

  5. Cheap upkeep. compact.py --aggressive --semantic 0.92 weekly keeps the DB lean.

  6. Privacy. All-local. Model cached. No telemetry. No data leaves the machine.


Cost accounting

Cost

Amount

Disk usage

~320 MB (.venv 227 MB + model 90 MB + graph.db ~2 MB + log <1 MB)

Per-call CPU

<50 ms for lean recall; <100 ms for semantic search on 620 obs

Network at runtime

Zero

Lock-in

Zero — schema is plain SQLite, inspect anytime with sqlite3 graph.db

Bootstrap dependency

One-time ~90 MB download from HuggingFace (qdrant/all-MiniLM-L6-v2-onnx)


Memory Protocol (what Claude is told to do, from ~/.claude/CLAUDE.md)

Session start ritual

  1. summarize_project() — one line per entity, ~95% cheaper than full recall

  2. flag_for_summary() — for any entity with >5 observations and no summary, call set_summary(name, "<gist>") to short-circuit raw observations on future recalls

  3. recall_context() for the active subset, or get_entity(name) for specific entities

What to write

  • decision + rationale (entity_type="decision")

  • file purpose / important state (entity_type="file")

  • pending task / blocker (entity_type="todo")

  • user preference / convention (entity_type="convention")

  • external service / API / dependency (entity_type="service")

  • person / team member (entity_type="person")

Active-voice relations only: uses, depends_on, blocks, replaces, owns, reports_to, calls, extends.

Keep tokens low

  • Default lean shape; pass verbose=True only when needed

  • Read omitted_names from truncated responses; use get_entity() for specifics

  • @dup:<name> in observations means deduplicated, not missing

  • Tag stale entities archived for auto-exclusion

  • Use since_days=N for recent-only context

  • Use search(query, project="*") for cross-project lookups

  • Run compact.py --aggressive --semantic 0.92 periodically

Safety

Never store secrets, API keys, passwords, or PII. Reference them by name only (e.g., "uses Stripe API key stored in 1Password as STRIPE_PROD").


Maintenance commands

# Check semantic search status
~/.claude-memory/.venv/bin/python -c "
import sys; sys.path.insert(0,'$HOME/.claude-memory')
import embeddings; print(embeddings.status())"

# Re-embed every observation (after model swap)
~/.claude-memory/.venv/bin/python ~/.claude-memory/bootstrap_embeddings.py --rebuild

# Dry-run dedupe (no writes)
~/.claude-memory/.venv/bin/python ~/.claude-memory/compact.py --dry-run --aggressive --semantic 0.92

# Real dedupe + VACUUM
~/.claude-memory/.venv/bin/python ~/.claude-memory/compact.py --aggressive --semantic 0.92

# Health check
claude mcp list

# Activity log
grep "memory server starting" ~/.claude-memory/server.log

# Errors
grep -E "ERROR|failed|Traceback" ~/.claude-memory/server.log

# Direct DB stats
sqlite3 ~/.claude-memory/graph.db "
  SELECT project, COUNT(*) AS entities FROM entities GROUP BY project;
  SELECT 'total observations: ' || COUNT(*) FROM observations;
  SELECT 'embedded observations: ' || COUNT(*) FROM observations WHERE embedding IS NOT NULL;
  SELECT 'total relations: ' || COUNT(*) FROM relations;
"

What this means in practice

A typical session before this work would either (a) ignore memory and re-ask questions Claude should know the answer to, or (b) load 2-8 KB of memory tokens at session start whether useful or not, possibly missing concept-level matches when searching.

Now: Claude opens the session, calls summarize_project() for ~100 tokens, scans for what's relevant, calls flag_for_summary() to spot bloated entities, then fetches detail only where it matters. Median session-start memory cost: 300-800 tokens, down from 2,000+ tokens. Over 100 sessions a month that's 120K+ tokens saved on memory loading alone — and that ignores the bigger win of finding context lexical search would have missed entirely.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers