| name | build-binder |
| description | Build a complete open-book exam binder (vault + per-lecture .docx + cross-reference validation) for a course, using your agent's own LLM session — no separate API key. Triggers - `/build-binder <course-path>`, "build the binder for <course>", "process my lectures into a binder". For an already-extracted course, the skill is incremental — it skips unchanged sources. |
Build-Binder Skill
Walks you through the full binder build for a course. Replaces
python -m pipeline.run for users who'd rather have their agent do
the LLM work in-session (no ANTHROPIC_API_KEY required) than run a
standalone Python script.
You are the LLM doing the extraction, concept synthesis, and binder
content generation. The pipeline's Python helpers handle the
deterministic parts: file discovery, manifest tracking, frontmatter
wrapping, .docx rendering, cross-reference validation.
Prerequisites
- The course folder exists with a valid
course.yaml
uv sync has been run (pyyaml, python-frontmatter, pydantic,
python-docx available in the environment)
- For
.pptx sources: python-pptx available (already in uv sync)
- For
.pdf sources: you can read PDFs via the Read tool
The skill does NOT require ANTHROPIC_API_KEY. All LLM work happens
in your agent session.
Step 0: Load and validate the course
cd <repo-root-or-anywhere-with-pipeline-installed>
.venv/bin/python -c "from pipeline.course import load_course; c = load_course('<course-path>'); print(f'OK {c.name} — modules: {c.module_codes}')"
If validation fails, fix <course-path>/course.yaml and re-try.
Throughout the rest of this skill, treat <course> as the absolute
course path. All agent_helpers commands take --course <course>.
Step 1: Stage 1 — extract each source into a vault lecture
For each module declared in course.yaml.modules:
1a. List the sources
.venv/bin/python -m pipeline.agent_helpers list-sources --course <course> --module <MODULE>
Outputs one path per line. Sources may be .md/.markdown/.txt (read
inline), .pdf (read via Read tool — multimodal), or .pptx (extract
text first via python -m pipeline.extract_pptx <path>).
1b. For each source — assign a code, read it, generate the vault markdown
For each source path in parallel where possible (use multiple Read tool
calls or dispatch sub-agents for big courses):
Step 1b.i — assign code:
.venv/bin/python -m pipeline.agent_helpers assign-code --course <course> --module <MODULE> --source <source-filename>
Outputs JSON: {"code": "L01", "hash": "abc123...", "vault_path": "...", "skip_reason": ""}. If skip_reason is non-empty, the source is unchanged since last run — skip to the next source.
Step 1b.ii — read the source content:
.md / .markdown / .txt: use the Read tool
.pdf: use the Read tool (it handles PDF multimodally)
.pptx: .venv/bin/python -m pipeline.extract_pptx <source-path> and capture stdout
Step 1b.iii — read the extraction prompt:
cat pipeline/prompts/extraction.md
This is the rules-of-extraction spec; follow it exactly when generating the vault markdown.
Step 1b.iv — generate the vault markdown body. Follow pipeline/prompts/extraction.md. Output structured markdown with:
- Frontmatter block
# {LECTURE_CODE} — {Lecture Title} at the top
## Summary — 2-3 paragraphs
## Key concepts — bullet list of [[concept name]] wiki-links
## Sections with ### A., ### B., etc. — single capital letters at heading-3 level (these letters are stable cross-reference anchors)
## Cross-references to other lectures — leave empty (none yet on first pass — Stage 2 fills these from concept aggregation)
Hedge preservation rules from the prompt are HARD: italicise hedged claims, mark figure-derived claims with (fig).
Step 1b.v — write it:
.venv/bin/python -m pipeline.agent_helpers write-vault \
--course <course> \
--module <MODULE> \
--code <L##> \
--source <source-filename> \
--hash <hash-from-step-1b.i> \
--out vault/lectures/<MODULE>_<L##>_<title>.md \
--body - <<'BODY'
<the vault markdown body you just generated>
BODY
(The helper wraps the body with proper YAML frontmatter and preserves any AUTO-zone markers if the file already exists.)
Repeat 1b for every source in every module before moving to Step 2.
Step 2: Stage 2 — concept aggregation + synthesis
2a. Aggregate
.venv/bin/python -m pipeline.agent_helpers concept-aggregate --course <course>
Outputs JSON keyed by canonical (lowercased) concept name:
{
"trophic cascade": [
{"module": "MARINE", "code": "L02", "lecture_filename": "MARINE_L02_Coral_Bleaching", "section": "B", "excerpt": "...", "display": "trophic cascade"},
...
],
...
}
2b. Optional dedup pass
Inspect the concept names. If you see obvious case/plural/hyphen variants
of the same concept (e.g. "trophic cascade" + "trophic cascades", or
"K-strategy" + "k strategy"), pick the shorter/lowercase form as
canonical and merge appearances into it. Be conservative — only merge
true variants, not related-but-distinct concepts.
2c. Synthesise each concept page
For each concept, read pipeline/prompts/concept_aggregation.md (the
synthesis prompt) and generate the body of the concept page using the
captured excerpts as evidence.
Write each:
.venv/bin/python -m pipeline.agent_helpers write-concept-page \
--course <course> \
--concept "<canonical name>" \
--modules '["MARINE"]' \
--aliases '["trophic cascade", "Trophic Cascade", "trophic cascades"]' \
--body - <<'BODY'
<concept synthesis body>
BODY
Modules: JSON array of module codes the concept appears in (from the aggregate JSON).
Aliases: JSON array of all observed display forms (Obsidian wiki-link resolution will use these).
2d. Regenerate the concept index
.venv/bin/python -m pipeline.agent_helpers write-concept-index --course <course>
Step 3: Stage 3 — generate per-lecture .docx files
For each module:
3a. Build the module corpus
.venv/bin/python -m pipeline.agent_helpers build-corpus --course <course> --module <MODULE>
Outputs the concatenated text of all module lectures + module-scoped
concept pages. Read this once per module and keep it in your conversation
context for the rest of Step 3.
3b. Read the lecture-format template
.venv/bin/python -c "from pipeline.course import load_course; print(load_course('<course>').template_path('lecture_format.md').read_text())"
This is the binder format spec. The course's templates/lecture_format.md
overrides pipeline/templates/lecture_format.md if present.
3c. For each lecture in the module — generate the .docx JSON
Read the vault .md for the lecture. Combining (a) the lecture content,
(b) the module corpus from 3a, (c) the lecture-format template from 3b,
generate a JSON document matching this Pydantic schema:
{
"lecture_code": "L01",
"title": "Ocean Stratification and the Vertical Structure of the Pelagic Realm",
"blocks": [
{"type": "heading", "level": 1, "text": "..."},
{"type": "paragraph", "text": "...", "bold": false, "italic": false},
{"type": "table", "headers": ["..."], "rows": [["...", "..."]]},
{"type": "decision_tree", "rows": [["IF condition", "THEN outcome", "explanation"]]},
{"type": "spacer"}
]
}
Block types: heading (level 1-3, has text), paragraph (has text, optional bold/italic/colour), table (has headers and rows), decision_tree (rows of [condition, outcome, explanation] triples; renders as a Condition/Outcome/Explanation table), spacer.
3d. Render the .docx
Pipe the JSON to the renderer:
echo '<json blob>' | .venv/bin/python -m pipeline.agent_helpers render-docx \
--course <course> --module <MODULE>
Or write the JSON to a temp file and pass --file /tmp/lec.json. The helper validates the JSON against the schema, then renders to <course>/binder/<MODULE>/L##_<title>.docx with the module's header/subheader colours.
Repeat 3c-3d for every lecture in every module.
Step 4: Stage 5 — cross-reference validator (deterministic)
.venv/bin/python -m pipeline.agent_helpers validate --course <course>
Exits 0 if all see L## §X "..." cross-refs in the rendered .docx files
resolve to real lecture sections in the same module's vault. Exits 1
otherwise; the report at <course>/pipeline_state/validation_report.md
names each unresolved ref. Fix by editing the offending vault .md
section anchors or by editing the cross-ref text in the source lecture's
vault md and re-running Stage 3 for that lecture only.
Step 5: (Optional) build the combined compendium
.venv/bin/python -m pipeline.build_compendium --course <course>
Pure Python. Builds the module-level Combined .docx + .pdf (the
single PDF compendium with TOC). Needs LibreOffice headless for PDF
rendering; falls back to .docx-only if missing.
Step 6: Stage 4 — verification (optional, recommended)
For each lecture: re-read the source, re-read the vault .md, identify
claims in the vault not supported by the source. Flag each by severity
([HIGH] = factual error / fabricated quote, [MED] = unsupported framing,
[LOW] = stylistic). Write reports to
<course>/pipeline_state/verification/<lecture>.md.
This is a manual (agent-driven) audit pass. Not required for a fresh
build; recommended after edits or for higher-stakes courses.
Tips
- Parallelise Step 1b across lectures. Read all source files in one
tool batch; generate vault
.mds in parallel via sub-agent dispatch
if the course has more than ~10 lectures.
- Save the corpus from Step 3a to a
/tmp/<MODULE>-corpus.txt file
so you don't have to re-read it across multiple lecture-generation
passes.
- Don't invent cross-references. Use only
see L## §X "Title" where
L## is a real lecture in the same module and §X is a real ### X.
section heading. Stage 5 will catch fabricated refs.
- Section letters (
A, B, C) are sacred. They're cross-reference
anchors; renaming sections breaks refs from other lectures.
- Unchanged sources skip cleanly. The manifest tracks content hashes;
re-running the skill on an unchanged source returns
skip_reason and
you skip to the next.
When to use the standalone Python pipeline instead
python -m pipeline.run --course <course> does the same work via the
Anthropic API directly (requires ANTHROPIC_API_KEY). Faster than the
agent-driven path for big courses (uses prompt caching for the module
corpus across all lectures in one Stage 3 sweep). Use it if:
- You're running headless / unattended (CI, scheduled job)
- You have a 50+ lecture course and want maximum throughput with cache hits
- You explicitly prefer the API-key path
For everything else — when you've got an agent open already — use this
skill.