Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StayTuned

Stay Learning

A course generator built entirely from VS Code Copilot customization files. The agents, skills, prompts, instructions and hooks under .github/ are the runtime, and courses/ is the data they produce. The only application code is web/, a static site that reads that data and renders it.

Give it a topic and an audience, and it works through audience analysis, curriculum design, learning outcomes, lesson plans and lesson prose — one stage at a time, writing each stage to disk before the next one starts.

Contents

Setup

./scripts/setup.sh

Creates .venv with PyYAML, which the validator needs. Run it once.

↑ Back to top

Using it

Everything is driven from the chat panel. Type / and the prompt name.

/new-course

Creates a course from scratch.

/new-course a two-hour introduction to Docker for backend developers who have never used containers

It will ask for anything missing from the brief — topic, audience, experience level, time budget, constraints — as a single batch of questions rather than one at a time. Then it runs audience, curriculum, outcomes and assessment, and stops for your approval before any lesson is planned, reporting the module sequence, the time budget, the outcome count and how each outcome is assessed. That checkpoint is deliberate: outcomes are cheap to change and prose is not. After approval it plans every lesson in order, stops once more, and then writes the whole course in a single parallel wave.

This prompt must be run from a top-level chat session. An orchestrator invoked as a subagent has no agent tool and cannot delegate to the stage agents.

/regenerate-lesson

Re-plans and rewrites a single lesson, leaving every other file untouched.

/regenerate-lesson m01-l02

This is the operation the file-based state exists for. If the id does not exist it lists the valid ones and stops.

/regenerate-quiz

Rewrites one lesson's quiz and nothing else.

/regenerate-quiz m01-l01

The cheapest regeneration in the pipeline: the quiz is a leaf artefact, so nothing depends on it and nothing downstream goes stale. Measured on intro-to-containers, regenerating one quiz left 17 of the course's 19 files byte-identical — the two that changed were the quiz and the append-only run log.

/course-status

Read-only. Reports what exists, what is stale, and what the next action is.

/course-status intro-to-containers

Omit the slug to report on every course.

↑ Back to top

Reading the courses

web/ is a static site that renders courses/ for a learner — lessons, exercises, quizzes, glossary and capstone, with completion tracked in the browser.

cd web
npm install
npm run dev

It enumerates courses/ rather than holding a list, so a course appears on the site as soon as it appears on disk; in dev that happens without a restart. It only ever reads. See web/README.md.

↑ Back to top

How a course is built

one stage at a time
    audience -> curriculum -> outcomes -> assessment -> lesson plans

then all at once, in a single wave
    lesson prose + exercises + quiz     for every lesson in every module
    capstone project                    once

Each stage is a separate agent that can only see what came before it. course-orchestrator is the entry point and the only agent you invoke directly; it delegates to the nine stage agents below and keeps course.yaml in step with what is on disk.

Agent Produces
audience-analyst audience.yaml — learner profile, prerequisites, time budget, misconceptions
curriculum-designer curriculum.yaml — modules, lessons, sequencing, pacing, cognitive load
outcomes-designer outcomes.yaml — measurable outcomes with Bloom levels and evidence
assessment-designer assessment.yaml — diagnostic, formative items per outcome, checkpoints, summative
project-designer project.yaml — capstone brief, per-module milestones, rubric against module skills
lesson-planner one <lesson>.plan.yaml — sections, minutes, outcomes covered, terminology
lesson-writer one <lesson>.md plus its <lesson>.glossary.yaml fragment
exercise-generator one <lesson>.exercises.yaml — standalone practice with hints and solutions
quiz-generator one <lesson>.quiz.yaml — one item per outcome, every distractor justified

The nine stage agents are not user-invocable. Go through course-orchestrator.

Prose, exercises and quiz are siblings: all three derive from the lesson plan and none of them depends on the others, which is why a quiz can be regenerated on its own.

The wave

The stages in the first block — audience through lesson planning — each read what the last one wrote, so they run one at a time. The second block does not. The capstone, and the prose, exercises and quiz of every lesson in every module, all derive from artefacts that are finished by the time planning ends, and none of them reads another. So they are dispatched together, in one batch. A twelve-lesson course is thirty-seven delegations and one round trip rather than thirty-seven round trips.

What makes that safe is not optimism about file locking. It is that no two agents in the wave write the same file, and that holds only because they do not write the shared files at all:

File Written by
course.yaml the running stage, if exclusive; otherwise the orchestrator
glossary.yaml the orchestrator, always
.state/run-log.md the running stage, if exclusive; otherwise the orchestrator

