The Rail · Reference & analysis

V9 Wonder & Recall → V10 Rail channel integration

The Rail should reuse V9's real FSRS transition and mastery functions, but it must add a player-scoped persistence and command boundary before exposing a review micro-act:

9sections8 minread1table

On this page

Audit date: 2026-07-17

Scope: RA.6 source-first integration note for FSRS state, due-review selection, active-recall answer write-back, and the review's ≤5s first-truth recommendation.

Integration decision#

The Rail should reuse V9's real FSRS transition and mastery functions, but it must add a player-scoped persistence and command boundary before exposing a review micro-act:

text
gated V9 lesson artifact + player FSRS trace snapshot
  → filter by persisted nextReviewAt
  → rank due concepts within the Rail micro-act budget
  → render the artifact's grounded retrieval prompt from cache
  → collect and ground a score in [0, 1]
  → applyRetrievalCheckpoint (real FSRS-v4)
  → atomically persist card + nextReviewAt + mastery + answer receipt
  → refresh Atlas mastery and the fading-star projection

The existing V9 packages are algorithm and view-model libraries. They do not provide a durable per-learner review repository, a due-card query, a review request/answer transport, an answer scorer, or a progressive/latency-enforced consumer endpoint. Those are real integration seams, not reasons to replace the shipped scheduler with a new one.

Audited public API map#

Layer Public entry points Input Output / Rail role
Mnemosyne FSRS core fsrsReview, fsrsRetrievability, FSRS_DEFAULT_PARAMETERS FSRS card fields, a four-level ReviewGrade, parameters, and caller-supplied epoch time The authoritative FSRS-v4 transition. It updates difficulty, stability, state, reps, lapses, interval, and exact nextReviewAt. Defaults use the published 17 weights, 0.9 requested retention, and a 36,500-day maximum interval.
Mnemosyne queue helpers prioritizeReviews, calculateReviewLoad Candidate card summaries plus an explicit time budget, or cards with next-due instants prioritizeReviews ranks urgency and caps count; it does not filter for due time. Its source default is 15 seconds per card, not 40. calculateReviewLoad forecasts counts but is not a repository query.
V9 FSRS binding ConceptTraceCard, newConceptCard, reviewConcept, conceptRetrievability, atForgettingFrontier A concept trace and caller-supplied clock Binds a V9 concept to the shared FSRS engine and projects retrievability/mastery. ConceptTraceCard is a value object; it has no learner id, concept id, revision, or nextReviewAt.
V9 answer transition retrievalScoreToGrade, applyRetrievalCheckpoint, buildAtlasMasteryMap Concept id, current trace, numeric recall score in [0,1], and answer instant Maps 0.95/0.75/0.5 thresholds to easy/good/hard/again, executes the real FSRS review, and returns the updated trace, mastery, interval, and next-review ISO instant. Invalid scores throw.
Lesson contract V9LessonArtifactSchema, V9MnemosyneScheduleSchema Forged lesson data Carries one gated retrieval prompt with a validated claimRef, plus checkpoint ref, interval, and next-review instant. It is immutable lesson content/schedule metadata, not mutable player FSRS state.
Prometheus forge forgeLesson Wonder, Atlas, writer/gates, optional input trace, and a numeric retrieval score Calls applyRetrievalCheckpoint during Stage 6 and bakes its schedule into the artifact. It awaits the complete forge and does not expose a progressive first-answer result. It is not the runtime answer command.
Artifact cache LessonCache, InMemoryLessonCache, compileLesson Profile-class content cache key and immutable artifacts Reuses gated lesson content. The shipped implementation is process-local, and the cache lookup occurs at Stage 8 after earlier forge work, so it is not yet a cache-first consumer service.
Consumer projections buildLessonPlayer, planLessonDelivery Publishable V9 lesson artifact Expose the artifact's retrieval prompt and refuse ungated lessons. Neither accepts an answer or updates scheduler state.
Separate SM-2 path buildRetrievalCheckpoint Lesson id, SM-2 ease/interval/repetition state, 0–5 grade, clock Produces a real SM-2 checkpoint for the separate lesson-explorables assembly/gate path. It is not the FSRS player trace the Wonder & Recall Rail should update.

Where FSRS state lives today#

The complete live V9 trace shape is ConceptTraceCard:

ts
interface ConceptTraceCard {
  difficulty: number;
  stability: number;
  state: 'New' | 'Learning' | 'Review' | 'Relearning';
  reps: number;
  lapses: number;
  lastReviewAt: number | null;
}

Today this state lives only in caller-owned values. Prometheus optionally accepts one as ForgeLessonInput.retrievalCard; tests construct it in memory; applyRetrievalCheckpoint returns a replacement value. No audited V9 or Mnemosyne module durably associates that value with a learner and concept. The InMemoryLessonCache stores immutable lesson artifacts, not player traces.

The Rail integration therefore needs a durable, player-scoped record at least equivalent to:

ts
interface WonderRecallTraceRecordV1 {
  learnerId: string;
  conceptId: V9ConceptId;
  lessonArtifactId: string;
  checkpointRef: string;
  card: ConceptTraceCard;
  nextReviewAt: string;
  mastery: MasteryLevel;
  revision: number;
  updatedAt: string;
}

lessonArtifactId and checkpointRef bind the mutable trace to the immutable, gated prompt. revision is required for compare-and-swap answer commits. The exact nextReviewAt returned by FSRS must be persisted alongside the reduced V9 trace because ConceptTraceCard itself omits it.

Exact due-time rule#

The due query must use nextReviewAt <= now before it calls prioritizeReviews. The queue helper ranks every card it receives and has no due-time field, so passing the entire learner deck would surface future cards.

atForgettingFrontier remains useful for the gentle fading-star projection, especially for cards in Review, but it cannot be the only runtime due rule. For an again transition, the shared FSRS state machine may return a same-day interval and an exact nextReviewAt; the reduced ConceptTraceCard plus a fresh retrievability calculation does not preserve that learning-step deadline. The transition result's explicit instant is authoritative.

After due filtering, prioritizeReviews can rank mapped stable card ids within an explicit Rail budget. The implementation defaults to 15 seconds per card. No audited V9/Mnemosyne source implements the ambient specification's 40-second figure. The later adapter task should pass its own measured estimate and cap one micro-act to ≤30 seconds rather than relying on either prose value.

How a review is requested#

There is no current ReviewRequest API. The available pieces establish the following honest request path:

  1. Read the learner's trace records whose persisted nextReviewAt is due.
  2. Rank only those records with prioritizeReviews and a caller-supplied duration estimate.
  3. Load each bound V9LessonArtifact from durable/cache storage.
  4. Require isV9LessonPublishable(artifact) before delivery.
  5. Resolve artifact.assessment.retrievalCheck; its contract-validated claimRef must index artifact.groundTruth.claims.
  6. Return a player-scoped request id, concept/artifact/checkpoint bindings, expected trace revision, prompt, and expiry. Do not include the referenced truth in the pre-answer tile payload.

A cold-cache-safe request needs enough immutable context to replay exactly the same card. A generic "latest review" deep link would allow the prompt and trace revision to drift between the glance and the micro-act.

How an answer is applied#

There is no current answer transport or free-text scoring API. The only live V9 scheduler input is a numeric recall score in [0,1]. The adapter must not silently turn arbitrary prose into a plausible score.

An answer command should:

  1. validate learner, request id, concept id, artifact id, checkpoint ref, expected revision, answer instant, and one-use/idempotency key;
  2. evaluate the response through an explicit grounded scorer bound to the artifact claim, or through a separately product-approved explicit self-grade action—never a hidden heuristic;
  3. reject non-finite or out-of-range scores before the scheduler call;
  4. call applyRetrievalCheckpoint(conceptId, current.card, score, answeredAt);
  5. atomically compare-and-swap the prior revision to the returned card, nextReviewAtIso, and mastery; and
  6. retain an idempotent answer receipt so a retry cannot increment reps or lapses twice.

The updated trace set can then flow through buildAtlasMasteryMap for future wonder scoping. The immutable lesson artifact must not be rewritten with player-specific state.

