Skip to content

How Remem Works

Remem’s core is a single Rust service, remem-server (port 4545): the REST API (Axum), the MCP server (Streamable HTTP at /mcp), and the embedded storage engine, all in one process. No external database, no Qdrant/Postgres/Redis dependency for the core.

For MCP clients that only speak stdio (Claude Desktop, Claude Code), remem-mcp is a small companion binary you run directly — it holds no data itself and simply forwards MCP tool calls to remem-server over HTTP. Clients that speak MCP Streamable HTTP can skip it and talk to remem-server’s /mcp route directly.

remem-server embeds five purpose-built indexes:

all-MiniLM-L6-v2 embeddings (384 dimensions) generated locally via fastembed. No external embedding API — embeddings are computed in the same process. The HNSW index enables approximate nearest-neighbour vector search for semantic queries.

A compressed sparse row graph where nodes are memories and edges are typed connections (relationship_type: related_to, caused_by, part_of, references, contradicts, supports, similar_to, derived_from). Edges are written automatically by the auto-discovery worker and explicitly via the connections API.

Time-windowed range queries — e.g. listing memories created or accessed within a date range.

Tag-based lookup with soft-delete support, used by keyword search and tag filters.

Durable, write-ahead-logged storage for memory content and metadata. Every write goes through the WAL before being acknowledged, so a crash mid-write never loses committed data. Indexes checkpoint to disk periodically (checkpoint_interval_secs, default 300s / 5 minutes) rather than on every write — but reads are read-after-write consistent because reads hit the in-memory segment first, not the on-disk checkpoint.

store_memory(content, memory_type, tags, importance, emotional_valence, arousal)
├─ embed(content) → 384-dim vector [fastembed, in-process]
├─ write(payload) → LSM-tree [WAL-backed, durable]
├─ arousal >= 0.8? → force-promote to long_term,
│ floor importance at 0.9, set flashbulb_until [flashbulb protection]
└─ dispatch(auto-discovery) → background worker
├─ query HNSW index (top-K similar memories)
├─ filter by auto_discovery_threshold (default: 0.7)
└─ write connections → CSR graph (capped at auto_discovery_top_k, default 5)

The API returns after the LSM write (and any flashbulb promotion, which is synchronous). Auto-discovery of connections is asynchronous — store_memory returns before it completes.

Semantic search:

  1. Embed the query
  2. Query HNSW index for top-K nearest memories
  3. Return ranked results

Hybrid search:

  1. Embed the query
  2. Query HNSW index (semantic score)
  3. Query inverted index (keyword score)
  4. Combine semantic and keyword scores
  5. Return ranked results

Graph traversal:

  1. Start from a memory node
  2. Walk outgoing edges in the CSR graph (breadth-first)
  3. Return nodes at each depth level
StageTriggerEffect
Short-termmemory_type: "short_term" + ttl setExpires automatically at ttl
Long-termmemory_type: "long_term", promotion, or flashbulb creationPersists indefinitely (subject to importance decay and active forgetting)
PromotionManual (promote_to_longterm / POST .../promote), or automatic at creation for flashbulb memories (arousal >= 0.8)Short-term → long-term
Importance decayBackground task, dailyLong-term importance decrements ~0.5%/day; flashbulb memories exempt while protected
Active forgettingBackground task, dailyhealth decays (short-term −8/day, long-term −2/day since last recall); recall boosts health +10 (capped 100); memory is hard-deleted at health 0. Flashbulb memories exempt while protected
Archivehard=false (default) on deleteHidden from search, retained in storage
Deletehard=true on deleteRemoved from all indexes

store_memory accepts emotional_valence (-1.0 to 1.0) and arousal (0.0 to 1.0). A memory created with arousal >= 0.8 becomes a flashbulb memory: it’s force-promoted to long_term, its importance is floored at 0.9, and it’s exempt from importance decay and active forgetting for 30 days (flashbulb_until). This only happens at creation time — raising arousal later via update_memory does not retroactively apply it. See Memory Lifecycle for details.

Lifecycle tasks run as Tokio tasks inside remem-server — no separate worker process is needed. There is no single unified interval; each task has its own schedule, configured under [tasks] in remem-server.toml:

TaskDefault interval
expire_short_term5 minutes
apply_importance_decaydaily
active_forgettingdaily
consolidate_similarweekly
cleanup_archivedmonthly
discover_connectionshourly

See Configuration for the full [tasks] config, and Memory Lifecycle for what each task does.