Skip to content

Rust (Advanced)

This guide goes beyond the Rust quickstart to cover the complete API surface: all field semantics, valid-time succession, the oracle constructors, reconcile, and audit. Every symbol shown here exists in source and is verified against the real engine.

SQLite (development and single-process production)

Section titled “SQLite (development and single-process production)”
mempill-sqlite constructors
use mempill_sqlite::{open_default, open_default_in_memory};
// File-backed — durable across restarts.
let engine = open_default("/path/to/agent.db")?;
// In-memory — ephemeral; useful for tests.
let engine = open_default_in_memory()?;

Both return DefaultEngine, a type alias for EngineHandle<SqlitePersistenceStore, NoOpOracle, NoOpVector>.

When a conflict requires external resolution, use the oracle constructors:

open_with_oracle
use mempill_sqlite::{open_with_oracle, open_with_oracle_in_memory};
use std::sync::Arc;
// Implement OraclePort for your resolver:
let engine = open_with_oracle("/path/to/agent.db", Arc::new(my_oracle))?;
// or in-memory:
let engine = open_with_oracle_in_memory(Arc::new(my_oracle))?;

The returned type is OracleEngine<O>, another alias defined in mempill-sqlite/src/lib.rs. See Writing an Oracle for the OraclePort trait contract.

mempill-postgres constructor
use mempill_postgres::{open_postgres, open_postgres_with_oracle, PostgresEngine};
use mempill_core::{EngineConfig, NoOpOracle, NoOpVector};
let engine: PostgresEngine<NoOpOracle, NoOpVector> = open_postgres(
"host=localhost port=5432 user=mempill dbname=mempill password=secret",
None, // no oracle (use open_postgres_with_oracle for oracle wiring)
None, // no vector
EngineConfig::default(),
)?;

See PostgreSQL Backend for the full topology-b topology.

EngineHandle::ingest_claim is async and takes IngestClaimRequest:

ingest_claim — all fields
use mempill_core::application::IngestClaimRequest;
use mempill_types::{
AgentId, Cardinality, Confidence, Criticality, ExternalKind,
ProvenanceLabel, ValidTime,
};
use chrono::DateTime;
let resp = engine.ingest_claim(IngestClaimRequest {
agent_id: AgentId("my-agent".into()),
subject: "acme:ceo".into(),
predicate: "held_by".into(),
value: serde_json::json!("Alice"),
// Provenance: External(UserAsserted) or External(ExternalFirstHand)
// means this is first-hand evidence — cheap-path eligible.
provenance: ProvenanceLabel::External(ExternalKind::ExternalFirstHand),
// Functional = at most one authoritative value at any valid-time instant.
// SetValued = multiple co-existing values are legitimate.
cardinality: Cardinality::Functional,
// valid_time = the real-world interval the claim covers.
// None means "I don't know the validity window" — falls back to tx-time ordering.
valid_time: Some(ValidTime {
start: Some(DateTime::parse_from_rfc3339("2020-01-01T00:00:00Z")?.into()),
end: None, // open-ended (still true)
}),
confidence: Confidence {
value_confidence: 0.95, // how confident in the value itself
valid_time_confidence: 0.95, // how confident the window is correct
},
// Criticality gates the adjudication threshold: Low, Medium, High, Critical.
criticality: Criticality::Medium,
// For ModelDerived claims: UUIDs of source claims this was derived from.
derived_from: vec![],
}).await?;
println!("ref={} disposition={:?}", resp.claim_ref, resp.disposition);
// ref=8b1c02ad-... disposition=CommittedCheap

ProvenanceLabel is an enum in mempill-types:

Variant Cheap-path eligible Meaning
External(ExternalKind::UserAsserted) Yes First-hand human assertion
External(ExternalKind::ExternalFirstHand) Yes Tool result, system-of-record, sensor
RecallReEntry No Content served earlier, re-entering the write path
ModelDerived No Model-emitted / inferred content

ModelDerived claims are committed down-weighted and cannot overturn External claims without anchoring. RecallReEntry is caught by the Amplification Guard (C6) — re-ingesting the same value five times does not strengthen the belief.

IngestClaimResponse has three fields:

pub struct IngestClaimResponse {
pub claim_ref: ClaimRef, // stable UUID reference
pub disposition: Disposition, // one of the 12-state model variants
pub contested_with: Vec<ClaimRef>, // non-empty when Contested or PendingConflict
}

Common dispositions after ingest:

Disposition Meaning
CommittedCheap Fast-path commit — no conflict, external provenance
CommittedInferred Committed after reconciliation narrowed to a winner
Contested Conflict with an existing claim; no winner; both preserved
QueuedForAdjudication Oracle wired — conflict handed to the oracle queue
Superseded This claim superseded an older one (valid-time succession)
Quarantined Amplification Guard blocked a RecallReEntry echo

For the full 12-state model see Concepts: Contested and Dispositions.

