The Undocumented iOS Limit That Rewrote My Architecture
SFSpeechRecognizer's hidden concurrency lock and the three paths around it
Building AnyStudio’s remote guest recording feature, I hit an architecture decision that looked simple on paper: transcribe two audio streams simultaneously — one for the host, one for the guest — so the AI co-host knows who’s talking.
The plan was to run two SFSpeechRecognizer instances in parallel. One stream per participant. Clean speaker attribution with no diarization guesswork needed.
My AI agent went to implement it. Then went to research it. Then came back with something I hadn’t expected.
The Undocumented Limit
SFSpeechRecognizer has a system-wide concurrency lock. Starting a second recognition task kills the first one. Not a threading issue. Not a configuration problem. A hard system-level constraint.
Apple doesn’t document this. There’s no mention of it in the API docs, no warning in Xcode, no error message that explains what happened. It’s just a behavior that developers discover by trying it — and then find scattered mentions of in forum threads and Stack Overflow answers from other developers who hit the same wall.
My agent found it in 1 minute 40 seconds across 15 tool uses. A human developer might spend half a day before finding the right search terms.
Why This Matters for Speaker Attribution
The reason I needed two concurrent streams wasn’t transcription quality — it was architectural. In a remote recording session, the host and guest audio arrive on separate tracks. LiveKit (the WebRTC library I’m using) gives each participant a distinct audio stream. That separation is the speaker attribution. If I know “this audio came from track A (host) and this from track B (guest),” I don’t need diarization at all.
Diarization — figuring out who’s speaking from a mixed audio stream — is a hard, imprecise problem. Separate tracks sidestep it entirely. But only if I can transcribe them independently.
With SFSpeechRecognizer off the table, I had three options:
Option A: Two WhisperKit instances. WhisperKit is an open-source Swift library that runs Whisper models on-device via CoreML and the Neural Engine. It has no system-level concurrency lock because it’s not a system service — it’s just a library. Two instances, two audio streams, independent transcription. On A17 Pro and later chips, running two whisper-tiny or whisper-base models simultaneously is feasible.
Option B: Single mixed stream + diarization. Mix both audio channels, run one transcription engine, use FluidAudio (open-source, pyannote-based, runs on Neural Engine) to attribute segments back to speakers. Simpler but less precise — and adds another dependency for a problem I’d already solved at the architecture level.
Option C: Wait for SpeechAnalyzer. iOS 26 ships with SpeechAnalyzer, Apple’s replacement for SFSpeechRecognizer, built from the ground up for conversation scenarios with Swift Concurrency throughout. No confirmation yet whether it supports concurrent streams — but it’s designed for exactly this use case.
The Decision
iOS 26 is the target. SpeechAnalyzer is the native path — no third-party dependencies, designed for conversations, no rate limits. If it validates in the Phase 0 spike (two concurrent sessions on separate audio streams), the architecture is clean and stays entirely on-platform.
WhisperKit is the fallback. If SpeechAnalyzer doesn’t support concurrent streams, WhisperKit is the right answer. Better accuracy than SFSpeechRecognizer, no Apple rate limits, and it runs entirely on-device. The tradeoff is swapping out SFSpeechRecognizer in the existing transcription pipeline — a meaningful change, but one that also improves solo co-host mode in the process.
Either way, this gets validated in Phase 0 before building anything else. That’s the rule: don’t commit to an architecture before you’ve proven the core assumption works.
What My Agent Actually Did
The useful part of this story isn’t the technical finding — it’s how it happened.
I was reviewing the feature spec with my coding agent when it flagged the dual transcription assumption buried in Phase 3 of the project doc. Rather than building on it and discovering the problem later, it asked: should I research this, or just promote it to a Phase 0 spike item?
I said research it. Two minutes later I had a complete analysis: what the limit is, why it exists, three concrete options with tradeoffs, and a recommendation. The agent didn’t just find the problem — it found the path around it.
That’s the thing I keep noticing about working with AI agents on a real codebase. The value isn’t the code generation. It’s the research speed. Problems that would take a human developer hours to track down, document, and evaluate alternatives for — the agent does in the time it takes to get a coffee.
The iOS dual transcription limit has been tripping up developers for years. It’s now a two-minute problem.
AnyStudio is a native iOS app for recording, editing, and publishing video podcasts from your phone — with an AI co-host built in. Remote guest recording is coming in the next release.
Keep reading
- 10,600 Trajectories: Mutation Testing in a Video Game Pure Inference A harness flies each Kepler mission thousands of times with randomised inputs, then searches for the cheapest solution. It answers whether the puzzle has a solution at all — and it is what agentic engineering actually looks like: not generation, search.
- Designing a Test Runner for AI Agents Pure Inference Seven stuck Ruby processes, running for hours, holding database connections. The agent that spawned them had moved on, oblivious.
- Structure Over Prompts Pure Inference Everyone's building AI agents to orchestrate AI agents. I built a state machine instead. Zero orchestration tokens, no hallucinated transitions.