# How the OpenMAIC VoiceDesign Interface Ensures Agent Voices Travel with Exported Courses

> Learn how OpenMAIC's VoiceDesign interface keeps agent voices with exported courses by serializing and re-hydrating voice configuration metadata. Seamlessly transfer your voiceovers.

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

---

**The OpenMAIC VoiceDesign interface ensures an agent’s voice travels with an exported course by serializing voice configuration metadata into the course bundle during export and re-hydrating that state on import.**

The OpenMAIC platform manages AI-driven classroom environments where each course defines specific voice characteristics for both narrators and student agents. When instructors share or backup courses, preserving these audio preferences is critical for consistent learner experiences across different deployments. The `VoiceDesign` interface acts as a serializable container for all voice-related settings, embedding directly into the course metadata to guarantee that agent voices survive the export and import cycle.

## The VoiceDesign Interface Structure

The foundation of voice portability in OpenMAIC begins with the `VoiceDesign` interface defined in [`lib/audio/voice-design.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/audio/voice-design.ts). This TypeScript interface establishes a standardized schema for voice configuration that captures every customizable audio element.

The interface defines four primary properties:

- **`narratorVoice?: VoiceBinding`** – Specifies the default voice for the "teacher" or narrator role
- **`agentVoiceOverrides?: Record<string, VoiceBinding>`** – Maps specific agent IDs to custom voice configurations, allowing individual student agents to speak with distinct voices
- **`availableVoices?: VoiceOption[]`** – Lists voice options available for UI presentation during course authoring
- **`voiceDesignPrompt?: string`** – Carries optional prompts for backend voice registration services

```ts
export interface VoiceDesign {
  /** The voice the narrator (the “teacher”) will use. */
  narratorVoice?: VoiceBinding;
  /** Per‑agent voice overrides (e.g. student agents). */
  agentVoiceOverrides?: Record<string, VoiceBinding>;
  /** The list of voices that the UI can present to the user. */
  availableVoices?: VoiceOption[];
  /** Optional prompt that can be sent to a backend that supports voice registration. */
  voiceDesignPrompt?: string;
}

```

Because `VoiceDesign` is a plain JavaScript object without methods or circular references, it serializes cleanly to JSON without data loss, making it ideal for cross-platform course sharing.

## Serializing Voice Data During Course Export

When a user initiates an export operation, the OpenMAIC workbench collects the current course state and packages it into a portable bundle. In [`lib/workbench/run-courses.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/workbench/run-courses.ts), the `exportCourse` function constructs a JSON payload that includes a `voiceDesign` field copied directly from the workspace store.

The exporter accesses the current voice configuration through the store selector:

```ts
const voiceDesign = workspaceStore.getState().voiceDesign;

```

This object is embedded verbatim into the exported bundle. Since the `VoiceDesign` interface stores only primitive values and nested plain objects—such as `providerId` strings, `voiceId` identifiers, and custom prompts—all nested properties survive the JSON serialization process intact. The resulting export file contains a self-contained voice configuration that requires no external database references to reconstruct.

## Re-Hydrating Voice Settings on Course Import

The import routine in the same [`lib/workbench/run-courses.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/workbench/run-courses.ts) file reverses the serialization process. The `importCourse` function reads the `voiceDesign` entry from the imported JSON bundle and writes it back into the workspace store via the `voiceDesignSlice`.

Once stored, selectors such as `selectNarratorVoice` and `selectAgentVoiceOverrides` immediately access the re-hydrated state. This means the UI renders the previously configured narrator voice and per-agent overrides without requiring manual reconfiguration. The course author can execute the imported course knowing that every agent will speak with the exact same voice characteristics defined on the original machine.

## Runtime Voice Resolution and TTS Integration

During course execution, the runtime system resolves abstract voice bindings into actual TTS (Text-to-Speech) requests. The [`tts-stage-sync.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/tts-stage-sync.ts) module coordinates this process by calling helper functions from [`lib/audio/voice-resolver.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/audio/voice-resolver.ts):

- **`resolveNarratorVoiceBinding`** – Retrieves the narrator voice from the `VoiceDesign` data
- **`resolveAgentVoice`** – Looks up specific agent overrides or falls back to default provider settings

These resolvers read the same `VoiceDesign` payload that traveled with the exported course, ensuring consistent voice selection regardless of which machine executes the course. When the TTS stage generates speech, it embeds the resolved voice IDs into request payloads sent to providers like OpenAI TTS or Qwen TTS.

```ts
import { resolveNarratorVoiceBinding } from '@/lib/audio/voice-resolver';

const narrator = resolveNarratorVoiceBinding(
  workspaceStore.getState().voiceDesign,
);
ttsStageSync.speak(text, narrator);   // Uses the exported voice configuration

```

## Implementing VoiceDesign in Practice

### Creating a VoiceDesign Configuration

During course authoring, developers construct a `VoiceDesign` object and commit it to the workspace store:

```ts
import { VoiceDesign } from '@/lib/audio/voice-design';

const myVoiceDesign: VoiceDesign = {
  narratorVoice: { providerId: 'openai-tts', voiceId: 'alloy' },
  agentVoiceOverrides: {
    student_1: { providerId: 'qwen-tts', voiceId: 'Cherry' },
  },
  availableVoices: [
    { providerId: 'openai-tts', voiceId: 'alloy', voiceName: 'Alloy' },
    { providerId: 'qwen-tts', voiceId: 'Cherry', voiceName: 'Cherry' },
  ],
};

workspaceStore.getState().setVoiceDesign(myVoiceDesign);

```

### Exporting and Importing with Voice Preservation

The export operation automatically includes the voice design without additional configuration:

```ts
import { exportCourse } from '@/lib/workbench/run-courses';

const exportedBundle = await exportCourse({ courseId: 'my-course' });
// exportedBundle contains the voiceDesign field with all voice bindings

```

On the destination machine, importing the bundle restores the voice configuration:

```ts
import { importCourse } from '@/lib/workbench/run-courses';

await importCourse({ bundle: exportedBundle });
// Workspace store now contains the original voiceDesign state

```

## Summary

The OpenMAIC VoiceDesign interface guarantees voice portability through a serializable metadata pattern:

- **[`lib/audio/voice-design.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/audio/voice-design.ts)** defines the interface schema that captures narrator voices, agent overrides, and available options
- **[`lib/workbench/run-courses.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/workbench/run-courses.ts)** handles export serialization and import re-hydration of the `voiceDesign` field
- **Plain JSON serialization** ensures that provider IDs, voice IDs, and custom prompts survive the export/import cycle without data loss
- **[`lib/audio/voice-resolver.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/audio/voice-resolver.ts)** and **[`tts-stage-sync.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/tts-stage-sync.ts)** consume the restored configuration at runtime to resolve actual TTS voices
- **Workspace store selectors** provide immediate access to imported voice settings throughout the UI

## Frequently Asked Questions

### What happens if a voice provider ID in the exported course is not available on the import machine?

The `resolveAgentVoice` and `resolveNarratorVoiceBinding` functions in [`lib/audio/voice-resolver.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/audio/voice-resolver.ts) implement fallback logic that defaults to provider-specific voices when the exact `providerId` or `voiceId` from the exported `VoiceDesign` is unavailable locally. This ensures the course remains functional even when specific voice models differ between environments.

### Can individual student agents have different voices from the narrator?

Yes. The `agentVoiceOverrides` property in the `VoiceDesign` interface accepts a `Record<string, VoiceBinding>` map that assigns unique voice configurations to specific agent IDs. When the runtime resolves voices, it checks this override map before falling back to the narrator voice or system defaults.

### Does the voice design include actual audio files or just voice identifiers?

The `VoiceDesign` interface stores only metadata—specifically `providerId`, `voiceId`, and optional configuration prompts—not actual audio data. The voice identifiers reference TTS services (such as OpenAI TTS or Qwen TTS) that generate audio on demand during course execution.

### How does the UI know which voices to display during course authoring?

The `availableVoices` array within the `VoiceDesign` interface contains `VoiceOption` objects that populate the authoring interface dropdowns. When a course is imported, this array is restored from the exported bundle, ensuring the destination machine presents the same voice selection options that existed during original course creation.