# Design: Instructor Course-Prep Workflow

**Status:** Draft for review
**Author:** Benjamin Booth (with Claude Code)
**Date:** 2026-04-19
**Parent:** [prd-classroom.md](../prds/prd-classroom.md) — especially §2.5 (Enter through the end) and §10 Adoption Strategy.
**Related:** [spec-classroom.md](../specs/spec-classroom.md) §2.6/§2.7 (course/classroom prep state machines).

---

## 1) Goal & non-goals

**Goal.** Deliver an instructor-facing course-prep workflow that takes a
skeptical instructor from "what is this thing" to "this classroom is
running and I can grade it" in one session, without hand-authoring any
YAML.

**Non-goals.** Post-course conclusion flow (harvest/archive), student-
facing experience, analytics dashboards, course template marketplace.
Those are separate.

---

## 2) Guiding principles (already adopted)

1. **Enter through the end** (PRD §2.5). The first meaningful
   interaction is with a *running demo classroom*, not a blank
   manifest.
2. **Standalone-first, LMS optional** (memory: `feedback_standalone_first_lms_optional`).
   Canvas walkthrough is reachable from inside prep; it is never a
   prerequisite for the first useful run.
3. **Rail customization is an instructor-onboarding step**
   (memory: `feedback_instructor_onboarding_questionnaire_rail`).
   Question banks come from domain extensions, the rail concept
   lives in core.
4. **AXI is the REPL loop agent, not a steward** (memory:
   `project_repl_agent_framework`). "AXI-guided" = chat-driven
   narration, not an automated daemon.

---

## 3) What already exists (reuse map)

| Layer | Status | Module |
|---|---|---|
| `CoursePrepWorkflow` state machine (5 steps) | Built, 4 tests | `classroom/course_prep_workflow.py` |
| `ClassroomPrepWorkflow` state machine | Built | `classroom/classroom_prep_workflow.py` |
| Checklist + gating logic | Built | `classroom/course_prep.py` |
| CLI surfaces: `init`, `status`, `corpus`, `prompt`, `assessment`, `rails`, `rag`, `lms`, `dry-run` | Built | `classroom/cli.py` |
| Canvas LMS adapter | Built, 12 tests | `classroom/canvas.py` |
| Q&A engine | Built, 16 tests | `questionnaire/engine.py` (core) |
| Syllabus → manifest extraction | Built, 9 tests (today) | `classroom/syllabus_extraction.py` |
| Quiz scoring (LLM) | Built, 4 tests (today) | `classroom/quiz_scoring_llm.py` |
| RAG policy routing | Built, 15 tests | `rag/policy.py` (core) |
| Prompt composition (agent layer) | Built, 21 tests (today) | `infra/prompt_composer.py` |

**The gap is not state-machine scaffolding; it is the AXI narration
layer that binds the pieces into a guided flow, plus four missing
leaves:** demo entry, rail customization UX, LMS-setup walkthrough,
checkpoint configuration.

---

## 4) User journey — the golden path