A wave agent returns the manifest change and the log line it would have made, and the orchestrator applies them once the wave is in. Four agents appending to one log lose each other's writes without any of them failing, and a lost status flip is worse than a wrong one: nothing downstream can tell it went missing.

The glossary is the interesting case, because the lesson writer authors the definitions it uses and needs them before it writes a word. It cannot wait for a merge. So it writes them beside its lesson, in <lesson>.glossary.yaml, carrying only the terms that lesson introduces and no first_used — the file belongs to one lesson, so the field would restate the filename. The orchestrator merges the fragments afterwards and sets it.

That merge is only well-defined because a term is introduced by exactly one plan, which the validator checks. Two plans claiming the same term produce two fragments defining it, and the merge cannot choose between them; either definition might be the good one. Catching it before the wave costs one re-plan, and catching it after costs two lessons.

Why planning is not in the wave

A lesson plan is written against its siblings. It must not introduce a term an earlier plan already introduced, and it spends from a module-wide terminology budget that only the other plans can tell it the balance of. Planning is also the cheapest stage in the course, so serialising it costs least and buys the most.

That pushes work onto the planner. Anything a wave agent needs to know about the rest of the course has to be in the plans, because the plans are the last place it can still be written down. continuity is that field: the running example domain, and what the learner already holds coming in. It is what the lesson writer used to read the previous lesson's prose to learn, which is exactly the dependency that made lessons sequential.

It is optional, and deliberately so — a plan without it can still be written, just not alongside the lesson before it. The orchestrator leaves those lessons out of the wave rather than guessing.

Why the stages are separate

Each agent sees only its own inputs, so it cannot quietly paper over a defect upstream. A lesson planner that finds an outcome it cannot cover in the time available reports it rather than silently trimming the outcome. In practice this is where most real defects surface.

Assessment sitting before lesson planning is the same argument. A quiz written after the prose can only test what the prose happened to cover, so it can never reveal that the lesson missed its outcome — the two agree by construction. Deciding the evidence first gives the lesson a target it did not choose for itself.

Typed examples

Examples are the one thing that is not its own artefact. An example cannot be regenerated without rewriting the paragraphs around it, and a file that can never change alone is a lie about granularity. So the planner types each one — worked, completion, contrasting, non_example, analogy — and the writer renders it as an ### heading titled verbatim.

The type carries obligations a restatement cannot meet: a contrasting example needs two cases and a named dimension they vary on, an analogy needs the point where it breaks down, a worked example needs three steps or more. Carrying another kind's field fails, because a wrong label is worse than none — it satisfies the mix check dishonestly. And a lesson with an outcome at apply or above must carry at least one worked or completion example: analogies and contrasts explain and bound a concept, but neither shows the learner how the thing is done.

The heading is what stops a type being a claim the prose never honours.

Why the capstone marks skills, not outcomes

Everything else in a course marks one outcome at a time. That is what makes quizzes and exercises markable, and it is also what makes them incomplete: a learner can pass every item and still be unable to do the job, because the job needs several skills held at once under a constraint that decides between them. The capstone is the only artefact that tests that, so its rubric maps to the module-level skills_unlocked in curriculum.yaml rather than to lesson outcomes.

That mapping is checked in both directions, and each direction catches a different lie. An orphan criterion marks something the course never taught. An unassessed skill means the course claims a capability nothing ever asks the learner to demonstrate — the more common failure, and the one nobody notices, because every individual artefact still passes.

Milestones make the same argument about time. One per module, placed after it, so a misunderstanding surfaces while it is still cheap to fix rather than the night before the hand-in. A milestone may only be marked against skills its own module or an earlier one unlocked; if it needs a later skill, either the milestone or the module order is wrong.

The capstone sits outside the taught-time budget, like exercises, but is bounded by it. A capstone longer than the course preparing it is teaching new material, and that belongs in a module.

Retrieval and spacing, counted

Two of the most reliable findings about retention are also two of the easiest to fake, so the pipeline counts them instead of asking an agent whether a course "reinforces its material".

Retrieval. Every glossary term must be asked for in an exercise or a quiz at or after the lesson that introduces it. Being shown a term is not being taught it — the learner recognises it and mistakes recognition for recall.

Spacing. Every term must be met again in learner material after its own lesson. The lesson's own quiz does not count: that is minutes later, with the term still in working memory. Neither does the lesson plan, because nobody reads the plan. For terms introduced in the final lesson the capstone is the only place left, which is one more reason a course should have one.

Both checks count the course's own words, and that is deliberate. A later passage that paraphrases a term rather than using it does not satisfy spacing. Defining vocabulary and then avoiding it teaches the concept and withholds the language, and the language is half of what a course is for — a learner who never acquired the phrase can follow every sentence and still not recognise it in a manual page or a colleague's question. Enforcing this caught a real defect on the first run: the capstone marked entirely in paraphrase, asking for "the reference form to pin it by" in a course that had taught tag and digest.

And a module after the first that re-uses none of the earlier modules' terminology is a separate course sharing a folder. That is checked at the module boundary rather than the lesson boundary, because within a module continuity is nearly automatic — consecutive lessons repeat each other's words without anyone trying. Across a boundary is where a course actually breaks in two, and where the break is invisible, since every module still reads well on its own.

↑ Back to top

Skills

Skills carry the domain knowledge the agents share. They are loaded on demand, not injected into every request.

  • course-state — the schema, id rules, artefact status lifecycle and the write procedure. Every agent that touches a course file reads this first. Its schema reference is the authority on file formats.
  • instructional-design — backward design, Bloom verb selection, assessment design, example design, capstone design, retrieval and spacing, and cognitive load heuristics. Used by the curriculum, outcomes, assessment, project and planning stages.

↑ Back to top

State and staleness

courses/<slug>/course.yaml is the manifest. It records every artefact's status:

Status Meaning
missing not generated yet
current consistent with everything upstream of it
stale something upstream changed and this has not been regenerated

Rewriting an artefact marks everything downstream stale, and stale means it must be regenerated — not merely reviewed. Correcting two fields in curriculum.yaml will invalidate the outcomes, the assessment, every lesson plan and every lesson. That is intentional. The alternative is a course whose prose quietly disagrees with its own curriculum.

Regeneration is largely idempotent in practice: re-deriving an artefact whose inputs have not changed usually reproduces it byte for byte, and the agents will decline to write a file they did not change rather than log a write that did not happen.

↑ Back to top

Validation

.github/skills/course-state/scripts/validate.py runs automatically after every edit, via the PostToolUse hook in .github/hooks/. No agent has shell access; the hook is how validation reaches the shell.

To run it by hand:

.venv/bin/python .github/skills/course-state/scripts/validate.py            # all courses
.venv/bin/python .github/skills/course-state/scripts/validate.py courses/my-course
.venv/bin/python .github/skills/course-state/scripts/validate.py --strict
  • FAIL blocks. Something is wrong now.
  • WARN marks a state that is valid midway through a write procedure but wrong once the run stops — for example, a lesson's prose not yet matching its regenerated plan. Finish the procedure and it clears.
  • --strict promotes every warning to a failure. Use it in CI, or after a run you believe is finished.

Fix the cause of a failure, not the symptom. Numbers that disagree usually mean one side is wrong, not that both need forcing into line.

↑ Back to top

Editing courses by hand

Don't. Anything under courses/** is regenerable output; a manual edit is silently overwritten the next time that file is generated, and it will not be reflected in the plan it came from. Change the artefact upstream of the problem and re-run.

↑ Back to top

Layout

.github/
  agents/          eight agents; course-orchestrator is the entry point
  prompts/         the four slash commands
  skills/          course-state, instructional-design
  instructions/    auto-applied rules for courses/**/*.md and courses/**/*.yaml
  hooks/           PostToolUse validation
  copilot-instructions.md
courses/<slug>/
  course.yaml      the manifest
  audience.yaml  curriculum.yaml  outcomes.yaml  assessment.yaml  project.yaml  glossary.yaml
  modules/<module>/<lesson>.plan.yaml
  modules/<module>/<lesson>.md
  modules/<module>/<lesson>.glossary.yaml
  modules/<module>/<lesson>.exercises.yaml
  modules/<module>/<lesson>.quiz.yaml
  .state/run-log.md
web/                     static site that renders courses/ (read-only)
scripts/setup.sh
implementation-plan.md   the spec this was built from

↑ Back to top

Design constraints

Three rules hold across the whole pipeline, and are worth preserving if you extend it.

  1. No model: field anywhere in .github/. The pipeline is provider-agnostic; pinning a vendor string in one agent breaks that.
  2. No agent has the execute tool. Validation reaches the shell only through the hook.
  3. Every subagent is user-invocable: false. course-orchestrator is the single entry point, so stages cannot be run out of order.

↑ Back to top

About

Agentic Pipeline for course creation. Mechanical verification, diagrams and house style. Highly parallel with guardrails and UI included.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages