Quickstart (Rust)
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 Python on the
Python quickstart.
Add the crate (single dependency — the facade re-exports everything):
[dependencies]mempill = "0.2"tokio = { version = "1", features = ["full"] }Step 1 — Open an engine
Section titled “Step 1 — Open an engine”use mempill::{open_default_in_memory, history, recall, remember, RememberOptions};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = open_default_in_memory()?; let agent = "my-agent";Use mempill::open_default(path) instead to persist facts across restarts.
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, 2025-01-01) → covered the past remember( &engine, agent, "user", "city", "Berlin", RememberOptions::new() .valid_from("2020-01-01") .valid_until("2025-01-01"), ) .await?;
// Munich valid [2025-01-01, ∞) → covers NOW (2026), so it wins remember( &engine, agent, "user", "city", "Munich", RememberOptions::new().valid_from("2025-01-01"), ) .await?;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)” let result = recall(&engine, agent, "user", "city").await?;
assert_eq!(result.as_str(), Some("Munich")); assert!(!result.is_contested());
println!("city = {:?} (is_contested={})", result.as_str(), result.is_contested()); // city = Some("Munich") (is_contested=false)Because 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. history().current() is guaranteed to
agree with recall().
let h = history(&engine, agent, "user", "city").await?;
for e in &h.entries { println!("{:?} {:?}", e.value.as_str(), e.status); } // Some("Berlin") Superseded // Some("Munich") Current
// current() agrees with recall() assert_eq!( h.current().and_then(|e| e.value.as_str()), recall(&engine, agent, "user", "city").await?.as_str(), );Note: there is no valid_at parameter yet — point-in-time valid-time as-of queries are
planned for v0.3. For the full history API see Query Patterns.
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", RememberOptions::new()).await?; remember(&engine, agent, "acme", "ceo", "Bob", RememberOptions::new()).await?;
let ceo = recall(&engine, agent, "acme", "ceo").await?;
assert!(ceo.is_contested()); assert_eq!(ceo.candidates.len(), 2); assert!(ceo.value.is_none()); // Contested: no winner — use candidates
println!( "acme/ceo: is_contested={}, candidates={:?}", ceo.is_contested(), ceo.candidates.iter().map(|c| &c.value).collect::<Vec<_>>(), ); // acme/ceo: is_contested=true, candidates=["Alice", "Bob"]
Ok(())}recall never silently picks a winner. When facts genuinely conflict, is_contested()
is true, value / as_str() 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”| Field | Resolved | Contested | NoBelief |
|---|---|---|---|
as_str() |
Some("Munich") |
None |
None |
is_contested() |
false |
true |
false |
candidates.len() |
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. - Surface to a human via the MCP adapter’s
submit_adjudicationtool.
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 DTO API directly.
use mempill_sqlite::open_default_in_memory;use mempill_core::application::{IngestClaimRequest, QueryMemoryRequest};use mempill_types::{AgentId, Cardinality, Confidence, Criticality, ExternalKind, ProvenanceLabel};
#[tokio::main]async fn main() -> anyhow::Result<()> { let engine = open_default_in_memory()?; let agent = AgentId("my-agent".into());
// Write a claim with explicit provenance, confidence, and criticality. let resp = engine.ingest_claim(IngestClaimRequest { agent_id: agent.clone(), subject: "user".into(), predicate: "city".into(), value: serde_json::json!("Berlin"), provenance: ProvenanceLabel::External(ExternalKind::UserAsserted), cardinality: Cardinality::Functional, valid_time: None, confidence: Confidence { value_confidence: 0.95, valid_time_confidence: 0.0 }, criticality: Criticality::Medium, derived_from: vec![], }).await?;
println!("claim_ref={}, disposition={:?}", resp.claim_ref, resp.disposition); // disposition=CommittedCheap
// Read back the belief. let query = engine.query_memory(QueryMemoryRequest { agent_id: agent.clone(), subject: "user".into(), predicate: "city".into(), as_of_tx_time: None, }).await?;
println!("belief status={:?}", query.belief.status); // belief status=TimingUncertain (no valid_time supplied)
Ok(())}See the API Reference for the full IngestClaimRequest field table and
the 12-state disposition model.
Next steps
Section titled “Next steps”- Quickstart (Python) — same flow in Python
- Choosing a Backend — SQLite vs PostgreSQL
- Concepts: Valid-Time Succession — how time windows resolve conflicts automatically
- Concepts: Contested & Dispositions — the full 12-state model
- Concepts: The Gate — how conflicting claims are adjudicated