Skip to main content
Glama

Mnemochain

A memory, ledger, reflection, replay, and citation-backed semantic recall layer for AI agents — built as one data structure, not six separate systems. Runs entirely on your own machine. $0 to build, $0 to run, no server, no API key required.

(This project used to be two separate ones — Chronicle, the log/ledger/ replay layer, and agentmem, a hallucination-resistant memory library. They merged, with agentmem's trust layer becoming just another view on Chronicle's log — a genuine simplification, not just glue code, see "Why merging simplified things" below. The merged project was renamed to Mnemochain afterward: "Chronicle" was already taken by several other published projects on PyPI and elsewhere, and mnemo- [memory] + chain [the hash chain] describes what this actually is more precisely anyway.)

The idea

An agent's entire history is a single append-only, hash-chained log (think "git for agent cognition"). Every other capability is just a view on that log:

Capability

How it's implemented

Memory (raw)

entries table in SQLite — every action/observation is a row

Memory (semantic, cited)

semantic.SemanticView — TF-IDF (or swap in real embeddings) recall over the log, every result carries its source; remember() / recall()

Paraphrase-augmented recall

optionally store 3-5 alternate phrasings of each fact so recall() also matches differently-worded queries — a local Ollama model if you have one, or a zero-dependency synonym swap if you don't; only the original fact is ever cited — paraphrase.py

Hallucination checking

verify_claim() — confidence tier (high/medium/low/none), not just yes/no, plus a warning if the fact is contested

Contradiction detection

two active memories that disagree get flagged automatically, even with no explicit key linking them

Verifiable ledger

each row's hash covers its content + the previous row's hash (plain SHA-256, no blockchain, no gas fees) — ledger.verify_chain()

Reflection

a scheduled job folds recent entries into a distilled "lesson," using a local model if you have one, or a zero-dependency rule-based summarizer if you don't — reflect.reflect()

Skill-sharing

lessons are just JSON — export them, commit to a git repo or drop in a shared folder, another agent imports them — skills.py

Replay / rollback

the log is immutable, so "rollback" is a read-only view of an earlier point, never a delete — replay.fold(), replay.view_as_of()

Cost control

a rule-based router defaults every task to the local backend; it only touches a paid API if you explicitly flip allow_api=Truerouter.py

Important naming distinction: ledger.verify_chain() checks the log hasn't been tampered with. semantic.SemanticView.verify_claim() checks whether a fact is actually true according to memory. These are deliberately different names for deliberately different kinds of trust — don't confuse them.

Related MCP server: neurakeep

Why merging simplified things, not just combined them

agentmem used to track supersession with an explicit superseded_by field that got mutated in place. Mnemochain's log is append-only — nothing can be mutated. So "the active fact for key X" is just the latest log entry with that key, derived fresh each time. No mutation tracking needed at all; the append-only design gives you supersession for free.

It also structurally fixed a real bug: the standalone agentmem had a cached TF-IDF vocabulary that could go stale between calls (see docs/ROADMAP.md for that story). SemanticView caches nothing between calls — every recall()/verify_claim() re-derives from the log — so that whole bug class is now impossible by construction, not just patched.

Why this stays at $0

  • Storage: SQLite (stdlib, no server).

  • Hashing: hashlib.sha256 (stdlib, no blockchain fees).

  • Semantic recall: TF-IDF via scikit-learn by default (one local install); swap in sentence-transformers for real embeddings, still local and free.

  • Reflection: Ollama running a local open-weight model if installed; otherwise an offline rule-based summarizer that ships with zero dependencies and always works.

  • Paraphrase generation: same local-first pattern — Ollama if reachable, otherwise a zero-dependency synonym swap. Either way, a few extra sentences of SQLite storage per fact; nothing like the cost of training or fine-tuning anything.

  • Sharing: a git repo or plain file copy — no hosting bill.

  • MCP server: pip install "mcp[cli]" — one local install, still $0, no hosting.

  • The only code path that can ever cost money (backends/api.py) is intentionally unimplemented and inert until you wire a key yourself.