query_memory
use mempill_core::application::QueryMemoryRequest;
let result = engine.query_memory(QueryMemoryRequest {
agent_id: AgentId("my-agent".into()),
subject: "acme:ceo".into(),
predicate: "held_by".into(),
as_of_tx_time: None, // None = current moment
}).await?;
let belief = &result.belief;
println!("status={:?}", belief.status);
if let Some(primary) = &belief.primary {
println!("value={}", primary.fact.value);
println!("value_conf={}", primary.confidence.value_confidence);
if let Some(vt) = &primary.valid_time {
println!("start={:?} end={:?}", vt.start, vt.end);
}
}
// When status is Contested, primary is None and alternatives has all candidates:
for alt in &belief.alternatives {
println!("alternative: {}", alt.fact.value);
}

Pass as_of_tx_time to read the engine’s belief as it was at a past transaction time:

time travel query
use chrono::{Duration, Utc};
let seven_days_ago = Utc::now() - Duration::days(7);
let result = engine.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "acme:ceo".into(),
predicate: "held_by".into(),
as_of_tx_time: Some(seven_days_ago),
}).await?;

reconcile re-runs conflict resolution for a set of (subject, predicate) subject lines. Pass an empty subject_lines list to reconcile every subject line the agent owns.

reconcile
use mempill_core::application::ReconcileRequest;
let result = engine.reconcile(ReconcileRequest {
agent_id: AgentId("my-agent".into()),
// Reconcile a specific subject line:
subject_lines: vec![("acme:ceo".into(), "held_by".into())],
// Or reconcile everything: subject_lines: vec![]
}).await?;
println!("oracle_escalations={}", result.oracle_escalations);
for (claim_ref, disposition) in &result.outcomes {
println!(" {} -> {:?}", claim_ref, disposition);
}

When an oracle is wired, oracle_escalations is the count of subject lines that could not be resolved without oracle input. Those lines are left QueuedForAdjudication.

The audit ledger is an append-only record of every disposition event. Use query_audit for provenance forensics, debugging, and compliance:

query_audit (AuditQueryRequest)
use mempill_core::application::AuditQueryRequest;
// Query full ledger for an agent (last 100 entries):
let audit = engine.query_audit(AuditQueryRequest {
agent_id: AgentId("my-agent".into()),
claim_ref: None, // None = all claims
from_tx_time: None, // None = from the beginning
limit: 100,
}).await?;
for entry in &audit.entries {
println!(
"{:?} {} ref={}",
entry.event_kind, entry.recorded_at, entry.claim_ref
);
}

Filter to a specific claim by supplying claim_ref:

let audit = engine.query_audit(AuditQueryRequest {
agent_id: agent.clone(),
claim_ref: Some(resp.claim_ref.clone()),
from_tx_time: None,
limit: 50,
}).await?;

LedgerEventKind variants include: ClaimCommitted, ValidityAsserted, AdjudicationRequested, AdjudicationResolved, RecallReEntryDetected, Quarantined, AdjudicationExpired.

When you supply non-overlapping, high-confidence valid-time windows, the reconciler classifies the relationship as ConflictType::Succession and commits both claims without conflict. The fold then answers temporal queries like “who was CEO in February?”:

valid-time succession — CEO handoff
// Alice: 2020–2023 (open-ended in practice; Bob supersedes)
engine.ingest_claim(IngestClaimRequest {
subject: "acme:ceo".into(),
predicate: "held_by".into(),
value: serde_json::json!("Alice"),
provenance: ProvenanceLabel::External(ExternalKind::ExternalFirstHand),
cardinality: Cardinality::Functional,
valid_time: Some(ValidTime {
start: Some("2020-01-01T00:00:00Z".parse()?),
end: Some("2023-03-14T23:59:59Z".parse()?),
}),
confidence: Confidence { value_confidence: 0.95, valid_time_confidence: 0.95 },
// ...
}).await?;
// Bob: from 2023-03-15 onward
engine.ingest_claim(IngestClaimRequest {
subject: "acme:ceo".into(),
predicate: "held_by".into(),
value: serde_json::json!("Bob"),
valid_time: Some(ValidTime {
start: Some("2023-03-15T00:00:00Z".parse()?),
end: None,
}),
// ...
}).await?;
// Query now -> Bob; query 2022 -> Alice.

The succession shortcut fires when: every valid-time window has valid_time_confidence ≥ 0.7, windows are bounded and pairwise non-overlapping, and there is exactly one live incumbent. Overlapping or low-confidence windows fall back to Contested. See Concepts: Valid-Time Succession.

Cargo.toml
[dependencies]
# SQLite topology-a (most common starting point):
mempill = "0.2" # facade: re-exports mempill-core + mempill-sqlite
# Postgres topology-b (multi-agent shared database): replace mempill with:
# mempill-postgres = "0.2"
# mempill-core = "0.2"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
anyhow = "1"
chrono = { version = "0.4", features = ["serde"] }

All crates are published on crates.io at 0.2.0. See Changelog for version history.