```mermaid
flowchart TD
  START["Instructor runs 'axi classroom demo'"]:::entry
  SEED["Seeded Intro-to-Neutronics classroom<br/>5 synthetic students, 2 quizzes, sample corpus"]:::demo
  TRY["Instructor runs one quiz-score-brief cycle<br/>(all mocked, zero external calls)"]:::demo
  HOOK{"Hooked?"}:::decision
  LEAVE["Bye — demo left intact"]:::exit
  FORK["'axi classroom prep from-demo'<br/>Clones demo into drafts, unlocks editing"]:::forks
  SYL["Drop syllabus.pdf / .md<br/>Syllabus extraction populates manifest"]:::stage
  LOS["Review / tweak learning objectives"]:::stage
  CORPUS["Upload real corpus + preview retrieval"]:::stage
  PROMPT["Tune system prompt<br/>Test turn as AXI"]:::stage
  ASSESS["Configure checkpoints<br/>(baseline / mid / final)"]:::stage
  RAILS["Customize onboarding rails<br/>(pick from NeutronOS question bank)"]:::stage
  RAG["Select RAG policy profile"]:::stage
  LMS["LMS walkthrough<br/>(Canvas | Moodle | Blackboard | None)"]:::stage
  DRY["Dry-run as test student<br/>Complete a rail + quiz end-to-end"]:::stage
  PUBLISH["'axi classroom publish'<br/>Roster sync + classroom activated"]:::exit

  START --> SEED --> TRY --> HOOK
  HOOK -->|No| LEAVE
  HOOK -->|Yes| FORK --> SYL --> LOS --> CORPUS --> PROMPT --> ASSESS --> RAILS --> RAG --> LMS --> DRY --> PUBLISH

  classDef entry fill:#1976d2,color:#fff
  classDef demo fill:#2d6f4a,color:#fff
  classDef decision fill:#6f6f2d,color:#fff
  classDef forks fill:#4a6f2d,color:#fff
  classDef stage fill:#c8e6c9,color:#000
  classDef exit fill:#1a1a2e,color:#fff
```

**Stage order is a suggestion, not a contract.** Instructors can jump
out-of-order; the checklist gates publishing (not navigation). This
matches the existing `course_prep.py` validator design.

---

## 5) AXI's role

AXI is the conversational surface. At each stage he:

1. **Narrates** what the stage is for and the instructor's current
   position on the checklist.
2. **Proposes** concrete next actions via tool calls (syllabus
   extraction, corpus preview, prompt test, dry-run, etc.).
3. **Asks** one or two targeted questions — the fewer the better.
4. **Defers** to the underlying workflow for validation — AXI
   never invents checklist state.

AXI is **not** required to progress: every stage is reachable via
`axi classroom prep <stage>` subcommands directly, so power users
who hate chat can skip him entirely. The chat is the guided lane,
the CLI is the expert lane, same state machine underneath.

**Implementation:** AXI surfaces the prep workflow through the
existing `ChatAgent` + tool-call mechanism, using a new set of
classroom tools (`classroom_prep_status`, `classroom_prep_upload_syllabus`,
`classroom_prep_tune_prompt`, `classroom_prep_add_checkpoint`, etc.).
No new agent class; just tools that wrap the workflow methods.

---

## 6) The four missing leaves

### 6.1 `axi classroom demo` — demo entry

Single command, zero prompts. Seeds a deterministic classroom with:

- Course: "Intro to Neutronics (Demo)" — synthetic, clearly labeled
- 5 fake students with names, Matrix-style principals
- Sample corpus (10 public-domain reactor physics excerpts)
- 2 quizzes (baseline + midpoint) with model answers
- Pre-filled trace log so `status`, `brief`, `explain` all work instantly

Demo data lives under `~/.axi/runtime/classrooms/demo/` and never
mixes with real runtime data. Running `demo` twice is idempotent
(same seed). `demo --reset` wipes and re-seeds.

`prep from-demo <new_course_id>` copies demo artifacts into a fresh
prep workflow so the instructor edits a running thing, not a blank.

### 6.2 Rail customization UX

Existing `axi classroom prep rails` accepts a YAML list. Add
interactive path:

- `prep rails list-banks` — show installed question banks
  (via `[[question_banks]]` manifest entries from extensions)
- `prep rails add --bank <name> --ids <q1,q2,...>` — pull from bank
- `prep rails edit <rail_id>` — open the rail in `$EDITOR` as YAML,
  validate on save
- `prep rails preview <rail_id>` — run the rail against a stub
  student to show what the student sees

No new engine — just CLI affordances on top of the existing Q&A
engine and the question-bank manifest surface.

### 6.3 LMS-setup walkthrough

Extend `axi classroom prep lms`:

