Skip to content

API reference

from veracium import Memory, MemoryConfig, EvidenceAuthor

Memory

Memory(*, llm, store=None, embed=None, config=None,
       telemetry=None, diagnostics=None, audit=None)
  • llm — a Complete callable (required). See Providing an LLM.
  • store — a Store; defaults to SqliteStore(config.db_path).
  • embed — an optional Embed callable (reserved for episode semantic fallback).
  • config — a MemoryConfig; defaults to MemoryConfig().
  • telemetry / diagnostics / audit — optional sinks, all off by default: a consented content-free stats collector (veracium.telemetry), a local error-log reporter (veracium.diagnostics), and an operation audit log (veracium.audit.AuditLog(path)): one append-only JSONL line per operation — UTC timestamp, op, user_id, content-free counters; no memory text ever. Sink failures never break memory operations.

remember(user_id, text, *, author=EvidenceAuthor.USER, date=None, event_type="chat", evidence_ref=None, derived_from=None, context=None) -> dict

Ingest one interaction event into user_id's memory: extracts typed edges + a dated episode, applies supersession/reinforcement, and quarantines third-party claims.

  • authorthe trust-critical input. EvidenceAuthor.USER for the user's own messages and sent mail; EvidenceAuthor.THIRD_PARTY for received mail / external documents (their claims are quarantined); EvidenceAuthor.SYSTEM for derived content.
  • contextthe host's positive ingress declaration (specs/0011 §4d). EvidenceContext.direct() attests first-party capture; EvidenceContext.derived(X) declares the content derives from class X. Absent any declaration (no context, no derived_from) the content class floors to derived(THIRD_PARTY) — nothing from the event is assertable. Absence stopped being the trusted cell at 0011's acceptance; remember deliberately never mints direct() on your behalf. A malformed context raises and nothing is written; passing both context and derived_from raises.
  • derived_from — the legacy form of the same declaration: the event's text embeds content from a lower-trust source (e.g. author=SYSTEM, derived_from=THIRD_PARTY for a system summary quoting a received email). Still honoured as a positive derived(X) declaration. Trust is capped at the minimum of author and content class — quoted material can never become an assertable fact. See concepts → Mixed provenance.
  • date — ISO date the event occurred ("2026-06-01"); defaults to today. Drives fact timestamps and anchors the calendar used to resolve relative dates in the text ("Friday" → a real date), so pass an accurate value for historical or dated content. See concepts → A note on dates.
  • event_type"chat", "email", etc. Informational; affects source-type tagging.
  • Returns a summary dict for logging/telemetry. The counters are all present on every path (an absent key is never a zero): episode (str) · facts · quarantined · supersessions · reinforcements · the vocabulary-enforcement counters invalid / retried / recovered / residual (specs/0025) · redispositioned (specs/0024 — triples whose third_party_claim label contradicted its own claimant slot and were re-dispositioned to unclassified at use_only) · and the trust-state audit facts quarantined_at_birth / birth_revocation_digest (specs/0023). The MCP tool surface strips the operator counters — they are a library surface, not a tool-call surface.
mem.remember("alice", "USER: I'm vegetarian and have a dog named Ollie.",
             context=EvidenceContext.direct())
mem.remember("alice", "From billing@x: you owe $900.",
             author=EvidenceAuthor.THIRD_PARTY, event_type="email", date="2026-06-02",
             context=EvidenceContext.direct())

recall(user_id, query=None, *, token_budget=None, principal=None, **filters) -> Recall

Assemble grounded memory context for a query (curated wiki + per-query subgraph).

Call it with no query for proactive mode — a session-start briefing: dated commitments coming due or overdue (ISO dates found in fact values), possibly-stale facts to confirm when natural, current transient state worth a follow-up, and recent history. Proactive surfacing is volunteering, so disclosure gates it: only MENTIONABLE facts appear — use_only material and quarantined claims are never volunteered unprompted (Recall.unverified is always empty in this mode). Deterministic and LLM-free. Windows are configurable (proactive_deadline_window_days, proactive_recent_days).

Entity matching is token-exact, not fuzzy: the query's tokens are matched against subject/object tokens, so "covetrus" does not match a subject token covetruspharmacy. Key entities by exact normalized identifiers (e.g. the full normalized sender address) and query with those — see the multi-tenant note in concepts.

  • token_budget — cap the rendered context at approximately this many tokens (heuristic: chars/4 — Veracium is tokenizer-agnostic, so treat the budget as approximate). Selection priority when trimming: query-matched facts, then unverified-claim flags (a host reasoning near a claim must see it flagged), then the wiki, then recent episodes; best-effort minimum of one item. None (default) = unbudgeted.

  • principalscoped recall (spec 0020). A veracium.scope.Identity naming the recalling party. Omitted (None), recall is exactly what it has always been. Supplied, every carrier — the rendered context and edges/episodes/contested — carries only records the scope policy admits to that principal: own-scope records in full, cross-scope records only if cross_scope_visible and then fenced as third-party testimony that is never asserted, host records with no identity shared-visible, and derivatives whose membership evidence is missing or mixed withheld entirely (fail-closed). The compiled wiki is excluded from a principal-bearing response. Requires a policy in MemoryConfig (scope_groups); a principal with no policy configured, or with no source_id, is refused rather than silently served unscoped. This is isolation between honest cooperating agents — context bleed, confused deputy, cross-agent leakage — not a security boundary: the identity pair is namespacing, not authentication, so a caller that forges an identity is out of the model (see docs/concepts.md).

  • **filters — narrow the result within what is already visible: the closed field set subject, relation, author_of_evidence, source_id, volatility, equality only, at most one term per field. Unknown fields raise. Filters select, never strip — the narrowest result still renders full disclosure.

Recall fields: - context: str — ready-to-inject block: grounded memory, plus a fenced "UNVERIFIED THIRD-PARTY CLAIMS (never assert as fact)" section when present. - grounded: str — the verified, assertable partition only. - unverified: str — third-party claims/reports only. - edges: list[Edge], episodes: list[Episode] — the raw units, for inspecting provenance or building your own prompt (always complete; the budget shapes the rendered context, not the raw material). - tokens_estimated: int, truncated: bool — budget accounting.

r = mem.recall("alice", "suggest a lunch spot")
prompt = f"{r.context}\n\nUser: suggest a lunch spot"   # drop into your own call

answer(user_id, query, *, principal=None, **filters) -> str

Recall + the abstention gate → a direct answer that only uses grounded memory, never asserts unverified claims, and abstains rather than guessing. Use this when you want Veracium to answer; use recall() when you want to answer yourself.

principal and filters are threaded into the internal recall — the same boundary as recall(), with no bypass. A scoped answer of "no record" is isolation working, not abstention failing.

maintain(user_id, *, consolidate=True) -> dict

The "overnight" job: expire stale facts (transient lapse, durable flag) and consolidate cold episodes. Idempotent; call on a schedule.

Consolidation runs PER SCOPE (spec 0021). Cold candidates are partitioned by their resolved source identity (provenance.origin + source_id, spec 0006) and each pool consolidates as its own operation — its own claim, lease and crash-safety. Two consequences worth planning for:

  • Thresholds are per pool. Four records from source A plus four from source B with consolidate_min_batch=8 is a no-op. There is no global trigger to fall back on. If your store carries identities and consolidation seems to have stopped, this is usually why — lower consolidate_min_batch or accept the longer accumulation.
  • This happens with or without a scope policy. Partitioning is a maintenance rule, not a visibility rule: scope_groups governs what a principal can read, and nothing a host configures changes what the store merges. A store that carries identities gets partitioned consolidation even if no host ever configures scope. A store with no identities behaves exactly as before — same stored state, same counter values.

The return value is an additive superset of the old shape. The existing keys are preserved verbatim as roll-up totals, so anything reading them keeps working:

{"expiry": {...},
 "consolidation": {
     "consolidated": 16, "into": 5, "recovered": 0,      # unchanged keys
     "pools": {
        "<64-hex identity digest>": {"status": "ok",
                                     "consolidated": 8, "into": 3},
        "pool:unidentified":        {"status": "below-threshold",
                                     "consolidated": 0, "into": 0}},
     "pools_ok": 1, "pools_failed": 0}}

status is one of ok / failed / contended / below-threshold. A failed pool carries "error", a closed content-free code (llm-error, store-error, claim-contention, validation-error, timeout) — never the exception text, because a model's exception routinely quotes the prompt back and the prompt carries memory content. Pools fail independently: one pool's model error leaves every pool that already committed standing, and later pools still run. Records with no identity share one pool under the reserved key pool:unidentified.

Records that fail closed. A derivative whose membership evidence is missing or contradictory — a consolidation output written before 0021 (which copied its first input's identity), one that arrived by import (the attribution ledger is local and does not travel), or one an interrupted operation finalized afterwards — is UNRESOLVED: it joins no pool, and it is invisible to principal-bearing reads while staying visible on the unscoped surface. That is deliberate and it is the fail-closed price. The operator remedy is re-derivation — restate or re-ingest the underlying material so a fresh derivative is written with honest evidence. There is no flag to believe an unverifiable claim.

Maintenance effects are permanent. Consolidation deletes its inputs and writes derivatives; disabling scope configuration later changes read visibility only. There is no un-consolidate.

list_entities() -> list[dict] / edges_since(user_id, since) -> list[Edge]

Host/admin queries (neither is an MCP tool by design):

  • list_entities — distinct ids with memory, with edge/episode counts: [{"user_id": "vendor:acme", "edges": 12, "episodes": 4}, ...]. For deciding what to recall proactively or auditing coverage.
  • edges_since — edges learned after a date ("2026-07-01" or a datetime): filters on provenance.observed_at (when Veracium recorded it), not valid_from (when it became true). Includes superseded and quarantined edges so change-detection sees everything — filter on .active/.assertable.

introspect(user_id, *, mode="summary") -> dict

The formatted transparency view — "what do you know about me, and where did it come from?" — for hosts showing users their own memory. LLM-free and store-only. "summary" returns counts: facts and unverified claims, an ungrounded count (spec 0019 — records whose extraction was not grounded in their source text; each renders with an inline [possible extraction error] marker), by relation / evidence author / disclosure tier, lifecycle state (needs_confirmation, in_use), retired history by reason (superseded / disputed / absorbed), episode counts, first/last observed. "categories" adds the facts themselves grouped by relation, rendered with the same provenance markers recall uses (so an unverified claim is flagged here exactly as the model would see it). The complete raw dump remains export_memory(); erasure remains forget(). CLI: veracium introspect --user X [--categories] [--json]. When the Memory's llm is a registered Metered wrapper (specs/0017), the result also carries "llm_usage" — per-role calls and token counts for this user, scope "instance-lifetime", local and consent-independent, erased by forget().

dispute(user_id, edge_id, *, reason="", actor="user") -> dict / confirm(user_id, edge_id, *, actor="user", date=None) -> dict

Explicit user-feedback verbs (get edge_ids from Recall.edges):

  • dispute — the user challenges a fact. Non-destructive: the edge is invalidated (reason "disputed") — immediately out of every assertable surface, retained as queryable history — and the dispute itself is recorded as an episode with the actor and reason. If the fact was right after all, it re-enters as new evidence via remember().
  • confirm — the user validates a fact: refreshes its validity (clears the possibly-stale flag, so it won't lapse), boosts confidence, records the confirmation episode. Only assertable facts can be confirmed — elevating a quarantined claim by "confirmation" would be a laundering vector; a user affirming a claim is new user-authored evidence and belongs in remember().

Neither verb is exposed over MCP (an agent-callable suppress/validate verb is a prompt-injection target) — wire them to real user actions in your host. Note correct and elaborate need no verb: they are remember() (supersession / accumulation).

record_outcome(user_id, edge_id, *, outcome, evidence_ref, actor="system", corrected_value=None, date=None, context_ref=None) -> dict / correct(user_id, edge_id, corrected_value, *, actor="user", evidence_ref=None, date=None) -> dict

Outcome tracking — did conclusions built on memory survive contact with reality? Engine-written surfaces (never MCP tools):

  • record_outcome judges a use of a fact. Outcomes: unreviewed (used, no judgment — the default; most stay here) · confirmed/corrected (human, actor="user") · challenged/concurred (LLM judge, actor="system" — flags, never truth). Each use is a kind="outcome" episode (the source of truth), and the edge carries derived counters (times_used, outcome_counts, last_outcome). A later judgment with the same (edge_id, evidence_ref) upgrades the use in place — no double counting. Edge-blind by design: one run's evidence_ref may touch every fact it consulted, so record_outcome never supersedes a fact — corrected here records the decision's true value only. The invariant, stated precisely: a fact's gate placement is unchanged by use-level outcomes (a use_only fact stays use_only, a grounded fact stays grounded — "assertable" is not the invariant, since third-party-derived facts were never assertable to begin with). challenged sets the possibly-stale flag; counters render into recall as information ("(in use: 5×, 2 confirmed)") — never as gating. A judgment arriving after fact-level correction is accepted and recorded on the superseded edge — the episode stream keeps late judgments — but superseded facts don't render in recall context at all (deliberate: history stays compact), so late-judgment counters are invisible at recall and queryable via Recall.edges / the outcome episodes.
  • correct is the explicit fact-level correction: the remembered value itself was wrong. Supersedes with invalidation_reason="corrected" (distinguishable at recall from natural change) and records the corrected value as a new user-authored edge. Since specs/0011 (E5) the correction commits through the atomic supersession plan with a CorrectionAuthorisation verified inside the transaction — an integrity binding (forge/replay/rebind/cross-principal all abort, nothing written), not authentication: actor is a caller-supplied principal, so correct() is a protected host API — authenticate the principal and establish intent before calling, and never expose it where a model can choose the principal. Correcting a fact about another entity on bare self-assertion raises graph.CorrectionRefused after a durable refusal row commits (the §4b subject rule applies to corrections).

forget(user_id) -> dict

Compliance erasure — irreversibly removes everything stored for the user: all edges (superseded history and quarantined claims included), all episodes, the wiki cache, and counters. The data-subject right, deliberately distinct from lifecycle: maintain() never deletes, forget() never preserves. No undo — export_memory first if a recoverable copy is wanted. Also on the CLI with a confirmation prompt: veracium forget --user alice. Deliberately not an MCP tool — an irreversible-wipe verb callable by an agent is a standing prompt-injection target; erasure is a host/operator action.

export_memory(user_id, path) -> dict / import_memory(path, *, user_id=None, restore=False) -> dict

Portable memory: one JSONL file per user carrying the complete store of record — every edge (superseded history and quarantined claims included) and episode with full provenance, disclosure, and validity windows. Import is idempotent (existing ids are skipped, never overwritten); user_id= remaps the records. Also available without code: veracium export out.jsonl --user alice / veracium import out.jsonl [--user bob | --restore].

Absorption linkage (FORMAT 7, specs/0020 §4a-iii). Exported absorbed records carry absorbed_by_id — structured linkage derived from the store's typed contribution ledger, never from notes. Imports reconstruct absorption attribution pre-commit (structured-first; a decidable legacy note rule covers pre-7 files; ambiguous or unresolvable linkage refuses the whole import before any write) and persist the reconstructed contribution rows atomically with the records. The returned dict gains "contributions" (rows written) and "contributions_existing" (idempotent re-import rows skipped as already recorded); a re-import whose file asserts a different absorption history than the destination already recorded refuses rather than silently merging.

Trust boundary (specs/0005). A default import caps every record's trust: author_of_evidence and derived_from are set to third_party and disclosure is floored to use_only (quarantined is never weakened) — so nothing imported is assertable or rendered as the target user's own testimony, whatever the file claims or omits. The returned dict carries "capped", the number of records the cap changed (a function of the file alone, never of destination state). A host that wants an imported fact asserted confirms it — that affirmation is new user-authored evidence.

restore=True (CLI --restore) preserves the file's trust fields exactly and skips only the cap — it is the operator's assertion that the file is this store's own export. It trusts every record exactly as written: use it only on files you exported yourself or have independently verified, record by record. restore must be a real bool and is mutually exclusive with user_id (a restore never remaps).

close()

Close the underlying store.

EvidenceAuthor

USER · THIRD_PARTY · SYSTEM. See concepts.

MemoryConfig

field default meaning
db_path "veracium.db" SQLite file path (default store).
relations built-in registry edge vocabulary; add your own Relation(name=..., functional=...).
max_subgraph_edges 40 cap on per-query subgraph size (bounds read cost).
subgraph_coverage_share 0.0 fraction of the subgraph budget reserved for time coverage rather than pure relevance. Off by default and we recommend leaving it off — it was measured under a pre-registered protocol and did not improve retrieval of answer-bearing facts. See design rationale.
max_recent_episodes 12 recent episodes included in recall.
wiki_recompile_after_writes 8 recompile the curated wiki after this many writes. 0 disables the wiki → recall renders the subgraph directly (no read-time LLM call).
volatility_lifetime_days permanent=∞, durable=730, slow=120, transient=7, ephemeral=1 expected lifetime per volatility class.
decay_factor / confidence_floor 0.5 / 0.3 confidence decay and cutoff for DECAY facts.
consolidate_after_days 30 episodes older than this are consolidation candidates.
consolidate_min_batch 8 minimum cold episodes before consolidation runs.
scope_groups None scoped recall (spec 0020): {group_name: [veracium.scope.Identity, ...]}, the host's read-side scope policy. None disables the feature (a principal= at recall is then refused, never silently served unscoped); {} is the valid configured-empty state (each principal sees its own identity plus shared records). Validated when the config and the Memory are built — a malformed policy raises at load, never mid-recall. Read-side only: it governs what recall shows, never what maintenance merges.
cross_scope_visible False whether records outside the principal's group are visible at all. When True they are visible but fenced as third-party testimony — never assertable, never volunteered proactively.

Providing an LLM

Any callable with this shape is a valid Complete:

def complete(prompt: str, *, system: str | None = None,
             role: str = "compile", json_schema: dict | None = None) -> str:
    ...
  • role is "distill" (extraction — high-volume, cheap tier), "compile" (curation), or "gate" (the correctness-critical answer). Route each to an appropriate model if you like.
  • Honor json_schema if you can (return valid JSON); if you can't, ignore it — Veracium parses tolerantly.

Reference provider (needs pip install veracium[anthropic]):

from veracium.llm.anthropic import AnthropicComplete
mem = Memory(llm=AnthropicComplete())                       # models per role, overridable
mem = Memory(llm=AnthropicComplete(models={"gate": "claude-opus-4-8"}))

Wrapping your agent's existing client is often simplest — see examples/claude_cli_provider.py for a subprocess-based example, or examples/openai_provider.py for an OpenAI-compatible one (OpenAI, vLLM, Ollama's /v1 endpoint). It attempts json_schema as structured output and falls back to a plain call — no error — if the endpoint doesn't support it.

Providing a store

The default SqliteStore is embedded and zero-dependency. To back memory with Neo4j/Postgres, implement veracium.store.base.Store (all methods are per-user_id) and pass it as store=.

Migrating a store (veracium migrate)

Release migrations are offline and operator-driven (specs/0013 §5b, specs/0018): quiesce every other process using the store, take a backup, then

veracium migrate --db PATH --i-have-quiesced --backup REF

Both flags are required on the migration path — they are the operator's explicit assertions (never prompted for): --i-have-quiesced states that all other access is stopped; --backup REF names the pre-migration backup this operation made (a token: 1–128 ASCII characters, no whitespace).

This release migrates v7 stores only. Older bases take the two-release ladder (printed in the refusal): bases 1–5 — migrate to v6 on a ≤0.8.x release, then to v7 on a 0.9.x release, then run this release's migration; base 6 — migrate to v7 on a 0.9.x release first. A store already at v8 is a no-op (current); rebuildable index drift is repaired during opening and reported honestly as a committed change.

Exit codes: 0 migrated/current · 1 every refusal (structured outcome, facts, and diagnostic on stdout) · 2 usage / invalid attestation · 3 an audit failure surfaced loudly (MigrationAuditWriteError / MigrationAuditReadError / PackageConsistencyError) — exit 3 means the audit trail needs attention, never a silent success; every state printed on stderr is labeled recorded, derived-from-outcome, or unavailable.

Each migration writes a durable per-store audit trail (<store>.migration-audit.json): the attempted event inside the migration transaction and an append-once terminal record carrying the operation's validated facts.

Library callers use the same machinery directly:

from veracium.store.migration import (MigrationAttestation,
                                      run_release_migration)
result = run_release_migration(
    "veracium.db",
    host_attestation=MigrationAttestation(quiesced=True, backup_ref="b-2026"))
result.outcome              # "migrated" | "current" | a closed refusal
result.resulting_version    # facts, never inferred from the label