Skip to main content
Glama

ROOTMEM

A human-brain-inspired, MCP-native memory system for AI agents.

CI Python 3.13 mypy --strict Phase 0

ROOTMEM gives an AI coding agent (Claude Code, Cursor, or anything else that speaks MCP) a real, persistent memory — one that survives process restarts, new chats, and new machines, instead of evaporating the moment a context window ends. This repository is Phase 0 of a chartered, multi-phase build: the minimal end-to-end slice — five MCP tools, a real Postgres store, a soft-delete safety model — proven to work over the actual protocol, not just in unit tests.

Why this codebase is worth a second look

Most weekend MCP-server projects stop at "it imports asyncpg and calls await conn.execute(...) from inside the tool handler." That works until you try to write a test, at which point you either need a live database for every test run or you don't test the handler logic at all. This project made a different call early and paid for it immediately:

  • The storage layer is a typing.Protocol, not a driver import. Every tool handler depends on MemoryRepository — an abstract interface with create / get_by_id / get_by_key / update / soft_delete / search_text — never on asyncpg directly (see ADR 0003). Two implementations exist: an in-memory fake for unit tests and a real asyncpg-backed one for production and integration tests.

  • One contract, two backends, enforced by inheritance. The exact same test suite (tests/unit/storage/contract.py) runs against both implementations — subclass it once for the fake, once for real Postgres, and both are held to identical behavioral guarantees. This isn't a theoretical nicety: it's what caught the two real bugs below.

  • Real bugs, caught before they shipped, not after. Running the contract suite against an actual Postgres instance (not the fake) surfaced two defects the in-memory tests structurally could not see: a confidence column declared REAL (32-bit float) that silently corrupted values like 0.9 into 0.8999999761581421 on round-trip (fixed to DOUBLE PRECISION), and a malformed-UUID lookup that crashed as an opaque StorageError instead of behaving like "not found," the way the fake naturally does. Both are fixed, both are now permanently pinned by the contract suite. This is the entire argument for testing against a real backend, demonstrated rather than asserted.

  • Soft-delete is the only delete, and it's verified at the database layer, not just through the app's own read path. forget never issues DELETE. It sets deleted_at / deleted_reason and nothing else touches the row. A dedicated integration test bypasses the repository entirely and inspects the raw row with a direct SQL query, because "the code that claims to soft-delete says it soft-deleted" is a weaker guarantee than "the row is still physically there" (see ADR 0004).

  • Every non-obvious decision is a written ADR, not tribal knowledge. Five of them so far, each with real context, a decision, and rejected alternatives — including one documenting a wrong assumption caught and corrected against the actual installed package version rather than left wrong in the docs (see ADR 0002).

  • mypy --strict passes across the whole src/ and tests/ tree, with zero suppressions. Not "mostly typed." Not # type: ignore sprinkled where inference got hard. Every public function signature, every Pydantic model, every repository method — fully typed and checked.

  • The schema degrades gracefully instead of hard-failing on missing infrastructure. pgvector has no simple Windows binary install. Rather than making the whole project Docker-only, the init migration detects whether the extension actually loaded and conditionally creates the content_embedding column — one migration file, correct against a full docker-compose Postgres or a bare native install, with zero branching in application code.

  • The exit criterion was a real, human-observed, cross-session test — not a green checkmark. Phase 0's charter defines success as "an agent remembers a fact across two separate sessions via MCP." That was validated with two genuinely independent Claude Code processes, sharing one persisted fact through this server, then exercised through update, forget, and a direct-database soft-delete check. The full log — exact timestamps, exact prompts, exact returned values — is in scripts/manual_recall_check.md.

None of this is decoration. It's what "production-grade" costs, paid up front, on a project small enough that most people wouldn't have bothered.

Related MCP server: ilma

Architecture

flowchart LR
    subgraph Client["MCP Client"]
        CC["Claude Code / Cursor / any MCP client"]
    end

    subgraph Server["rootmem.integration.mcp.server (stdio)"]
        T1[remember]
        T2[recall]
        T3[update]
        T4[forget]
        T5[search]
    end

    subgraph Domain["Pure domain logic — zero I/O imports"]
        P["MemoryRepository Protocol"]
    end

    subgraph Impl["Two interchangeable implementations"]
        FAKE["InMemoryMemoryRepository\n(unit tests, zero I/O)"]
        PG["PostgresMemoryRepository\n(asyncpg, production + integration tests)"]
    end

    DB[("Postgres\nmemories table\nsoft-delete only")]
    REDIS[("Redis\nconnectivity smoke test\n(session cache, dark until Phase 1+)")]

    CC <-->|stdio, MCP protocol| Server
    T1 & T2 & T3 & T4 & T5 --> P
    P -.implemented by.-> FAKE
    P -.implemented by.-> PG
    PG --> DB
    Server -.ping only.-> REDIS