Packaging note: import mnemochain alone stays stdlib-only — it does NOT pull in numpy. Only from mnemochain import semantic (or running the CLI's semantic commands / the MCP server) needs pip install numpy scikit-learn. This was almost broken during the merge — an earlier draft eagerly imported semantic from __init__.py, which silently forced numpy onto every user of the base log/ledger/replay functionality. Caught by testing the import with numpy blocked before shipping.

Quickstart

python examples/quickstart.py          # the original log/ledger/reflect/replay demo
python examples/semantic_demo.py       # the new citation/verify/contradiction demo

Or via the CLI:

python -m mnemochain.cli add agent-1 action '{"task": "search papers"}'
python -m mnemochain.cli reflect agent-1
python -m mnemochain.cli verify                                    # tamper check
python -m mnemochain.cli export lessons.json
python -m mnemochain.cli import lessons.json agent-2

# semantic commands (need: pip install numpy scikit-learn):
python -m mnemochain.cli remember agent-1 "The user's name is Priya." --key user.name
python -m mnemochain.cli recall "what's the user's name?"
python -m mnemochain.cli verify-claim "The user's name is Priya."   # fact check, not tamper check
python -m mnemochain.cli contradictions

# store alternate phrasings too, to improve recall against different wording:
python -m mnemochain.cli remember agent-1 "The user moved to Munich." --key user.location --paraphrase
python -m mnemochain.cli recall "where does the user live?"

Using it as an MCP server

Needs one local install with network access — still $0, no hosting:

pip install "mcp[cli]"
python -m mnemochain.mcp_server

Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "mnemochain": {
      "command": "python",
      "args": ["-m", "mnemochain.mcp_server"]
    }
  }
}

Exposes: remember (with an optional paraphrase flag), recall, verify_claim, list_contradictions (semantic memory) plus verify_chain, reflect (audit + distillation) — one server, both capabilities. Every tool call opens its own fresh SQLite connection rather than sharing one cached globally (see "Bugs found and fixed" below for why that matters). Everything persists to a single SQLite file (default ./mnemochain.db, override with MNEMOCHAIN_DB_PATH) — that IS the durable store, no separate save/load step.

mcp_server.py is directly tested via MCPServer.call_tool()/ .list_tools() in tests/test_mcp_server.py (needs pip install "mcp[cli]", skips itself gracefully otherwise) — not just the do_* functions it calls. That closed a real gap: an earlier version of this project had no network access to install mcp at all, so the server file had never actually been run. The first real install surfaced two genuine bugs on the very first try (see below) — worth stating plainly, since "never run" and "no bugs" are different claims and it would have been easy to conflate them.

Run the tests

python -m unittest discover -s tests -v

62 tests with every optional extra installed (numpy, scikit-learn, mcp[cli]) — 55 without mcp, which test_mcp_server.py detects and skips gracefully rather than failing. The base log/ledger/reflect/replay suite needs neither.

Project layout

mnemochain/
  log.py          # append-only hash-chained log
  ledger.py       # chain verification / tamper detection
  reflect.py      # distill entries into lessons
  skills.py       # export/import lessons between agents
  replay.py       # fold/replay, checkpoints, rollback views
  router.py       # local-first cost router
  mirror.py       # JSONL durability mirror, rebuild a lost DB from it
  identity.py     # HMAC / Ed25519 signing
  embeddings.py   # pluggable embedding backends (TF-IDF now, real embeddings later)
  semantic.py     # citation-backed recall, verify_claim, contradiction detection
  paraphrase.py   # optional alternate-phrasing generation to widen recall()
  mcp_tools.py    # MCP tool logic, fully tested, zero mcp-package dependency
  mcp_server.py   # MCP protocol wiring, directly tested via call_tool()
  cli.py          # command-line interface (base commands + semantic commands)
  backends/
    rule_based.py   # zero-dependency fallback (always available)
    local.py        # Ollama backend (free, local)
    ollama_client.py # shared HTTP client for the local Ollama server
    api.py          # optional paid backend (inert by default)
tests/
  test_basic.py
  test_robustness.py
  test_semantic.py
  test_paraphrase.py
  test_mcp_server.py   # skips itself without `pip install "mcp[cli]"`
examples/
  quickstart.py
  semantic_demo.py
docs/
  ROADMAP.md          # full phase-by-phase history, what's done, what's next

Robustness

Hardening passes on the base log design, plus the merge itself, each with its own tests:

Concern

What was added

Honest limit

Concurrency

WAL journal mode + 30s busy_timeout on every connection

Doesn't make concurrent writers coordinate — just stops them crashing on lock contention

Reflection & paraphrase reliability

ollama_client.py talks to Ollama's local HTTP API directly instead of shelling out to ollama run, with a single BackendUnavailable on any network/timeout/error condition

Still falls back to the rule-based summarizer / synonym swap, which is much less rich than a real model

MCP server concurrency

Every tool call opens its own SQLite connection instead of sharing one cached global

A cached connection crashed the moment two calls landed on different worker threads — see "Bugs found and fixed" below

Authorship

identity.HmacSigner (zero-dep) signs/verifies per agent; verify_signatures() checks a row's claimed author

HMAC uses one shared secret per agent — forgeable by anyone who also holds that secret. For strangers, use identity.Ed25519Signer (pip install cryptography)

Scale

log.get_page() for pagination; ledger.verify_incremental() re-walks only entries since the last checkpoint

Incremental verification trusts prior checkpoints — won't catch tampering behind one. Run a full verify_chain() periodically for a real audit

Durability

mirror.append_mirrored() writes each entry to a .jsonl alongside the DB; rebuildable from just that file

The mirror only helps if anchored somewhere an attacker doesn't also control (e.g. committed to git separately)

Hard $0 ceiling

router.SpendLedger enforces a real call/dollar cap, logs every attempt as a normal chain entry

Only enforced where you actually call it — nothing stops code that bypasses the router

Semantic recall (merge)

SemanticView re-derives everything from the log fresh each call — no cache to go stale

O(n²) contradiction scan and a full TF-IDF refit on every call — fine to a few thousand memories, a documented scale ceiling, not a silent one

Bugs found and fixed

