How the Two-Stage Lesson Generation Pipeline Works in OpenMAIC's lib/generation
OpenMAIC builds lesson content through a two-stage process that first generates a structured outline with an LLM, then produces slide decks, images, audio, and interactive media from that outline.
The two-stage lesson generation pipeline in OpenMAIC cleanly separates architectural planning from asset creation. This design prevents race conditions, enables progress tracking, and allows generation jobs to resume after page refreshes. The pipeline lives across multiple modules in the codebase, with core logic in lib/types/generation.ts, orchestration in lib/workbench/use-workbench-session.ts, and media handling in lib/store/media-generation.ts and related files.
Stage 1: Outline Generation
The first stage uses a large language model to create a structured lesson blueprint. This includes chapters, sections, learning objectives, and the high-level content flow.
Key components driving outline generation:
-
lib/types/generation.tsdefines theLessonOutlinetype and related generation interfaces. These type definitions enforce the contract between the LLM output and downstream consumers. -
lib/workbench/use-workbench-session.tsmanages the generation lifecycle. It maintains agenerationref that increments with each new request, ensuring only results from the current generation are applied. The hook dispatches aSTART_GENERATIONaction and stores the resulting outline in the stage's generation store. -
lib/server/agent-runtime/generation-tools.tscontains server-side definitions for the "generation tools" that invoke the LLM. The agent runtime calls these tools, receives a JSON outline, and persists it to the stage record.
The outline is stored with generationComplete: false until the second stage finishes. This flag, combined with a runtimeGenerationSessionId in lib/utils/chat-storage-core.ts, provides generation fencing that isolates consecutive lesson builds from one another.
// lib/workbench/use-workbench-session.ts (conceptual)
function startLessonGeneration() {
// Increment generation token to fence this request
generation.current += 1;
dispatch({
type: 'START_GENERATION',
token: generation.current,
stageId: activeStage.id
});
// Server-side agent runtime invokes generation-tools.ts
// Result: JSON outline written to stage.outline
}
Stage 2: Media and Content Generation
Once the outline exists, the pipeline generates slide decks, images, audio, video, and other interactive assets. This stage is placeholder-driven, meaning the system creates references for required media before the actual content exists.
How media generation works:
-
lib/store/media-generation.tsimplements a Dexie-backed store that tracks both placeholders and completed media tasks. Placeholders represent pending assets (slides, images, TTS segments) with unique IDs and status flags. -
lib/workbench/media-lifecycle.tsmonitors placeholder references viauseMediaGenerationStore. When it detects new placeholders, it launches appropriate generation jobs—image synthesis, text-to-speech, video stitching—and updates the store on completion. -
lib/video-export-app/*compiles finished assets into exportable packages (video files, HTML presentations).
// Placeholder-driven media pipeline
useEffect(() => {
const placeholders = useMediaGenerationStore.getState().placeholders;
placeholders
.filter(ph => ph.status === 'pending')
.forEach(ph => {
// Launch generation based on placeholder type
switch (ph.type) {
case 'slide':
generateSlideImage(ph);
break;
case 'narration':
generateTTS(ph);
break;
case 'video-segment':
generateVideoClip(ph);
break;
}
});
}, [placeholders]);
When all placeholders resolve, the stage record's generationComplete flag flips to true. This triggers UI updates and enables export functionality.
State Management and Resumability
The pipeline persists critical state to support recovery across refreshes.
| Mechanism | Purpose | Location |
|---|---|---|
| Generation token | Prevents stale LLM responses from overwriting current work | lib/workbench/use-workbench-session.ts |
| Session ID fencing | Isolates different generations of the same stage | lib/utils/chat-storage-core.ts |
| Placeholder status | Tracks which media assets exist vs. pending | lib/store/media-generation.ts |
| Completion flags | Enables skip/resume logic on reload | lib/store/stage.ts, lib/utils/database.ts |
On page load, lib/store/stage.ts checks the stage record's generationComplete flag and the outline's completion status. If generation finished, the UI skips the pipeline. If interrupted, the system resumes in-flight jobs by scanning for pending placeholders in lib/store/media-generation.ts.
Summary
- Stage 1 (Outline) uses
lib/types/generation.ts,lib/workbench/use-workbench-session.ts, andlib/server/agent-runtime/generation-tools.tsto generate and store a structured lesson blueprint. - Stage 2 (Media) leverages
lib/store/media-generation.tsandlib/workbench/media-lifecycle.tsto produce assets from placeholder definitions, with final compilation inlib/video-export-app/*. - Generation fencing via tokens and session IDs in
lib/utils/chat-storage-core.tsprevents race conditions between concurrent generation requests. - Resumable design through Dexie persistence in
lib/store/media-generation.tsandlib/utils/database.tsallows generation to continue after browser refreshes.
Frequently Asked Questions
What files control the lesson generation pipeline in OpenMAIC?
The pipeline spans lib/types/generation.ts (type definitions), lib/workbench/use-workbench-session.ts (orchestration), lib/server/agent-runtime/generation-tools.ts (LLM tools), lib/store/media-generation.ts (placeholder tracking), and lib/workbench/media-lifecycle.ts (job launching).
How does OpenMAIC prevent race conditions during generation?
The system uses a generation token incremented in use-workbench-session.ts and a runtimeGenerationSessionId in chat-storage-core.ts to fence each generation request. Only responses matching the current token are applied, discarding stale results from prior requests.
Can generation resume after a browser refresh?
Yes. Placeholder states persist in Dexie (lib/store/media-generation.ts) and stage records store generationComplete flags (lib/store/stage.ts). On reload, the system checks these flags and resumes any pending media generation jobs automatically.
What triggers the transition from outline to media generation?
The outline's presence in the stage record with generationComplete: false triggers placeholder creation in media-lifecycle.ts. Once placeholders exist, useMediaGenerationStore detects them and launches the appropriate generation tasks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →