Quickstart (Python)
This guide shows how to open an engine, store facts with remember, read them back with
recall, and observe what Contested looks like. The same flow is shown in Rust on the
Rust quickstart.
Install from PyPI:
pip install mempill# or: uv add mempillStep 1 — Open an engine
Section titled “Step 1 — Open an engine”from mempill import open_in_memory, history, remember, recall, RememberOptions
engine = open_in_memory()agent = "my-agent"Use mempill.open_for_agent(base_dir, agent_id) instead to persist facts across process
restarts — the database file is derived automatically as base_dir/agent_{agent_id}.db.
Step 2 — Remember two facts with non-overlapping time windows
Section titled “Step 2 — Remember two facts with non-overlapping time windows”# Berlin valid [2020-01-01, 2024-12-31] → covered the pastremember( engine, agent, "user", "city", "Berlin", RememberOptions(valid_from="2020-01-01", valid_until="2024-12-31"),)
# Munich valid [2025-01-01, ∞) → covers NOW (2026), so it winsremember( engine, agent, "user", "city", "Munich", RememberOptions(valid_from="2025-01-01"),)Dates are lenient: "2026", "2026-06", "2026-06-01", and RFC 3339 are all
accepted. Natural-language parsing ("next month", "last Tuesday") is the host’s job —
mempill stays LLM-free and expects unambiguous date strings.
Step 3 — Recall: Munich wins (succession, not conflict)
Section titled “Step 3 — Recall: Munich wins (succession, not conflict)”result = recall(engine, agent, "user", "city")
assert result.as_str() == "Munich"assert not result.is_contested()
print(f"city={result.as_str()!r} is_contested={result.is_contested()}")# city='Munich' is_contested=FalseBecause the two time windows do not overlap, the engine can resolve a single winner via valid-time succession — no conflict, no human needed.
Step 4 — Read the history / timeline
Section titled “Step 4 — Read the history / timeline”history() returns every claim for a subject-line, ordered oldest→newest. Each entry
carries a .status of "Current" or "Superseded". .current() is guaranteed to agree
with recall(). The History object is iterable.
h = history(engine, agent, "user", "city")
for e in h: print(e.value, e.status)# Berlin Superseded# Munich Current
# current() agrees with recall()assert h.current().value == recall(engine, agent, "user", "city").as_str()assert not h.is_empty()Note: history() itself has no valid_at parameter — point-in-time valid-time as-of
queries are available as of mempill 0.3.0 via the valid_at parameter on query_memory
(see Python guide). For the
full history API see Query Patterns.
Each entry also carries honest per-endpoint date precision (0.4.0): .valid_from_display /
.valid_until_display (pre-rendered strings, e.g. "2020-03" for month-precision facts)
and the raw .valid_from_granularity / .valid_until_granularity tags — see
Query Patterns: history() granularity.
Step 5 — Contested: two timeless facts for the same line
Section titled “Step 5 — Contested: two timeless facts for the same line”remember(engine, agent, "acme", "ceo", "Alice")remember(engine, agent, "acme", "ceo", "Bob")
ceo = recall(engine, agent, "acme", "ceo")
assert ceo.is_contested()assert ceo.value is None # Contested: no winner — use candidatesassert len(ceo.candidates) == 2
print(f"is_contested={ceo.is_contested()} candidates={[c.value for c in ceo.candidates]}")# is_contested=True candidates=['Alice', 'Bob']recall never silently picks a winner. When facts genuinely conflict, is_contested()
is True, value is None, and candidates lists every preserved claim. The agent
decides what to do next: surface to a human, request oracle adjudication, or accept the
ambiguity.
RecallResult at a glance
Section titled “RecallResult at a glance”| Attribute | Resolved | Contested | NoBelief |
|---|---|---|---|
as_str() |
"Munich" |
None |
None |
is_contested() |
False |
True |
False |
len(candidates) |
0 | 2+ | 0 |
Note on resolution
Section titled “Note on resolution”To resolve a Contested belief you can:
- Call
engine.reconcile(...)to let the oracle re-adjudicate. - Supply a new
rememberwith a tighter valid-time window that makes one claim a successor. - Wire an oracle and resolve it with the engine-level
submit_adjudicationmethod — see Writing an Oracle for the human-in-the-loop pattern (the MCP adapter itself exposes 4 tools —ingest_claim,query_memory,reconcile,audit— and does not have asubmit_adjudicationtool; HITL resolution happens at the engine or host application level).
Advanced: the full claim API
Section titled “Advanced: the full claim API”Most users only need remember / recall. If you need fine-grained provenance, confidence
weights, or explicit cardinality, you can call the underlying dict API directly.
import mempillfrom mempill import ProvenanceLabel, Disposition
engine = mempill.open_in_memory()
# Write a claim with explicit provenance, confidence, and criticality.resp = engine.ingest_claim({ "agent_id": "my-agent", "subject": "user", "predicate": "city", "value": "Berlin", "provenance": ProvenanceLabel.external_user_asserted(), "cardinality": "Functional", "confidence": {"value_confidence": 0.95, "valid_time_confidence": 0.0}, "criticality": "Medium", "derived_from": [],})
print(resp["claim_ref"], resp["disposition"])# <uuid> CommittedCheapassert resp["disposition"] == Disposition.CommittedCheap
# Read back the belief.result = engine.query_memory({ "agent_id": "my-agent", "subject": "user", "predicate": "city",})print(result["belief"]["status"]) # TimingUncertainprint(result["belief"]["primary"]["fact"]["value"]) # BerlinSee the API Reference for the full IngestClaimRequest field table and
the 12-state disposition model.
Next steps
Section titled “Next steps”- Quickstart (Rust) — same flow in Rust
- Choosing a Backend — SQLite vs PostgreSQL
- Concepts: Valid-Time Succession — how time windows resolve conflicts automatically
- Concepts: Contested & Dispositions — the full 12-state model
- Guides: Python Advanced — oracle wiring, async patterns
- Guides: MCP Integration — connecting to Claude and other hosts