Every entry here came with its own regression test, so if the mistake ever tries to sneak back in, it gets caught immediately instead of silently breaking things again. docs/ROADMAP.md has the full phase-by-phase story; the ones below are specifically what real network access and a real Windows + Ollama environment surfaced that a network-less sandbox couldn't:

  • ollama run corrupted output on Windows two independent ways. subprocess.run(text=True) decodes stdout using the process's locale codepage (cp1252 on this machine), not UTF-8 — any non-cp1252 byte in a model's reply raised UnicodeDecodeError in a background thread. Separately, ollama run renders as if for an interactive terminal, so raw ANSI cursor-control codes (e.g. \x1b[7D\x1b[K) leaked into the captured text. Both reproduced against a real local Ollama install before being fixed by talking to Ollama's HTTP API directly (ollama_client.py) instead of shelling out.

  • MemoryView.citation() crashed on narrower Windows codepages. It embedded a Unicode em-dash; encoding that on cp437 (a common cmd.exe default) raises UnicodeEncodeError and crashes the process outright — reproduced directly against the exact string, not hypothetical. Fixed by using a plain ASCII separator in every string that's actually printed or raised at runtime (citations, and two similar exception messages); docstrings/comments were left alone since those are never printed.

  • mcp_server.py had never actually been run. An earlier version targeted the mcp package's older FastMCP API from memory, with no network access available to verify it. The first real pip install "mcp[cli]" pulled mcp 2.x, which renamed FastMCP to MCPServer — immediate ModuleNotFoundError. Fixed by migrating to the real, installed API and testing it directly via MCPServer.call_tool().

  • The MCP server's cached connection crashed under real dispatch. MCPServer runs synchronous tool functions on a thread pool; a sqlite3 connection can only be used from the thread that created it. The first version cached one connection in a module global, which broke with SQLite objects created in a thread can only be used in that same thread the moment two tool calls landed on different worker threads — reproduced directly in-process, then fixed by opening a fresh connection per call instead (see the Robustness table above).

Contradiction detection: what it catches, what it doesn't

Flags two active memories as conflicting when they share a strong anchor and either (a) one negates the other ("is open" vs "is not open" — catches yes/no, done/not-done, enabled/disabled) or (b) they differ on a real value — a number or proper noun (dates, places, names, amounts). Known miss: plain lowercase antonyms with no negation and no number ("the light is red" vs "the light is green") — closing that needs real NLP, tracked in docs/ROADMAP.md, not silently pretended to be solved.

Paraphrase generation: what it catches, what it doesn't

SynonymSwapParaphraser (the zero-dependency fallback) only recognizes literal phrases in its own small built-in table — it returns nothing rather than fabricating a variant for wording it doesn't know, same honesty standard as the rest of this project's fallback backends. OllamaParaphraser is far more general (it's a real local model) but still, like the underlying TF-IDF matcher, works on exact tokens: it doesn't stem, so a paraphrase using "lives" won't match a query using "living" or "live" unless the wording is genuinely identical. Swapping in SentenceTransformerBackend narrows that gap further; paraphrasing and better embeddings are complementary, not substitutes for each other.

Roadmap ideas

See docs/ROADMAP.md for the full phase-by-phase history (including every bug found and fixed along the way — the merge-time stale-vocabulary bug and numpy-import leak, plus the Windows/Ollama/MCP bugs above) and what's next: LangChain/CrewAI adapters, a proper hallucination benchmark (LOCOMO/LongMemEval), peer-to-peer lesson exchange, a static-HTML chain browser, and real NLP for the contradiction-detection antonym gap.

License

AGPLv3, dual-licensed with a commercial option — see LICENSE and COMMERCIAL.md.

Free to use, self-host, and modify under AGPLv3, including commercially — with AGPLv3's core condition: if you run a modified version as a network service, you must make that modified source available to your users. A separate commercial license (terms negotiated directly, not fixed here) is available for organizations that specifically don't want that condition.

Why this over plain MIT or a Business Source License: MIT alone gives a well-funded competitor no reason not to just take this and sell a hosted version with nothing owed back. A Business Source License is NOT OSI-approved open source, which weakens the "real open-source project, real community credibility" story that's part of the plan here. AGPLv3 is OSI-approved open source (genuine stars, genuine community trust) while its network-use clause is exactly what creates a reason for a company that wants to avoid that clause to pay for a commercial license instead of just forking it.

Said plainly: this is a business judgment call, not a legal one — get real legal review before actually relying on the commercial side to make sales. No commercial contract has been drafted; COMMERCIAL.md explains the model, it isn't a signable agreement.

Author

Maintained by Nulfied. Commercial licensing inquiries: see COMMERCIAL.md, or open an issue on this repo.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Local-first AI memory layer with hybrid retrieval and brain-inspired namespaces. Enables agents to save, search, and manage memories directly via MCP tools.
    3 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides a local-first, source-cited memory layer for AI agents, with MCP tools to search, read, explain sources, and propose/apply memory updates.
    26 npm
    7
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to store, search, assemble, and manage local-first memories through seven MCP tools, including conversation turns, feedback, status, and dashboard access without cloud dependencies.
    AGPL 3.0
  • F
    license
    A
    quality
    A
    maintenance
    Shared fleet memory for AI agents — what one agent learns, the whole fleet knows. Nightly self-correction: stale facts rewritten in place, duplicates merged. Recall pushed into every prompt in supported coding agents. Self-hosted.
    8
    11
    2
    -