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:
- What can the learner do? A per-learner mastery ledger records evidence at levels L1βL4.
- What is ready next? A prerequisite-aware frontier exposes the next constrained skills and a separate free-roam territory.
- What does the teacher need to do? A workflow store derives a queue for choosing, delivering, assessing, and scoring work.
- What keeps learning alive? Printed
ed-packetwork, ingesteded-artifactevidence, 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
requiresorhelpstargets; - 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
requiresandhelps; - valid S/G/C/N representation keys and resolvable node-like
downpointers; - 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; andfree_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_packetmeans βthe physical material was finished.β It closes the workflow item and, by default, starts the packet's spaced-review pack.scoremeans β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:
- explicit
manifest.review_pack_slug, when present; - otherwise the packet
topic; or - no pack when
review_pack_slugis 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, andcontent_missionviews 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:
- The builder rasterizes and reads every page for overflow, ambiguous figures, collisions, and broken art.
- Three reviewers inspect pedagogy, mathematical/content accuracy, and print production.
- Findings are adjudicated as a real content error, a perceptual rendering problem, or reviewer error.
- 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
densebadge at 25 or more nodes, otherwiseskeleton; - 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.
-
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, butScoreFormonly 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. -
The live graph projection currently drops
gates. The underlying node andvita.education.nodeinclude mastery gates, but theGraphNodeoutput model omits them. Consequently the Graph detail panel cannot show the gates it is prepared to render. Addgatesto the capability output model and its contract test. -
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.
-
Packet completion state is coupled to Gumball activation. The library's
completedflag 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. -
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.
-
Packet builds have admission and final artifacts, but no first-class job status. The node becomes
assignedas soon as generation is enqueued. A failed deferred run can leavepacket (generating)in flight until someone repairs it. Persist build IDs/states and reconcile failures into the queue. -
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.
-
Writes are atomic per file, not transactional across files/systems. A rare failure after
set_levelbut beforeset_status(done)leaves valid evidence with an open workflow item. That state is recoverable, but the UI should detect and explain it. -
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. -
ed-artifactis 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. -
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.
-
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.