API reference¶
from veracium import Memory, MemoryConfig, EvidenceAuthor
Memory¶
Memory(*, llm, store=None, embed=None, config=None,
telemetry=None, diagnostics=None, audit=None)
llm— aCompletecallable (required). See Providing an LLM.store— aStore; defaults toSqliteStore(config.db_path).embed— an optionalEmbedcallable (reserved for episode semantic fallback).config— aMemoryConfig; defaults toMemoryConfig().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.
author— the trust-critical input.EvidenceAuthor.USERfor the user's own messages and sent mail;EvidenceAuthor.THIRD_PARTYfor received mail / external documents (their claims are quarantined);EvidenceAuthor.SYSTEMfor derived content.context— the 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 (nocontext, noderived_from) the content class floors toderived(THIRD_PARTY)— nothing from the event is assertable. Absence stopped being the trusted cell at 0011's acceptance;rememberdeliberately never mintsdirect()on your behalf. A malformed context raises and nothing is written; passing bothcontextandderived_fromraises.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_PARTYfor a system summary quoting a received email). Still honoured as a positivederived(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 countersinvalid/retried/recovered/residual(specs/0025) ·redispositioned(specs/0024 — triples whosethird_party_claimlabel contradicted its own claimant slot and were re-dispositioned tounclassifiedatuse_only) · and the trust-state audit factsquarantined_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. -
principal— scoped recall (spec 0020). Averacium.scope.Identitynaming the recalling party. Omitted (None), recall is exactly what it has always been. Supplied, every carrier — the rendered context andedges/episodes/contested— carries only records the scope policy admits to that principal: own-scope records in full, cross-scope records only ifcross_scope_visibleand 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 inMemoryConfig(scope_groups); a principal with no policy configured, or with nosource_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 (seedocs/concepts.md). **filters— narrow the result within what is already visible: the closed field setsubject,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=8is 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 — lowerconsolidate_min_batchor accept the longer accumulation. - This happens with or without a scope policy. Partitioning is a
maintenance rule, not a visibility rule:
scope_groupsgoverns 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 onprovenance.observed_at(when Veracium recorded it), notvalid_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 viaremember().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 inremember().
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_outcomejudges 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 akind="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'sevidence_refmay touch every fact it consulted, sorecord_outcomenever supersedes a fact —correctedhere records the decision's true value only. The invariant, stated precisely: a fact's gate placement is unchanged by use-level outcomes (ause_onlyfact staysuse_only, a grounded fact stays grounded — "assertable" is not the invariant, since third-party-derived facts were never assertable to begin with).challengedsets 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 viaRecall.edges/ the outcome episodes.correctis the explicit fact-level correction: the remembered value itself was wrong. Supersedes withinvalidation_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 aCorrectionAuthorisationverified inside the transaction — an integrity binding (forge/replay/rebind/cross-principal all abort, nothing written), not authentication:actoris a caller-supplied principal, socorrect()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 raisesgraph.CorrectionRefusedafter 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:
...
roleis"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_schemaif 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