AI engineering · 9 min read

Speech to text on an iPhone, offline, built with an AI agent in 6 steps

speech engines
3
work offline
2 of 3
automated tests
146
tickets, one plan
37
A recording with its sound wave as the seek bar and the transcript below it
The sound wave is the seek bar. The text sits below it.

Record a meeting, lock your phone, and read it as text without internet. Three speech engines behind one switch, and the 6 step method that kept an AI coding agent on track while it built the app.

Ken Ruiz Inoue · October 2026

I wanted an app that records a meeting or a class, keeps going with the phone in my pocket, and gives me the text afterwards, without sending the audio to a server by default. Another Transformer is that app, for iPhone, open source under the MIT license.

It was also a test of a way of working. Claude Code wrote almost all of the code, and I wanted to see how far an AI agent gets when it works inside a strict method instead of free prompting. This post covers both: how the app works, and the 6 steps that kept the agent on track.

1

What it does

Tap record and lock the phone; recording keeps going in the background, to a 16 kHz mono WAV file. Each recording gets a title you can edit in place, plays back with its own sound wave as the seek bar, and turns into text with one tap. The text can be copied or exported as a PDF. The spoken language is set once in Settings, English, Spanish or Japanese, and every engine uses it.

The record screen: a running timer and live sound bars, with a note that the phone can be locked
Recording keeps going with the phone locked.
A recording with its sound wave as the seek bar and the transcript below it
The sound wave is the seek bar. The text sits below it.

3

speech engines

2 of 3

work offline

146

automated tests

37

tickets, one plan

2

Three engines, one interface

Apple Speech is the default: free, offline, and forced to recognize on the device, so it never falls back to Apple's servers. Whisper base is OpenAI's open model, run on the phone through whisper.cpp after a one-time 148 MB download that Settings can also delete. The cloud engine calls gpt-4o-mini-transcribe with your own OpenAI key.

All three implement the same Transcriber interface: a file in, text out. A small router picks the engine from Settings and passes the language along. The screens, the hooks and the tests only ever see that interface.

  • Built into iOS

    Default engine

    Apple Speech

    Free and offline. The app forces it to stay on the phone.

  • Pretrained, used as is

    Best offline quality

    Whisper base

    OpenAI's open model, 74M parameters, a one-time 148 MB download.

  • Cloud API

    Cloud option

    gpt-4o-mini-transcribe

    Uses your own OpenAI key. A build without a key never sends audio out.

3

A fallback that says what happened

Any engine can be unavailable. Whisper needs its download, OpenAI needs a key in the build, and on-device Apple Speech is not available everywhere. The router tries the picked engine first, then the others, and records why each one failed so a total failure can be explained even in a release build with no console.

Two fixes came from using it. A stalled OpenAI request could hold up the whole chain, so it now gives up after 30 seconds. And a fallback used to be silent, so the transcript now names the engine that wrote it. In one simulator test, Apple Speech was picked, could not run there, Whisper was not downloaded, and the text arrived marked "via OpenAI".

One transcription, with Apple Speech picked
  1. 1

    Apple Speech

    Text? Done

    The engine picked in Settings goes first.

  2. 2

    Whisper

    Text? Done

    Next, if its model is downloaded.

  3. 3

    OpenAI

    Last step

    Last, if the build has a key. A stalled request gives up after 30 seconds.

The text shows which engine wrote it, so a silent fallback never hides a broken engine.

4

Recording on a locked phone

Background audio is declared in the app config, and recording runs in a gateway the screens never touch directly. While recording, the screen shows live sound bars from the microphone level, and the sheet cannot be swiped away by accident, because that used to lose the recording.

The seek bar is the recording itself: the app reads the WAV file and draws 48 bars, each the loudness of its slice of the audio. iPhone WAV files carry an extra block before the sound, so the reader walks the file's chunks instead of assuming a fixed header.

5

The method, step by step

