JingxuanC/causal-memory

causal-memory

Causal memory layer for AI agents — MCP server that records decision→outcome relationships. Survives compaction.

待分类agent-memorycausal-inferencedeepseek-harness

causal-memory

An agent memory system with a causal core — and the only one that models inhibition.
Facts, temporal state, and decision → outcome causal edges on one SQLite store,
powered by a hippocampus-style engine: typed spreading activation (excitatory
*and* inhibitory), Hebbian co-occurrence reinforcement, Q-value dynamics, and
immutable SWR consolidation. Agents recall *what* happened, *when* it was true,
*why* it worked — and *what would happen if* they acted differently.

English · 简体中文

Why

Every agent forgets *why* it made past decisions after a few context compactions. It re-fixes the same bug the same wrong way, re-debates the same architecture choice, relearns the same lesson.

This happens because causal information is the most fragile type under text compaction. Real-LLM benchmark (grok-build's production compaction prompt):

Compactions (k) · Textual recall · Causal-table recall 1 · 100% · 100% 2 · 85% · 100% 3 · 55% · 100% 5 · 45% · 100%

The causal table survives because it lives outside the agent's context window — compaction cannot touch it.

Demo

30-second single scene — the agent is about to git push --no-verify; intervention_query fires a DANGER chain citing the lesson it recorded last time ("production login failed for 40 minutes; emergency rollback"):

Download video · DANGER-scene screenshot · Regenerate: scripts/capture_demo30.py → scripts/render_demo30.py

A 21-second hands-on demo (real memory store, no mocks): pre-action warning (intervention_query → DANGER chain) → experience recall (search_causal) → counterfactual comparison (counterfactual_query) → write loop (record_decision → immediately searchable).

Download video · Warning-scene screenshot · Brand card · Regenerate: scripts/render_demo.py

Benchmarks

CausalEval — the causal memory benchmark (primary)

Most agent-memory benchmarks (LoCoMo, LongMemEval, Memora) test fact recall ("what is the user's preference"). causal-memory's differentiators — typed causal edges, inhibition, intervention prediction, cross-task transfer — are invisible on those suites. CausalEval measures them.

Design: the causal graph is the answer key. Typed DAGs are generated deterministically; conversations are narrated from the graph; gold answers are derived from graph structure — zero hand annotation, zero ambiguity.

CausalEval v13 (soft supersession) — 140 questions, 20 graphs (same LLM, same judge; v12 baseline was 70q/10 graphs; mem0 comparison ran on the 70q protocol):

Capability · causal-memory · v12 (70q) · mem0 (70q) · What it tests C7 Update · 100% · 50% · 80% · Supersede old belief after falsification (soft superseded_by annotation) C3 Counterfactual · 95% · 90% · 80% · Choosing between alternatives with known outcomes C2 Intervention · 75% · 70% · 40% · Forward prediction: "if X again, what happens?" C4 Inhibition · 80% · 90% · 50% · Distinguishing root-cause fix vs blast-radius limiter (prevented edges) C1 Attribution · 85% · 90% · 90% · Backward causal chain → root cause C5 Temporal-causal · 90% · 100% · 90% · Ordering on a causal chain C6 Lesson transfer · 20% · 20% · 30% · Cross-task analogy via meta edges (open limitation) Overall · 78% · 81% · 65% ·

Key result: C7 update 50% → 100% (+50pp, 20/20 questions) and it holds at doubled sample size. Soft supersession annotates superseded edges (superseded_by) instead of hiding them — the falsification signal reaches the answer model while the old lesson stays retrievable for counterfactuals (C3 unharmed at 95%). The C6 gap (20% vs mem0 30%) is the remaining open limitation; C1/C4/C5 dips vs v12 are within re-distillation variance and new-graph difficulty (v12 and v13 do not share a distilled corpus).

Fact-recall benchmarks (not our strong suit)

On traditional fact-recall suites, causal-memory performs competitively but does not beat mem0 — this is expected, because fact recall is mem0's specialty and not where causal-memory adds value.

Benchmark · causal-memory · mem0 · Note LoCoMo (strict judge) · 79.1% · 91.6% · mem0's home turf LongMemEval-S (full pipeline, deepseek-chat) · 76.4% @ 11.5K tok/q · 94.4% @ 6.8K tok/q (official) · 73.8% (ind. repro) · single-model stack vs platform stack; see docs/benchmarks/longmemeval.md Memora MPA · 67.4% · 71.8% · −4.4pp Compaction survival · 100% · 45% · External table = immune to compaction Agent repeat-mistake · 33% · 67% · −34pp on trap-world

Capability tests (322 across the workspace)

These test capabilities that no fact store (mem0, Zep, Letta) can offer.

Capability · What it proves · Tests Prevented-edge warning · prevented edge spreads −0.3 activation (GABA analogue) · 2 Trace-cause attribution · Backward CSR traversal finds root cause · 2 Multi-hop causal chain · Forward K-hop spreading reaches 2-3 hop outcomes · 2 Inhibitory filtering · Prevented outcomes appear as negative, not false positives · 1 Intervention comparison · Same outcome has +0.9 for "skip tests" and −0.3 for "add tests" · 4 SWR consolidation · LTP strengthens replayed edges, LTD weakens unvisited, GC forgets dormant · 5 Q-value dynamics · Good decisions rank higher; Bellman propagates to parents · 3 Novelty entropy · Diverse experience triggers consolidation; uniform does not · 3 Meta-edge mining · Cross-session pattern discovery (similar_to / repeated) · 3 Hebbian co-occurrence · Repeated co-activation strengthens connection · 3

What makes it different

Capability · causal-memory · mem0 · Zep · Letta · HeLa-Mem Typed causal semantics (caused/enabled/prevented) · ✅ · ❌ · ❌ · ❌ · ❌ prevented negative spread (inhibitory) · ✅ · ❌ · ❌ · ❌ · ❌ Hebbian co-occurrence edges (excitatory) · ✅ · ❌ · ❌ · ❌ · ✅ Immutable consolidation (delta + clone) · ✅ · ❌ · ❌ · ❌ · ❌ Q-value dynamic utility · ✅ · ❌ · ❌ · ❌ · ❌ Forward simulation (intervention_query) · ✅ · ❌ · ❌ · ❌ · ❌ SWR offline consolidation (LTP/LTD/GC) · ✅ · ❌ · ❌ · ❌ · ❌ Novelty-entropy consolidation trigger · ✅ · ❌ · ❌ · ❌ · ❌ Meta-edge cross-session pattern mining · ✅ · ❌ · ❌ · ❌ · ❌ Compaction survival evidence · ✅ +20.8pp · ❌ · ❌ · ❌ · ❌ One graph unifying all memory types · ✅ · ❌ · ⚠️ · ❌ · ⚠️ Write-time gatekeeping (raw → session_logs) · ✅ · ✅ · ❌ · ❌ · ❌ Local ONNX embedding (offline) · ✅ · ✅ · ❌ · ❌ · ❌

Core innovation: the excitatory/inhibitory duality. HeLa-Mem (ACL 2026) builds the excitatory side (Hebbian co-activation, positive spread). causal-memory adds the inhibitory side (prevented edges spread negative activation — a GABA analogue). A complete memory needs both: "what caused this" *and* "what prevents this from happening again."

Architecture

*Interactive version: docs/architecture.html*

┌───────────────────────────────────────────────┐
  │           causal-memory (Rust, MCP)            │
  │                                                │
  │  14 tools ← Agent (stdio / HTTP)                 │
  │    ↓                                           │
  │  Write-time gatekeeping                        │
  │    raw turns → session_logs (audit only)       │
  │    distill → facts + causal edges (searchable) │
  │    ↓                                           │
  │  Unified retrieval (RRF fusion)                │
  │    BM25 + semantic cosine → RRF merge          │
  │    Fact layer (BM25 + embeddings)              │
  │    ↓                                           │
  │  ┌──── Hippocampus engine ──────────────────┐  │
  │  │ CSR graph + spreading activation          │  │
  │  │  caused (+1.0)   enabled (+0.5)           │  │
  │  │  prevented (−0.3) ← GABA inhibitory       │  │
  │  │  fact (+0.8)     meta (+0.6)              │  │
  │  │  co_occurrence (Hebbian, dynamic)         │  │
  │  │                                            │  │
  │  │ DG: SimHash pattern separation             │  │
  │  │ CA3: K-hop spreading (forward + reverse)   │  │
  │  │ CA1: Novelty entropy trigger               │  │
  │  │ SWR: LTP/LTD/GC (immutable delta + clone)  │  │
  │  │ Q-value: Bellman dynamics (MemRL-style)    │  │
  │  └────────────────────────────────────────────┘  │
  │    ↓                                           │
  │  SQLite (causal.db) — never compacted          │
  └───────────────────────────────────────────────┘

The causal_edges table is never compacted — it lives outside the agent's context window. That's the entire point.

Edge types

Edge type · Spread coeff · Biological analogue · Meaning caused · +1.0 · Glutamate (strong excitatory) · "Doing X caused Y" fact · +0.8 · Semantic association · "User is/has Z" meta · +0.6 · Cortical top-down · Cross-task pattern link enabled · +0.5 · Weak excitatory · "Doing X enabled Y" co_occurrence · dynamic · Hebbian LTP · "X and Y frequently co-occur" `prevented` · −0.3 · GABA (inhibitory) · "Doing X prevented Y" no_effect · 0.0 · — · No causal relationship

Quick start

git clone https://github.com/JingxuanC/causal-memory.git
cd causal-memory
cargo build --release

MCP integration (Claude Code, Cursor, grok-build, etc.)

First-class installers: Claude Code (integrations/claude-code/) and Codex CLI (integrations/codex/) — MCP registration + activation prompt/skill in one idempotent script each.

{
  "mcpServers": {
    "causal-memory": {
      "command": "/path/to/causal-memory/target/release/causal-memory",
      "env": {
        "CAUSAL_MEMORY_DB": "~/.local/share/causal-memory/causal.db"
      }
    }
  }
}

No Rust toolchain? pip install causal-memory puts the same causal-memory console script on your PATH (the full CLI — bare invocation is the stdio MCP server), so "command": "causal-memory" works as the MCP entry too.

To teach the agent *when* to use the memory tools (agents don't call them proactively without instruction), install the bundled agent skill skills/causal-memory/SKILL.md:

Guided installer (recommended) — one command installs the skill, the causal-memory server, and the MCP registration across every detected client (Claude Code / Cursor / Opencode / Claude Desktop / Kimi Code):

./install.sh                     # interactive, walks you through each step
./install.sh --yes               # non-interactive, accept all defaults
./install.sh --uninstall         # remove the skill (+ optional MCP)

Or via the Skills CLI, or by hand:

# Skills CLI (skills.sh ecosystem) — works with Claude Code, Cursor,
# Kimi Code CLI, Codex, Copilot and more:
npx skills add JingxuanC/causal-memory@causal-memory

…or copy skills/causal-memory/ into your agent's skills directory (e.g. ~/.agents/skills/causal-memory/), or paste CLAUDE.md into your system prompt / AGENTS.md.

HTTP transport (remote agents, multi-agent shared memory)

./target/release/causal-memory http --port 9938   # MCP Streamable HTTP

Observability endpoints on the same port:

GET /metrics                    Prometheus text (RED + recall metrics)
GET /healthz / /readyz          liveness / readiness (readiness probes the store)
GET /debug/recall?query=...     run a recall now, return the full JSON trace
                                (seeds, hop summary, per-result provenance)
GET /debug/recalls              newest-first recall audit rows (persisted,
                                schema v13 recall_audit table; survives restarts)

/metrics and /debug/* expose the recall corpus, so they accept opt-in bearer auth: set CAUSAL_MEMORY_HTTP_AUTH_TOKEN (env or causal-memory setconfig) and they require Authorization: Bearer (unset = open, the previous behavior). /healthz / /readyz stay open on purpose — kubelet probes cannot send bearer headers and leak nothing. /mcp client auth is a separate mechanism: per-tenant bearer tokens (see *Multi-tenant HTTP* below). Without the token, do not expose the port to the public internet.

Structured JSON logs on stderr: CAUSAL_MEMORY_LOG_FORMAT=json.

Multi-tenant HTTP (per-tenant databases)

Point CAUSAL_MEMORY_TOKENS_FILE (env or causal-memory setconfig) at a JSON file mapping bearer tokens to tenant names:

{ "token-for-alice": "alice", "token-for-bob": "bob" }

With the file configured and non-empty, /mcp requires Authorization: Bearer , and each tenant reads and writes its own SQLite store at /tenants/..db — one database per tenant, opened lazily on first request, so one tenant can never see another's facts or causal edges (covered by an end-to-end isolation test). Missing or unknown tokens get 401 and never create a database file. The file is reloaded when its mtime changes, so tenants can be added or revoked without a restart; if the file turns unreadable at runtime the last-good map stays in force (fail closed).

Unset (or an empty/unreadable file) keeps the previous behavior: no /mcp auth, one shared store — local and stdio usage are unaffected. The startup log states the mode: auth=multi-tenant (N tokens) vs auth=open.

Memory residency: every active tenant holds its own CausalStore and in-memory graph, so RSS grows with the tenant count — see before packing many tenants into one process. The observability endpoints (/metrics, /debug/*) keep reporting on the default store and are still gated by CAUSAL_MEMORY_HTTP_AUTH_TOKEN, independently of tenant auth.

With local embeddings (no API key needed)

cargo build --release --features local-embed
# Uses BAAI/bge-small-en-v1.5 (384 dims, ~130MB, downloads once then offline)

With HTTP embeddings (OpenAI/ZhiPu/etc.)

export CAUSAL_MEMORY_EMBED_API=https://open.bigmodel.cn/api/paas/v4
export CAUSAL_MEMORY_EMBED_KEY=your-key
export CAUSAL_MEMORY_EMBED_MODEL=embedding-3

Python bindings (PyO3)

All 14 memory operations are also available as a Python package, built on the same causal_memory::memory::Memory facade the MCP server uses:

cd crates/causal-memory-py
pip install maturin
maturin develop          # builds and installs into the active venv
from causal_memory import CausalMemory

mem = CausalMemory("~/.local/share/causal-memory/causal.db")  # or CausalMemory.in_memory()
mem.record_decision("used Redis mutex for cache stampede protection",
                    "deadlock under load", "caused", "concurrency",
                    context="go 1.22, single redis, 5k rps")
print(mem.search_causal(query="cache stampede protection"))
print(mem.intervention_query("skip the test suite before shipping"))

Methods mirror the 17 MCP tools one-to-one and return the same text. Embedding and LLM features use the same CAUSAL_MEMORY_EMBED_* / CAUSAL_MEMORY_LLM_* environment variables; without them the bindings degrade gracefully to BM25-only retrieval. Smoke tests: maturin develop && pytest tests/.

macOS note: always build the bindings through maturin. Plain
cargo build -p causal-memory-py --release fails to link — the Xcode CLT
Python ships no libpython3.9 dylib (this is why the py crate sits outside
the workspace default-members).

Integrations

Hermes memory provider

hermes-plugin/ turns causal-memory into a drop-in Hermes MemoryProvider — flat facts + decision→outcome causal lessons on one local SQLite store, per-profile by construction (/causal-memory/causal.db). Wired hooks: system-prompt causal directory, budgeted prefetch recall, non-blocking sync_turn write-behind, and a hermes causal-memory stats CLI. Because the store lives outside the context window, Hermes compaction can't touch it. Install and configuration: hermes-plugin/README.md.