Shipping a 234MB Python Interpreter Inside a Swift App: MinuteAI's Release Pipeline for Optional ML Runtimes
The diarization feature is one story. Building, validating, and releasing the 743MB Python/PyTorch runtime behind it is another. Here's the pipeline MinuteAI runs before that download ever reaches a user's Mac.
The Feature Is the Easy Part to Talk About
We’ve written before about why MinuteAI ships speaker diarization as an optional 234MB download instead of bundling pyannote.audio and PyTorch into the app itself. That post covered the architecture: a subprocess-based Python runtime, fetched on demand, kept at arm’s length from the SwiftUI binary. What it didn’t cover is how that 234MB file gets made in the first place — the part that never shows up in a feature walkthrough, but that has to happen correctly every time before a single byte of it reaches a user’s Mac.
MinuteAI keeps the release artifact in its own repository, separate from the app’s source — a deliberate split, not an accident of project history. The app repo owns the Swift code that downloads, extracts, and drives the runtime; the release repo exists purely to host the built tarball as a GitHub Release asset and to document how to reproduce it. Nothing in the release repo is source code you’d read to understand what diarization does — it’s the runbook for how to build the thing the app downloads.
Build, Then Prove It Works
The runbook is a fixed three-stage sequence, and it’s meant to be run in order, by a person, not by a CI runner: build the runtime, validate it, then package it. The build stage assembles a self-contained Python 3.13.11 environment with PyTorch 2.10.0 and pyannote.audio 4.0.4 installed, and it’s documented as idempotent — safe to re-run — because a clean build downloads and compiles roughly 5 to 10 minutes’ worth of dependencies, landing at about 712MB before packaging.
What happens between build and package is the part worth dwelling on, because it isn’t a formality. The validation stage is a five-check gate: confirm Python and the ML libraries import and report the expected versions, pull the diarization model to a scratch directory using a real Hugging Face access token, synthesize a two-speaker test clip, run the model against it, and confirm it actually detects two speakers across at least one segment. The synthesis step matters more than it sounds like it should — the runbook is explicit that the test audio has to come from macOS’s own text-to-speech engine, not a generated sine-wave tone, because the model doesn’t reliably register synthetic tones as speech at all. A validation pass that used fake audio would still return a clean result while proving nothing. Nothing moves to packaging until all five checks report success.
Two Bugs Worth Documenting Twice
Two specific failures are written into the runbook’s troubleshooting section as things that have already happened once and are cheap to prevent from happening again. The first is a dependency-pruning bug: an earlier version of the build script trimmed packages it assumed were unused, and sklearn and networkx got caught in that pass — except pyannote.audio 4.x actually depends on both, and pruning them produced a runtime that failed at import with ModuleNotFoundError: No module named 'networkx'. It’s the kind of failure that only shows up after packaging, when the pruned artifact is already built, which is presumably why it earned a permanent line in the troubleshooting doc rather than just a one-off fix.
The second is upstream, not local: pyannote.audio 4.x changed what its own pipeline returns, wrapping the diarization result in a DiarizeOutput object instead of handing back the Annotation object older code expects directly, breaking anything written against the 3.x API — including the call to itertracks() MinuteAI’s own inference script relied on. That specific migration pain isn’t unique to this project; the same DiarizeOutput change broke other diarization tooling built on pyannote, including WhisperX, badly enough that its GitHub issue tracker has an open thread about it. MinuteAI’s fix — unwrap speaker_diarization off the result object before calling the old methods — is the same pattern the wider pyannote community converged on. Which is a useful way to read this whole pipeline: most of what it guards against isn’t MinuteAI-specific fragility, it’s the normal cost of depending on a fast-moving ML library, made visible instead of silently absorbed.
Why Not Just Use Apple’s Own Mechanism For This
It’s worth asking why a 743MB optional payload is fetched through a hand-rolled downloader talking to GitHub Releases at all, rather than Apple’s own On-Demand Resources system, which exists precisely for content an app wants to fetch after install. Two things rule it out. On-Demand Resources caps a resource tag at 512MB after app slicing, with 64MB called out as the ideal target — a Python/PyTorch/pyannote environment doesn’t fit inside that even before accounting for the difference between “a media asset” and “an executable runtime with its own site-packages tree.” And separately, Apple has been deprecating On-Demand Resources across recent OS releases in favor of a newer Background Assets framework, which makes it a shrinking target to build against regardless of size. Shipping a self-contained tarball through GitHub Releases and driving the download from application code sidesteps both problems — at the cost of losing whatever Apple’s own infrastructure would have handled for free, like resource-tag bookkeeping.
That trade shows up elsewhere in the pipeline too. GitHub Releases is a convenient place to host a large binary — no size limit on the file itself, bandwidth that isn’t metered back to the publisher — but it was never built as a release-hosting platform in the way a dedicated CDN is: no download analytics, no built-in way to resolve “latest” programmatically, brittle long-form URLs. MinuteAI’s Swift code works around the second problem the direct way — the download URL is version-pinned to an exact release tag in source, not resolved at runtime — which means bumping the runtime to a new pyannote or PyTorch version is an app code change and a rebuild, not a server-side config flip. That’s a real constraint on how often the diarization runtime can move independently of the app, even though the two are already decoupled from each other’s release cycles.
The Part That Doesn’t Show Up in the Architecture Diagram
None of this changes what shipped in the diarization feature itself. It’s a different claim: that “optional download instead of bundling it” is a one-line architectural decision with a much longer operational tail behind it — a build script that has to stay idempotent, a validation gate that has to generate real speech rather than trust a tone generator, two specific bugs that already happened once, and a distribution mechanism chosen as much for what Apple’s own tooling doesn’t fit as for what GitHub’s does. Anyone can decide to make an ML dependency optional. Making that decision safe to repeat every time the model or the runtime needs an update is the less visible work.
MinuteAI is available on the App Store for macOS and iOS, or you can learn more at getminute.app. Questions about how it fits your workflow? Reach out at hello@aitytech.com.
See Our Work
From MinuteAI to AgentKits — explore the products and projects we've shipped.
View PortfolioRelated Articles
Apple and Google Just Started Transcribing Calls for Free. Neither One Touches a Zoom Tab.
iOS 26 and Google's Pixel Recorder now do on-device call transcription and summarization for free. Here's the specific boundary neither platform crosses — and why that's exactly where MinuteAI's Chrome Extension operates.
GuidesJapan's ¥12 Trillion Legacy-System Warning Isn't a COBOL-Skills Story. It's an Encoding Story.
METI's 2025 digital cliff warning gets read as workforce attrition and rip-and-replace math. The failure mode that actually corrupts migrations first is smaller and easier to miss: EBCDIC and Shift-JIS don't even agree on whether letters or numbers sort first. Why Legacy Dragon treats character encoding as a parsing-layer concern, not a pre-processing step bolted on later.
GuidesNo Pricing Page, No API Meter: The Economics Behind PrivateAI's Free Tools
Cloud AI is priced by the token because every query costs the vendor real compute. On-device tools don't have that bill. Here's what that structural difference actually buys — and doesn't — for a product like PrivateAI.