# How PBL Scenes Are Handled in the OpenMAIC Generation Pipeline

> Discover how OpenMAIC handles PBL scenes in its generation pipeline. Learn about design-time planning and runtime hydration for efficient project creation and learner state management.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: internals
- Published: 2026-09-06

---

**OpenMAIC (THU-MAIC/OpenMAIC) processes Problem-Based Learning (PBL) scenes through a two-phase pipeline: design-time generation via the PBL v2 planner agent for initial project creation, followed by runtime-aware hydration and design-template persistence to keep learner state separate from the immutable project blueprint.**

Problem-Based Learning (PBL) scenes require special handling in educational AI systems because they combine structured project design with dynamic learner collaboration. The OpenMAIC platform, developed by THU-MAIC, implements a sophisticated generation pipeline that separates **author-defined project templates** from **learner-specific runtime state**. This article examines how PBL scenes move through the OpenMAIC generation pipeline, from initial LLM-based planning to document persistence.

## Design-Time Generation: The PBL v2 Planner

When converting classroom outlines into concrete scenes, the `generateClassroom` routine in [`lib/server/classroom-generation.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/classroom-generation.ts) detects PBL outlines by their `type: 'pbl'` field. Standard scenes receive a direct LLM call, but PBL scenes trigger specialized planning logic.

### Injecting the PBL Planner as a Fallback

The content generation call receives a `pblLoopFallback` option that delegates project construction to the **PBL v2 planner agent**:

```typescript
// Inside generateClassroom (lib/server/classroom-generation.ts)
const content = await withGenerationRetry(
  () =>
    generateSceneContent(
      safeOutline,
      contentCall.aiCall,
      {
        agents,
        languageDirective,
        allowProceduralSkill: vocationalActive,
        // PBL scenes use the planner as a fallback LLM loop
        ...(safeOutline.type === 'pbl'
          ? {
              pblLoopFallback: (input) =>
                generatePBLV2Project(
                  input,
                  contentCall.model,
                  callLLM,
                  { logger: log },
                  contentCall.thinking,
                ),
            }
          : {}),
      },
    ),
  { /* retry options */ },
);

```

The `generatePBLV2Project` function in [`lib/pbl/v2/agents/planner.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/agents/planner.ts) executes a **tool-calling loop** that populates a complete `PBLProjectV2` schema:

- Project metadata and description
- Exactly one Instructor role
- Milestones with associated micro-tasks
- Optional role-play scenario fields

The loop terminates when the `mark_design_complete` tool succeeds. The resulting `PBLProjectV2` object attaches to the scene as `scene.content.projectV2`, containing **no learner-specific state**—only the author-intended project structure.

## Runtime Hydration: Merging Live Learner State

Once a PBL scene exists, OpenMAIC must reconcile the static design template with each learner's progress. This happens through **runtime hydration** in [`lib/pbl/v2/runtime/hydration.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/runtime/hydration.ts).

### The hydratePBLScenesFromRuntime Function

```typescript
// lib/pbl/v2/runtime/hydration.ts
export async function hydratePBLScenesFromRuntime(
  stageId: string,
  scenes: readonly Scene[],
  options: Pick<HydratePBLProjectArgs, 'store' | 'kv' | 'learnerKey'> = {},
): Promise<Scene[]> {
  return Promise.all(
    scenes.map(async (scene) => {
      if (scene.content.type !== 'pbl') return scene;
      const resolved = resolvePBLContent(scene.content);
      if (resolved.kind !== 'v2') return scene;
      const result = await hydratePBLProjectFromRuntime({
        stageId,
        sceneId: scene.id,
        project: resolved.projectV2,
        ...options,
      });
      return { ...scene, content: { ...scene.content, projectV2: result.project } };
    }),
  );
}

```

For each v2 PBL scene, `hydratePBLScenesFromRuntime`:

1. Retrieves the learner's runtime snapshot from the KV store
2. Folds in any stored events (task completions, milestone progress)
3. Merges live learner state back into `projectV2`
4. Falls back to the original document state if hydration fails

This ensures instructors and learners always see current progress without polluting the underlying design template.

## Document Persistence: Separating Design from Runtime Data

