Skip to content

Education system

Last verified against implementation: 2026-07-15

Vita's education system is a teacher operating system built around a shared curriculum graph. It answers four different questions without collapsing them into one score:

  1. What can the learner do? A per-learner mastery ledger records evidence at levels L1–L4.
  2. What is ready next? A prerequisite-aware frontier exposes the next constrained skills and a separate free-roam territory.
  3. What does the teacher need to do? A workflow store derives a queue for choosing, delivering, assessing, and scoring work.
  4. What keeps learning alive? Printed ed-packet work, ingested ed-artifact evidence, Gumball spaced review, spelling harvests, and fluency re-tests feed back into the ledger.

The system is intentionally not a generic school-management product. It is a small, evidence-first loop for a family: choose work with the learner, make the material, observe the result, record evidence, and let the graph recompute.

Privacy

The live roster, learner identities, writing transcriptions, packet files, and mastery evidence are private. All examples on this public page are sanitized. learner-a is not a real roster key, and example evidence is invented while preserving the real schema.


Why the system is shaped this way

The system replaces three failure-prone habits:

  • choosing worksheet topics ad hoc, without a global view of prerequisites;
  • treating mastery as a completed/not-completed bit; and
  • expecting a human to remember which work is in flight, which timed check is due, and when a skill should be revisited.

The design keeps four concepts separate because each changes for a different reason:

