← Files BodhiKitARCHIVED FILE

references/knowledge/state-schema.md

18 KB · Oct 3, 2026 · 06:33 UTC

↓ Download file

<!-- Generated from skills/state-schema/SKILL.md; edit the canonical source. -->

# State Schema — Canonical Shape of BodhiKit Tracking Files

This KB is the field-level reference. **Routine skill fires do NOT load it** — the operational surface (discovery, the `bodhi-state` write path and subcommand table, session-type vocabulary, gate/mastery semantics) lives in the `state-ops` KB, and `bodhi-state` owns every JSON mutation, so most sessions never need field shapes at all.

Load this KB only when:

- performing a **manual carve-out** — `learn` skill project scaffolding (the `.bodhi/` skeletons and the parent `.bodhi-profile.json`), the `currentBloomLevel` / `initialBloomLevel` maps;
- running the **script-unavailable fallback** (see *Fallback discipline* below);
- you are `housekeep` skill (rotation and migration);
- you are authoring or reviewing plugin files.

If a field is not listed here, do not add it. If a new field is genuinely needed, update this KB first, then `scripts/bodhi-state`, then the skills that touch it.

See also:
- `state-ops` KB — the per-session operational surface (write path, discovery, vocabulary, gate/mastery).
- `spaced-repetition` KB — box→interval mapping and update rules (implemented by `bodhi-state`).
- `state-lifecycle` KB — rotation/archive/collapse protocol (loaded by `housekeep` skill only).
- `state-migration` KB — version transforms (loaded by `housekeep` skill with request context `migrate`).

## Guiding Principle: Progressive Disclosure for the Learner's State

The learner's accumulated work is sacred — nothing is deleted, nothing is gatekept. But context is finite, so every tracking surface is shaped so that **the smallest useful slice is what loads by default**, and the full history remains on disk behind explicit pointers. Three mechanisms: live + archive + summary for narrative surfaces, sectional files for the plan, slim JSON for state. Rotation is `housekeep` skill's job alone (see `state-lifecycle` KB); every other skill stays oblivious to compaction.

## Fallback Discipline (script unavailable, or a declared manual carve-out)

Only after BOTH the `<BODHIKIT_PLUGIN_ROOT>` path and the package-relative lookup come up empty (per the `state-ops` KB), say so plainly, then perform the minimal write by hand following this KB's shapes — Read the file, mutate the parsed JSON in place preserving every unknown field, fill the three v3 per-concept defaults on any concept missing them and set `version: 3` (read-tolerance — the eval harness caught a manual fallback skipping exactly this), Write it back, re-read to verify. Never re-serialize from a schema template.

The same read → mutate-in-place → write → verify discipline applies to the declared manual carve-outs even when the script is available.

## File Shapes

### `learningWithBodhi/<project>/.bodhi/state.json`

Slim. No long narrative fields — session narrative lives in `progress.md`.

```json
{
  "version": 2,
  "projectName": "string",
  "topic": "string",
  "createdAt": "ISO-8601",
  "lastSessionAt": "ISO-8601",
  "totalSessions": 0,
  "sessionDates": ["YYYY-MM-DD"],
  "currentStreak": 0,
  "currentPhase": "string",
  "currentModule": "string",
  "previousModule": "string",
  "currentModuleIndex": 0,
  "lastActivity": "string",
  "initialBloomLevel": { "<sub-topic>": 0 },
  "currentBloomLevel": { "<sub-topic>": 0 },
  "overallCompletion": 0
}
```

- `currentStreak`: consecutive days. Reset to 1 if `sessionDates` gap exceeds 1 day. Maintained by `bodhi-state touch-state`.
- `previousModule`: set automatically by `touch-state --module` when the module advances; read by `gate-check`.
- `initialBloomLevel` / `currentBloomLevel`: the per-topic Bloom maps are an explicit **manual carve-out** — no script subcommand owns them. `learn` skill seeds both; `assess` skill and `evaluate` skill may update `currentBloomLevel` via the Fallback discipline above.
- `lastActivity`: one short sentence (≤120 chars) used by `progress` skill with request context `quick` and `continue` skill. Anything longer belongs in `progress.md`.
- **No `lastSessionSummary` / `bloomResetNote` in v2** — migrated to `progress.md`.

### `learningWithBodhi/<project>/.bodhi/progress.md`

Live narrative document. Latest session in full, then a "Summary of earlier sessions" block with pointers.

```markdown
# Progress Log

## <YYYY-MM-DD> — Session <N> (<short label>)

**Duration:** <approx>
**Activities:**
- <bullets>

**Outcomes:**
- <bullets>

**Bloom adjustments:**
- <area>: <from> → <to>

**Next:** <what the next session should open with>

---

## Summary of earlier sessions

- **<YYYY-MM-DD> — <one-line headline>**
  <optional 1-3 more lines>
  → `archive/<filename>.md`
```

