Imported from amroja-biz/claude-code-classroom (
.claude/skills/claude-classroom-new-curriculum/SKILL.md). Install upstream withnpx skills add amroja-biz/claude-code-classroom --skill claude-classroom-new-curriculum. Copyright stays with the author.
Scaffold a new curriculum
Your job is to give a course designer a working skeleton and a clear path to
their first class, without making them read docs/SPEC.md or reverse-engineer
student-image/seed-home.sh.
You create structure. They create content. Do not write lessons, exercises or teaching material unless they explicitly ask you to — a scaffold full of plausible-looking filler is worse than an empty one, because it gets taught.
What you are building
A curriculum is a directory. At spawn time its contents are copied into every
student's home directory by student-image/seed-home.sh. Three rules govern
that copy, and everything else follows from them:
| In the curriculum | Lands at | Why it matters |
|---|---|---|
skills/ |
~/.claude/skills/ |
The only directory that is relocated |
welcome.txt |
~/.lab-welcome, shown in the terminal banner |
The only file that is renamed |
| everything else | ~/ verbatim |
lessons/ becomes ~/lessons because it is named lessons |
There is no magic beyond those three. ~/lessons is a convention the author
creates by naming a directory lessons, not a feature the platform provides.
The platform does provide ~/work, created empty for every student. The
read-only-material / writable-workspace split that students rely on is asserted
nowhere in code — it lives in the curriculum's CLAUDE.md, which is why that
file is effectively required even though the seeder treats it as ordinary.
Step 1 — Ask where it goes, and get this right
Two questions, asked together. The second is the one people get wrong.
Where should the curriculum live? Offer both, with the trade-off:
- Inside this repo, under
curricula/— then--curriculum <name>works by bare name. Their material is then in this git repository, which is the wrong place for anything private or client-owned. - Anywhere else (
~/courses/, a private git clone, a shared drive) — keeps content out of this repo.--curriculumtakes the path. Nothing to configure.
What is the curriculum called? The directory's basename is what the
instance calls it and what appears in workshop up's summary. Lowercase,
hyphens, no spaces — intro-to-agents, not Intro to Agents. up rejects
names with spaces.
workshop up ships exactly that one directory, so anything inside it — a
.git, a data/ folder, stray notebooks — goes to every student.
Step 2 — Create the skeleton
Create exactly this, with the placeholder content below. Substitute their
curriculum name for <name> and use their chosen parent directory.
<parent>/<name>/
├── CLAUDE.md how the agent should behave for this course
├── welcome.txt the terminal banner students see first
├── lessons/
│ └── 01-<first-topic>/
│ └── README.md one directory per lesson, numbered
└── skills/ optional — delete if unused
└── .gitkeep
Do not create data/, solutions/ or anything else unless they ask. Mention
that any directory they add lands in the student's home under the same name.
CLAUDE.md
This is the file that makes a class feel designed rather than improvised. Write the structural parts — they are the same for every course on this platform and are drawn from what the go-jupyter workshop learned the hard way — and leave the course-specific parts as marked gaps.
# <Course title> — workshop context
<!-- TODO: who is the student? What do they already know? Are they programmers?
The agent adapts its explanations to this sentence more than anything
else in this file. -->
## Where things live
- `~/lessons/` is the course material. **Read it; do not write into it.**
- `~/work/` is the student's workspace. **Put every file you create here**,
unless the student explicitly asks for somewhere else. Do not write to the
home directory itself.
## How to work
- Work through `~/lessons` in order. Don't skip ahead unless asked.
- Explain what you're doing before you do it. Show the command, then run it.
- Prefer small, verifiable steps over long automated runs.
- If the student seems stuck, ask what they expected to happen.
<!-- TODO: course-specific working rules. Which tools should the agent reach
for? Is there a house style, a validator, a dataset it must not modify? -->
## Session hygiene
- Tell the student to run `/clear` at the end of each exercise. Context that
carries across unrelated exercises triggers compaction mid-task, after which
the agent appears to "forget" what it was doing.
- Red error text during iteration is normal — a failed command you are about to
fix is not a broken environment. Say so when it appears, because students
reasonably read red as "I broke it".
- If an exercise calls an external API, do not fan out concurrent requests. A
room of students hitting the same endpoint at once looks like an attack to it.
## Tone
Write as a knowledgeable colleague: plain, direct, professional. Skip
exclamation marks, cheerleading, and filler enthusiasm — the default informal
register reads as wrong in a working context.
Leave the TODO comments in. They are the author's checklist, and an unedited
CLAUDE.md should be visibly unfinished rather than quietly generic.
welcome.txt
Plain text, no markdown — it is echoed into a terminal banner, so what they type is what students see. Keep it short enough to survive a small window.
<Course title>
Lessons: ~/lessons (course material — read only)
Your work: ~/work (everything you create goes here)
Start with ~/lessons/01-<first-topic>/README.md
Ask Claude: "walk me through lesson 1"
lessons/01-<first-topic>/README.md
One numbered directory per lesson, zero-padded so they sort. A stub only:
# Lesson 1 — <title>
<!-- TODO: what the student asks the agent to do. Write it as an instruction to
the student, not to the agent. -->
**Goal:** <!-- TODO: what they should understand afterwards, not what they
should produce. -->
> Your files belong in `~/work`. The `~/lessons` folder is the course
> material — read it, don't write to it.
skills/ — offer, don't impose
A curriculum can ship Claude Code skills that appear in every student's
~/.claude/skills/. Useful for a course-specific helper: an orientation skill,
a validator, a wrapper around a domain API.
Ask whether they want one. If yes, create skills/<skill-name>/SKILL.md with
frontmatter, since a skill without name and description will not load:
---
name: <skill-name>
description: <TODO: what it does, and the situations where the agent should reach for it. This sentence is what triggers it — write it as trigger conditions, not as a summary.>
---
<!-- TODO: the instructions the agent follows when this skill fires. -->
If no, create skills/.gitkeep and tell them they can add one later, or delete
the directory — an empty skills/ is harmless.
Step 3 — Names that will collide
Check what you created against this list, and warn the author before they add directories of their own. Curriculum files overwrite base files of the same name, deliberately, so a collision is silent.
work— the student's workspace is created at~/work. A curriculum directory of that name puts course material where students save their files..claude— holds the seeded Claude Code state. Useskills/instead; that is what it is for..profile,.bashrc,.lab-bashrc,.lab-welcome,.lab-seeded— platform files..profileis what a login shell reads, and the chain.profile→.bashrc→.lab-bashrcis what starts Claude Code. Overwrite any link in it and students land on a bare prompt.lessons— not reserved, but by convention it is the read-only material andCLAUDE.mdtells the agent so. If they name it something else, changeCLAUDE.mdandwelcome.txtto match, or the agent will protect a directory that does not exist.
Step 4 — Tell them how to expose it to the platform
This is the step the README leaves implicit, so be concrete. Give them the commands with their real paths filled in, not a template.
If they built it under this repo's curricula/ — bare name works:
make curricula # their new name should be listed
./scripts/workshop up --students <N> --curriculum <name>
If they built it anywhere else — pass the path:
./scripts/workshop up --students <N> --curriculum /Users/<them>/courses/<name>
Explain what happens next, because it is short and it reassures: up checks
the layout (it refuses, showing the expected shape, if lessons/ is missing),
tars that directory, ships it to the instance, and bind-mounts it read-only
into every student container. Editing a lesson costs a re-run of up — about
five minutes — not an AMI rebuild. There is no rebuild step for content, ever.
Step 5 — Verify before it matters
up validates the layout, but not the content. Confirm the directory is where
you both think it is:
ls <parent>/<name>/lessons # must list at least one lesson
If Docker is available, offer a local run — it exercises the same seeding code the instance uses, costs nothing, and is the only way to see the banner and the home directory as a student will:
make dev-reset # forces a re-seed
make dev-up CURRICULUM=<parent>/<name>
# open http://localhost:8000, log in with a code from hub/codes.json
./scripts/test-curriculum.sh # seeding, cross-contamination, fallback
make dev-down
Report what you checked and what you did not. If Docker was unavailable, say that the layout is correct but unrun rather than implying it is verified.
Finally
Tell them, in this order:
- Where the skeleton is, as an absolute path.
- Which files have TODOs in them, listed — that is their work queue.
- The exact
workshop upcommand for their class, with the name filled in. - That the flag takes the curriculum directory itself, not a parent folder.
Do not summarise the layout contract back at them. They have the files.