Concept Meaning Canonical store
Curriculum The shared skill graph and its teaching/assessment contract data/education/curriculum/domains/*.yaml
Mastery What one learner has demonstrated, with dated evidence data/education/curriculum/ledgers/<learner>.yaml
Workflow What the teacher has chosen, assigned, or still needs to score data/education/workflow/<learner>.yaml
Re-test state When a fluency probe must run again data/education/retests/<learner>.yaml

A learner can finish a packet without mastering its skill. A learner can retain a skill while the teacher has no active work assigned. A node can be ready on the frontier while no printable instrument exists. Keeping those states separate makes each mismatch visible instead of manufacturing a reassuring but false single status.

System architecture

                         shared curriculum graph
                  domains/*.yaml + requires/helps edges
                                  β”‚
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚               β”‚                β”‚
                  β–Ό               β–Ό                β–Ό
        learner ledger      teacher workflow   build roadmap
       levels + evidence    chosen/assigned/   density + gaps
                            scoring/done
                  β”‚               β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                          β–Ό
              scripts/education/* engines
              frontier Β· queue Β· retests
                          β”‚
                          β–Ό
              vita.education.* capabilities
             read Β· write Β· deferred spawn
                          β”‚
             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β–Ό            β–Ό             β–Ό
       /education UI     agents/CLI   weekly reviews
       Status/Console/   and skills   and reporting
       Map/Graph/Packets
             β”‚
             β”œβ”€β”€ Generate β†’ ed-packet β†’ PDFs β†’ physical work
             β”‚                                β”‚
             β”‚                                β–Ό
             β”‚                          complete + score
             β”‚                                β”‚
             └── Gumball review β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β”œβ”€β”€ SM-2 retention and spelling practice
                    └── read-only engagement signal back to Status

 physical paper/photo β†’ ed-artifact β†’ artifact index β†’ ledger evidence
                                      └──────────────→ spelling harvest

The capability layer is the shared boundary. The React page, CLI, and agents call the same typed operations instead of each reading or rewriting YAML. See Capabilities for the transport, envelope, provenance, and policy model.


Curriculum graph

Domains and traversal

The graph currently has four domains. At the 2026-07-15 verification point it contained 151 nodes: 90 math, 22 reading-and-writing, 12 thinking, and 27 world models. Those counts are live build-state, not a promised fixed taxonomy; the Our map tab computes them on every request.

Domain Traversal Intended shape
Math constrained A relatively dense prerequisite spine
Reading & writing constrained A semi-ordered path from decoding through comprehension and composition
Thinking free-roam Transfer skills exercised inside other work, not a long mandatory checklist
World models free-roam Interest-led breadth with weaker ordering

constrained and free-roam are not visual labels only. They change how the frontier and teacher queue present eligible nodes. Math and reading nodes appear as individual next-edge choices. Thinking and world-model nodes are grouped as β€œopen to explore” so dozens of zero-prerequisite topics do not become a fake to-do list. A missing or unrecognized traversal value defaults to constrained, which is the safer failure mode.

Node model

A node is a packet-rung-sized skill: coarser than one generated exercise, but smaller than a broad academic standard. A node splits when the strategy or representation materially changes, or when an untimed competence skill needs a separate fluency target.

domain: math
traversal: constrained
strands:
  addsub: "Addition & subtraction"
nodes:
  - id: math.addsub.make-a-ten-strategy
    title: "Make a ten strategy"
    strand: addsub
    band: [6, 8]                    # advisory; never gates the frontier
    tier: L3                        # target is L3 or L4
    gates:
      L2: "at least 9/10 fresh items, untimed"
      L3: "retained on a fresh check after a gap of days"
    requires:
      - math.counting.compose-ten
    helps:
      - math.measure.count-up-change
    resources:
      - type: packet
        ref: "make-a-ten"
        record: true
    sources:
      - "curriculum-source Β§addition"
    instrument_gap: false
    representations:
      C:
        move: "build both addends with counters and fill one ten"
        check: "show 8 + 6 with counters"
      G:
        move: "draw a ten-frame and move enough dots to fill it"
        check: "how many dots remain outside the full frame?"
      S:
        move: "write the decomposition and resulting number sentence"
        check: "solve 8 + 6 and name the make-a-ten step"
      N:
        move: "put the addends into a short real-world story"
        check: "how many objects are there altogether?"

Important fields:

Field Contract
id Globally unique <domain>.<strand>.<kebab-slug>
tier The node's target, L3 for retained competence or L4 for rate-based fluency
gates Measurable criteria for L2 and L3; L4 nodes also require an L4 gate
fluency_aim Required exactly when tier: L4; includes metric, target, sustain rule, and source
requires Hard prerequisite edges; must form a directed acyclic graph
helps Soft transfer edges; informative, never frontier gates
resources Packets, Gumball generators, books, curricula, activities, or tests
instrument_gap Honest flag that no automated L3 instrument exists; parent scoring is still valid
representations Optional S/G/C/N ways to teach and check the same skill

The S/G/C/N teaching representations are orthogonal to mastery. They answer β€œhow should this be taught?” while L1–L4 answer β€œhow well is it held?” Packet generation normally teaches in C β†’ G β†’ S β†’ N order: concrete manipulation, graphical model, symbolic notation, then narrative transfer. Each rung contains a teaching move, a fresh-item check, and optionally a down pointer to an entry prerequisite.

Edge semantics and frontier

A node enters a learner's frontier when both conditions are true:

current learner level < node target tier
AND
every requires-prerequisite is held at L3 or above

band is advisory and helps never gates. A prerequisite node whose own target is L4 still unlocks downstream work at L3: automaticity is valuable, but it is not allowed to block the rest of the curriculum.

Each frontier item includes the prerequisite evidence that admitted it. That makes the recommendation explainable and lets the packet library say why a packet is β€œnext up.”

Validation

scripts/education/curriculum_lib.py::validate() checks the structural contract, including:

  • globally unique, correctly shaped IDs and declared strands;
  • no dangling requires or helps targets;
  • no cycles in the hard-prerequisite graph;
  • valid L3/L4 tier and fluency-aim combinations;
  • required bands, gates, and sources;
  • no edge duplicated in both requires and helps;
  • valid S/G/C/N representation keys and resolvable node-like down pointers;
  • ledger references and level values; and
  • warnings for explicitly ungrounded claims plus informational counts for instrument gaps.

Run the validator and a frontier query from the Vita root:

PYTHONPATH=scripts uv run python -m education.curriculum_lib validate
PYTHONPATH=scripts uv run python -m education.curriculum_lib frontier learner-a

The example learner key is sanitized; a live call must use a configured roster key.


Mastery ledger

Mastery is an evidence ladder, not a completion flag.

Level Meaning Evidence bar
L0 Unseen Implicit: there is no ledger entry
L1 Introduced A teaching resource was completed or the idea was explicitly introduced
L2 Capable Accurate on fresh items without time pressure
L3 Competent/retained Still correct after a gap, through Gumball SM-2 evidence or a parent-observed spaced check
L4 Proficient/fluent Rate-based automaticity sustained on separate days; only meaningful for L4-tier nodes

The ledger preserves evidence history. Writing the same level again with new evidence is a valid corroboration, not a duplicate to discard.

kid: learner-a
profile_id: 42
entries:
  - node: math.addsub.make-a-ten-strategy
    level: L3
    as_of: 2026-07-15
    evidence:
      - date: 2026-07-12
        note: "9/10 on fresh items after the packet"
      - date: 2026-07-15
        note: "retained on a new mixed check after three days"
    rate_history: []

Ledger writes must go through vita.education.set_level, vita.education.score, or curriculum_lib.py set-level; they are not routine hand-edits. The write path requires an existing node, an explicit date, an L1–L4 value, and non-empty evidence. It appends the evidence and optional rate record, then atomically rewrites the learner's ledger YAML.

vita.education.score is the common teacher action. It writes mastery first and only then marks the workflow node done. A bad level therefore cannot falsely close the workflow item.

./run capabilities call vita.education.score --input '{
  "kid": "learner-a",
  "node": "math.addsub.make-a-ten-strategy",
  "level": "L3",
  "evidence": "retained on 8/9 fresh items after a three-day gap",
  "as_of": "2026-07-15"
}'

Teacher workflow and action queue

Mastery describes the learner. Workflow describes the teacher's process.

available β†’ chosen β†’ assigned β†’ needs_scoring β†’ done
     β”‚                      β”‚
     └──────────────────────┴────────→ parked
Status Meaning Typical next action
available Implicit default; eligible work has not started Choose with the learner, or administer a test for a fluency node
chosen The learner selected it Deliver a packet or another material
assigned Material was delivered Wait while it is in flight, then score
needs_scoring Work is finished but not evaluated Record level and evidence
done Teacher loop closed None; retained for history
parked Deliberately deferred None

Only non-default or historically meaningful states need an entry:

kid: learner-a
entries:
  - node: math.addsub.make-a-ten-strategy
    status: assigned
    material: "packet:make-a-ten-2026-07-15"
    since: 2026-07-15
    note: "learner picked this from three frontier choices"

The queue is derived every time from frontier Γ— workflow, not stored as a second list that can drift:

  • needs_you_now: chosen work to deliver, finished work to score, and available fluency/test nodes that need an assessment;
  • in_flight: assigned work;
  • ready_to_choose: constrained frontier nodes still available; and
  • free_roam: eligible thinking/world-model nodes grouped as an interest-led count and list.

Test detection is deliberately small: an L4 tier or timed/oral/writing-rate language in the L4 gate/aim produces an administer action. It is a queue hint, not a new mastery rule.


End-to-end learning loops

Loop A: choose, generate, complete, and score a packet

frontier exposes a node
        β”‚
        β–Ό
learner chooses it ── vita.education.choose ── status=chosen
        β”‚
        β–Ό
teacher delivers
        β”œβ”€β”€ assign an existing resource ── status=assigned
        └── generate a packet ── deferred ed-packet run
                                      β”‚
                                      β–Ό
                          PDFs + manifest + review pack
                                      β”‚
                                      β–Ό
                           print and complete on paper
                                      β”‚
                    vita.education.complete_packet
                         β”œβ”€β”€ workflow status=done
                         └── activate Gumball review pack
                                      β”‚
                                      β–Ό
                         vita.education.score separately
                         level + evidence + optional rate
                                      β”‚
                                      β–Ό
                         frontier and graph recompute

Completion and scoring are intentionally two actions:

  • complete_packet means β€œthe physical material was finished.” It closes the workflow item and, by default, starts the packet's spaced-review pack.
  • score means β€œthis evidence supports level Lx.” It changes mastery.

Finishing is evidence of introduction, not proof of retained competence. The completion capability therefore does not set a mastery level automatically.

Loop B: ingest paper evidence with ed-artifact

ed-artifact is the ingest counterpart to ed-packet. It is a skill-driven agent workflow rather than a vita.education.* capability.

photo(s) of paper work
        β”‚
        β”œβ”€β”€ clarify who authored prompts/models and what was copied
        β–Ό
copy original into data/education/artifacts/<learner>/
        β”‚
        β–Ό
append structured record to artifacts/index.jsonl
        β”œβ”€β”€ literal transcription preserving learner spelling
        β”œβ”€β”€ process/handwriting/composition/grammar read
        β”œβ”€β”€ linked curriculum nodes
        β”œβ”€β”€ proposed ledger changes
        └── identity or graph-gap signals
        β”‚
        β”œβ”€β”€ set level through the ledger write path
        β”œβ”€β”€ harvest useful spelling misses into Gumball
        └── update education working memory / daily context

The ingestion read distinguishes copied work from independent production. A model sentence copied accurately can support handwriting evidence, but not independent spelling mastery. A single sample rarely proves L3 retention. Unknown names or places are transcribed literally and resolved with the parent rather than guessed.

A sanitized artifact-index record looks like this:

{
  "id": "learner-a-2026-07-15-weekend-recount",
  "kid": "learner-a",
  "date": "2026-07-15",
  "kind": "guided-recount",
  "prompt": "Write three sentences about the weekend.",
  "context": "adult wrote the prompt; learner wrote the response independently",
  "linked_nodes": [
    "reading-writing.writing-composition.sentence-expansion"
  ],
  "paths": [
    "data/education/artifacts/learner-a/2026-07-15-weekend-recount.jpg"
  ],
  "transcription": "We went to the park...",
  "extracted_read": {
    "composition": "three sequenced events with repeated sentence openings",
    "spelling_errors_orthographic": {
      "frend": "friend"
    },
    "signal": "independent paragraph; sentence-variety remains the growth edge"
  },
  "proposed_ledger": {
    "reading-writing.writing-composition.sentence-expansion":
      "L1 (independent attempt; not yet retained evidence)"
  }
}

Corrections are auditable. If later context changes the interpretation, the record gains a dated corrected field; the original read is not silently rewritten.

Loop C: spaced review and spelling through Gumball

Each generated packet normally produces a Gumball review-pack definition with four to seven concepts following the packet's teaching sequence. The concepts use deterministic generators where possible and include only notation the packet actually taught.

The pack remains inactive while the learner works the physical packet. When vita.education.complete_packet runs, it resolves the review pack by:

  1. explicit manifest.review_pack_slug, when present;
  2. otherwise the packet topic; or
  3. no pack when review_pack_slug is explicitly null.

Activation goes through the running Gumball admin API because it owns SM-2 bookkeeping. It is not a raw activation-row insert. Once active, review concepts can appear in Gumball's daily input_review module.

vita.education.activity reads Gumball's SQLite database in read-only mode to build the Status view. It applies an important signal-quality rule:

  • responses to input_* modules count as real work;
  • auto-recorded content_photo, content_joke, and content_mission views do not count as work; and
  • passive views remain visible but cannot complete the real-work plan.

The ed-artifact spelling harvest is narrower. Clean, common misspellings can enter the learner's spelling bank with the original form and a sentence from the artifact. Proper nouns and low-value one-offs are deliberately skipped.

Loop D: fluency scoring and re-tests

An L4-tier score with a rate automatically invokes the re-test policy. The policy writes one durable record per learner and node:

kid: learner-a
entries:
  - node: math.addsub.add-sub-fluency-10
    verdict: building
    last_run_date: 2026-07-15
    last_rate: 21
    aim: 25
    retest_due: 2026-07-22
    reason: "21 vs a 25 aim β€” close but under; practice, then re-check."
Result Verdict Next action
Completed the timed sheet, or rate β‰₯ 1.2Γ— aim fluent-locked No required re-test
At/over aim on two distinct recorded dates fluent-locked No required re-test
At/over aim on only one date confirming Re-test in 2 days
Under aim building Practice, then re-test in 7 days
No comparable numeric rate, but teacher records L4 fluent-locked Trust the explicit human judgment

vita.education.retests divides records into due, upcoming, and locked. For due/upcoming work it joins the newest packet whose node_id or covers_nodes contains the target, allowing one multi-node fluency kit to serve several checks.


ed-packet: generation, QA, and delivery

The ed-packet skill turns a topic or curriculum node into a publication-quality black-and-white mini-workbook. It can be invoked directly when the user asks to generate and print a packet, or indirectly through vita.education.generate_packet from the Console.

Packet build contract

The automated Console path enqueues a deferred agent run because packet creation includes content design, figure generation, rendering, and review. It returns immediately, marks the node assigned with material packet (generating), and deduplicates the same learner/node/day request. The finished packet appears in the Packets tab when its manifest exists. This path stops after PDF creation; it does not print without a human deciding to do so.

The deferred build prompt's substantive contract is:

1. Fetch vita.education.node for the node + learner before authoring.
2. Use the node's S/G/C/N teaching rungs and current mastery level to design
   the sequence and weight the practice.
3. Build with packetlib and verified programmatic figures.
4. Run an own-pass plus pedagogy, accuracy, and production reviewers;
   adjudicate findings, fix, and re-render.
5. Split packet-main.pdf, certificate.pdf, and answer-key.pdf.
6. Write a node-linked manifest.json under data/education/packets/.
7. Author but do not activate the corresponding Gumball review pack.
8. Do not print or message the user unless the build fails.

Pedagogical sequence

When a node has representations, the packet uses them as its backbone:

  • L0/L1 entry: mostly concrete and graphical work, with a short symbolic landing and one narrative application;
  • L2/L3 consolidation: graphical-to-symbolic transfer carries most of the packet; and
  • L4 fluency push: symbolic/timed volume dominates, with only enough concrete, graphical, and narrative work to preserve meaning.

Every check is a template, not an item to reproduce verbatim. Practice must use fresh instances. The hardest rung gets the most practice; production matters at least as much as recognition; worked examples cannot reappear as the first exercise; scaffolds fade; and one honest misconception trap is paired with a counterexample so the trap does not become a false rule.

Figures and layout

Pedagogically meaningful geometry is programmatic, never generative art. The skill includes verified generators for clocks, coins, fractions, maps, number facts, ten-frames, place-value blocks, comparisons, bars, balance models, spelling, and related worksheet figures. Decorative art may use an image model, but it must remain black-and-white line art and cannot carry the lesson's exact quantity or geometry.

packetlib.py is the shared layout/runtime layer. It creates letter-sized HTML, renders through Chromium, splits the outputs, and rasterizes pages for visual inspection. Exercise prompt, figure, and answer-key entry derive from the same manifest data wherever possible so the answer key cannot disagree with the artwork.

Review and print

The QA loop is deliberately adversarial:

  1. The builder rasterizes and reads every page for overflow, ambiguous figures, collisions, and broken art.
  2. Three reviewers inspect pedagogy, mathematical/content accuracy, and print production.
  3. Findings are adjudicated as a real content error, a perceptual rendering problem, or reviewer error.
  4. The builder fixes, re-renders, and re-reads until the packet is dry.

The outputs are separate because they have different physical roles:

File Printing intent
packet-main.pdf Learner workbook; duplex, long-edge
certificate.pdf Keepsake sheet; simplex
answer-key.pdf Adult reference; simplex and never exposed on a learner page
packet.pdf Optional legacy/combined form found in older packet directories

Direct ed-packet runs may send those files to the configured local printer and wait for the print queue to drain. The /education page instead opens the internal PDF route and leaves the browser/teacher in control of printing.

Manifest

The packet manifest is the join key among curriculum, UI, re-tests, and Gumball:

{
  "node_id": "math.addsub.make-a-ten-strategy",
  "covers_nodes": [
    "math.addsub.make-a-ten-strategy"
  ],
  "kid": "learner-a",
  "topic": "make-a-ten",
  "title": "The Case of the Missing Ten",
  "created": "2026-07-15",
  "review_pack_slug": "make-a-ten",
  "pdfs": {
    "main": "packet-main.pdf",
    "answer_key": "answer-key.pdf",
    "certificate": "certificate.pdf"
  },
  "status": "generated"
}

covers_nodes lets one assessment kit instrument several skills. The packet library recommends a packet when either its primary node or any covered node is on the learner's frontier. A null review_pack_slug explicitly opts out of spaced-review activation, which is useful for a pure timed-check kit.


Capability API

Every operation returns the standard v1 envelope:

{
  "ok": true,
  "data": {},
  "error": null,
  "meta": {
    "v": 1,
    "capability": "vita.education.queue",
    "as_of": "2026-07-15T12:00:00Z",
    "source": "data/education/ (...)"
  }
}

Call from the Vita root:

./run capabilities call vita.education.queue \
  --input '{"kid":"learner-a"}'

or over the internal HTTP transport:

POST /api/v1/capabilities/call/vita.education.queue
Content-Type: application/json

{"kid":"learner-a"}

The full generated field reference lives at docs/api/capabilities/vita.education.md and is regenerated from the registry with ./run gen-capabilities.

Read capabilities

Capability Input Purpose
vita.education.kids optional kid Roster merged with live mastery, frontier, and queue counts
vita.education.frontier kid Constrained next edge plus grouped free-roam territory
vita.education.node id, optional kid Full node detail; optional learner level and workflow status
vita.education.queue kid Derived teacher action queue
vita.education.map none Domain density, tier counts, instrument gaps, frontier sizes, and roadmap
vita.education.progress kid, optional since, limit Newest-first ledger evidence history
vita.education.graph optional kid Live graph projection with optional mastery/frontier overlay
vita.education.packets optional kid Manifest/legacy packet library, graph joins, recommendations, PDF URLs, and review state
vita.education.activity kid, optional days Deterministic Status summary joining Gumball, ledger, packets, and artifacts
vita.education.retests kid, optional as_of Due, upcoming, and locked fluency checks with instrument joins

Write and spawn capabilities

Capability Class Purpose
vita.education.set_level write Append mastery evidence and optional rate history
vita.education.set_status write Set teacher workflow state and delivery metadata
vita.education.choose write Convenience action for status=chosen
vita.education.score write Set level first, then close workflow as done
vita.education.complete_packet write Close packet workflow and activate its Gumball review pack
vita.education.generate_packet spawn Enqueue a deferred ed-packet build and mark it in flight

The React page usually identifies as JavaScript transport. Because JavaScript transport rejects spawn operations by policy, its one packet-generation call uses the trusted internal HTTP transport. The capability endpoint and packet PDF route are excluded from public tunnel allowlists.


Web UI: /education

The first-class React surface is web/src/routes/education/index.tsx. It has a shared learner switcher and five tabs.

Status

Status answers β€œwhat is happening with this learner now?” It shows:

  • track labels, active/partial/not-started state, streak, recent sessions, and real module count;
  • current math and reading frontier edges;
  • the count needing teacher attention and the latest mastery change;
  • today's real-work plan, pending modules, spaced review, and spelling;
  • a 14-day active-day timeline expandable to 45 days, joined to mastery changes, artifacts, and packet events;
  • spelling-bank mastery, hard words, and writing-harvest inflow;
  • recent mastery evidence, packets, and writing samples; and
  • a compact fluency re-test warning that links to Console.

The summary is rule-based and deterministic. No language model invents the headline. If Gumball's database is unavailable, ledger/frontier/packet/artifact sections still return and an explicit note marks the omitted engagement blocks.

Console

Console is the teacher's operational desk:

  • Needs you now: deliver chosen work, score finished work, or administer an assessment.
  • In flight: material already assigned; a β€œReady to score” action opens the scoring form.
  • Ready to choose: constrained frontier choices.
  • Free to explore: a collapsed interest-led list rather than tasks.
  • Fluency re-tests: loud due cards, dated upcoming checks, and a collapsed confirmed-fluent history.

The assessment guide fetches the node and matching packet lazily. It explains the target level, gate, fluency aim, available instrument, and S/G/C/N check questions. The score form asks for structured β€œnailed / wobbled on” evidence so the next packet can be calibrated from more than a bare level.

Delivery has two paths: generate a new packet asynchronously, or record another material such as a curriculum chapter, Gumball module, book, or activity.

Our map

Our map describes the system's build state rather than a learner's mastery:

  • live node and instrument-gap totals;
  • per-domain traversal, node count, fluency count, and gap count;
  • a dense badge at 25 or more nodes, otherwise skeleton;
  • every learner's constrained/free-roam frontier sizes; and
  • the hand-maintained roadmap grouped as done, active, next, and later.

Graph

The Graph tab is a live Cytoscape projection, not the older frozen HTML export. It filters by domain, lays nodes out in strand columns and prerequisite depth, and draws hard requires edges separately from dashed helps edges.

With a learner overlay enabled, node color reflects L0–L4, constrained frontier nodes receive a bright ready-next ring, and free-roam nodes receive a dashed ring. Hover traces prerequisite neighborhoods. Click opens detail for tier, band, current level, frontier state, fluency aim, representations, incoming and outgoing edges, resources, sources, and notes.

Packets

Packets scans data/education/packets/*/manifest.json newest-first. It:

  • filters by learner;
  • labels node, domain, strand, and current learner level;
  • recommends unfinished packets that cover a frontier node;
  • joins the Gumball review-pack status and mastery progress;
  • embeds or opens workbook, answer key, certificate, and combined PDFs; and
  • provides Mark completed, which closes Vita workflow and activates review.

