memory-reconciler
v0.1.0
Published
The write side of an LLM memory system: reconcile new claims against stored memory (confirm / supersede / contradict / independent) with cardinality, valid-time, calibrated confidence, and an audit trail.
Maintainers
Readme
memory-reconciler
The write side of AI memory — decide what to keep, and what wins when facts disagree.
AI memory systems mostly append facts. But facts change, conflict, and repeat — so memory fills with stale entries and contradictions, and the model confidently repeats them. memory-reconciler decides, at write time, how each new fact relates to what's already known — keeping stored memory consistent, explainable, and safe for multiple agents to write to.
flowchart LR
N([New fact]) --> R{Reconcile}
R -->|same value| C[Confirm]
R -->|value changed| S[Supersede]
R -->|both can hold| I[Coexist]
R -->|real conflict| D[Disputed]
C --> M[(Consistent<br/>memory)]
S --> M
I --> M
D --> M| Incoming fact | Naive append | memory-reconciler | |---|---|---| | "moved to SF" (was NYC) | both kept → contradiction | supersede — SF active, NYC kept as history | | "allergic to shellfish" (has peanuts) | flagged as a conflict | coexist — both true | | two sources disagree on a birthday | one silently wins | disputed — surfaced, not guessed |
Why a reconciler?
Most "AI memory" tools reconcile by per-write LLM judgment — an LLM picks add/update/delete per fact, or an agent self-edits and only fixes a stale fact if it happens to notice. There's no explicit predicate cardinality (so likes(tea)+likes(coffee) and lives_in(NYC)+lives_in(SF) are handled the same), no valid-time ("the world changed" vs "this was always wrong"), no calibrated confidence, no audit trail of why a belief flipped, and no disputed state. The bet: make cardinality and time mechanical, and use an LLM only where you must.
Features
- Five relations, decided mechanically — confirm · supersede · contradict · refine · independent.
- Cardinality-aware — single- vs multi-valued predicates → conflict vs coexistence.
- Time-aware — supersede (value changed) vs contradict (genuinely incompatible).
- Calibrated confidence — log-odds belief; corroboration raises, contradiction lowers, time decays.
disputedstate + re-resolution — equal-evidence conflicts are surfaced, not guessed; later evidence breaks the tie.- Multi-agent safe — optimistic-concurrency commits reject stale writes.
- Portable storage — persists to an OKF markdown bundle: the files are the source of truth.
- Production Postgres adapter —
memory-reconciler/postgressubpath: scoped multi-tenant tables, shipped DDL, no extra dependencies. - ESM + CJS — works in
importandrequirecodebases (NestJS, Express, Jest) out of the box.
Install
npm install memory-reconciler # use as a library
npx -y memory-reconciler # or run the MCP server, no installRequires Node.js 20+. Ships both ESM and CommonJS builds — import and require both work, including inside NestJS/Express apps and Jest test suites, no transform config needed.
Quick start
import { reconcile, OkfClaimStore } from 'memory-reconciler';
const store = new OkfClaimStore('./memory-bundle');
await store.load();
const prov = (sourceId: string) => ({
sourceId, sourceTrust: 0.8, extractorConf: 0.9, observedAt: new Date().toISOString(),
});
// Alice lives in New York.
await reconcile(
{ subject: 'user:alice', predicate: 'located_in', object: 'New York', provenance: prov('chat-1') },
{ store },
);
// Later, she moves.
const outcome = await reconcile(
{ subject: 'user:alice', predicate: 'located_in', object: 'San Francisco', validFrom: '2026-06-01', provenance: prov('chat-9') },
{ store },
);
console.log(outcome.action); // 'superseded' — SF is active; New York is kept as historyThe bundle now holds claims/*.md, entities/*.md, and a human-readable log.md. Because it's plain OKF markdown, any LLM can also read it directly — no server required.
Postgres (production servers)
For server apps, use the Postgres adapter — a subpath export with zero extra dependencies (anything with a query(text, values) method works: pg.Pool, pg.Client, a transaction client, PGlite):
import { Pool } from 'pg';
import { PostgresClaimStore, POSTGRES_SCHEMA } from 'memory-reconciler/postgres';
import { reconcile } from 'memory-reconciler';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
await pool.query(POSTGRES_SCHEMA); // idempotent DDL — or paste it into your migration tool
const store = new PostgresClaimStore(pool, { scope: 'user-123' });
await reconcile(
{ subject: 'user:123', predicate: 'located_in', object: 'Toronto', provenance: prov('chat-1') },
{ store },
);POSTGRES_SCHEMA creates three tables (memory_claims, memory_claim_log, memory_entities); table names are overridable per store. Optimistic concurrency (ConcurrencyError on stale writes) matches the OKF store. A PostgresEntityStore is included for the entity-resolution module.
Multi-tenant apps (one store per user)
All shipped adapters are scoped: a store instance is bound to exactly one tenant, so cross-user memory leakage is impossible by construction. In a multi-user app, create a store per user and pass it to reconcile:
// e.g. a NestJS/Express service
const storeFor = (userId: string) =>
new PostgresClaimStore(pool, { scope: userId });
await reconcile(candidate, { store: storeFor(req.userId) });Rows carry a scope column and every query filters on it — one set of tables serves all users. (Single-tenant deployments simply omit scope; it defaults to ''.) The same pattern applies to the in-memory and OKF stores: one instance / one bundle directory per tenant.
Use as an MCP server (Claude)
Add it to any MCP client — no clone, no build.
Claude Code
claude mcp add memory-reconciler -- npx -y memory-reconcilerClaude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"memory-reconciler": {
"command": "npx",
"args": ["-y", "memory-reconciler"],
"env": { "MEMORY_BUNDLE_DIR": "~/.memory-reconciler/bundle" }
}
}
}| Tool | Description |
|------|-------------|
| remember | Reconcile a new fact into memory (returns the action taken). |
| recall | List active claims for a subject. |
| why | Explain a claim — its lineage + audit log. |
Memory persists to MEMORY_BUNDLE_DIR (default ~/.memory-reconciler/bundle) as plain OKF markdown you can read or commit to git. Pass subjectType to collapse names ("Acme" ↔ "Acme Corp") onto one entity.
Prefer running from source (for development)?
git clone https://github.com/jsingh0026/memory-reconciler && cd memory-reconciler && npm install claude mcp add memory-reconciler -- npx tsx "$(pwd)/src/mcp/server.ts"
How it works
Each candidate claim flows through: normalize (canonicalize predicate → cardinality + time-varying) → resolve entity → gather the (subject, predicate) slot → classify → resolve conflicts (winner, or disputed) → update confidence (log-odds + decay).
What makes it precise:
- Cardinality is the keystone —
allergic_tois multi-valued (peanuts and shellfish coexist);located_inis single-valued (NYC vs SF is a real conflict). - Time separates supersede from contradict — a newer value for a single-valued, time-varying predicate supersedes; same-time incompatible values contradict.
- Append-only + audit — claims are immutable; state changes via status transitions and new claims, so there's always a record of why a belief changed.
Development
For contributors / running from source:
npm install
npm test # test suite (vitest)
npm run typecheck # tsc --noEmit
npm run eval # reconciliation benchmark; exits non-zero on regression
npm run build # bundle to dist/ (tsup)
npm run mcp # run the MCP server from sourceLicense
MIT © jsingh0026
