Atlas is V9's single largest net-new build: the unified knowledge graph that
makes "start from a wonder, pull the thread" possible. A learner types a
free-text question — "why is the night sky dark?", "how old is the
universe?" — and Atlas turns it into a concrete, prerequisite-ordered set of
concepts a grounded lesson can teach now. It is the input to Stage 0 of the
Prometheus lesson pipeline, the substrate the
Theia wonder-director walks to find the next, better question, and the map the
consumer front door binds to. Crucially, Atlas is assembled, not authored:
it unifies three knowledge graphs that already exist in the monorepo — the Metis
concept graph
(services/metis/src/metis/services/source_concept_graph_helpers.py), the
Sophia knowledge graph (libs/sophia/knowledge-graph, 15 entity types and 27
relationship types), and the Mnemosyne prerequisite graphs
(libs/mnemosyne/core) — and binds the domain kernels (Nyx, Kalika, Nisaba) as
grounded leaves. The whole engine is the package @oshun/v9-atlas
(libs/v9/atlas/src, ten source files), whose own module doc-comment states the
discipline plainly: it "composes the real engines (Sophia BM25, Mnemosyne
KnowledgeGraph) — no new graph store, retrieval, or psychometrics"
(libs/v9/atlas/src/index.ts:1).
Why is it built this way — content-addressed nodes, two hard invariants, and a
build-time gate? Because in V9 a lesson is a reproducible, provenance-stamped
artifact, and that promise can only be as honest as the knowledge it draws
from. Atlas refuses to hold an unsourced fact or a hand-typed physical constant:
every fact node must carry a vetted Sophia source pin, and every STEM value node
must carry a reference to the computation that yields it rather than the
number itself, so the value is reproducible and current instead of a literal
that silently drifts (libs/contracts/src/v9/concept-graph.ts:66). Those two
rules are not lint suggestions — they are baked into the Zod schema, enforced
again at store insert, and aggregated by a build gate that blocks on any
violation. This page is the deep companion to the architecture set; the section
hub is overview.md and the monolith is
../V9_ARCHITECTURE.md.
What ships, honestly#
Implemented and tested (real today). The @oshun/v9-atlas engine is real
and green: 21 tests pass across atlas-graph.spec.ts (11),
wonder-resolution.spec.ts (5), and grounded-leaves.spec.ts (5). The pieces
are genuine compositions, not reimplementations. The AtlasStore enforces both
invariants at insert by parsing every node through V9ConceptNodeSchema
(libs/v9/atlas/src/atlas-store.ts:61) and delegates all prerequisite
reasoning — closure, topological sort, gap analysis — to the real
@mnemosyne/core KnowledgeGraph (libs/v9/atlas/src/atlas-store.ts:113).
Wonder resolution runs real Sophia BM25 (createBM25LexicalIndex,
libs/sophia/semantic-search/src/bm25/bm25.ts:522) and the real Mnemosyne
identifyKnowledgeGaps (libs/mnemosyne/core/src/knowledge-graph.ts:432). The
grounded-leaf binders point at real in-repo kernels —
kalika:cosmology#ageTodayGyr resolves through the genuine @kalika/cosmology
Friedmann solver, and the grounded-leaves test asserts the bound value really
is ≈ 13.8 Gyr (13.6 < age < 14.0), i.e. it checks domain correctness, not
just shape. The graph-unifier, source adapters, and the build-time grounding
validator are all present with real assertions. Atlas is consumed for real by
Prometheus
(libs/v9/prometheus/src/ground-truth.ts:43), Theia
(libs/v9/theia/src/theia.ts), and the experience view-models
(libs/v9/experience/src/wonder-front-door.ts:43).
Spec-only / not yet wired to live data. The honest limit is upstream of the
engine: nothing in the repo populates a production Atlas from the three live
graphs. A search for callers of unifyAtlas or the from* source adapters
outside libs/v9/atlas/src returns only the package's own tests and the
consumer packages' test fixtures — there is no seed job, ETL, or runtime that
drives the unifier over real Metis/Sophia/Mnemosyne exports and persists the
result. So Atlas today is a real, tested assembly engine over an in-memory
store, not a deployed, populated knowledge spine. This matches
the gap analysis §2.1 exactly: "the pieces exist;
unification + grounding-pin coverage is the work." The AtlasStore is in-memory
only (Map-backed); no persistence/serialization layer ships.
Two honest spec corrections. (1) The monolith's ConceptNode sketch writes
kernelRefs: { nyx?, kalika?, ... } — an object keyed by kernel
(V9_ARCHITECTURE.md:88). The shipped contract is an array of
V9KernelRef { kernel, ref, params?, unit? }
(libs/contracts/src/v9/primitives.ts:152), so a node can carry several kernel
bindings of the same kernel. (2) The Sophia adapter is intentionally lossy
on Sophia's rich relation vocabulary: of the 27 RelationType values, only
three are special-cased (PART_OF → part_of, OPPOSITE_OF → conflicts,
DERIVED_FROM → specializes); the remaining 24 collapse to a single lateral
related edge (libs/v9/atlas/src/source-adapters.ts:150). Treat the "27
relationship types" in the monolith as the source's expressiveness, not what
survives unification.
The node and edge contract#
The typed vocabulary lives in libs/contracts/src/v9/ and is re-exported flat
and under the V9Contracts namespace (libs/contracts/src/v9/index.ts). A node
is a V9ConceptNode (libs/contracts/src/v9/concept-graph.ts:66); its fields:
| Field | Type / enum | Notes |
|---|---|---|
id |
V9ConceptId |
Content-hash cn:<64-hex> or namespaced slug <ns>:<slug> (see grammar) |
label, aka[] |
string (1–300), ≤32 aliases | aka carries wonder-match synonyms ("dark night sky", "Olbers' paradox") |
wonderAxis |
cosmos | laws | mind | meaning | deep-time | living-world | made-world |
The 7 axes a wonder can enter on (primitives.ts:42) |
discipline |
core-six ∪ 12 extended (18 total) | Metis taxonomy placement (primitives.ts:58) |
kind |
topic | skill | fact | procedure | principle |
Drives both invariants |
groundingPins[] |
V9SophiaPin[] (≤64) |
{ pinId, sourceId, sourceTitle?, locator?, credibility∈[0,1], quote? } |
kernelRefs[] |
V9KernelRef[] (≤16) |
{ kernel: nyx|kalika|nisaba|demeter, ref, params?, unit? } |
epistemicStatus |
established | contested | one-tradition | frontier |
Required on every claim (feeds the G6 gate) |
safetyPolicyRef |
optional ref | A Metis per-discipline curriculum-safety policy id |
The schema is .strict(), so unknown keys are rejected. The id grammar is
the load-bearing detail (libs/contracts/src/v9/primitives.ts:17): the regex
^(cn:[a-f0-9]{64}|[a-z][a-z0-9-]*:[a-z0-9][a-z0-9._-]*)$ admits exactly two
forms — a content hash minted by the unifier, or a stable namespaced slug minted
by an adapter or leaf binder (metis:olbers-paradox,
kalika:age-of-the-universe, nisaba:the-republic). That is what lets the same
concept resolve identically no matter which source graph first named it.
An edge is a V9ConceptEdge { from, to, type, weight∈[0,1] }
(libs/contracts/src/v9/concept-graph.ts:102). The nine edge types unify the
three sources' relations plus one V9 addition:
prerequisite, related, part_of, generalizes, specializes, enables, conflicts, complements, bridges
— where bridges is the cross-axis thread V9 adds (e.g. cosmology ↔
deep-time), the structural hook the Theia director uses to walk from one wonder
to the next. The edge schema superRefine forbids self-loops.
The two invariants — what makes Atlas a map of reality#
The difference between Atlas and "a bag of facts" is two structural rules, both defined once on the contract and re-checked everywhere a node can enter the graph.
Invariant 1 — every fact node carries ≥1 grounding pin. A kind: 'fact'
node with groundingPins.length === 0 fails to parse with the message
Invariant 1 violated: fact node "<id>" has no grounding pin
(concept-graph.ts:67). No ungrounded fact may exist.
Invariant 2 — every STEM value node carries a kernelRef. A node whose
discipline is in the STEM set and whose kind is fact | principle | procedure
must carry ≥1 kernelRef, or it fails with
Invariant 2 violated: STEM <kind> node ... has no kernelRef (its value must be computed, not asserted)
(concept-graph.ts:74). The STEM set is exactly five disciplines —
astronomy, physics, mathematics, chemistry, earth-science
(V9_STEM_DISCIPLINES, primitives.ts:93) — and the gate function
v9NodeRequiresKernel(discipline, kind) (concept-graph.ts:36) is the single
source of truth for "is this node subject to Invariant 2." Topics and skills are
organizational and exempt; a humanities fact is subject to Invariant 1 but not
2 (its value is textual, grounded by a pin, not computed).
These are enforced at three layers, deliberately redundant:
- At parse —
V9ConceptNodeSchema.superRefine(concept-graph.ts:66) throws. - At insert —
AtlasStore.addNodere-parses through the same schema before storing (atlas-store.ts:61), so a node can only exist in a store if it is already valid. The unifier-test "rejects an ungrounded fact node at insert" pins this. - At build — a non-throwing aggregator,
checkV9ConceptNodeInvariants(node)(concept-graph.ts:133), returns every violation across a whole candidate set so the build gate can report and block (see the grounding gate below).
The reason for the redundancy is the unifier: when several source drafts
collapse into one node, the merged node might still violate an invariant (e.g.
an ungrounded fact that no source supplied a citation for). Rather than silently
inventing a pin, the merged node is re-validated at insert and, if it fails, is
reported as skipped, not coerced — a logged truncation, never a fabricated
grounding (graph-unifier.ts:9).
Assembling the spine: source adapters and the unifier#
Each source graph "speaks its own dialect," so an adapter maps it into the
shared V9ConceptNode/V9ConceptEdge vocabulary
(libs/v9/atlas/src/source-adapters.ts):
- Metis (
fromMetisConceptGraph,source-adapters.ts:87) mirrors theConceptGraphNodedataclass shape from the Python helper (source_concept_graph_helpers.py): topic/skill/fact/procedure/principle kinds already match V9, and fact nodes are grounded from theirevidence_citation_ids— each citation becomes aV9SophiaPinat a caller-supplied default credibility (0.7 unless overridden). This is the seed of Invariant-1 coverage. The test "Metis adapter grounds fact nodes from evidence citations" verifies two citations become two pins at the configured credibility. - Sophia (
fromSophiaKnowledgeGraph,source-adapters.ts:163) consumes a duck-typedSophiaGraphStoreLike(getAllNodes/getAllEdges), not the concrete@sophia/knowledge-graphGraphStoreclass — a structural seam that keeps Atlas from hard-coupling to Sophia's internals. Sophia entities (figures, texts, traditions) are organizationaltopicnodes with no grounding pin (they are not atomic facts), and the relation mapping is the lossy one noted above. - Mnemosyne (
fromMnemosynePrereqGraph,source-adapters.ts:212) maps real@mnemosyne/coreKGNode/KGEdge: it supplies the prerequisite skeleton, mappingprerequisite/part_ofthrough and skills toskillkind.
All three adapters are pure transforms — they build draft nodes; they never
enforce invariants themselves (that is the store's job), and mapEdges drops
dangling/self-loop edges at the source.
The unifier (unifyAtlas, graph-unifier.ts:96) is the largest single
piece of net-new logic. It works in three passes:
- Group by content-addressed label key. Every draft's
labelis slugged (atlasSlug: lowercased, NFKD-normalized, non-alphanumerics → hyphens, ≤100 chars,source-adapters.ts:49) and the slug is hashed to the canonical idcn:<sha256(slug)>(graph-unifier.ts:68). Two drafts that name the same thing — "Photosynthesis" from Metis and from Mnemosyne — slug identically and therefore land in the same group, independent of namespace. - Merge each group into one node. Grounding pins and kernel refs are
unioned and de-duplicated (by
pinId/ref); aliases absorb the other drafts' labels so cross-source naming becomes searchableaka; and the kind is resolved to the most specific present via the priorityprinciple > procedure > fact > skill > topic(KIND_PRIORITY,graph-unifier.ts:30) — so atopicfrom one source and afactfrom another collapse to a groundedfact. The merged node is added to the store, which re-validates it; a failure goes toskippedwith its reason. - Remap, dedupe, and prune edges. Every per-source id is remapped to its
canonical id; edges whose endpoints were skipped/unknown, became self-loops
after the merge, or duplicate an existing
(from,to,type)are dropped and counted indroppedEdgeCount.
The result is an AtlasUnificationResult reporting rawNodeCount,
mergedNodeCount, the skipped list, and droppedEdgeCount — so a build can
see exactly how much collapsed and what was honestly left out. The unifier tests
pin each behaviour: same-concept collapse to one cn: node, an ungrounded fact
surfacing as skipped (not coerced), and correct edge remapping.
Grounded leaves — references, not values#
The fourth input is domain data bound as leaves (grounded-leaves.ts). The key
idea, and the reason Invariant 2 exists, is that a STEM node holds a reference
to the computation, never the number. kalikaKernelLeaf mints a node carrying
kernelRef.ref = 'kalika:cosmology#ageTodayGyr' (grounded-leaves.ts:70);
nyxEphemerisLeaf binds nyx:ephemeris#sun.position (:106);
nisabaSourceLeaf binds a humanities fact to a vetted source pin with no kernel
(:139). The leaf holds the ref string; Atlas itself does not execute
kernels.
The actual recomputation happens later, at G2, in
Aletheia. Its kernel registry
(libs/v9/aletheia/src/kernel-evaluators.ts:33) is the binding from a ref to a
real in-repo computation —
kalika:cosmology#ageTodayGyr → ageTodayGyr(PLANCK_2018_COSMOLOGY),
nyx:constants#speedOfLight → SPEED_OF_LIGHT (299 792 458 m/s,
libs/nyx/constants/src/physical.ts:18). The registry currently knows six
refs. This is a deliberate fail-loud seam: when Prometheus builds ground truth
it calls the evaluator (ground-truth.ts:65), and if a node references a ref
the registry can't compute, the Aletheia accuracy gate emits
cannot recompute ...: unknown kernel ref "<ref>" and blocks — it never passes
an unverifiable number. So the leaf binders can name refs ahead of the evaluator
supporting them; the cost is an honest G2 failure, not a silently-skipped check.
Wonder resolution#
Resolution (resolveWonder, libs/v9/atlas/src/wonder-resolution.ts:86) turns
a free-text string into the minimal, prerequisite-ordered concept set to teach
now.
Step by step:
- Lexical recall (always). A fresh BM25 index is built over each node's
text —
label + aka + discipline + wonderAxis(nodeText,wonder-resolution.ts:63) — and searched for the wonder. BM25 is the Sophia engine, not a reimplementation. - Optional dense rerank (the Nous boundary).
WonderEmbedderis an injected interfaceembed(texts) → vectors(wonder-resolution.ts:21). When present, every node is scored(1 − denseWeight)·bm25 + denseWeight·cosine(wonder, node)over all nodes, so a wonder with no lexical overlap ("the beginning of everything") can still reach the right concept. Absent ⇒ BM25-only. Honest characterization: the fusion is a weighted linear blend of the raw BM25 score and the dense cosine — not a rank-normalized fusion (RRF); the shipped tests exercise the pure-lexical end (no embedder) and the pure-dense end (denseWeight: 1), where the toy embedder steers "the beginning of everything" to the finite-age node. - Disambiguation. The top-ranked candidate is the
target; all ranked candidates are returned so the front door can show alternatives. If nothing scores above zero (or the Atlas is empty), it fails loud withWonderUnresolvedErrorrather than inventing a concept (wonder-resolution.ts:56). - Mastery-scoped prerequisite frontier. Atlas is projected onto a Mnemosyne
KnowledgeGraphannotated with the learner's mastery (toMnemosyneGraph,atlas-store.ts:113), andidentifyKnowledgeGaps(mastery, target, masteryFloor)returns the unmastered prerequisites (default floorintermediate). Onlyprerequisite/part_ofAtlas edges become prerequisite/structural KG edges; everything else is a lateralrelated_to, so the closure follows true dependencies, not mere relatedness (toKgRelation,atlas-store.ts:31). - Minimal teaching set. The gaps are ordered by the graph's topological
sort and the target is appended, giving
conceptSet = [...orderedGaps, target]— prerequisite-first, target last. A cyclic prerequisite graph degrades gracefully:topologicalSortthrows, and resolution falls back to the given order rather than failing the whole query (orderByPrerequisite,wonder-resolution.ts:153).
Worked example (from the test suite). Build an Atlas with How light
travels, The universe has a finite age, and Olbers' paradox, with the first
two as prerequisites of the third. resolveWonder('why is the night sky dark?')
resolves to a:olbers (BM25 over the aliases), and with a learner who knows
nothing the conceptSet is the full chain [finite-age, light, olbers], target
last (wonder-resolution.spec.ts:55). Give the learner advanced mastery of
a:light and the frontier drops to [finite-age]; the teaching set becomes
[finite-age, olbers] (wonder-resolution.spec.ts:64). This is the
cognitive-load discipline the product calls for, computed — not guessed.
The build-time grounding gate#
The validator (libs/v9/atlas/src/grounding-validator.ts) turns the per-node
invariant checks into a graph-wide verdict. validateConceptNodes(nodes) walks
a candidate set and returns an
AtlasGroundingReport { ok, totalNodes, factNodes, stemNodes, groundedFactNodes, violations[] }
(:28); validateAtlasGrounding runs it over a built store; and
assertAtlasGrounded(atlas) throws on any violation, listing each offending
node id and message (:57). The intent is a CI gate —
Atlas grounding invariant violated by N node(s): ... — so an ungrounded node
can never reach a shipped lesson. Because the store already rejects bad nodes at
insert, a built store should always pass; the validator's real value is over a
candidate set before it enters the store (e.g. an offline coverage audit that
counts groundedFactNodes / factNodes).
How Atlas connects to its neighbours#
Atlas is a spine precisely because so much hangs off it:
- Prometheus, Stage 0 → Stage 1. Wonder resolution feeds the concept set
into
buildGroundTruth(libs/v9/prometheus/src/ground-truth.ts:43), which reads each node back out of the store: grounded fact nodes becomeV9GroundedClaims carrying their pins; STEM kernel nodes become recomputedV9KernelResults via the Aletheia evaluator. If the concept set has no grounded fact node, Prometheus refuses to forge an ungrounded lesson (ground-truth.ts:86). See the Prometheus pipeline. - Theia, the wonder-director.
nextWonderswalks the outgoingenables → bridges → specializes → relatededges to surface "the next, better question," andsurpriseMeranks unlearned concepts by the fraction of their prerequisites the learner has already mastered — both read straight off the Atlas store and its Mnemosyne projection (libs/v9/theia/src/theia.ts:31,92). - The experience layer.
buildWonderFrontDoorwrapsresolveWonderinto the tested view-model the "ask a wonder" screen binds to, including the scaffolding depth (prerequisiteCount) and frontier "surprise me" suggestions (libs/v9/experience/src/wonder-front-door.ts:43); the personal Atlas star-map reads the same graph. See delivery and cross-cutting.
The reuse ledger for all of this — which @sophia, @mnemosyne, @kalika, and
@nyx surfaces Atlas leans on, and where the seams are — is catalogued in
the subsystem map and reuse ledger and
the six-layer reuse stack.
Failure modes and edge cases#
- Empty Atlas / no match →
WonderUnresolvedError(fail loud, never invent). - Ungrounded merged node → reported in
skippedwith its invariant reason and left out of the store; the edges touching it are dropped and counted. - Cyclic prerequisite graph →
topologicalSortthrows insideorderByPrerequisite, which catches and falls back to the input order so resolution still returns a set. - Unknown kernel ref → Atlas accepts the leaf (the ref is a valid string), but the downstream Aletheia G2 gate fails loud at recompute time. The binding is honest about absence rather than fabricating a value.
- Edge to a missing endpoint →
AtlasStore.addEdgethrowsedge endpoint "<id>" is not a node in the Atlas(atlas-store.ts:70). - Performance shape (documented, not a bug).
resolveWonderrebuilds the BM25 index and the Mnemosyne projection fromallNodes()on every call (wonder-resolution.ts:96,atlas-store.ts:113). That is O(N) per query and fine at the current in-memory scale; it is a known cost to revisit when a persisted, large Atlas ships (see the spec-only note above).
Related#
- Architecture overview and the monolith hub ../V9_ARCHITECTURE.md
- Subsystem map and reuse ledger and the six-layer reuse stack — where Sophia, Mnemosyne, Kalika, and Nyx plug in
- Prometheus lesson pipeline — Atlas is Stage 0
- Seven gates and Aletheia — the kernel registry and the G1/G2 grounding/accuracy gates
- Hephaestus explorables — concept nodes bound to manipulable kernels
- The lesson artifact and delivery and cross-cutting — what the resolved concept set ultimately becomes