Older directories without a manifest remain visible in an unfiltered listing as legacy; fields and PDF roles are inferred from the directory name and files.


Data and schema map

Path Data Writer/owner
data/education/kids.yaml Private roster, labels, and Gumball profile mapping Hand-maintained configuration
data/education/roadmap.yaml Education-system build roadmap Hand-maintained configuration
data/education/curriculum/domains/*.yaml Shared curriculum graph Curriculum authoring + validator
data/education/curriculum/ledgers/*.yaml Per-learner level, evidence, and rates set_level write path
data/education/curriculum/graph.json Compiled/exported projection for older viewers/tools curriculum_lib export
data/education/workflow/*.yaml Per-learner teacher status education.workflow / capabilities
data/education/retests/*.yaml Fluency verdict and due date Re-test policy after L4-node scoring
data/education/packets/<topic>-<date>/ Build script, manifest, HTML, figures, PDFs, render/review artifacts ed-packet
data/education/artifacts/<learner>/ Original paper-work images ed-artifact
data/education/artifacts/index.jsonl Structured artifact reads and correction audit ed-artifact
data/education/working-memory.md Learning-agent context and open questions ed-artifact and education workflows
Gumball review-pack directory Concept definitions and generators ed-packet
Gumball SQLite database Review state, spelling, daily activity Gumball; Vita reads engagement read-only

YAML stores use atomic text replacement. Artifact-index appends use the atomic JSONL helper. Packet manifests are the one JSON boundary joining file artifacts to graph nodes and Gumball packs.


External and manual dependencies

Dependency Why it exists Failure behavior
Gumball backend API Review-pack discovery and activation Packet completion still closes Vita; gumball_error reports partial success
Gumball SQLite database Read-only engagement and spelling summaries Status returns ledger-derived data with a note and omits Gumball blocks
Deferred agent queue Full ed-packet build Rejection returns admission_rejected; same-day duplicate returns duplicate_suppressed
Chromium/PDF tooling Render, split, rasterize, and inspect packets Packet build must fail rather than claim delivery
Image generation Decorative art only Reuse existing art or continue without lesson-bearing generative art
Local printer and paper handling Physical-first learner workflow Console generation stops before printing; direct ed-packet printing is explicit
Parent judgment Clarify artifact provenance, assess qualitative work, adjudicate levels Human evidence outranks ambiguous automation

The packet PDF endpoint only serves .pdf files whose resolved path remains inside the packet root. It is internal-only and sends files inline. Raw learner data is not intended for public tunnels or public documentation builds.


Failure handling and invariants

Situation Behavior
Unknown learner or node Capability returns ok: false with not_found
Invalid level/status or empty required evidence Returns invalid_input before the intended write
Score has an invalid level Workflow is not marked done because mastery writes first
Duplicate packet generation request on the same day Returns duplicate_suppressed and does not enqueue twice
Deferred queue refuses the build Returns admission_rejected; never a fake prompt ID
Missing packet manifest during listing Directory is retained as legacy rather than dropping the whole library
Gumball unavailable while listing packets Packets still list; review state becomes unavailable
Gumball unavailable while completing a packet Vita done write remains; output includes gumball_error
Gumball unavailable while reading activity Ledger/frontier/history remain; notes identify the omitted source
Corrupt artifact-index line Activity skips that line rather than failing the whole view
PDF traversal or non-PDF request Internal route returns 404

The system favors honest partial results over pretending an external source is a zero. Cross-system completion is β€œatomic in intent,” not a database transaction: Vita and Gumball have independent stores, and the response explicitly exposes which leg succeeded.


Known limitations and improvement opportunities

These are current implementation constraints, not hypothetical product ideas.

  1. The fluency score UI does not collect a numeric rate. The capability and ledger support {metric, value}, and the re-test policy depends on it, but ScoreForm only submits level and evidence. Until the form adds rate input, use CLI/API for a measured fluency result. Recording L4 without a comparable rate trusts the human judgment and locks the node; recording L3 schedules a generic seven-day rebuild check.

  2. The live graph projection currently drops gates. The underlying node and vita.education.node include mastery gates, but the GraphNode output model omits them. Consequently the Graph detail panel cannot show the gates it is prepared to render. Add gates to the capability output model and its contract test.

  3. Artifact binaries do not have a corresponding served route. Status builds writing-sample links, but only packet PDFs have a confined internal file endpoint. Add an authenticated, path-confined artifact route or remove the misleading links. The artifact index also exposes an ID, not the stored path, so the route contract needs an explicit lookup.

  4. Packet completion state is coupled to Gumball activation. The library's completed flag means β€œreview pack active,” not merely β€œVita workflow done.” A packet with no review pack or a partial Gumball failure can therefore be done in Vita without appearing completed in the library. Surface the two states separately.

  5. Legacy packets cannot fully participate in the loop. They can be listed and viewed, but without a manifest they lack reliable learner, node, and review-pack joins. Backfill manifests instead of adding more inference.

  6. Packet builds have admission and final artifacts, but no first-class job status. The node becomes assigned as soon as generation is enqueued. A failed deferred run can leave packet (generating) in flight until someone repairs it. Persist build IDs/states and reconcile failures into the queue.

  7. Re-test scheduling is best-effort after mastery writes. Exceptions are intentionally swallowed so a scheduler failure cannot undo valid evidence. That protects the ledger but can hide a missing re-test. Emit a visible warning or repair queue, and add the currently described low-priority maintenance schedule for locked skills if it is actually desired.

  8. Writes are atomic per file, not transactional across files/systems. A rare failure after set_level but before set_status(done) leaves valid evidence with an open workflow item. That state is recoverable, but the UI should detect and explain it.

  9. Date validation is shallow. Capability models require non-empty date strings, while downstream ordering assumes ISO YYYY-MM-DD. Validate the format centrally to protect due-date calculations and lexical sorting.

  10. ed-artifact is still an agent procedure, not a typed capability. Image storage, index append, ledger proposals, corrections, and spelling harvests span several manual steps. The spelling path currently backs up and writes the Gumball database directly, unlike review activation's service-owned API. Promote artifact ingest/correction and spelling-add operations behind typed, idempotent owners.

  11. Gumball mastery does not advance the Vita ledger. The shipped coupling reinforces completed packets and reports engagement; it does not feed SM-2 mastery back into the graph. A safe next step is a human-confirmed proposal (β€œGumball evidence supports L3”) rather than an automatic level write.

  12. Generated capability prose can lag implementation between regeneration runs. For example, the current source includes a confined packet-PDF route even though an older generated note still says the mount is missing. Treat registry generation as part of any capability change and test the served URL, not only the emitted string.


Implementation file map

Area Primary files
Curriculum graph, validation, frontier, ledger writes scripts/education/curriculum_lib.py
Teacher workflow and derived queue scripts/education/workflow.py
Fluency re-test policy and store scripts/education/retests.py
Gumball read model and passive-vs-real classification scripts/education/gumball_engagement.py
Gumball review-pack API writes scripts/education/gumball_client.py
Capability registry and all vita.education.* verbs scripts/capabilities/namespaces/education.py
Generated capability reference docs/api/capabilities/vita.education.md
Packet generation workflow and shared tools .claude/skills/ed-packet/SKILL.md, .claude/skills/ed-packet/tools/
Paper-work ingestion workflow .claude/skills/ed-artifact/SKILL.md
React route and learner/tab shell web/src/routes/education/index.tsx
Status, Console, Map, Graph, Packets, assessment, and re-test UI web/src/components/education/
React capability hooks and mirrored types web/src/hooks/useEducation.ts
Internal packet-PDF route api/src/main.py
Capability contracts and isolated write tests tests/test_capabilities.py
Loader/frontier fixtures scripts/education/tests/fixtures/

The minimum safe change discipline is: update the owner engine, expose or adjust the typed capability, regenerate its reference, update mirrored UI types, add an isolated contract test, and then verify the first-class page. A data-model change also requires graph validation and, when relevant, a regenerated compiled graph.json.