An AI agent can write a feature in minutes. The risk is not speed, it is drift: code that works today and quietly breaks the shape of the project. These 6 steps are how I keep an agent inside the lines, and they apply to any project, not only mobile apps.

The method, in order
  1. Rules first

  2. Questions

  3. Small tickets

  4. Test first

  5. Audit

  6. Approve

6

Steps 1 and 2. Rules first, questions before code

The agent starts every session by reading a short set of rules: how the code is structured, how tests are written, how secrets are handled, and that nothing is committed without asking. They live in the repo, so every session starts from the same page instead of from memory.

Before any code, the agent reads the requirement, splits must-haves from nice-to-haves, and lists every unclear point with a proposed answer. I confirm or change each one. These are four of the decisions it made up front.

Open questionDecided up front
Text to audio too?Later, not now
What if an engine fails?Try the next one
Where does the key live?A local file, not git
Full app tests on each change?No, on a device
7

Step 3. A plan of small tickets

The plan is a file in the repo: phases, and tickets inside them. Every ticket has a goal, the tests to write first, the edge cases, and the acceptance criteria that say when it is done. Ticket 6, for example: record 16 kHz mono audio and save it; a stop with no recording does nothing; an empty recording is thrown away; done when the recording tests pass.

It started at 23 tickets in 6 phases and grew to 37 in 11 as the app met reality. A phase is not done until its checks are green.

37

tickets

11

phases

23

tickets on day 1

32

done in 10 days

8

Step 4. Test first, one ticket at a time

For each ticket the agent writes a failing test, then the code that makes it pass, then runs every check, updates the plan, and stops for review. Native modules like the microphone and the speech engines sit behind gateways, so tests inject fakes and run on any computer.

Every push runs the same checks in GitHub Actions: lint, a type check, the 146 Jest tests, and a script that fails the build if code that touches the phone's hardware appears outside its three folders. Full user flows run with Maestro, locally, on a device or simulator; Maestro replaced Detox halfway through.

One ticket

Done is something a machine checks, not something the agent claims.

  1. 1Write a failing test
  2. 2Write the code
  3. 3Run every check
  4. 4Update the plan
  5. 5Stop for review
9

Step 5. Audit every phase, then a real phone

At the end of each phase the agent lists the inputs, states and failure paths no test covers yet: an empty recording, the phone locking mid-recording, Whisper not downloaded, no key in the build, a very long recording. It adds tests for the real gaps and fixes what they expose.

Then the app goes on a real iPhone, which finds what no test runner can. Where a test can catch the bug, the fix comes with one, so it stays fixed.

Found on the iPhoneWhy tests missed it
"undefined:undefined" titlesIts dates work differently
Record button behind the tabsTests never see screen edges
A stalled cloud requestTest networks always answer
A button stuck to the leftTests check what, not where
10

Step 6. A human yes at every step

The agent pauses after the questions, after the plan, and after every ticket. It never commits on its own; I review, and I say when something goes into the history. Secrets follow the same rule: the OpenAI key lives in a local file that is never committed.

One honest limit: an app reads its key at build time, so a key baked into a build can be pulled out of it. That is fine for your own build on your own phone, which is what the cloud engine is for. A public release would send that request through a server instead.

11

What is next

The repo already holds a benchmark protocol for comparing the three engines on the same recordings: word error rate, time to text and cost, in English, Spanish and Japanese, in quiet and noisy rooms. Running it on real voices is the last open ticket. Text to audio was left out of this plan on purpose and may come later.

The point

Most of the value here came from the method, not the model. An agent that reads the rules, asks before it builds, works one tested ticket at a time and waits for a human yes produced an app I trust, and the real iPhone still found what the tests could not. Fast code is cheap now; the guardrails are the work.

Building something like this?

I build AI systems and mobile apps for companies that ship software. Tell me what you want to build and get a fixed quote.

Book a free 30-minute call

On Google Meet, in English, Spanish or Japanese.