### `learningWithBodhi/<project>/.bodhi/assessments/latest.md`

Most recent assessment in full, then a "Summary of earlier assessments" block with `→ archive/<filename>.md` pointers. Multi-stack projects may have multiple assessments per phase; each becomes its own archive entry.

### `learningWithBodhi/<project>/.bodhi/plan/`

Sectional. Generated at plan creation; rewritten only by `plan` skill with request context `regenerate`.

`plan/README.md`: arc overview — current phase pointer, target arc, pace, bulleted phase list with `→ phase-<N>.md` pointers.

`plan/phase-{N}.md`: per-phase goals, duration, success criteria, then `### Module <N>.<M> — <name>` sections with concepts, exercises, success markers. From Module 2 onward each module section carries a `**Prerequisites for next module:**` line naming the concepts the next module builds on (feeds `gate-check --prereqs`).

### `learningWithBodhi/<project>/.bodhi/spaced-review.json`

Whole JSON; written exclusively through `bodhi-state`. `housekeep` skill does not rotate this file.

```json
{
  "version": 3,
  "lastReviewCheck": "ISO-8601",
  "concepts": [
    {
      "name": "string",
      "module": "string",
      "introduced": "YYYY-MM-DD",
      "box": 1,
      "nextReview": "YYYY-MM-DD",
      "lastReviewed": "YYYY-MM-DD",
      "question": "string",
      "lastResult": "string",
      "bloomLevel": 0,
      "feynmanPassed": false,
      "feynmanPassedAt": "YYYY-MM-DD",
      "consecutiveCorrectAtL4Plus": 0,
      "reviewHistory": [
        { "date": "YYYY-MM-DD", "result": "correct|incorrect|partial", "bloomLevel": 3, "boxBefore": 1, "confidence": "sure|mostly|guessing", "source": "string" }
      ]
    }
  ],
  "sessionHistory": [
    { "date": "YYYY-MM-DD", "type": "spaced-review", "conceptsReviewed": 0, "passes": 0, "misses": 0 }
  ]
}
```

- `box`: integer 1–5; intervals and update rules in the `spaced-repetition` KB, implemented by `record-review`.
- `parked` (optional, 1.16.0): `true` = the learner has consciously taken the concept out of review rotation (`bodhi-state park`, surfaced as `forget` skill with request context `--park`); `nextReview` is `null` while parked. Parked concepts leave the due/retention surfaces (reported as counts — `parkedCount` in `due`, `parked` in `mastery`/`snapshot` — never silently) but keep box, Bloom, Feynman, and mastery standing. `park --resume` re-enters rotation (review tomorrow, box preserved). Parking is scheduling, never an outcome — no result is invented and nothing else moves.
- `reviewHistory[].retry` (optional, 1.11.1): `true` marks a successive-relearning rep — recorded evidence that did NOT move the box. `reviewHistory[].note` (optional): short free-text annotation (e.g. "learner-initiated demote"). The script caps `reviewHistory` at the most recent 100 entries per concept; older entries roll into a `reviewHistoryArchived` counter and an `archivedSummary` `{ "through": <last archived date>, "appliedEvidence": N, "evidenceAt3Plus": N, "exposure": "taught"|"built", "lastMiss": <date> }` (each of the last two only when the slice proves it), so the since-last-miss counts, the exposure floor and `retention`'s first retained gap survive the cut — a truncation never changes what a reader says happened.
- `reviewHistory[].confidence` (optional, 1.11.0): the learner's pre-reveal confidence tag — `sure` / `mostly` / `guessing`, collected by `quiz` skill and `reflect` skill BEFORE the answer is judged. `bodhi-state calibration` aggregates these into overconfidence/underconfidence rates (see `metacognition` KB for why predict-before-reveal is the load-bearing order).
- `reviewHistory[].source` (optional): which skill produced the review.
- `reviewHistory[].applied` (optional, 1.20.0): `true` = this outcome was demonstrated in working code the tutor read (a `teach` skill exercise, a `practice` skill exercise, a piece the learner drove in `pair` skill), not in an explanation or a quiz answer. Written by `record-review --applied`; absent on verbal reviews and on every entry before 1.20.0. `bodhi-state` derives `appliedEvidence` (correct + applied entries since the most recent `incorrect`, the same reset as the apply-rung count) on every read surface; nothing stores it.
- `reviewHistory[].selfReport` (optional): `true` on the entry `forget` skill writes — a learner-initiated reset, not a tested outcome. It still resets the box and the since-last-miss counts, but `retention` excludes it (`selfReportsExcluded`); pre-field entries carry `note: "learner-initiated demote"` and are excluded by that note.
- `reviewHistory[].boxBefore` (optional, 1.11.3): the box the concept occupied when this review was answered — the box whose interval scheduled it. Written by `record-review` on every new entry; absent on older entries (readers tolerate absence; `retention` reports how many legacy entries lack it). This is what makes retention-at-review analysis exact instead of reconstructed.
- `reviewHistory[].result` is a closed vocabulary: `correct` / `incorrect` / `partial` — enforced by `record-review`, checked by `verify` (1.12.1). A due concept the session never reached gets a **deferral entry** instead: `{ "date": "YYYY-MM-DD", "deferred": true, "days": N, "note": "..." }` — no `result`, written by `defer`. Deferrals are excluded from `retention` (they are scheduling events, not retrieval evidence). Do not invent result values like `skipped`; found in the wild pre-1.12.1, repaired by `normalize`.
- `sessionHistory` is an append-only audit trail. `evaluate` skill reads it; routine skills do not. The `type` field is a closed vocabulary enforced in code by `record-session` — the canonical table lives in the `state-ops` KB.

