← All posts
📅

BookTS: What Happened When an iOS App Went Through 100% of an Agentic Development Life Cycle

I built a 9-phase Agentic Development Life Cycle (ADLC) as reusable execution scaffolding, then ran a real App Store product — BookTS, an on-device EPUB-to-audiobook app — through all of it. Two of the phases caught bugs I would have shipped blind.

TL;DR — I built ADLC, a 9-phase Agentic Development Life Cycle (Discovery → Spec → Architect → Implement → Reflect → Review → Ship → Wrapup, with two human-gated Validate steps), as reusable scaffolding for shipping apps with an AI coding agent. Then I ran a real product through it end to end: BookTS, a native SwiftUI iOS/macOS app that turns a DRM-free EPUB or text PDF into a chaptered .m4b audiobook using an on-device neural TTS model (Kokoro-82M via MLX), account-free, $19.99 one-time or $3.99/month. Three make-or-break technical spikes had to pass before the design was allowed to lock. A grep-verify step in Reflect caught 5 silently-dropped requirements. An adversarial multi-agent review engine caught 2 P0 bugs — including the fact that the committed tree didn’t actually build from a fresh checkout — that an earlier, less rigorous review pass had missed entirely. And a real-device smoke gate at Ship caught bugs that a clean macOS build and passing render tests had both missed. This is the phase-by-phase account, with the actual numbers, and an honest list of what the pipeline still couldn’t do for me.

The job to be done

I ship personal software as a single-person shop: no QA team, no second engineer to catch what I miss, and an AI coding agent doing most of the typing. The recurring failure mode wasn’t bad code — it was process debt: skipping a design review because I was excited to code, shipping something that “worked on my machine” without ever touching the real device, discovering three weeks later that a requirement had quietly been dropped somewhere in the implementation.

When I start a new app idea, I want a fixed sequence of phases with real gates between them, so that the corners I’m tempted to cut under deadline pressure get cut for me by a process instead of by exhaustion.

That’s ADLC — an Agentic Development Life Cycle, a personal execution pipeline that sits underneath the coding agent, not inside it. It’s not a prompt technique. It’s a pipeline: 9 phases, each with an entry condition and an exit gate, plus an Autonomy Contract per project that defines exactly which moments require me to personally sign off (a “HG” — human gate) versus which the agent is trusted to push through alone.

#PhaseGate to advance
0Discoveryidea validated + a scoped V0, or an explicit waiver
1–2Spec → Validate Speczero open clarifications + my sign-off → spec LOCKED
3–4Architect → Validate Architecturedesign-critique loop clean + spikes pass + my sign-off → design APPROVED
5Implementall design-derived tasks done, build/tests green
6Reflectself-review clean or every deviation justified
7ReviewP0/P1 resolved, P2 triaged or explicitly waived
8Shipreal-device smoke passes
9Wrapuplessons and docs closed out

BookTS was the first app to run through all 9 phases start to finish. I want to walk through what actually happened at each gate, because the interesting part isn’t the diagram — it’s where the pipeline caught something I wouldn’t have caught alone.

Discovery and Spec: three spikes that could have killed the whole product

BookTS’s core promise — private, offline, on-device text-to-speech that produces a real chaptered audiobook file — only works if three technical bets pay off. So Discovery didn’t start with a spec; it started with three make-or-break spikes, run before a single line of the real spec was written:

  1. Is on-device TTS fast enough? Kokoro-82M running through MLX on an iPhone 16 Pro hit a real-time factor of 7.25x (peak RAM ~980MB during a spike, ~550MB steady) — meaning a book converts in well under a seventh of its listening time. On an M4 Mac the pipeline separately measured 6.45x. Both comfortably clear “faster than real-time,” which was the hard success criterion.
  2. Can a chaptered .m4b actually work without a heavyweight audio toolchain? The original plan assumed a Swift audio-marker library; it turned out to write chapter metadata that Apple’s own player couldn’t read. A pure-Swift MP4 tagging library, chosen instead, produced chapters that were verified tappable in Apple Books. The spike refuted the original design decision before it ever reached Architect.
  3. Is the pronunciation quality (grapheme-to-phoneme) good enough for a paid product? Verified acceptable on real hardware, on real prose, by ear.

All three passed, which is what let the design lock. If even one had failed, the whole product concept would have needed to change before a spec was worth writing — that’s the point of running the spikes before, not during, implementation.

Implement, then Reflect: the pipeline auto-fixes to spec instead of asking

Implementation built the actual EPUB→.m4b pipeline, proved end-to-end on-device. Then came the phase I trust the least by default and now trust the most: Reflect, a grep-verify step that diffs what got built against what the spec and design actually said.

It caught real drift: 5 functional requirements silently dropped during implementation (DRM refusal, persisted library state, chaptered export among them), and 3 design decisions quietly changed from what Architect had approved. In an earlier version of the process, this would have surfaced as a report and stopped for my review. I changed that rule: Reflect now auto-fixes to spec/design without halting — only a genuine business decision or something structurally unfixable gets escalated to me. Every one of the 5 dropped requirements got rebuilt and re-verified in the same pass; nothing needed my judgment call.

Review: the run that caught what the first review missed

This is the part I’d tell another solo builder to pay attention to. BookTS went through Review twice.

The first pass was an ad-hoc adversarial review across five lenses (security, correctness, concurrency, resource, App Store compliance) — 24 agents, 15 confirmed findings, 1 P0 fixed, and a verdict of “build green, advance to Ship.”

That verdict was wrong. I re-ran Review under the actual pipeline engine — a structured, multi-agent adversarial critique with 41 agents examining the diff — and it caught two P0s the first pass missed entirely: the version of the code that had actually been committed to git did not build from a fresh checkout. The earlier review had unknowingly verified a different, gitignored working copy sitting next to the real source tree. “Green build” was true of the wrong folder.

That’s the finding I keep coming back to. A review that passes against the wrong artifact is worse than no review, because it manufactures false confidence. The fix was a real repo restructure — relocating the actual buildable project to be the canonical source tree — verified afterward with a clean xcodebuild from scratch, not a rerun of the same broken check.

Ship: the device gate is real, not procedural

Six passing review lenses and a clean macOS build later, I still assumed the app was close to done. The real-device smoke gate disagreed, hard.

Three bugs surfaced on a physical iPhone that no amount of macOS testing had shown:

  • A zip-slip containment guard, added during Review to stop malicious archive entries from writing outside their directory, behaved correctly on macOS and silently skipped every single entry on iOS — because a path-normalization call strips a /private prefix differently depending on whether the target file already exists on disk, and the two platforms disagreed. On macOS the effect was invisible (temp paths didn’t trigger it); on iOS it meant no EPUB could import at all.
  • Two on-screen controls (chapter list, playback speed) that had rendered correctly in every screenshot and every UI mockup comparison were, on closer look, not wired to anything — dead icons that looked finished.
  • Backgrounding the app during conversion caused an outright crash on-device (the GPU framework doing the on-device inference gets forcibly killed by iOS when backgrounded on most hardware), something no simulator run or unit test would ever exercise.

Once found, the fixes were straightforward: per-component .. checks instead of a whole-path normalization, actually wiring the two controls to real state, and a cooperative-cancel-to-checkpoint pause instead of a hard crash when the app backgrounds mid-conversion (verified live: backgrounded 12 seconds mid-convert, resumed cleanly). The device gate didn’t teach me anything about TTS or audio encoding. It taught me that “builds clean and renders correctly” is a claim about the wrong environment — the only environment that counts is the one shipping to the customer.

With that fixed, an 18-chapter book converted end-to-end on a real device in about 66 minutes, producing a playable 3-hour-21-minute .m4b with working chapter navigation, checkpoint-resume surviving an interruption mid-book.

What ADLC still can’t fix

In the spirit of an earlier post on this blog — a harness that only advertises its wins is lying to you — here’s what the pipeline didn’t solve:

It doesn’t remove the need for a human to actually hold the phone. Every device-only bug in this project was found because I picked up an iPhone and tapped every control myself, not because a gate ran unattended. The pipeline made that a mandatory, named step instead of an optional nice-to-have — it didn’t automate it away.

It doesn’t compress App Store platform latency. Getting in-app purchases live involved a paid-apps agreement that needed to reach an “Active” state before Apple’s sandbox catalog would even populate, and the two purchase products propagated on different, staggered timelines — one in under an hour, the subscription in several. No amount of process discipline shortens a queue that isn’t mine.

It doesn’t make business calls for me. Pricing, the account-free product decision, subscription value — every one of those got flagged by the pipeline as a decision only I could make, and correctly stayed a human gate instead of getting auto-resolved.

It doesn’t catch the class of bug neither the process nor a single model can see. The two-Review-passes story above is really a story about a bad check, not a bad agent — the fix was making the review verify the right artifact, the same discipline covered in more depth in the piece on generator-critic loops for idea-to-ship pipelines. A process is only as good as what it actually measures.

Closing

The headline version of this story is “an app shipped through a 9-phase pipeline.” The more useful version is narrower: at three separate points — a design spike that refuted an assumption before it got expensive, a grep-verify pass that caught silently dropped requirements, and a device gate that caught bugs no simulator would ever surface — the pipeline caught something that a competent solo developer, moving fast on their own judgment, plausibly would have shipped anyway. That’s the actual return on building the scaffolding first: not that it writes better code, but that it forces the artifact under test to be the real one, on the real device, against the real spec, every time. If you’re building a measurement layer instead of a build layer, the eval-framework writeup covers the same discipline applied to picking a model instead of shipping an app.