- `lms` with no subcommand → prompt `Canvas | Moodle | Blackboard | None`
- `lms canvas` → walk through: instance URL, API token (or OAuth
  stub for v0), course picker, roster preview, grade-push probe
- `lms none` → manual `students.yaml` path (already works)

Each provider registers as an extension via the LMS factory pattern
(already built at `integrations/lms/factory.py`). Adding a provider
= adding a walkthrough, not a new command.

### 6.4 Checkpoint configuration

Formalize `checkpoints` in `CourseManifest` per
`project_course_checkpoints` memory. Ship default 3
(baseline / midpoint / final). Add:

- `prep checkpoints list` — show configured checkpoints
- `prep checkpoints add <id> --method quiz --timing <when>`
- `prep checkpoints remove <id>`
- `prep checkpoints skip-defaults` — instructor opts out

Defaults applied at manifest load time if absent, so the instructor
has to *remove* something to end up with zero checkpoints. Matches
the enter-through-the-end principle (defaults are always correct).

---

## 7) Phased delivery (one-slice-at-a-time)

Per `feedback_phased_work_must_deliver_per_phase`, each phase ships
a complete instructor experience at that level.

| Phase | Ships | Instructor can… |
|---|---|---|
| P1 | `classroom demo` + `prep from-demo` | Play with a seeded classroom, clone it, edit against running code |
| P2 | AXI prep tools (status/upload-syllabus/tune-prompt) | Run the full syllabus→manifest→prompt flow via chat |
| P3 | Checkpoint configuration + rail customization UX | Author a complete course template ready for Prague |
| P4 | LMS walkthrough (Canvas first) | Bind to a real Canvas course, sync roster, publish |
| P5 | Dry-run-as-student polish + publish command | Ship a classroom end-to-end |

Each phase passes its own e2e test (`tests/classroom_e2e/`) and adds
no Python dependencies beyond what the codebase already has.

---

## 8) Out-of-scope (explicit)

- Course-conclusion workflow (archive, analytics, grade finalization,
  harvest, promotion). That is a separate design pass.
- Authentication / sign-in UX. Inherits whatever Axiom has.
- Multi-instructor co-authoring. v1 is single-instructor per course.
- Template marketplace (share a finished course with another
  instructor). Federation work, separate track.
- LMS providers other than Canvas. Moodle + Blackboard are factory
  extension points; their walkthroughs come with their providers.

---

## 9) Open questions (resolved 2026-04-19)

1. **`classroom demo` data source — INLINE.** Ship the corpus, roster,
   and quizzes as Python-literal fixtures inside the demo command's
   module. Simpler, deterministic, no network dependency. A future
   phase can add `--from-pack <url>` for richer demos.
2. **Rail preview with stub student — STUB.** Hardcoded `@alice:demo`
   persona for v0 rail previews. Real-student preview
   (`prep rails preview --as <student>`) lands with P3.
3. **Checkpoint timing — BOTH keywords + ISO dates.** Manifest field
   accepts a string; the validator disambiguates:
   - Keywords: `enrollment_complete`, `course_start`, `course_end`,
     `midway`
   - ISO-8601 date: `2026-07-15` or `2026-07-15T09:00:00-05:00`
   Any other string → validation error at manifest load.
4. **Bonsai narration — same thing as AXI narration.** "Bonsai" is
   the local LLM backend (Bonsai-1.7B llamafile). AXI runs through
   the gateway, which routes to Bonsai / Claude / OpenAI per the
   node's provider config. No separate "Bonsai narrator" needed —
   AXI is the narrator, Bonsai is one of its possible engines. The
   prep tools (§5) don't need to know which model is serving them.

---

## 10) Immediate next step

Implement **P1** (`classroom demo` + `prep from-demo`) as a single
PR. Under 300 LOC plus tests. Ships standalone value (skeptic can
evaluate the platform in 60 seconds) and unblocks everything below.
_Copyright (c) 2026 The University of Texas at Austin and B-Tree Labs. Apache-2.0 licensed._