Before storing a classroom document, `preparePBLScenesForDocumentPersistence` in [`lib/pbl/v2/runtime/document-persistence.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/runtime/document-persistence.ts) performs a two-step synchronization:

```typescript
// lib/pbl/v2/runtime/document-persistence.ts
export async function preparePBLScenesForDocumentPersistence(
  stageId: string,
  scenes: readonly Scene[],
): Promise<Scene[]> {
  // 1️⃣ Write any pending runtime changes
  await Promise.all(
    scenes.map(async (scene) => {
      const content = scene.content;
      if (content.type !== 'pbl') return;
      const resolved = resolvePBLContent(content);
      if (resolved.kind !== 'v2') return;
      await synchronizePBLProjectRuntime({
        stageId,
        sceneId: scene.id,
        project: resolved.projectV2,
      });
    }),
  );

  // 2️⃣ Return scenes with only the design template stored
  return scenes.map((scene) => {
    const content = scene.content;
    if (content.type !== 'pbl') return scene;
    const resolved = resolvePBLContent(content);
    if (resolved.kind !== 'v2') return scene;
    const designTemplate = stripToDesignTemplate(resolved.projectV2);
    return {
      ...scene,
      content: { ...content, projectV2: designTemplate },
    };
  });
}

```

This function:

- **Synchronizes** pending learner state to the runtime store via `synchronizePBLProjectRuntime`
- **Strips** the full `projectV2` to its design template using `stripToDesignTemplate`
- Returns scenes containing only the immutable blueprint, ensuring collaborative editing always starts from a consistent baseline

## Key Code Files and Responsibilities

| File | Purpose |
|------|---------|
| [`lib/server/classroom-generation.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/classroom-generation.ts) | Orchestrates generation; injects `pblLoopFallback` for PBL outlines |
| [`lib/pbl/v2/agents/planner.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/agents/planner.ts) | `generatePBLV2Project` — agentic planner building `PBLProjectV2` |
| [`lib/pbl/v2/runtime/hydration.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/runtime/hydration.ts) | `hydratePBLScenesFromRuntime` — merges runtime state into scenes |
| [`lib/pbl/v2/runtime/document-persistence.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/runtime/document-persistence.ts) | `preparePBLScenesForDocumentPersistence` — persists runtime changes and strips to template |
| [`lib/pbl/legacy/read.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/legacy/read.ts) | `resolvePBLContent` — detects v1 vs. v2 PBL project formats |

## Summary

- **PBL scene generation** in OpenMAIC uses a specialized planner agent (`generatePBLV2Project`) invoked through `pblLoopFallback` when `type: 'pbl'` is detected in [`lib/server/classroom-generation.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/classroom-generation.ts).
- **Runtime hydration** (`hydratePBLScenesFromRuntime`) merges live learner progress from the KV store without modifying the stored document.
- **Document persistence** (`preparePBLScenesForDocumentPersistence`) writes runtime changes to storage, then strips scenes to their design template for clean separation of concerns.
- The `PBLProjectV2` schema maintains strict boundaries: design-time data is immutable, runtime data is ephemeral and learner-specific.

## Frequently Asked Questions

### What triggers the PBL v2 planner during generation?

The planner triggers when a classroom outline specifies `type: 'pbl'`. The `generateClassroom` function passes this type to `generateSceneContent`, which activates the `pblLoopFallback` option. This fallback delegates to `generatePBLV2Project` rather than making a standard LLM call, enabling the multi-tool planning loop required for complex PBL project structures.

### How does OpenMAIC keep learner progress separate from the project design?

OpenMAIC maintains two distinct storage layers: the classroom document stores only the **design template** (the output of `stripToDesignTemplate`), while learner-specific state lives in a separate **KV/runtime store**. The `hydratePBLScenesFromRuntime` function merges these on demand when a learner loads the scene, and `synchronizePBLProjectRuntime` writes changes back before persistence. This architecture enables multiple learners to share the same project blueprint with individual progress tracking.

### What happens if runtime hydration fails for a PBL scene?

According to the implementation in [`lib/pbl/v2/runtime/hydration.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/v2/runtime/hydration.ts), the hydration function keeps the original document state as a fallback when retrieval or merging fails. Learners see the baseline project template rather than an error, though they may need to re-establish their runtime session. This graceful degradation ensures classroom usability even with transient storage issues.

### How can I distinguish between v1 and v2 PBL projects in the codebase?

Use `resolvePBLContent` from [`lib/pbl/legacy/read.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/pbl/legacy/read.ts). This utility examines `scene.content` and returns a discriminated union indicating whether the project follows the legacy v1 format or the current v2 schema. All modern PBL generation in OpenMAIC targets v2, but the resolver maintains backward compatibility for existing classrooms.