JSON Schemas and Python contracts for AI agent memory integrity: evidence, candidate, ledger, taste, state, and ContextPack planes.
Project description
agent-memory-contracts
JSON Schemas and Python contracts for AI agent memory integrity. With a stdlib-only reference runtime and a 5-invariant acceptance suite for any product-side port.
The core design question this library answers: if an LLM extracts something from raw sources, how do you keep that extraction from silently becoming "memory" the agent treats as truth?
The contracts enforce five rules in the type system, because the alternative is a runtime bug you'll only catch in production:
- Untrusted extraction cannot become memory without a reducer.
Candidates have id prefix
cand_*; ledger entries havefact_/pref_/dec_. ACandidate*cannot carry the fields a ledger entry needs, and aLedgerEntrywill refuse to validate if it carries candidate-only fields. - Every trusted record is authorized by an explicit reducer
decision. The validator rejects a ledger entry whose
reducer_decision_iddoesn't authorize it. - Supersession is a directed graph, not a flag. If entry B
supersedes entry A, then A's
superseded_bymust contain B and B'ssupersedesmust contain A. The bundle validator checks reciprocity and temporal ordering. - IDs are content-derived, not assigned. A record with the same evidence and the same normalized payload has the same id forever. Reproducible, deduplicatable, falsifiable.
- Generated views are views, not memory. A
ContextPackis a task-ready bundle that carries aBuildReceipt(what was selected) and aValidationReport(what passed). The receipt, not the pack, is the audit trail.
The same contracts ship a stdlib-only reference runtime (sqlite3,
6 planes, single transactional write path, hash-chained audit
anchors, cite-or-refuse answer) and a 5-invariant acceptance suite
(tests/invariants/) that doubles as the
port-spec for any product-side implementation. See
docs/ROADMAP-to-product.md for the
ADRs that map to the runtime.
Install
pip install agent-memory-contracts
The library has zero runtime dependencies (stdlib only). The optional extras are:
pip install agent-memory-contracts[jsonschema] # for the JSON Schema validator + CLI validate
pip install agent-memory-contracts[langchain] # for the BaseMemory integration
pip install agent-memory-contracts[mcp] # for the FastMCP server
pip install agent-memory-contracts[all] # everything
After install, the CLI is available as both a module and a console script:
python -m agent_memory_contracts --version
agent-memory-contracts --version # same, via the [project.scripts] entry point
This library was extracted from a 30+ sprint falsification-first build of a private agent memory kernel. The schemas, id formats, and public API are frozen at v1.0.0; subsequent 1.x releases are backwards-compatible.
The six memory planes
raw sources
|
v
+---------+ +-------------+ +-----------+
| EVIDENCE| ---> | CANDIDATE | ---> | LEDGER | (trusted memory)
+---------+ +-------------+ +-----------+
^ untrusted extraction
|
+-------------+ +-----------+
| TASTE | | REDUCER | (only authority that can promote)
| TasteCard |<----| DECISIONS |
+-------------+ +-----------+
|
v
+-----------+ +-----------+
| STATE |------->|CONTEXTPACK| (task-ready bundles)
+-----------+ +-----------+
See docs/architecture.md for the full design
document, including the reducer authorization pattern, temporal
validity rules, and the supersession-reciprocity invariant.
Quickstart
pip install agent-memory-contracts
from agent_memory_contracts import (
SourceRecord, EvidenceSpan,
CandidateTasteSignal, PreferenceLedgerEntry, MemoryReducerDecision,
validate_ledger_bundle,
make_source_id, make_span_id, make_candidate_id,
make_ledger_entry_id, make_reducer_decision_id,
)
# 1. Build a SourceRecord and an EvidenceSpan.
source_id = make_source_id("chatgpt_conversation", "https://...", "a" * 64)
span_id = make_span_id(source_id, "line_range", "10-15")
# 2. An LLM extracts a candidate interpretation (untrusted).
ctsig_id = make_candidate_id("taste_signal", [span_id], {"signal_kind": "principle", ...})
# 3. The reducer promotes it to a trusted ledger entry.
ledger_id = make_ledger_entry_id("preference", [span_id], {"ledger_type": "preference", ...})
reducer_id = make_reducer_decision_id("promote", [ctsig_id], [ledger_id], [span_id], "...")
# 4. Build all three; the bundle validator checks the whole graph.
validate_ledger_bundle(
source_records=[...], evidence_spans=[...], candidate_records=[...],
reducer_decisions=[...], ledger_entries=[...],
)
Three runnable end-to-end examples:
examples/quickstart.py-- minimal source -> span -> candidate -> ledgerexamples/extract_taste_cards.py-- full transcript -> multiple taste cards, with contrast pairsexamples/reference_reducer.py-- complete reference reducer (~1000 lines) with three worked scenarios: happy path, rejection of low-confidence / no-evidence / stale candidates, and a deliberate validator-enforcement case. This is the canonical answer to "what does a contracts-library reducer look like in production?"examples/conflict_resolution.py-- five worked scenarios: pick-one resolution, merge resolution, split resolution, weekly hygiene report, windowed + diff-augmented hygiene report.examples/decay.py-- freshness scoring on a small bundle of facts; first concrete schema migration.examples/company_brain_demo.py-- the full 7-stage pipeline (ingest → extract → reduce → cite → access → embed → compile) end to end.examples/langchain_memory.py-- LangChainBaseMemoryintegration; replaceConversationBufferMemory()withContractsMemory().examples/mcp_server.py-- exposevalidate_bundle,compile_context,check_accessas MCP tools over stdio.examples/poisoning_demo/run.py-- one memory-poisoning attack against two stores: a naive extract-append-retrieve store (silently poisoned) and a contracts-governed store (forgery rejected with a receipt). See Poisoning demo.examples/arthashila_demo/build.py-- the real NBFC dataset through the contracts end to end, with the--runtimeflag running it through the reference runtime. Skips cleanly if the dataset isn't available.
What's in the box
- 23 JSON Schemas (Draft 2020-12) in
src/agent_memory_contracts/schemas/ - 37 Python modules of
frozen=Truedataclasses and validators - 5 bundle validators that reject the bundle on dangling references, non-reciprocal supersession, candidate/ledger field leakage, and reducer authorization mismatch
- 10 temporal query helpers for the taste and state planes
- Content-derived ID helpers for every record type, using SHA-256 of canonical JSON. Same payload = same ID, forever.
bundle_fingerprint(records)for content-addressed bundle digests. Set-semantic, order-insensitive, last-write-wins on duplicate ids. Same primitive the id helpers use, applied to the bundle as a whole. Useful as a cache key, idempotency token, or change-detection digest.bundle_diff(a, b)for set-semantic diff between two bundles. Returns aBundleDiff(added, removed, changed, unchanged_count)with full pre/post records for the changed entries. Short-circuits viabundle_fingerprintwhen both bundles hash equal, so the "no changes" case is one hash comparison.merge_bundles(*bundles, ..., prefer=...)for many-to-one bundle union. Returns aBundleMerge(records, conflicts, duplicate_ids). Records are deduplicated byid_field; conflicts are surfaced for the reducer to triage.prefer='last'(default) /'first'resolve silently,prefer='raise'fails loudly. Useful for multi-source ingest, bidirectional sync, and backfill.- Opt-in
jsonschemavalidator (pip install agent-memory-contracts[jsonschema]) for polyglot producers that want to validate Python dataclasses against the bundled JSON Schemas before the record leaves the producing system. Includesvalidate_jsonlanditer_validated_jsonlfor streaming. - CLI (
python -m agent_memory_contracts) for the non-Python use case: validate a JSON or JSONL file against a schema, compute a bundle fingerprint, diff two bundles, merge N bundles into one, compute a hygiene report, or compute an audit pack. Stdlibargparse. Optional--jsonflag on every subcommand for programmatic consumption. - Zero runtime dependencies (stdlib only)
- ~17,000 lines of Python, ~600 lines of JSON Schema
- 729 tests (plus 23 subtests, 1 environment-skipped) covering
id derivation, contract validation, bundle integrity, temporal
queries, the bundle fingerprint, diff, and merge primitives, the
optional JSON Schema validator, the CLI (including
--jsonmode), the reference reducer, the SQLite-to-contracts migration example, conflict resolution, the memory hygiene report, the audit pack, the decay primitives, the LangChain and MCP integrations, the schema migration framework, the 5 differentiator invariants, the reference runtime (store, gate, anchors, grounding), the poisoning demo, and the Arthashila demo mypy --strictclean on the library code (CI gate)- Stdlib-only benchmark suite at
benchmarks/for the three bundle primitives (100/1k/10k/50k records, ~135ms for 50k fingerprints) - Migration guide at
docs/migration.mdfor adopting the library from a SQLite-style memory store, with a worked end-to-end example (docs/migration_example.py) - Conflict resolution (
resolve_conflict/apply_resolutions/validate_resolutions): pick-one, merge, and split policies for surfaced bundle conflicts. Audit-trail records with content-derived ids; rejected variants stay in the bundle flagged. - Memory hygiene report (
compute_hygiene_report/hygiene_report_to_markdown): structural snapshot of a bundle's health — per-plane / per-type / per-privacy counts, temporal state, evidence integrity. CLI subcommandhygiene <path>produces a Markdown report (or JSON with--json). - Audit pack (
compute_audit_pack/audit_pack_to_markdown): the chain of custody behind a bundle — every trusted ledger entry with its full authorization chain (entry → decision → candidates → evidence → sources), rejections in the period, and the supersession changelog. CLI subcommandaudit <path>produces a Markdown report (or JSON with--json).
Design principles
These are the rules the contracts enforce, in the type system, because the alternative is a runtime bug you'll only catch in production.
- Untrusted extraction cannot become memory without a reducer.
Candidates have ID prefixes
cand_*; ledger entries havefact_/pref_/dec_. ACandidate*object physically cannot carry the fields a ledger entry needs, and aLedgerEntrywill refuse to validate if it carries candidate-only fields. - Every trusted record is authorized by an explicit reducer decision.
A
MemoryReducerDecisionlists the candidate ids, ledger entry ids, and evidence span ids it authorizes. A ledger entry whosereducer_decision_iddoesn't authorize it -- or whose status and decision_type don't match -- is rejected. - Supersession is a directed graph, not a flag. If entry B
supersedes entry A, then A's
superseded_bymust contain B and B'ssupersedesmust contain A. The bundle validator checks reciprocity and temporal ordering. - IDs are content-derived, not assigned. A taste card with the same evidence and the same normalized payload has the same id forever. Reproducible, deduplicatable, falsifiable.
- Generated views are views, not memory. A
ContextPackis a task-ready bundle; it carries aBuildReceipt(what was selected) and aValidationReport(what passed). The receipt, not the context pack, is the audit trail. - Bundles are content-addressed.
bundle_fingerprint(records)returns a deterministic SHA-256 of the whole bundle, set- semantic and order-insensitive. Same records in any order, same fingerprint. A bundle is treated as a set of records keyed byid; duplicate ids are collapsed (last write wins) before hashing.
Poisoning demo
What does the reducer boundary actually buy you? The runnable demo at
examples/poisoning_demo/ attacks two
memory stores with the same forged document (a spoofed-sender payout
memo, the classic BEC shape):
PYTHONPATH=src python examples/poisoning_demo/run.py
- Store A ("silent", ~80 lines of the usual architecture: extract dict -> append list -> keyword retrieve) swallows the forgery and starts serving the attacker's number as truth.
- Store B (the same extraction fixtures routed through the real
contracts) rejects it in the candidate plane -- untrusted source
tier, no authorizing chain -- and emits a rejection receipt (a
real
MemoryReducerDecision). The trusted ledger'sbundle_fingerprintis byte-identical before and after the attack.
Deterministic, no API keys, no network. Outputs a console table and
a generated report.md. For the same governance run against a full
synthetic NBFC corpus (30 emails, 5 policy documents, 2 poisoned
documents, a supersession trap, and an Audit Pack export), see
examples/arthashila_demo/build.py
-- it requires the demo dataset directory (--dataset) and answers
the corpus's walkthrough question from the trusted ledger with every
clause cited.
Audit packs
The audit pack is the report you hand an auditor, a regulator, or a
CFO. Where the hygiene report says what the bundle looks like, the
audit pack says how every trusted entry got there: it walks each
ledger entry's full authorization chain (entry → authorizing reducer
decision → candidates → evidence spans → sources), collects every
rejected candidate decision with its rationale, and renders the
supersession changelog — all tied to the bundle's
bundle_fingerprint. The default title is the promise: "Every fact
your AI relies on, who authorized it, and the evidence behind it."
from agent_memory_contracts import compute_audit_pack, audit_pack_to_markdown
pack = compute_audit_pack(records, as_of="2026-06-30T00:00:00Z")
print(pack.complete_chain_count, pack.incomplete_chain_count, pack.rejected_count)
markdown = audit_pack_to_markdown(pack)
Broken chains are findings, not errors: a missing decision, a
dangling candidate, or an unresolved span flags the entry
INCOMPLETE (with exactly what is missing) instead of raising. The
pack id is content-derived, so the same bundle and as_of reproduce
the same pack — an attested pack can be re-verified later. From the
shell:
python -m agent_memory_contracts audit bundle.jsonl
python -m agent_memory_contracts audit bundle.jsonl --as-of 2026-06-30T00:00:00Z --json
Runtime (reference implementation)
The contracts say what a governed memory record is; the
agent_memory_contracts.runtime package is a complete, stdlib-only
reference implementation of how a runtime holds and moves them —
sqlite3-backed, no new dependencies:
runtime.store.MemoryStore— six-plane persistence with immutable payloads, insert-only edge tables, and SQL triggers that make silent writes impossible at the schema level. Supersession (and retraction) live in edge tables and are materialized at read, so the append-only ledger still satisfies the library's reciprocity validators — including time travel (active_entries(as_of=...): what was true on May 3rd).runtime.gate.MemoryGate— the only write path. Idempotent, server-verified ingestion (same payload = no-op; forged id = structuredid_mismatch), and one transactionalpromote()that carries a reducer decision + its entries + its supersessions as a single validated closure. There is no API that writes a ledger entry outside it. Conflicts (double promotion, double supersession) resolve first-commit-wins with the winning decision id in the error.runtime.anchors— a hash-chained audit anchor on every committed batch.verify_chain()recomputes every anchor and returns the first divergence (edited payload, deleted row, reordered history);verify_coverage()flags any trusted row no anchor accounts for.runtime.grounding— deterministic, receipted ContextPack builds (same memory + same request ⇒ byte-identical fingerprint) and a cite-or-refuseanswer(): every factual sentence carries a[Fi]citation checked mechanically, and below-threshold support returns a structured refusal with the unreviewed candidates that might be relevant. No model calls — the reference answerer is a deterministic template, so the whole contract is testable.
The five differentiator invariants are executable in
tests/invariants/ — no silent writes (fuzzed),
full provenance (chain-walked), reciprocal supersession (raced),
deterministic receipts (tamper-tested), grounded-or-refused
(adversarially checked). To see the runtime run the full NBFC
corpus end to end (poisons quarantined, 12.50% answered with
citations from a receipted pack, audit pack emitted from store
state, anchor chain verified):
PYTHONPATH=src python examples/arthashila_demo/build.py --runtime --dataset /path/to/05-demo-dataset
The runtime is the semantics, not the service: the product layer
(Postgres backend behind runtime.store.StorageBackend, FastAPI,
console, MCP wiring) is mapped ADR-by-ADR in
docs/ROADMAP-to-product.md.
Bundle fingerprint
(Since 0.3.0 — see also Bundle diff and CLI below.)
Since 0.3.0, the library ships a bundle_fingerprint primitive
that hashes a set of records into a single 64-char hex digest.
The same records in any order produce the same hash; any byte
change in any record changes the hash.
from agent_memory_contracts import bundle_fingerprint
records = [source_record, evidence_span, taste_card, ...]
digest = bundle_fingerprint(records)
# '4f3a2b1c...' (64 hex chars; SHA-256 of canonical-JSON bundle)
# Re-running the same pipeline on the same records gives the
# same digest, so a downstream write layer can dedupe on it.
assert bundle_fingerprint(records) == digest
# A bundle of dataclass instances and a bundle of equivalent
# dicts hash to the same value -- the canonical form is the
# same in both cases.
assert bundle_fingerprint(records) == bundle_fingerprint(
[dataclasses.asdict(r) for r in records]
)
Use cases: cache key for ContextPack rebuilds, idempotency token for sync writes, change-detection digest, audit-chain anchor.
Bundle diff
Since 0.4.0. bundle_diff(a, b) returns a BundleDiff(added, removed, changed, unchanged_count) describing the set-semantic difference
between two bundles. Same primitive the id helpers use, applied to
"what changed between bundle A and bundle B":
from agent_memory_contracts import bundle_diff, BundleDiff
from dataclasses import asdict
diff = bundle_diff(bundle_a, bundle_b)
print(diff.added, diff.removed, len(diff.changed), diff.unchanged_count)
# When both bundles hash to the same fingerprint, the function
# short-circuits and returns the empty diff without iterating
# records. The common "no changes" case is one hash comparison.
For the non-Python case, the CLI exposes the same primitive:
python -m agent_memory_contracts diff bundle-a.json bundle-b.json
# 1 added, 0 removed, 0 changed, 12 unchanged
# + src_new_id
Use cases: cache invalidation ("did this rebuild's inputs change?"), audit chains ("what changed in the last cycle?"), UI rendering of diffs in a ContextPack inspector.
CLI
Since 0.4.0. The library is usable from the command line without
writing Python. Stdlib argparse, no extra dependencies:
# Validate a JSON or JSONL file against one of the bundled schemas.
python -m agent_memory_contracts validate records.json --schema taste_card
python -m agent_memory_contracts validate records.jsonl --schema source_record --jsonl
# Content-addressed digest of a bundle (JSON or JSONL).
python -m agent_memory_contracts fingerprint bundle.json
# 4f3a2b1c... (64 hex chars)
# Diff two bundles.
python -m agent_memory_contracts diff before.json after.json
# Merge N bundles.
python -m agent_memory_contracts merge a.json b.json c.json --prefer last
# Memory hygiene report.
python -m agent_memory_contracts hygiene weekly.jsonl
python -m agent_memory_contracts hygiene bundle.jsonl --from 2026-04-01 --to 2026-06-30 --json
# Audit pack (authorization chains, rejections, supersessions).
python -m agent_memory_contracts audit bundle.jsonl
python -m agent_memory_contracts audit bundle.jsonl --as-of 2026-06-30T00:00:00Z --json
# Misc.
python -m agent_memory_contracts --help
python -m agent_memory_contracts --version
Exit codes: 0 on success, 1 on validation error, 2 on usage error. The CLI uses the same public Python API as the library, so anything you can do from Python you can do from the shell.
Install
pip install agent-memory-contracts
Or from source:
git clone https://github.com/eoniclife/agent-memory-contracts.git
cd agent-memory-contracts
pip install -e ".[dev]"
Requires Python 3.10+. No runtime dependencies.
Development
pip install -e ".[dev]"
pytest -q # 325 tests
PYTHONPATH=src python examples/quickstart.py
PYTHONPATH=src python examples/extract_taste_cards.py
PYTHONPATH=src python examples/reference_reducer.py
PYTHONPATH=src python examples/conflict_resolution.py
PYTHONPATH=src python docs/migration_example.py
python -m mypy src/agent_memory_contracts # strict-clean
PYTHONPATH=src python benchmarks/run_all.py # ~3.8s
Tests are stdlib unittest (no test framework dependency at runtime).
CI runs on Python 3.10, 3.11, 3.12 via GitHub Actions. The CI workflow
includes a mypy job (strict) and a smoke-test step that iterates
for f in examples/*.py so any new example is auto-checked.
License
Apache-2.0. See LICENSE.
Origin and provenance
These contracts are extracted from avs-memory-kernel (private), a
governance-heavy AI agent memory kernel built with a falsification-first
sprint protocol. Each sprint was scoped, falsified against a bench,
and sealed by GPT review before merging. The extracted slice here is
what the rest of the kernel (workers, runtime, retrieval substrate)
builds on top of -- published first because it is the most generally
useful part.
If you build on this and want the upstream kernel to track your
changes, open an issue; if you want to see the full design history
(sprints, review packets, evals), the eoniclife/avs-memory-kernel
review packets are part of the public record of how these schemas got
where they are.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_memory_contracts-1.2.0.tar.gz.
File metadata
- Download URL: agent_memory_contracts-1.2.0.tar.gz
- Upload date:
- Size: 273.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b193c9da20ed0ef2fff4fee9025eadd927aee6a1e1a85e07729f4f21c73d84ba
|
|
| MD5 |
59e10ffe9809c4cf79b62c42da81714c
|
|
| BLAKE2b-256 |
f40be037519de199fd1681f7695abe8cbe74583a21ab2729900ad23067f6f454
|
Provenance
The following attestation bundles were made for agent_memory_contracts-1.2.0.tar.gz:
Publisher:
publish.yml on eoniclife/agent-memory-contracts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_memory_contracts-1.2.0.tar.gz -
Subject digest:
b193c9da20ed0ef2fff4fee9025eadd927aee6a1e1a85e07729f4f21c73d84ba - Sigstore transparency entry: 1862062469
- Sigstore integration time:
-
Permalink:
eoniclife/agent-memory-contracts@b13bae4cac437ba310f6fb0d77cd151ccaaa1167 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/eoniclife
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b13bae4cac437ba310f6fb0d77cd151ccaaa1167 -
Trigger Event:
release
-
Statement type:
File details
Details for the file agent_memory_contracts-1.2.0-py3-none-any.whl.
File metadata
- Download URL: agent_memory_contracts-1.2.0-py3-none-any.whl
- Upload date:
- Size: 212.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f08255494271f3d8b4757388319266aeb3ae96394434ec4813e604e8384a9504
|
|
| MD5 |
af0bb8bb740184e614c1e4097a27713c
|
|
| BLAKE2b-256 |
2f30d1a738f25bd3d27645f137cf13c213fc2892eb0d0062f7b3226b7608ab50
|
Provenance
The following attestation bundles were made for agent_memory_contracts-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on eoniclife/agent-memory-contracts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_memory_contracts-1.2.0-py3-none-any.whl -
Subject digest:
f08255494271f3d8b4757388319266aeb3ae96394434ec4813e604e8384a9504 - Sigstore transparency entry: 1862062659
- Sigstore integration time:
-
Permalink:
eoniclife/agent-memory-contracts@b13bae4cac437ba310f6fb0d77cd151ccaaa1167 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/eoniclife
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b13bae4cac437ba310f6fb0d77cd151ccaaa1167 -
Trigger Event:
release
-
Statement type: