Architecture
July 1, 2026 · View on GitHub
learn is two engines sharing one accountability spine. This page maps the organs, the verbs
between them, and where the integrity line actually lives in the code, not just in prose.
The one shape underneath both engines
Every capability in learn, credential logistics and the teach-you loop alike, is built from the
same three primitives:
witness ── content-addressed hashing of what actually happened (sha256hex, observe)
ledger ── a hash-chained, tamper-evident append log (Ledger.append / .verify)
gate ── default-deny decision over a step (decide -> allow | deny | needs-human)
Nothing in learn records a fact without a witness, nothing accumulates state outside a ledger,
and nothing graded proceeds without passing the gate. This is the same shape the rest of Project
Telos uses: perceive, recover an invariant, check it, emit a verdict you can re-derive rather than
take on trust. Here the "artifact" is a study session or a course run, and the invariant is "did
the operator's own work satisfy the threshold," never a guess the tool supplies for them.
The credential engine
workflow (declarative steps)
│
▼
gate.decide(step) ──► deny (undeclared kind, nothing actuated)
│ needs-human (assess, credentials, payment, CAPTCHA, submit-without-auth)
▼
actuate (FakeDriver | NativeDriver)
│
▼
witness (before/after snapshot digest) ──► ledger.append
│
▼
receipt (json + markdown + html: logistics vs. human assessment vs. aid visualizations)
workflow/schema.mjs: the declarative step schema.STEP_KINDSis the engine's global allowlist; a step outside it is denied before it ever reaches a driver.accountability/gate.mjs:decide(step, {sealedKinds, allowIrreversible}).assessalways returnsneeds-human, unconditionally. Asubmit(or anything cost/irreversible-flagged) only proceeds automatically when the operator has explicitly authorized "witnessed-auto" for the run; otherwise it halts too.assessis never gated by that flag; it is not a submission mode concern, it always halts.runtime/runner.mjs:run/resume. Walks workflow steps, asks the gate first, actuates only onallow, snapshots before/after every actuated step, and witnesses the digest into the ledger. Adenyorneeds-humandecision stops the loop immediately; nothing after that point is actuated.actuation/:FakeDriver(deterministic, offline, used by every test and bydoctor) andNativeDriver(real browser control vianative-control, used only for the operator-run smoke indocs/smoke.md).adapters/: aSource-style seam (getAdapter(name)) so each course platform's "how do I read the completion certificate" logic stays isolated from the runner. Shipped:generic(config-driven) and an LMS pack (Coursera, Udemy, LinkedIn Learning, edX, Credly, Microsoft Learn, NonprofitReady, and a generic self-paced fallback). No adapter contains grading logic; they only read what already happened.receipt/receipt.mjs: folds the ledger into three structurally separate channels (humanAssessments,manualSubmissions/witnessedAutoSubmissions,aidVisualizations), so a render can never be filed as if it were graded human work.
The tutor / learning loop
tutor.newSession(topic, objectives)
│
▼
tutor.recordAttempt ── the operator's OWN answer to a practice prompt, scored true/false
│
├──► schedule.due (spaced repetition: what's due for review, most-overdue first)
├──► misconception.misconceptions (ranked aggregation of the operator's own wrong attempts)
├──► retrieval.clozePrompts / interleave (blanked recall prompts from the operator's OWN
│ assist-extracted claims; deterministic mixed order)
├──► predict.recordPrediction / scorePrediction (predict-then-observe, pending until scored)
├──► explain.explanationThesis / gradeExplanation (self-explanation via crucible MATCH/DRIFT)
└──► map.normalizeObjectives / learningPath / readiness (prerequisite gating)
│
▼
tutor.mastery(session) ── ready iff EVERY objective has >= minAttempts and >= threshold accuracy,
│ computed ONLY from session.attempts (never a render, never a pending
│ prediction, never a visualization)
▼
study.studyPlan / studyReceipt ── composes all of the above into one plan and one witnessed,
hash-chained study record
tutor/tutor.mjs: the session shape and the mastery-gate itself.recordAttemptis the only way an attempt enters the log, and it always coercescorrectto a boolean (!!correct), so anull/pending value can never be silently read as true.tutor/schedule.mjs: an SM-2-lite / Leitner ladder oversession.attempts. Pure function of the practice log and an explicitnow; no wall clock, no hidden state.tutor/misconception.mjs: aggregates wrong attempts (correct === false) per objective, ranked by count. It surfaces nothing the operator did not already see; it never fabricates a "correct answer" field.tutor/retrieval.mjs:clozePromptsblanks a salient span out of a claim the operator's ownassist()already extracted, carrying asourceso the operator can check themselves afterward.interleaveis a seeded (mulberry32), deterministic shuffle, neverMath.random.tutor/predict.mjs:recordPredictionstores the operator's prediction as a pending attempt (correct: null);scorePredictionrequires an explicit index and throws rather than silently no-op-ing on an already-scored or invalid attempt.tutor/explain.mjs: wraps the operator's own explanation into a crucible thesis (viaassist+toCrucibleThesis) and buckets the returned MATCH/DRIFT/UNVERIFIABLE verdicts into grounded/shaky/unverifiable. It supplies no answer key; the falsification field stays for crucible or the operator to fill in.tutor/map.mjs: normalizes objectives (plain string or{id, text, requires}), computes a topologicallearningPath, and gatesreadinesson whether every prerequisite is itself mastered (readingmastery()'s ownreadyflag, adding no new judgment about correctness).tutor/study.mjs: the orchestrator. Composesdue,misconceptions,interleave,readiness, andmasteryinto onestudyPlan, andstudyReceipthash-chains the practice log underneath it into a witnessed, tamper-evident record.tutor/tutorstore.mjs: session persistence (load/save by id), so a study session survives across CLI invocations without a database dependency.
The visualization bridge (aid, never graded)
concept ──► toTelosSceneSpec (pure, no I/O) ──► telosRender (spawns LEARN_TELOS_CMD) ──►
render result, always tagged provenance:"aid"
│
▼
toAidLedgerEntry ── kind:"aid-visualization", structurally distinct from every graded-channel kind
│
▼
recordVisualization(session, ...) ── attached to the study log, NEVER read by mastery()
interop/telos.mjs delegates rendering to an external telos engine process over
LEARN_TELOS_CMD. It never imports telos internals and never chooses the render profile; it asks
over a process boundary and is fail-closed: no command configured returns UNVERIFIABLE
immediately, never a guess dressed as a pass. Every render carries provenance: "aid" all the way
into the ledger and the receipt, so it can never be misread as a graded answer or a human
assessment.
Where the integrity line actually lives in code
The rule ("no learning-aid capability may produce, hint, or auto-fill an answer to a graded
assessment; mastery() is a function of the operator's own practice only") is enforced at these
concrete points, each with a falsifiable test:
accountability/gate.mjs:decidereturnsneeds-humanforassessunconditionally; no flag, mode, or caller argument can move it toallow.runtime/runner.mjs: aneeds-humanordenydecision halts the loop before any actuation for that step;doctor.mjsasserts this halts with zero driver actions.tutor/tutor.mjs:mastery()reads onlysession.attempts; recording a visualization or a pending prediction changessession.visualizationsor the attempt count, never the accuracy of an already-scored objective.tutor/predict.mjs: a pending prediction iscorrect: null, coerced tofalsebyrecordAttempt's own boolean cast; it is never silently counted as correct.receipt/receipt.mjs: aid visualizations are filed underaidVisualizations, structurally separate fromhumanAssessments,manualSubmissions, andwitnessedAutoSubmissions.doctor.mjs: re-runs checks 1 to 5 at runtime and reportsMATCH/DEGRADED, so a future change that weakens any of them fails loudly instead of silently.
Peer composition
learn composes with gather (source receipts feed the assist pillar's citations), crucible
(measured claim evaluation powers self-explanation grading), and the telos engine (renders
learning aids over a process boundary). None of these are imported as internals; each is a clean
seam that defaults to absent (fail-closed) rather than a hard dependency, so learn stands alone
with zero external packages while still composing with the rest of Project Telos when those tools
are present.
See also: How it works for the study loop walked step by step, and Enterprise Readiness for the operational contract.