Skip to content

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 shardscompile(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 compilecompile_incremental() classifies ledger events since the last cursor into unchanged (bookkeeping only), prior_patch (usage-only — patches the ranking prior without re-interning anything), or full (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|scope signature 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.