Metis · Architecture

The Prometheus Lesson-Generation Pipeline

A focused page within the Metis Architecture documentation. The full map and every sibling page live in the Architecture hub.

6sections15 minread1diagram1table

On this page

Prometheus is V9's lesson forge: the function that turns a learner's free-text wonder — "how old is the universe?", "why is the night sky dark?" — into a grounded, interactive, provenance-stamped V9LessonArtifact, with no human authoring per lesson. Its governing discipline is three words, inherited from V8's "propose/dispose" mystery generator and retargeted from fair mystery to true explanation: solve-first, grounded, gated. The verified skeleton — the grounded claims and the kernel-recomputed numbers — is built from the real Atlas nodes before any prose exists, and the language model is constrained to realize that skeleton rather than invent it ("the LLM never invents a fact or a number", libs/v9/prometheus/src/ground-truth.ts:7). The whole pipeline is one async function, forgeLesson (libs/v9/prometheus/src/pipeline.ts:111), and it adds no new agent loop — it composes the already-shipped V9 packages (Atlas resolve, the Nyx/Kalika kernels, Hephaestus explorables, the Mnemosyne mastery loop, Aletheia + the seven contract gates) and treats the LLM writer as an injected boundary, not a bespoke orchestrator (libs/v9/prometheus/src/index.ts).

Why this shape? Because in V9 trustworthiness and cost are the same problem. A lesson is a reproducible artifact with a hard split — a verified skeleton that is authoritative and immutable once gated, and a regenerable surface (the prose, the Socratic dialogue, the media) that can be re-skinned over the same proven content. That split is encoded directly in the artifact as skeletonHash vs surfaceHash, and it is what lets V9 gate a popular lesson once and serve it many times, and swap a different teacher persona over identical verified truth. This page is the deep, stage-by-stage companion to the forge summary in ./overview.md; the section hub is ../V9_ARCHITECTURE.md.

What ships, honestly#

The forge is real and end-to-end tested, but the monolith's nine-stage table (../V9_ARCHITECTURE.md §3) is in places aspirational about who does the work, so this section separates the three honesty tiers. Honest "planned/seam" beats fake "shipped."

Implemented and tested (real today). forgeLesson composes Stages 0, 1, 2, 3, 5, 6, 7, and 8 end-to-end into a publishable artifact, and a single end-to-end test forges "how old is the universe?" all the way through — real ~13.8 Gyr ground truth, all seven gates, determinism, the profile-class cache, the block path, and the fail-loud path (libs/v9/prometheus/src/pipeline.spec.ts). Stage 1 recomputes STEM values from the genuine @kalika/cosmology Friedmann solver and @nyx/constants (libs/v9/aletheia/src/kernel-evaluators.ts:33); Stage 6 drives the real @mnemosyne/core FSRS engine (libs/v9/mnemosyne-glue/src/mastery-feedback.ts:42); Stage 8 emits a contract-validated V9LessonArtifact with deterministic hashes, an immutable ledger entry, and a profile-class cache (libs/v9/prometheus/src/compile.ts:152).

Spec-described / not wired into the forge. Two stages from the monolith table are not executed inside forgeLesson. Stage 4 "Realize media" (Isis + Euterpe + Psyche) is absent — the pipeline accepts media as a pre-built V9Media input and defaults it to { visuals: [] } (libs/v9/prometheus/src/pipeline.ts:213); Prometheus imports nothing from libs/isis, DOMAINS/euterpe, or DOMAINS/psyche. Stage 9 "Deliver" is a separate module, planLessonDelivery in @oshun/v9-chiron (libs/v9/chiron/src/lesson-delivery.ts:59), not called by the forge. The monolith also credits Stage 2 to a "Yemaya HTN planner" (reusing libs/yemaya/agents/planning + libs/yemaya/autonomous-pipelines) and Stage 3 to libs/hathor/llm-npc + libs/iris/conversation-*; in the actual code plan.ts is a self-contained deterministic decomposition that imports none of Yemaya, and write.ts defines an injected writer interface that imports none of Hathor or Iris. Those directories exist in the repo, but Prometheus does not depend on them (its package.json lists only @oshun/contracts and the v9-aletheia/atlas/hephaestus/mnemosyne-glue/lesson-gates packages). Treat "HTN-planned on Yemaya, written by Hathor over Iris" as architectural framing, not a wired call graph.

