How FluidVoice Downloads and Caches ASR Models: A Complete Technical Breakdown

FluidVoice downloads ASR models through a two-layer architecture where ASRService orchestrates the operation and model-specific providers handle the actual network fetch, streaming the file to a persistent cache directory in ~/Library/Application Support/FluidVoice/Models/ for subsequent offline use.

FluidVoice's automatic speech recognition (ASR) infrastructure relies on a clean separation between model-agnostic orchestration and provider-specific implementation. This article explains exactly how the open-source macOS dictation app downloads, caches, and retrieves speech-to-text models based on the source code in altic-dev/FluidVoice.


The Entry Point: ASRService Orchestration

All model download operations in FluidVoice flow through ASRService, a singleton service that manages the complete ASR lifecycle. When a user selects a speech model—or when the UI triggers an explicit download—the sequence begins with model selection and provider instantiation.

Model Selection and Provider Creation

The currently active model lives in SettingsStore.shared.selectedSpeechModel. The ASRService creates an appropriate transcription provider through its private getProvider(for:) method:

private func getProvider(for model: SettingsStore.SpeechModel) -> TranscriptionProvider

This method returns concrete implementations such as WhisperProvider, FluidAudioProvider, or ParakeetRealtimeProvider depending on the selected model type. Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L78-L105).

Initiating the Download

The public entry point ASRService.downloadModel(_:source:progressHandler:) enforces single-operation semantics and publishes state for UI binding:

try await provider.prepare(progressHandler: { progress in })

Key responsibilities at this stage include:

  • Recording the model ID in downloadingModelId
  • Setting modelPreparationPhase = .preparingDownload
  • Ensuring only one model operation runs concurrently

Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L107-L120).


Provider-Level Download Implementation

Each concrete provider implements the TranscriptionProvider protocol's prepare(progressHandler:) method. The WhisperProvider implementation demonstrates FluidVoice's download and caching pattern.

Streaming Download with Atomic Placement

In WhisperProvider.swift, the download process constructs a Hugging Face asset URL and delegates to ProgressiveFileDownloader:

let (downloadedURL, response) = try await ProgressiveFileDownloader.download(
    url, progressHandler: progressHandler)
try FileManager.default.moveItem(at: downloadedURL, to: destination)

Critical implementation details:

  • Temporary file handling: Data streams to a temporary location first
  • Atomic move: Only upon successful completion does the file relocate to its final destination
  • Destination path: ~/Library/Application Support/FluidVoice/Models/<model-id>

Source: [WhisperProvider.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/WhisperProvider.swift#L343-L409).


Caching Strategy and Cache Hits

FluidVoice's ASR model caching eliminates redundant network requests through explicit existence checks and state publishing.

Cache Validation

Before any network operation, providers call modelsExistOnDisk(). If the cached file is present and valid, provider.prepare skips the download entirely and proceeds directly to model loading.

UI-Bound Cache State

ASRService exposes three key properties for interface binding:

  • modelsExistOnDisk: Boolean indicating cache presence
  • isDownloadingModel: Active download status
  • downloadProgress: Fractional completion (0.0–1.0)

The computed property modelStatusMessage surfaces this state to users:

var modelStatusMessage: String {
    if self.isAsrReady { return "Model ready" }

    if self.modelsExistOnDisk { return "Model cached, needs loading" }
}

Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L22-L32).


Loading Cached Models into Memory

When a user initiates dictation, ASRService.ensureReadyTask invokes provider.prepare again. This time the provider detects the cached file and loads it—without network activity—into the appropriate runtime:

  • Whisper models: Loaded via Core ML or GGML inference
  • FluidAudio/Parakeet models: Initialized through the FluidAudio runtime

The cache directory persists across app launches, enabling immediate availability after first download.


Cancellation and Cache Consistency

FluidVoice handles interrupted downloads gracefully. If cancelModelDownload() is invoked, providers may trigger provider.clearCache() to remove partial files, preventing corrupted cache entries.


Practical Code Examples

Force Download with Progress Tracking

Button("Download Model") {
    Task {
        do {
            try await ASRService.shared.downloadModel(
                SettingsStore.shared.selectedSpeechModel,
                progressHandler: { fraction in
                    print("Download progress: \(fraction * 100)%")
                }
            )
        } catch {
            print("Download failed: \(error)")
        }
    }
}

Check Cache State Programmatically

if ASRService.shared.modelsExistOnDisk {
    print("Model is already cached – loading directly")
} else {
    print("Model not present – will download on first use")
}

Cancel Ongoing Download

ASRService.shared.cancelModelDownload()

Key Source Files

File Responsibility
Sources/Fluid/Services/ASRService.swift Central orchestration of model selection, download state management, and provider coordination
Sources/Fluid/Services/WhisperProvider.swift Whisper-specific download implementation with Hugging Face integration
Sources/Fluid/Services/FluidAudioProvider.swift Parakeet (FluidAudio) model handling
Sources/Fluid/Services/ExternalCoreMLTranscriptionProvider.swift Cohere Transcribe Core ML model caching
Sources/Fluid/Persistence/SettingsStore.swift User model preference persistence (selectedSpeechModel)

These files collectively implement the complete ASR model download and caching lifecycle in FluidVoice.


Summary

  • Orchestration layer: ASRService manages all model operations, enforces single-download semantics, and publishes observable state
  • Provider abstraction: Each ASR backend implements its own prepare(progressHandler:) for model-specific fetch logic
  • Atomic caching: Downloads stream to temporary files, then move atomically to ~/Library/Application Support/FluidVoice/Models/<model-id>
  • Cache validation: Subsequent preparations check disk existence before network requests
  • Graceful interruption: Cancellation APIs prevent partial file corruption

Frequently Asked Questions

Where does FluidVoice store downloaded ASR models?

FluidVoice caches ASR models in ~/Library/Application Support/FluidVoice/Models/<model-id>, a directory within the macOS application support folder that persists across app launches and system restarts.

Can FluidVoice work offline after downloading models?

Yes. Once a model is cached via modelsExistOnDisk, ASRService and its providers load the local file directly into memory without network activity, enabling complete offline dictation.

How does FluidVoice handle interrupted or failed downloads?

Providers may invoke clearCache() to remove partial files, and ASRService exposes cancelModelDownload() for user-initiated cancellation. The atomic move pattern ensures only complete, verified downloads reach the cache directory.

Which ASR models does FluidVoice support for download?

The codebase includes providers for Whisper (via WhisperProvider), Parakeet/FluidAudio (via FluidAudioProvider), and Cohere Transcribe (via ExternalCoreMLTranscriptionProvider), with extensibility for additional backends through the TranscriptionProvider protocol.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →