We build Sovereign Voice at Eidetic Works — a local-first TTS app for Mac that runs Qwen3-TTS on Apple Silicon via MLX. This is a side project alongside our main work on Nucleus OS.
The problem
When we launched on Product Hunt, we got 2,738 pageviews. Zero activations.
The reason? A 9-minute model download on first launch.
The app shipped as a 403MB DMG — small, fast to download. But on first launch, the daemon downloaded a 1.9GB model from HuggingFace Hub. Users saw "Warming up…" for 549 seconds (measured on an M3 Pro) and assumed the app was frozen.
3 real users tried it. All churned within hours.
The fix: bundle the model in the DMG
Instead of downloading the model on first launch, we bundled it directly into the app bundle's Resources directory before creating the DMG.
Step 1: Copy the HF cache snapshot into the build
In our build script, before the hdiutil create call, we added a step that searches for the model in the local HF cache and copies it into the app bundle. hdiutil auto-sizes the DMG based on content. The DMG went from 403MB to 1.9GB.
Step 2: Copy-on-first-launch (the read-only problem)
After code signing and notarization, the Resources directory is read-only. huggingface_hub writes lock files during cache lookups even when the model is present. Pointing HF_HOME directly at the bundled path would crash.
The solution: on first launch, copy the bundled weights to a writable location (~/Library/Application Support/Sovereign Voice/hf_cache/), then set HF_HOME there and HF_HUB_OFFLINE=1 so huggingface_hub resolves from local cache without any network access.
Step 3: UI feedback during the copy
The copy takes about 5-10 seconds (local SSD to SSD). We added a "Preparing models for first use…" status to the existing boot overlay.
Results
| Metric | Before | After |
|---|---|---|
| DMG size | 403MB | 1.9GB |
| First launch wait | 549s (9 min) | ~10s (local copy) |
| Network required | Yes (1.9GB download) | No (fully offline) |
| Model engine | Qwen3-TTS 0.6B 8-bit (Apache-2.0) | Same |
The tradeoff: the DMG is 5x larger. But users expect a big download for a local AI app — they don't expect a 9-minute wait after installing.
Why not just add a progress bar?
We tried. The existing download screen already had a progress bar with MB/total and percentage. But the download happens inside the daemon (mlx_audio.load_model), which emits only stdout log lines — no structured progress events. The frontend progress bar was dead code.
We added a dynamic ETA to the download screen as a fallback, but the real fix was eliminating the download entirely.
Stack
- Engine: Qwen3-TTS-0.6B-8bit via mlx-audio 0.4.5 (Apache-2.0)
- Runtime: MLX on Apple Silicon (Metal)
- App: Tauri 2.x + React
- Daemon: Python, frozen via PyInstaller (589MB binary)
- Build: PyInstaller, manual .app assembly, hdiutil DMG, codesign, notarize
Try it
Sovereign Voice is free to try (25 generations, 1 voice). Pro is $19 one-time — all voices, voice cloning, studio mode, unlimited generations. No subscription.
Works fully offline after install. Apple Silicon Macs only (M1+).
This is a side project at Eidetic Works. Our main product is Nucleus OS — an open-source agent control plane for cross-tool memory.
Top comments (0)