etude

module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT

README

etude

Formerly textbook-to-audiobook. The audiobook pipeline below is becoming one renderer of a larger course compiler — see PLAN.md for the product plan.

Turn a directory of structured lessons (markdown + diagrams) into:

  • a chapterised M4B audiobook (one chapter per unit)
  • per-unit MP3 files
  • a markdown study guide
  • an EPUB companion with the SDXL cover embedded

Built as a Go-DSL pipeline on top of vibe — vibe supervises the model backends (LLM, vision, TTS, image generation) under capability profiles; this binary describes the DAG, ships the prompts as embedded assets, and surfaces a first-class CLI.

Quickstart

# 1. install vibe + start the daemon
go install github.com/gallowaysoftware/vibe/cmd/vibe@latest
vibe daemon &

# 2. install this binary
go install github.com/gallowaysoftware/etude/cmd/etude@latest

# 3. see what the host needs
etude requirements

# 4. check what's missing (read-only), then bring everything up
etude doctor      # reports each declared service: ✓ or ✗
etude activate    # `vibe start`s every required profile

# 5. run
etude run \
  --source ./my-course/Module_1 \
  --module-num 1 \
  --topic "introductory thermodynamics"

activate reads the pipeline's RequireProfile + RequireService declarations and brings up everything via vibe in one go — same as running vibe start <each-profile> by hand, but the pipeline tells vibe what it needs. --skip-active brings up only the CPU sidecars when the GPU is busy elsewhere.

Run time: ~5 hours on a single RTX 5090 for a 60-lesson module. Deliverables land in $XDG_STATE_HOME/vamp/runs/etude_<timestamp>/.

Versioning note: v0.1.0 and v0.2.0 predate the rename and declare the old module path (github.com/gallowaysoftware/textbook-to-audiobook) — the Go module mirror and checksum database are immutable, so those two tags resolve only under that path. v0.3.0 onward is the installable line under github.com/gallowaysoftware/etude.

The drill coach

Point etude at a course whose material carries its own questions with model answers and you get an honest drill: questions relayed verbatim from the corpus, answer and confidence collected before any reveal, grading only against the official key (decomposed into rubric points at extraction), misses requeued at 7/20/40 minutes within the session, mastery re-verified across days at 1/2/4-day intervals, and confident-but-wrong answers — blindspots — drilled hardest. It never invents a question and never grades from the model's own opinion.

# the demo course is the five-minute tour — original material, ships with the repo
export ETUDE_LLM_URL=http://localhost:8080/v1   # any OpenAI-compatible endpoint
export ETUDE_LLM_MODEL=<your-model>

etude drill --course examples/demo-course    # the terminal drill loop
etude report --course examples/demo-course   # coverage, mastery, blindspots

# same coach, agent frontends
etude serve --course examples/demo-course              # MCP over stdio (study_* tools)
etude drill api next --course examples/demo-course     # one JSON object per call
etude skill --course examples/demo-course              # skill file for agent harnesses

Grading judgement is the one place a model decides a fact about you, so qualify yours before trusting it with weeks of study:

etude eval grading --course examples/demo-course \
  --golden examples/demo-course/eval/grading-golden.jsonl

Minimum-grader guidance, measured with the harness (your numbers will vary by quant and prompt budget): qwen3.6-35b-a3b Q4_K_XL passes with thinking enabled (within-±1 91.3%, mean signed error −0.43, mildly strict) and fails with thinking disabled (within-±1 82.6% — strict on partial-credit omissions). ETUDE_LLM_EXTRA_BODY='{"chat_template_kwargs":{"enable_thinking":false}}' buys a 6x latency cut; use it only if your model still passes with it set. A mildly strict grader is safe (a miss just gets re-drilled); a lenient one is the failure to rule out (blindspots silently graduate). Frontier-class endpoints pass comfortably and are the right default when one is configured.

The text legs (drill, grading, eval) need only an OpenAI-compatible endpoint — a local router or an external key; no vibe daemon. Audio, vision, and covers remain local-stack. Learner state is greppable JSON at <course>/.etude/study.json, single-writer locked. How a corpus marks its own questions is declared in course.yaml (assessment_markers; see docs/course-format.md).

RAG export

After a textbook run lands, the rag family of subcommands produces a portable retrieval-augmented dataset — chunked + embedded content + study aids — loadable in AnythingLLM, LangChain, LlamaIndex, or Open WebUI.

# Bring up the embedding sidecar (one-time per session).
vibe start embed_bge_large    # bge-large-en-v1.5-q8 on :14004

# Chunk + enrich + embed.
etude rag run \
  --lessons <run-dir>/processed_lessons.json \
  --out <run-dir>/rag \
  --module module_1

# Build a ChromaDB persistent directory.
etude rag pack \
  --chunks <run-dir>/rag/chunks.jsonl \
  --out <run-dir>/rag \
  --collection module_1

rag run outputs chunks.jsonl (source of truth, embeddings + LLM enrichment), flashcards.tsv (Anki), study_qa.md, glossary.md, key_numbers.md, equations.md, and manifest.json. rag pack then derives the loadable chroma_db/ directory from chunks.jsonl.

rag pack and rag anki shell out to Python helpers — rag pack needs the chromadb package, rag anki needs genanki. Both prefer a venv at ~/.local/state/etude/rag-venv if present (set ETUDE_RAG_PYTHON to point elsewhere); otherwise python3 on $PATH must have the package installed. The study-aid prompts frame themselves around --program, so pass it (e.g. --program "an introductory astronomy course") for material-appropriate questions; it defaults to generic exam-prep language.

What's the difference from a generic vamp YAML pipeline

A YAML pipeline is portable but stops at YAML's expressive ceiling — no real CLI, no embedded assets, no auto-generated runtime contract. This binary:

  • Embeds prompts + workflows via go:embed, so installing the binary is enough to run; no separate prompt directory.
  • First-class Cobra CLI — --source, --topic, --module-num instead of --input lesson_root=… --input module_topic=….
  • Pipeline runtime contract via the requirements subcommand — vibe doctor (or anything else that has to set the host up) reads it as JSON.
  • Single static binary — go build/go install produces one self-contained binary (prompts + workflows are go:embed-ed); no separate asset directory.

The runtime still needs vibe + the right model backends; the binary is the pipeline, not the inference stack.

Configurability

All curriculum-specific surface area is in pipeline/config.go as a Config struct. The top-level CLI flags write into it:

Flag Config field What it controls
--source LessonRoot Lesson directory root
--module-num ModuleNum Numeric module id (cover seed + filenames)
--topic ModuleTopic Short prose summary
--subject SubjectLabel "the course subject" → "astronomy", "physics", …
--program ProgramLabel "this course" → "an introductory astronomy course", "Stanford CS103", …
--persona ExpertPersona First-person voice the LLM adopts
--assessment AssessmentLabel "final assessment" → "course final", "qualifying review", …
--cover-prompt CoverPrompt SDXL prompt for the module cover
--voice Voice Kokoro voice (default af_bella)
--search-suffix SearchSuffix Appended to every web-search query

The rag study-aid prompts (enrichment + equation extraction) frame themselves around --program too, so a passed-in program label is the only place curriculum identity lives — the pipeline itself ships subject-agnostic.

For more elaborate forks (different output filename templates, EPUB metadata, etc.) see examples/example-course/config.go — it shows how to skin the pipeline for a specific curriculum by setting just the diverging fields.

Lesson directory layout

Module_1/
├── Lesson_1_-_Introduction_to_Stars/
│   ├── lesson.md
│   └── images/
│       ├── diagram_01.svg
│       └── photo_01.png
├── Lesson_2_-_Stellar_Spectra/
│   └── lesson.md
└── …

Each lesson.md is what the model reads. images/ is optional; SVGs and rasters are both supported. Identical diagrams across lessons (sha256-equal) get one vision pass shared via content-addressed cache.

Runtime requirements

Run etude requirements to see the live list. As of this writing:

  • Capabilities: long_form (27B+ text model, 128k+ context), vision (27B+ multimodal), tts, image_gen.
  • Services: searxng on :14002, kokoro-fastapi on :8880 (activated as the tts_kokoro vibe profile).
  • Hardware: ~32GB VRAM peak, ~20GB disk per module run.
  • Models tested against: Qwen3.6-27B-MTP @ 131k, Gemma 3 27B + mmproj, Kokoro-FastAPI, SDXL-Turbo.

Pipeline structure

The DAG was lifted from a production module.yaml pipeline:

list_lessons → flatten_unique_images → describe_image (vision, foreach unique image)
                                            ↓
                          compact_lesson_diagrams (per lesson)
                                            ↓
                          process_lesson → merge_lessons → compact_lessons
                                                                ↓
                          ┌─ extract_units ─────────────────────┤
                          ↓                                     ↓
                   generate_unit_script (per unit)         extract_topics
                          ↓                                     ↓
                   flatten_segments → chunk_for_tts          search → compact_search
                          ↓                                     ↓
                          audio (kokoro, per chunk) ←───────────┘
                          ↓
                          mp3 (per unit)
                          ↓
                          m4b (chapterised)        study_guide → epub
                                              cover_art ─┘

Run etude viz for the full Mermaid graph.

Status

Validated against the production pipeline it was lifted from, by reproducing several module deliverables end-to-end.

License

MIT — the pipeline is Galloway Software's. It ships no curriculum content: the subject-specific surface (labels, cover prompt, source lessons) is supplied at run time via flags or a Config fork — see examples/example-course/. Bring your own lesson material; whatever you generate from it is yours.

Directories

Path Synopsis
cmd
etude command
Command etude turns a directory of structured lessons (markdown + diagrams) into a chapterised audiobook (M4B), per-unit MP3s, a markdown study guide, and an EPUB companion.
Command etude turns a directory of structured lessons (markdown + diagrams) into a chapterised audiobook (M4B), per-unit MP3s, a markdown study guide, and an EPUB companion.
Package coach is the drill's scheduling policy: it decides WHAT comes next, so every frontend (terminal REPL, MCP tools, agent skill) runs the identical loop.
Package coach is the drill's scheduling policy: it decides WHAT comes next, so every frontend (terminal REPL, MCP tools, agent skill) runs the identical loop.
Package course implements the course manifest (course.yaml) and the lesson-tree contract it describes: loading, defaulting, validation, and scaffolding.
Package course implements the course manifest (course.yaml) and the lesson-tree contract it describes: loading, defaulting, validation, and scaffolding.
examples
example-course
Package examplecourse is a worked example of how to configure etude for a specific curriculum without touching the generic pipeline.
Package examplecourse is a worked example of how to configure etude for a specific curriculum without touching the generic pipeline.
Package grade owns the one place a model, not code, decides a fact about the learner: grading judgement.
Package grade owns the one place a model, not code, decides a fact about the learner: grading judgement.
internal
mathextract
Package mathextract auto-proposes a mathgen Catalog from a textbook run's processed_lessons.json using the LLM, then keeps only the entries whose encoded formula reproduces a worked example transcribed from the source.
Package mathextract auto-proposes a mathgen Catalog from a textbook run's processed_lessons.json using the LLM, then keeps only the entries whose encoded formula reproduces a worked example transcribed from the source.
rag
Package rag turns a etude pipeline run's processed_lessons.json into a portable retrieval-augmented-generation dataset: chunked + embedded content, plus study aids (flashcards, glossary, key-numbers reference, equation cheat sheet).
Package rag turns a etude pipeline run's processed_lessons.json into a portable retrieval-augmented-generation dataset: chunked + embedded content, plus study aids (flashcards, glossary, key-numbers reference, equation cheat sheet).
Package mathgen turns a catalog of computable equations into study material: a written "math textbook" (worked examples + practice problems with code-computed answers) and a live drill that generates fresh, never-repeating numeric problems.
Package mathgen turns a catalog of computable equations into study material: a written "math textbook" (worked examples + practice problems with code-computed answers) and a live drill that generates fresh, never-repeating numeric problems.
Package pipeline holds the etude pipeline definition, its embedded prompts, and the Config struct that pipeline binaries derive from CLI flags.
Package pipeline holds the etude pipeline definition, its embedded prompts, and the Config struct that pipeline binaries derive from CLI flags.
Package qbank extracts a course's own assessment material into a structured, gradeable question bank.
Package qbank extracts a course's own assessment material into a structured, gradeable question bank.
Package study implements the spaced retrieval-practice tracker behind the drill coach.
Package study implements the spaced retrieval-practice tracker behind the drill coach.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL