Architecture
One async core, consumed three ways — embedded (Smrititantra), over HTTP
(smrititantra serve), or as an MCP server (smrititantra mcp) — all calling
the same services over the same event-sourced store. Nothing behaves
differently by transport.
The event-sourced store
Every write lands as a ledger event first. Planes read/derive their state
from it: P1 data assets, P2 concept authoring, P3 episodes/usage
evidence, P4 per-subject personalization, S1 bindings tying a concept
to the asset that computes it. This is why almost every mutating service call
takes a tenant and appends an event — replay and audit are the same
mechanism.
The write router
Every candidate fact — a remember call, an observe digest, a gap answer —
flows through one classifier (core/router.py) instead of each write path
inventing its own rules: scope language becomes a P4 item, a definitional
claim becomes a P2 proposal, outcome language becomes a P3 event, structural
language becomes a P1 finding, and anything ambiguous noops rather than
writing org truth on a guess. A writing principal's capabilities cap which
sinks are reachable, and low-trust agents' proposals still land, just scaled
down and gated for review.
The review queue
core/reviews.py is the one curation mechanism for every kind of meaning
change — extractions, merges, drift remaps, binding proposals, importer
bundles, gap answers. accept materializes a proposal through the concepts
plane's real ingestion path (node + content version + edges + bindings, not a
side table); reject keeps the proposal's stable id so the same candidate
never gets re-filed.
The compiled snapshot
Recall never queries the live store per-question. core/engine/snapshot.py
folds the store into one immutable object — refs become integer indices,
edges become a degree-normalized sparse matrix, aliases become an n-gram
index, per-clearance visibility becomes bitmasks — and core/engine/recall.py
runs a pure-numpy activation pass over it: embed the question, compose the
visibility mask, activate, then assemble the pack (required/optional
concepts, their bindings and joins, applicable rules, exemplars). Nothing the
pass returns is ever widened after the fact — masking happens before
activation, not as a filter on the output.
SnapshotHolder swaps a newly compiled snapshot in atomically (hot reload);
pin is the kill switch back to a known-good version.
The scale kit — three techniques for keeping compile and serving cheap as a tenant grows, all opt-in and additive to the base path:
- Scoped shards —
compile(scope=...)restricts the built universe to a domain subtree (containment + a requires-closure + binding pull-through), registered but never flipping the whole-tenant live snapshot. - Incremental compile —
compile_incremental()classifies ledger events since the last cursor intounchanged(bookkeeping only),prior_patch(usage-only — patches the ranking prior without re-interning anything), orfull(any structural change — the safe default). - Mask caching — the time-independent part of a principal's visibility
mask (label ∧ status ∧ scope) is composed once per
clearance|scopesignature and cached on the snapshot; only the time-dependent validity factor is recomputed per request.
Past a configured embedding count, the dense vector channel externalizes to a
VectorIndex (in-memory, or Qdrant at scale) instead of living in the
snapshot's arrays.
The graph serving tier
Recall answers "what's relevant to this question"; the graph tier
(core/graph_store.py) answers the orthogonal question — lineage, impact,
neighborhood — from the same projected P1/P2/S1 data, directed so
"lineage" walks out-edges and "impact" walks in-edges. The in-memory
reference implementation projects at query time; the optional Neo4j tier
(SMRITITANTRA_GRAPH_BACKEND=neo4j) projects incrementally from the ledger
into a real graph database and pushes visibility down as a path predicate, so
a hidden node can't bridge two visible ones either way.
The knowledge-gap loop
core/gaps.py is what makes memory ask back instead of silently guessing.
Detectors run offline over the ledger and store — never inside recall itself
— scored by value-of-information and deduped against open gaps. A relevant
gap can ride a recall as a card, but only for a principal holding
gaps.relay, within budgets and cooldowns, and only when the principal's
clearance dominates the card's sensitivity. Answers route back through the
write router with elicitation provenance.
Access control
Every read surface enforces the same clearance/scope/status/validity
composition — there is exactly one mask, computed the same way whether the
request came in through smriti.recall(...), /v1/recall, or an MCP tool call.