Provider-gated injected seams (fail-loud, not faked). Several inputs are typed boundaries the forge injects rather than fakes: the LLM lesson writer (the Iris boundary, Stage 3 — absent ⇒ LessonWriterNotConfiguredError, libs/v9/prometheus/src/write.ts:37), the optional Nous dense embedder in resolution, the LLM-as-judge quality panel for G5 (whose default is an honest deterministic structural proxy, libs/v9/prometheus/src/gates.ts:77), and the Metis curriculum-policy + Sekhmet age safety seams for G6. Two further honesty notes: G7 verifies that the hashes and a ledger id are present and well-shaped, not that real cryptographic C2PA signing occurred (the explorable's c2paSigned defaults to false); and the supplied ledger/cache implementations are InMemory* (libs/v9/prometheus/src/compile.ts:65, :86), durable stores being the caller's to inject.

The forge contract: input, output, and the block rule#

Everything the forge needs arrives in one ForgeLessonInput (libs/v9/prometheus/src/pipeline.ts:53). The required fields are the wonder, the AtlasStore, the learnerProfileClass, a seed, a caller-supplied clock (nowUnixMs, "no fabricated time"), a signedAtIso provenance timestamp, an opaque runId (captured as [x] evidence, not part of the artifact), the explorableBuilder, and the retrievalScore. Everything else is an optional injected boundary or tuning input:

Field Stage Role
learnerMastery, embedder 0 mastery map to scope the frontier; optional Nous reranker
claimTextByConcept 1 override a node's default claim sentence
writer, misconceptionAnticipator 3 the Iris writer boundary; the G3 misconception pass
explorableBuilder (required) 5 the Hephaestus binding for the chosen concept
retrievalCard, retrievalScore, flowHistory 6 FSRS card + recall score + IRT history for the flow band
qualityJudge, safetyOptions, humanReviewed, nextWonders 7 the G5/G6 seams and the Theia thread
media, cache, ledger 8 pre-built media (Stage 4 is not run), durable stores

The output is a ForgedLessonResult (pipeline.ts:96): always runId, blocked, blockedGateIds, the seven gates, the aletheia report, and the resolved conceptSet; and — only when every gate passed — the compiled artifact, its cacheKey, and servedFromCache. The block rule is hard and central: blockedGateIds is the list of gate ids whose verdict did not pass, and if it is non-empty the forge returns early with blocked: true and no artifact is compiled, delivered, or cached (pipeline.ts:232). A blocked lesson never reaches Stage 8.

The flow, stage by stage#

flowchart TD W["wonder: string"] --> S0 subgraph forge["forgeLesson (pipeline.ts:111)"] S0["Stage 0 — resolve<br/>resolveWonder"] -->|"WonderResolution<br/>conceptSet (prereq-first) + target"| S1["Stage 1 — ground<br/>buildGroundTruth"] S1 -->|"V9GroundTruth + numericClaims<br/>(the verified skeleton)"| S2["Stage 2 — plan<br/>planLesson"] S2 -->|"LessonPlan: beats, assessmentClaimRef,<br/>explorableConceptId, personaRef"| S3["Stage 3 — write<br/>realizeNarrative"] S3 -->|"V9Narrative (claim-constrained)"| S5["Stage 5 — explorable<br/>explorableBuilder"] S5 -->|"V9Explorable"| S6["Stage 6 — assess<br/>applyRetrievalCheckpoint"] S6 -->|"schedule + assessment + flowChannel"| S7{"Stage 7 — gate<br/>composeGates → G1..G7"} S7 -->|"any verdict fails"| BL["blocked: true<br/>no artifact, not cached"] S7 -->|"all seven pass"| S8["Stage 8 — compile<br/>compileLesson"] S8 -->|"V9LessonArtifact + cacheKey"| OUT["ForgedLessonResult"] end EMB(["WonderEmbedder — Nous (optional)"]) -.->|rerank| S0 KER[["Nyx / Kalika kernels (real)"]] -->|recompute| S1 WR(["LessonWriter — Iris boundary"]) -.->|injected; absent ⇒ fail loud| S3 SAF(["Metis policy + Sekhmet"]) -.->|injected| S7 QJ(["QualityJudge — LLM panel; default structural"]) -.->|injected| S7 MEDIA[/"media: V9Media — passthrough (Stage 4 not run)"/] -.->|input| S8

Each stage takes the prior stage's typed artifact and produces the next; the verified skeleton from Stage 1 is the spine that every later stage references.

Stage 0 — resolve (Atlas)#

resolveWonder (libs/v9/atlas/src/wonder-resolution.ts:86) runs real Sophia BM25 lexical retrieval over the Atlas node text, optionally blended 50/50 with an injected Nous dense embedder, picks the top candidate as the target, then asks the real Mnemosyne engine for that target's prerequisite frontier filtered by the learner's mastery (identifyKnowledgeGaps). The output is the minimal teaching set in prerequisite order, then the target — cognitive-load discipline, not the whole subtree. A wonder that matches nothing throws WonderUnresolvedError rather than inventing a concept. The deep account is ./atlas-knowledge-graph.md.

Stage 1 — ground / solve-first (the verified skeleton)#

buildGroundTruth (libs/v9/prometheus/src/ground-truth.ts:43) walks the resolved concept set and builds the authoritative skeleton from the grounded Atlas nodes, never from the model. Each fact node with grounding pins contributes a V9GroundedClaim carrying up to 16 of its Sophia pins, the node's epistemicStatus, and truth: 'true' (ground-truth.ts:54). Each STEM kernelRef on a node is recomputed through the kernel evaluator — compute, don't assert — yielding a V9KernelResult with a contentHash over [ref, params, value] and a parallel numericClaim for the G2 accuracy gate (ground-truth.ts:64). The default evaluator is a registry of real in-repo computations: kalika:cosmology#ageTodayGyr calls ageTodayGyr(PLANCK_2018_COSMOLOGY), nyx:constants#speedOfLight returns the real constant (libs/v9/aletheia/src/kernel-evaluators.ts:33). If no grounded fact node is in scope, the stage throws "refusing to forge an ungrounded lesson" (ground-truth.ts:86) — there is nothing to teach, so nothing is fabricated.

Stage 2 — plan (deterministic HTN decomposition)#

planLesson (libs/v9/prometheus/src/plan.ts:35) decomposes the skeleton into a LessonPlan deterministically — the same (skeleton, seed) always plans the same lesson, which is half the determinism lever. The beat outline is Hook → Establish[i] for each grounded claim → "Why it matters" → "The next question" (plan.ts:39). The retrieval checkpoint is pinned to the most-credible claim — the claim whose highest pin credibility is greatest (assessmentClaimRef, plan.ts:47) — so the recall item probes the best-supported fact. The explorable defaults to the last concept in the set (the target), and the persona defaults to chiron:warm-generalist (libs/v9/chiron/src/persona.ts:35). Despite the monolith's "Yemaya planner" framing, this is a self-contained function; it is HTN in spirit (decompose a goal into ordered beats), not a call into the Yemaya planning library.

Stage 3 — write (the injected Iris boundary)#

realizeNarrative (libs/v9/prometheus/src/write.ts:77) is where the LLM enters — and where the forge refuses to fabricate. With no writer wired it throws LessonWriterNotConfiguredError (write.ts:78) rather than emit a hand-built lesson. The LessonWriter is a typed boundary: write({ wonder, plan, groundTruth }) → LessonDraft. The realization is then constrained to the grounded skeleton: every Socratic turn's claimRefs must index a real claim, and an out-of-range ref throws (write.ts:87); a draft with no beats is rejected. A misconception-anticipation pass (the G3 input) merges the writer's flagged misconceptions with a default anticipator that flags every non-established claim as misread-prone and pairs it with its grounded correction (defaultMisconceptionAnticipator, write.ts:49), deduped by text with every claimRef validated. The result is a V9Narrative whose every assertion traces back to a verified claim.

Stage 4 — realize media (not run by the forge)#

The monolith assigns visuals (Isis), score (Euterpe), and the Chiron face/voice (Psyche) to a Stage 4. In code the forge skips it: media is a pre-built input defaulting to { visuals: [] } (pipeline.ts:213). Media generation is therefore out-of-band today — the forge will carry and hash whatever media you hand it (it feeds the surfaceHash), but it does not generate it.

Stage 5 — build explorable (Hephaestus)#

The forge calls the injected explorableBuilder(plan.explorableConceptId) (pipeline.ts:147) to bind a manipulable surface to the chosen concept. The P1 builders are real computation: a Kalika orbit wrapping the velocity-Verlet symplectic integrator, or a Nyx sky integrating the real Sun position. The load -bearing field is the explorable's success state, whose reachable flag is set true only when a kernel result or headless verifier actually demonstrated it — never asserted blind. Stage 7's G4 verdict is computed from exactly these explorables; see ./hephaestus-explorables.md.

Stage 6 — assess & schedule (the Mnemosyne loop)#

applyRetrievalCheckpoint (libs/v9/mnemosyne-glue/src/mastery-feedback.ts:42) turns the retrievalScore into an FSRS review grade (≥0.95 easy, ≥0.75 good, ≥0.5 hard, else again, mastery-feedback.ts:21), runs the real @mnemosyne/core fsrsReview over the concept's trace card, and schedules the next review at the forgetting frontier — the moment recall is hardest-but-possible (the testing effect, knowledge-trace.ts:62). The forge records the schedule (mnemo:<concept>:int<days>d, intervalDays, nextReviewAtIso) and an assessment whose retrieval prompt is Recall: <the central claim text> keyed to plan.assessmentClaimRef. If flowHistory is supplied, tuneDifficultyToFlow (libs/v9/mnemosyne-glue/src/flow-channel.ts:70) uses the IRT ability MLE to recommend the next item's difficulty and classify it into Csíkszentmihályi's channel (success probability in [0.6, 0.85] is flow); that channel is handed to G3. The recall result also projects to a MasteryLevel that closes the loop back to Atlas (buildAtlasMasteryMap, mastery-feedback.ts:73), so a mastered concept drops out of the next wonder's frontier.

Stage 7 — gate (the seven contract verdicts)#

composeGates (libs/v9/prometheus/src/gates.ts:132) assembles the canonical V9Gates from four real evaluations. G1 grounding, G2 accuracy, and G6 safety come from runAletheia, which runs the three in parallel (libs/v9/aletheia/src/aletheia.ts:47): G1 blocks any claim without a pin of credibility ≥ 0.5 (grounding-gate.ts:43); G2 recomputes every numeric claim and blocks on an unknown kernel ref or a tolerance miss — "you cannot ship a number you cannot reproduce" (accuracy-gate.ts:66); G6 requires an epistemic-status label on every claim, applies the injected Metis/Sekhmet seams, and forces a human-review checkpoint for the high-stakes disciplines religion, psychology, and neuroscience (safety-gate.ts:19, :137). G3 pedagogy (gates.ts:36) fails on a prerequisite-coverage gap, on zero anticipated misconceptions, or on a difficulty out of the flow band. G4 completeness is the Hephaestus DoD — ≥1 computed-kernel explorable that reached its success state (evaluateExplorableDoD, explorable-runtime.ts:24). G5 quality (gates.ts:88) thresholds a judge score at 0.7; the default judge is an honest structural proxy that rewards ≥3 beats, ≥1 Socratic turn, ≥1 misconception, and a "next question" beat (gates.ts:77) — a real LLM panel is the injected qualityJudge. G7 provenance (gates.ts:105) checks the skeleton/surface/ cacheKey hashes are 64-hex and the ledger id is present.

On failure, Aletheia attaches a regenerate-with-direction routing label to each failing gate — reground, regenerate-with-corrected-ground-truth, re-scope, or block-or-escalate (libs/v9/aletheia/src/verdict.ts:11) — and the forge returns those in the aletheia report. Note the honest boundary: the forge computes and returns the routing, but does not itself run an automated regenerate loop or HITL round; the caller acts on the actions. The two-framing detail (this canonical G1–G7 vs the separate @oshun/v9-lesson-gates suite, which Prometheus does not call) is in ./seven-gates-and-aletheia.md.

Stage 8 — compile & cache (only a passing lesson)#

compileLesson (libs/v9/prometheus/src/compile.ts:152) runs only when every gate passed. It first checks the profile-class cache by cacheKeyFor([...conceptSet].sort(), learnerProfileClass, seed) — a sha256 over the order-independent concept set, the profile class, and the seed (compile.ts:31); a hit returns the cached artifact with servedFromCache: true and no recompilation. On a miss it stamps the C2PA bundle — skeletonHash = sha256(groundTruth), surfaceHash = sha256([narrative, media]), and a content-derived ledgerEntryId (ledger:<cacheKey…>, not run-derived, so the same inputs reproduce the same id) — then parses the result through V9LessonArtifactSchema (lesson.ts:190), so the contract's own superRefine re-validates that every claimRef indexes a real claim. The entry is appended to the immutable ledger (which never overwrites, compile.ts:67) and cached. Back in the forge, a defensive check asserts the contract's isV9LessonPublishable agrees with the gate sum, throwing if they ever disagree (pipeline.ts:257). Because previewProvenanceBundle is deterministic and called both for the G7 gate and inside compile, G7 verifies exactly the bundle Stage 8 stamps. The full artifact anatomy is in ./lesson-artifact.md.

Stage 9 — deliver (a separate Chiron module)#

Delivery is not part of forgeLesson. planLessonDelivery (libs/v9/chiron/src/lesson-delivery.ts:59) turns a gated artifact into a Chiron-fronted delivery plan under an integrity mode and a resolved delivery tier, and it refuses to deliver an ungated lesson (UngatedLessonError, :48) — nothing un-passed reaches a learner. The live voice/avatar runtimes are themselves injected boundaries. See ./delivery-and-cross-cutting.md.

A worked forge: "how old is the universe?"#

The end-to-end test (libs/v9/prometheus/src/pipeline.spec.ts) is the honest worked example, because it exercises the real composition rather than a narration. An AtlasStore is seeded with one grounded STEM fact bound to the real Kalika cosmology kernel (kalika:cosmology#ageTodayGyr, pinned to Planck-2018) and a cosmology-basics prerequisite. The forge runs with seed: 1234, a fixed clock, profile class curious-adult, a deterministic stand-in writer (no live LLM in the test), the real buildKalikaOrbitExplorable, a retrievalScore of 0.9, and a three-item flow history. The assertions establish the contract end-to-end:

  • A published, gated lesson. blocked is false, all seven gates pass (isV9LessonPublishable is true), and the skeleton holds the recomputed value — groundTruth.computedValues contains the age between 13.6 and 14.0 Gyr, produced by the Friedmann solver, never stored as the literal "13.8".
  • A real, reached explorable. The shipped explorable's kind is computed-kernel and its successState.reachable is true (the symplectic integrator's energy drift was actually measured under tolerance).
  • Determinism. Two forges of the same inputs produce identical skeletonHash, surfaceHash, cacheKey, and artifact id.
  • The cost lever. A second forge against the same cache returns servedFromCache: true with the same lesson id — gated once per profile class, served many times.
  • The block path. When the injected Sekhmet seam denies the content, G6 fails, blocked is true, blockedGateIds contains G6, the artifact is undefined, and the cache holds nothing — a blocked lesson is never cached.
  • Fail-loud. With writer: undefined, the forge rejects with LessonWriterNotConfiguredError instead of fabricating prose.

Failure modes, configuration, and determinism#

The forge is built to refuse rather than fabricate, and the refusals are specific and code-located: an unresolvable wonder throws WonderUnresolvedError (wonder-resolution.ts:56); nothing to ground throws "refusing to forge an ungrounded lesson" (ground-truth.ts:86); a writer that cites a claim outside range throws (write.ts:87); an unreproducible number blocks G2 (accuracy-gate.ts:66); an unreachable explorable blocks G4 (explorable-runtime.ts:33); any gate failure blocks the whole lesson with no compile and no cache (pipeline.ts:232); and a contract/gate disagreement at the finish line throws defensively (pipeline.ts:257).

Determinism is both a provenance and a cost lever, and it rests on three explicit choices in ForgeLessonInput: a seed, a caller-supplied nowUnixMs and signedAtIso (no clock is read inside the forge), and a profile-class cache key rather than a learner id. Numeric tolerances are explicit rather than implied — G2 defaults to a 1e-3 relative match while each V9KernelResult carries its own 1e-6 tolerance (lesson.ts:51) — which is what makes "computed, not asserted" a checkable property and not a slogan. Everything that could vary by environment — the writer, the embedder, the quality judge, the safety policy, the ledger, and the cache — is an injected boundary, so a forge is reproducible exactly to the extent its caller wires deterministic seams.