Concurrent/stale answers must fail closed. Recomputing from a stale card and blindly overwriting would lose a review transition; replaying the same answer would advance the FSRS state twice.

FSRS and SM-2 representation boundary#

V9 contains two real spaced-repetition paths:

  • the live concept mastery loop uses FSRS through @oshun/v9-mnemosyne-glue; and
  • @oshun/v9-lesson-explorables creates an SM-2 checkpoint for its separate assembled V9Lesson release-gate path.

The current Prometheus V9LessonArtifact forge uses the FSRS checkpoint path, despite older feature prose describing the artifact schedule as SM-2. The Rail must follow executable source: update ConceptTraceCard via applyRetrievalCheckpoint. It must not cast the SM-2 { easeFactor, interval, repetitions } record into FSRS fields, and it must not treat a forged artifact's schedule as a shared mutable player record.

Prometheus currently asks for retrievalScore while forging the lesson, before the runtime learner sees its retrieval prompt. That is a content-generation schedule input, not evidence that a user answer endpoint exists. Rail answers belong in the new atomic trace store and should not rerun forgeLesson.

The ≤5s first-truth SLO#

The original recommendation in V9_PRODUCT_REVIEW_2026-07-07.md §4.1 is for the ask-a-wonder front door: render a grounded, pinned first answer within five seconds, then progressively assemble the richer lesson. It is not a shipped scheduler guarantee and is not currently measured in code.

Three source facts matter:

  1. forgeLesson returns only after resolve, ground, plan, write, explorable, schedule, gates, compile, and cache; it has no partial grounded-answer event.
  2. InMemoryLessonCache is neither durable nor consulted before those earlier forge stages.
  3. buildLessonPlayer/planLessonDelivery synchronously project an already available gated artifact, but no consumer endpoint measures activation to first truth.

For Wonder & Recall, the ambient specification deliberately adopts the same number for a narrower cached path: Rail activation → first grounded, reviewable prompt rendered. The implementation should measure firstCardRenderedAt - activatedAt < 5_000 ms, including trace/artifact cache read and render. It should not include answer scoring or write-back, which occur after first truth, and it must never invoke a novel lesson forge on this critical path.

The prompt is truthful only when it comes from a publishable artifact and its validated claim binding. A fast invented question or unverified stale prompt does not satisfy the SLO. If the trace or artifact is unavailable, the channel should expose an honest unavailable/empty state rather than minting content inside the five-second budget.

Required Rail-owned ports#

The subsequent adapter can keep scheduler math in V9 and make infrastructure ownership explicit with narrow ports:

ts
interface WonderRecallTraceStore {
  readDue(
    learnerId: string,
    at: string,
    limit: number
  ): Promise<WonderRecallTraceRecordV1[]>;
  commitAnswer(
    command: AtomicWonderRecallAnswer
  ): Promise<CommittedWonderRecallAnswer>;
}

interface GatedLessonArtifactReader {
  readPublishable(artifactId: string): Promise<V9LessonArtifact | null>;
}

interface GroundedRecallScorer {
  score(input: BoundRecallAnswer): Promise<number>;
}

The store owns isolation, revisions, and idempotency. The artifact reader owns durable/cache lookup and publishability enforcement. The scorer owns how a human response becomes the numeric scheduler input. None of these ports may replace applyRetrievalCheckpoint; they prepare and persist its real result.

Verification seams for the adapter tasks#

The next checkboxes should prove these source-derived invariants:

  • a known FSRS card/score/clock round-trip matches the pinned Mnemosyne outputs, persists the returned exact due instant, and changes that instant after the answer;
  • stale and replayed answer commands do not advance the card;
  • future cards are filtered before urgency ranking, while due cards respect the Rail's ≤30-second micro-act cap;
  • prompt delivery refuses missing, ungated, mismatched, or changed artifacts and never leaks the bound truth before answer;
  • the player tile uses retrievability only as non-punitive fading and absence never mutates or deletes trace state; and
  • desktop/mobile browser automation measures cached activation-to-card render under 5,000 ms, with the scorer/write path outside that measurement.