**Per-concept Bloom + Feynman fields (v3).** These make mastery observable from state:

- `bloomLevel`: integer 0–6. `0` = uninitialized (no v3 writer has classified it). Ratchets up only — `record-review` takes `max(current, tested-bloom)` on a correct answer and never demotes; even `forget` skill demotes the box, not the Bloom classification.
- `feynmanPassed`: boolean. Set by `set-feynman` when `teach` skill (a full session or its understanding-only path) observes a genuine explain-back. Set, never unset.
- `feynmanPassedAt` (optional, 1.23.0): date of the latest passing explain-back (`set-feynman`). Mastery counts it only if **after** the last miss (latest `incorrect`, live or `archivedSummary.lastMiss`; `forget` skill included) — not the same day; an undated flag counts only with no miss on record. Derived as `feynmanCurrent`.
- `consecutiveCorrectAtL4Plus`: incremented on correct at tested-bloom ≥ 4 — only on a due review, the rule that moves the box (`spaced-repetition` KB) — early L4 corrects add nothing; reset to 0 on any incorrect, any partial, and on `forget` skill (1.11.2: "3 consecutive correct" means uninterrupted corrects). A correct at a lower tested level leaves the counter untouched (a routine low-level recall between two L4 demonstrations is a different measurement, not counter-evidence). `--retry` reps never touch it.

These fields, plus one `applied` correct since the last miss, feed the canonical mastery formula, computed by `bodhi-state mastery` and documented in the `state-ops` KB. Skills MUST NOT redefine it inline; see `blooms-taxonomy` KB for the underlying criteria.

**Legacy fallthrough (v2 → v3).** A concept with `bloomLevel: 0` has never been classified by a v3 writer — regardless of `lastReviewed` (real v2 data routinely has populated `lastReviewed` from pre-v3 quizzes; the 1.10.0 rule that also required `lastReviewed: null` was wrong and was corrected in 1.10.7). Gates treat `bloomLevel: 0` as "no opinion yet, allow advancement"; `progress` skill displays `—` (not 0%) for modules where every concept is at `bloomLevel: 0`. `bodhi-state` implements both rules.

### `learningWithBodhi/<project>/.bodhi/assessment-history.json`

Structured assessment data for `evaluate` skill and `progress` skill. Append-only; never edit past entries.

```json
{
  "version": 1,
  "entries": [
    {
      "date": "YYYY-MM-DD",
      "trigger": "learn-phase2 | assess | evaluate | plan-regenerate",
      "topic": "string",
      "subTopics": [
        { "name": "string", "bloomLevel": 0, "confidence": "high|medium|low", "evidence": "string" }
      ],
      "overallNote": "string",
      "predictionDelta": {
        "predictedBiggestGrowth": "string",
        "measuredBiggestGrowth": "string",
        "predictedBiggestGap": "string",
        "measuredBiggestGap": "string",
        "perTopicBloomPredictions": [ { "name": "string", "predicted": 0, "measured": 0 } ],
        "calibrationNote": "string"
      }
    }
  ]
}
```

- Skills that run assessments (`learn` skill Phase 2, `assess` skill, `evaluate` skill Phase 2, `plan` skill with request context `regenerate`) MUST append an entry here via `bodhi-state record-assessment` (prose version goes to `assessments/latest.md`).
- `predictionDelta` is populated by `evaluate` skill Phase 2.5 (predict-before-reveal); absent elsewhere. See `metacognition` KB.

### `learningWithBodhi/<project>/.bodhi/resources.md`

