Add fact provenance and truth-maintenance layer (ADR 0001)

What changed

Adds a Datalog-with-DRed engine that maintains structured facts alongside the existing FTS5+LanceDB episodic memory store. The full design rationale, the six resolved tensions, and the rejected alternatives are in docs/adr/0001-fact-provenance-and-truth-maintenance.md.

Scope. I only implemented this for the Rust version, the schema however has been updated for Python and Rust to ensure interoperability of the database between implementations. So the short version is that the Python version has not had the fact system added, but shouldn't break. The python version just won't use the new tables / datalog system.

Schema. Migration 038 adds six tables to the shared SQLite DB: base_facts, predicate_policies, rules, derived_facts, justifications, and epochs. All are append-only — retraction closes validity intervals rather than deleting rows, so the full audit trail is always queryable via the as_of parameter. Both runtimes share the same DB; the engine itself is Rust-only (the ADR explains why Python parity wasn't the right call).

Pure evaluation core (memory-core::facts). Rule parsing with range-restriction and stratification safety checks. A DFS stratification checker that rejects recursion through negation at batch-install time with no partial state written. A product/max confidence semiring, property-tested for bounds and monotonicity. A round-based fixpoint evaluator shared by the engine and the differential-testing oracle. A proof-tree builder that returns empty — never fabricated — when a fact has no live justification.

SQLite orchestration (storage::sqlite::facts). assert_fact applies the typed exclusive/non-exclusive contradiction policy and handles negation revalidation when a negated-predicate fact is asserted. retract_fact uses backward-forward recomputation: close stale derived facts via oracle, then forward-rematerialize anything the retraction newly enables. install_rules and uninstall_rules are both stratification-checked and atomic — a rejected batch leaves no partial state behind.

MCP tools. Seven new tools: memory_observe, memory_query, memory_why, memory_what_if, memory_retract, memory_install_rules, memory_uninstall_rules. archive_memory and delete_memory now auto-retract linked base facts. GET /events/facts is a minimal SSE stream carrying four event types. get_proactive_context gains facts[] and changed_since_last_turn[] with confidence-floor and untrusted-source gating, rendered by the plugin in a new three-section layout (facts first, change notices second, episodic memories last).

Why

Honestly, because I read this blog post and thought of GHMEM / opencode-memory straight away 😂

In real terms - the motivating failure mode: a model keeps re-proposing an approach already ruled out, disproven, or satisfied, because the memory that ruled it out and the memory proposing it both retrieve with similar relevance scores, and nothing tells the model which one is still true. There's nothing keeping tabs on the provenance of facts and the proofs, and evaluating the conditions for those facts to remain true or become false in a deterministic, rules-based way that bears re-evaluation as part of regular workflows.

The existing consolidation approach (LLM-driven similarity/staleness detection) can't solve this mechanically — it has no concept of structured triples, derivation, or retraction. A maintained fact store can: retract the supporting observation, the conclusion automatically closes, and memory_why on the closed conclusion returns found: false rather than a proof tree.

The ADR walks through the tradeoffs in detail. The short version: plain scoped DRed with a fixpoint fallback is provably correct at GHMEM's fact volumes and simpler to reason about than counting DRed or Backward/Forward.

Testing notes

The test suite has four layers:

  • 43 pure-core unit tests covering confidence semiring bounds, stratification rejection, proof-tree correctness, injection gating
  • 17 integration tests covering every API path including the motivating assert -> derive -> retract -> verify-closed scenario
  • 450-program differential harness (tests_facts_differential.rs): random assert/retract/rule-install sequences cross-checked against a from-scratch oracle after every operation — any divergence is a release blocker
  • 8 plugin tests covering the new three-section context rendering

The differential harness found three real bugs during development that the integration tests missed: a mutex deadlock in all event-push paths, DRed under-deletion in multi-hop chains, and negation staleness when a negated-predicate fact was asserted. All three are fixed and covered.

cargo test -p memory-core --lib facts    # 92 tests, ~40s
cargo test -p memory-server --lib        # 1063 tests

Deployment considerations

Migration 038 is additive and runs automatically on startup (non-destructive, idempotent). No data migration is needed. Python-only deployments get the schema but not the new MCP tools — the engine is Rust-only, so the tools only appear when memory-server is running. This is documented in the ADR and in ARCHITECTURE.md.

Edited by James Hebden

Merge request reports

Loading
Loading