# Multi-speaker Podcast Generation with Open-Notebook: Implementation Guide

> Generate multi speaker podcasts with Open Notebook. Learn how to implement this powerful pipeline combining domain models, job queues, and REST APIs for seamless podcast creation.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-17

---

**Open-Notebook implements a fully-featured, multi-speaker podcast generation pipeline that stitches together domain models, asynchronous job queues, and REST API services.**

The `lfnovo/open-notebook` repository provides a production-ready architecture for generating conversational audio content with multiple speakers. The system combines persistent entity definitions in the domain layer with crash-resilient execution via the surreal-commands framework to handle complex podcast synthesis workflows.

## Domain Models for Multi-Speaker Podcasts

The domain layer in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) defines three core entities that encapsulate podcast configuration, speaker definitions, and episode state.

### EpisodeProfile Configuration

The `EpisodeProfile` class manages high-level parameters for episode generation. It specifies which language models handle distinct phases of the creation process and defines structural constraints.

Key fields include:
- `outline_llm`: Model identifier for generating the conversation structure
- `transcript_llm`: Model identifier for creating dialogue content
- `speaker_config`: Reference to voice and personality definitions
- `default_briefing`: Context provided to all episodes using this profile
- `num_segments`: Target number of conversational segments to generate

### SpeakerProfile Definition

The `SpeakerProfile` class stores voice synthesis and personality data for 1-4 speakers. It integrates with TTS providers through the `voice_model` field while maintaining individual speaker characteristics.

Each speaker dictionary contains:
- `name`: Display identifier for the participant
- `voice_id`: TTS provider-specific voice reference
- `backstory`: Character history informing dialogue context
- `personality`: Behavioral traits affecting speech patterns

### PodcastEpisode Storage

The `PodcastEpisode` model persists generated content and tracks execution state. It maintains a reference to the surreal-commands job via the `command` field, enabling asynchronous status monitoring and crash recovery.

Stored content includes:
- `briefing`: Input context provided for the episode
- `outline`: Structured conversation flow generated by the outline LLM
- `transcript`: Generated dialogue text from the transcript LLM
- `audio_file`: Path to the final synthesized audio file
- `content`: Processed episode metadata and references

## Asynchronous Job Execution

The system uses **surreal-commands** for crash-resilient, asynchronous processing of long-running podcast generation tasks.

### The generate_podcast Command

Located in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py), the `generate_podcast` command encapsulates the complete generation workflow. It orchestrates the transition from outline to audio while maintaining persistent state.

This command coordinates:
- Outline generation using the configured `outline_llm`
- Multi-speaker dialogue creation via `transcript_llm`
- Audio synthesis across multiple voice models defined in `SpeakerProfile`
- Progress persistence through the `PodcastEpisode.command` reference

### Crash-Resilient Processing

By delegating execution to the surreal-commands queue, the system ensures that podcast generation survives process restarts. The `PodcastEpisode.command` field links the domain entity to the job queue state, allowing clients to poll for completion status and enabling automatic retry of failed operations.

## API Service Layer

The `PodcastService` class in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) provides the HTTP façade for client interactions with the podcast generation system.

This service exposes endpoints for:
- Triggering new `generate_podcast` jobs with validated configuration
- Verifying `EpisodeProfile` and `SpeakerProfile` schema compliance
- Polling generation status via the surreal-commands integration
- Retrieving completed episodes with `audio_file` URLs and metadata

## Model Registry Integration

Both `EpisodeProfile` and `SpeakerProfile` expose configuration resolution methods that delegate to the centralized model registry at `open_notebook.ai.models.Model`.

### Configuration Resolution Methods

The domain models implement three helper methods for dynamic provider configuration:
- `resolve_outline_config()`: Returns LLM parameters for outline generation
- `resolve_transcript_config()`: Returns LLM parameters for dialogue generation
- `resolve_tts_config()`: Returns TTS model parameters for audio synthesis

These methods abstract provider-specific implementation details, allowing the system to support multiple LLM and TTS backends without modifying the core generation logic.

## Summary

- **Domain models** in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py) define `EpisodeProfile`, `SpeakerProfile`, and `PodcastEpisode` for persistent configuration and state management across the generation lifecycle.
- **Asynchronous execution** via [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) leverages the surreal-commands framework for crash-resilient job processing and recovery.
- **API abstraction** provided by [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) enables HTTP-based interaction with validation, job triggering, and status polling capabilities.
- **Model registry delegation** through `resolve_outline_config()`, `resolve_transcript_config()`, and `resolve_tts_config()` methods enables flexible multi-provider support.

## Frequently Asked Questions

### How does Open-Notebook handle multiple speakers in podcast generation?

Open-Notebook supports 1-4 speakers through the `SpeakerProfile` model, which stores individual `voice_id`, `backstory`, and `personality` traits for each participant. The `generate_podcast` command in [`commands/podcast_commands.py`](https://github.com/lfnovo/open-notebook/blob/main/commands/podcast_commands.py) coordinates multi-voice synthesis by iterating through the `speakers` list and invoking the TTS provider specified in `voice_model` for each distinct voice.

### What happens if a podcast generation job fails mid-process?

The system uses **surreal-commands** for durable job queue management. The `PodcastEpisode` model maintains a `command` reference that links the domain entity to the job queue state, enabling automatic crash recovery and status polling. Failed jobs can be retried without losing the episode configuration or partial progress stored in the `outline` and `transcript` fields.

### How do I configure different LLM providers for outline and transcript generation?

The `EpisodeProfile` model exposes separate `outline_llm` and `transcript_llm` fields, allowing distinct model selection for each generation phase. The `resolve_outline_config()` and `resolve_transcript_config()` methods delegate to `open_notebook.ai.models.Model` to fetch provider-specific parameters, enabling flexible backend selection without modifying the core pipeline logic.

### Where does Open-Notebook store generated podcast audio files?

The `PodcastEpisode` model persists the `audio_file` path, which references the final synthesized audio location. The actual storage mechanism depends on the TTS provider configuration resolved through `resolve_tts_config()`, while the `PodcastService` in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py) handles URL generation and secure retrieval for client applications.