Markdown log, dated or status-grouped sections. Small; not housekept.

### `learningWithBodhi/<project>`teach` skill-backs/`

Capstone artifacts from `teach-back` skill, at the project root (learner-authored, not internal state). Each post: `teach-backs/<YYYY-MM-DD>-<slug>.md` with a header (thesis, reader, why-it-matters), the learner's prose, and a closing status block (`Status: draft | published | personal-notes`, `Decided:`, `Masters consulted:`, `Post-Phase-6 revisions:`). Only `teach-back` skill writes here. Not housekept.

### `learningWithBodhi/.bodhi-profile.json`

Cross-project learner profile (v2 split layout: this file holds top-level fields; the project list lives in `.bodhi-profile.projects.json`).

```json
{
  "version": 2,
  "careerGoal": "string",
  "whyLearning": "string",
  "priorExperience": "string",
  "learningStyle": "string",
  "overallBloomLevels": { "<area>": 0 },
  "cumulativeStats": {
    "totalSessions": 0,
    "totalExercises": 0,
    "totalConceptsLearned": 0,
    "totalMilestonesReached": 0,
    "totalProjects": 0,
    "teachBacksWritten": 0,
    "teachBacksPublished": 0
  },
  "patterns": {
    "persistentChallenges": ["<topic>"],
    "consistentStrengths": ["<topic>"]
  },
  "learnerBackground": {
    "domains": ["<string>"],
    "analogyHistory": [
      { "concept": "string", "domain": "string", "landed": true, "date": "YYYY-MM-DD" }
    ]
  },
  "lastUpdated": "ISO-8601"
}
```

- `cumulativeStats` counters are incremented via `bodhi-state bump-profile` (the skill decides WHEN — e.g. `teach` skill bumps `totalConceptsLearned` only when a concept first reaches Bloom 3+ and `progress.md` shows no prior count for it). Exception: `totalSessions` is bumped automatically by `touch-state` on the first touch of a new day — no skill calls it directly.
- `patterns.persistentChallenges` / `patterns.consistentStrengths` are appended by `bodhi-state profile-update-patterns`, which counts the project's `assessment-history.json`: a sub-topic with 3+ entries at Bloom <3 appends to `persistentChallenges`; 3+ at Bloom 4+ appends to `consistentStrengths`. Append-only and deduplicated — the script does the counting; skills never tally assessments in prose.
- `learnerBackground.domains[]` / `analogyHistory[]`: populated by the analogy-escalation protocol (see `feynman-technique` KB). Read-then-append; never overwrite.

### Project completion (canonical, 1.11.1)

A project is **complete** when every module in every plan phase is finished or explicitly skipped AND the learner confirms. Completion is never inferred silently — `evaluate` skill asks ("Shall we mark this path complete?") and only the learner's yes moves the entry from `activeProjects` to `completedProjects`. Completion gates the capstone offer, the mentor offer, and `teach-back` skill eligibility.

### `learningWithBodhi/.bodhi-profile.projects.json`

Per-project metadata, loaded only by cross-project skills (`mentor` skill, `evaluate` skill, `learn` skill).

```json
{
  "version": 2,
  "activeProjects": [
    { "name": "string", "topic": "string", "startedAt": "YYYY-MM-DD", "currentPhase": "string", "currentModule": "string", "bloomLevel": 0, "pace": "string", "status": "string", "trackPurpose": "string" }
  ],
  "completedProjects": [
    { "name": "string", "completedAt": "YYYY-MM-DD", "finalBloomLevel": 0, "trackPurpose": "string" }
  ]
}
```

Writers: since 1.16.0 the project-list mutations go through `bodhi-state` — `profile-add-project` (`learn` skill append on project start; creates this file if missing), `profile-update-project` (`evaluate` skill refresh of the active entry), `profile-complete-project` (`evaluate` skill on confirmed completion; `learn` skill Phase 1.5(c) replace-archive via `--status`). `mentor` skill writes career fields in the parent profile only. The entry shapes above are constructed by those subcommands and checked by `verify`; hand-editing these files is fallback-only (script unavailable — apply the Fallback discipline above).

## Schema-Evolution Rules

- Skills MUST NOT introduce new top-level fields, files, or directories under `.bodhi/` without updating this KB (and `bodhi-state`) first.
- Skills MUST NOT stuff long narrative into JSON fields — narrative belongs in `progress.md` / `assessments/latest.md`.
- Schemas evolve; readers tolerate older `version` values (the script read-tolerates v1/v2 and persists v3 on write). Skills MUST NOT branch behavior on version in prose. Full transforms live in the `state-migration` KB.

SHA-256: e33a2207fd74dc8abd454630180b45cdef37d8f0d849c43b20e21c16ad834559