Tool handlers never see a connection object, a SQL string, or an asyncpg type — only the MemoryRepository protocol. That boundary is what makes tests/unit/ run in milliseconds with no external services, and what makes tests/integration/ a genuine end-to-end proof rather than a slower copy of the same unit tests.

The five tools

Tool

Contract

remember

Persist a new memory. Idempotency-key collisions return the existing record instead of duplicating.

recall

Fetch one memory by id or key. Returns found: false, never an error, when nothing matches.

update

Partial update (content / confidence / metadata) to a non-deleted memory.

forget

Soft-delete. Idempotent — forgetting an already-forgotten memory returns its existing deletion timestamp instead of raising.

search

Ranked full-text search (ts_rank over to_tsvector) across non-deleted memories in a namespace.

Quickstart

Prerequisites: Python 3.13, uv, and either Docker Desktop or a native Postgres 17 + Redis install.

uv sync --all-extras --dev
cp .env.example .env          # adjust if needed
docker compose up -d          # Postgres+pgvector, Redis
uv run python scripts/migrate.py
uv run python -m rootmem.integration.mcp.server   # serves over stdio

No Docker? The app talks to Postgres/Redis only through .env config — run Postgres 17 and Redis however you like, point .env at them, then uv run python scripts/migrate.py. The init migration skips pgvector gracefully if it isn't installed (Phase 0 doesn't populate embeddings yet). Full detail on the native path this project itself used — including the exact fallback for a Windows box where pgvector has no binary install — is in scripts/manual_recall_check.md.

Point an MCP client at it:

claude mcp add rootmem -- <path-to-venv>/python -m rootmem.integration.mcp.server

or add it to .mcp.json / your client's MCP config directly. See scripts/manual_recall_check.md for the exact cross-session validation procedure this project ran to sign off Phase 0.

Development

uv run ruff check .                 # lint
uv run mypy --strict src/ tests/    # type-check — zero errors, zero ignores
uv run pytest tests/unit -v         # 31 tests, no external services required
uv run pytest -m integration -v     # 23 tests — real Postgres, real Redis, real MCP subprocess

tests/integration/test_mcp_server_e2e.py is not a mock of the protocol — it launches the actual server as a subprocess over real stdio and drives it through the real mcp client library, the same way Claude Code or Cursor would. All 54 tests are green.

Documentation map

This project follows a chained, phase-by-phase SDLC: research memo → requirements → ADRs → implementation → retro, for every phase, before the next phase is even planned.

Project layout

src/rootmem/
  storage/            MemoryRepository protocol (protocols.py), the in-memory
                       fake, and the Postgres implementation      — ADR 0003
    postgres/         asyncpg repository + yoyo migrations
    redis/            connectivity-only client, protocol=2 pinned
    fakes/            InMemoryMemoryRepository — zero I/O, unit-test backend
  integration/mcp/    the 5 tools + the stdio server that wires them up — ADR 0002
  observability/      per-operation latency/outcome logging
  retrieval/          reserved for Phase 4's multi-factor ranking formula
  capture/            reserved for later-phase capture pipeline
  extraction/         reserved for later-phase entity/relation extraction
  consolidation/      reserved for later-phase memory consolidation ("dreaming")
  trust/              reserved for later-phase provenance/trust scoring
docs/                 research memos, requirements, ADRs, math specs — one set per phase
tests/
  unit/               fast, no external services
  integration/        real Postgres + Redis + a real MCP subprocess

The empty-looking capture/ / extraction/ / consolidation/ / trust/ directories are not filler — they're placeholders with their own READMEs so later phases add files into an already-agreed layer boundary instead of restructuring the package around code that didn't anticipate them.

Status

Phase 0 — Foundations: complete. All 54 automated tests pass; the manual cross-session exit criterion is validated and logged. See scripts/manual_recall_check.md for the full sign-off record, including the two real bugs the integration suite caught before they could ship.

Phase 1 onward (episodic + semantic memory core, consolidation, retrieval ranking, trust/provenance) is scoped in the project's master charter and will each get their own research memo and requirements pass before implementation starts — per the same chained-SDLC discipline this phase followed.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Centralized agentic memory and MCP gateway for LLMs, enabling persistent memory recall, validation, and integration with upstream MCP servers.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Framework-agnostic MCP server for agent memory with Postgres + pgvector, enabling persistent memory, recall, and task management across sessions.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    A personal memory MCP server that ingests AI agent conversation logs from multiple platforms into a searchable PostgreSQL+pgvector database, enabling cross-session recall of past reasoning and decisions.
    6
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides MCP agents with persistent memory operations (store, recall, forget, consolidate, list) backed by Redis, Postgres, and pgvector, including semantic search and background consolidation.
    Apache 2.0