← back to the timeline

From the build log of an AI coach that learns what you can lift and tells you exactly what to do next.

Jun 30 2026

four things got done: Your daily workout is now built instantly, no internet needed. Tap any of these to read the whole thing.

Your daily workout is now built instantly, no internet needed

Until now, every time you started a workout the app had to call the AI to build that day's session from scratch, which meant a network…

the problem

Until now, every time you started a workout the app had to call the AI to build that day's session from scratch, which meant a network round-trip you had to wait on, and the exact thing behind the "Coach is offline" stalls (#555/#556): if the call hung, your workout was stuck. And it re-decided your exercises each session, so nothing was stable.

what I changed

Wired in the deterministic session builder. When you start a day, the app now takes your already-committed exercises for that slot (chosen once per block, last change) and figures out the sets, reps-target, and how-hard (RIR) by simple math from your training data, instantly, with no AI call and no internet. The rules: if a lift's in a recovery (deload) phase it cuts the volume ~in half; if you're regressing or coming back from a break it backs off a bit and leaves more in the tank; if a muscle's under its weekly target it adds a set. Your exercises and their rep-ranges stay exactly as committed, only the sets/effort flex. The per-set weight suggestion still uses the AI when reachable (with the offline fallback from earlier).

how I tested it

7 new tests prove the math: recovery week halves volume, regression/return back off + raise RIR, an under-target muscle gets an extra set, "nothing special" gives the plain target, the numbers stay in sane bounds, and a built day keeps the frozen exercises + rep-ranges. Full build + test suite green.

Heads-up. this changes how the live workout screen builds your session, so it wants a real on-device check (start a workout, pause/resume, force-quit and reopen). The old from-scratch builder is now unused and will be deleted in a follow-up cleanup. (#564, completes the program-generation spine, #558 / ADR-0030.)

The coach now commits your exercises once per block (groundwork)

Until now the AI picked your exercises from scratch every single session, and it only ever saw one day at a time, so it couldn't balance…

Shipped
the problem

Until now the AI picked your exercises from scratch every single session, and it only ever saw one day at a time, so it couldn't balance the whole week, and your "Push day" might have different lifts each time, making it hard to actually track whether you're getting stronger on a given lift. The right model: pick a solid set of exercises for each day ONCE when a training block starts, looking at the whole week together, and freeze them so you can chase progress on the same lifts.

what I changed

Rebuilt the program-builder into a single "block commit" call. In one pass it now sees every training day at once and, for each day, picks a fixed, ordered list of exercises (compounds first) with a frozen rep-range per exercise. It avoids anything your equipment can't do (a safety filter drops stragglers) and can take your injuries into account as a hard "don't program this" rule. It only chooses which movements and what rep-range, how many sets and how hard stays with the deterministic engine (that part lands next). Per your call: same lifts each time within a block, separate rep-ranges for compounds vs. isolations, and when a new block starts it keeps the core lifts and rotates a couple of accessories.

how I tested it

14 tests pass, including new ones proving the committed exercises flow through with frozen rep-ranges and repeat across the block, the equipment filter drops a lift you can't do, and your limitations ride along in the request. Full build green.

Important. this is groundwork, the committed exercises are now produced and stored, but the workout screen doesn't use them yet (the next change wires that in). So nothing visible changes for you today; existing programs are untouched.

iOS-only, groundwork, no server deploy. Injuries are carried when provided, wiring the injury source into the live path is a small follow-up (that data isn't persisted yet). (563, part of 558 / ADR-0030.)

New "queue" shape for programs (groundwork, nothing visible yet)

Now that the fake calendar is gone, a program is really just an ordered list of workout "slots" you work through one at a time, not a…

the problem

Now that the fake calendar is gone, a program is really just an ordered list of workout "slots" you work through one at a time, not a 12-week grid. But the code still stored it as the old week-by-week grid. Before the next steps (committing a fixed set of exercises per slot, and building each day's session without the AI), the app needs a clean "queue of slots" way to look at a program.

what I changed

Added a new queue-shaped program model (an ordered list of day-slots + a pointer to the next one you haven't done) and a converter that turns the existing stored program into it. Crucially, the converter works on the already-loaded program object, it doesn't re-read the saved data a new way, so there's zero chance it trips over a real saved program and breaks it. Everyone's existing day labels are carried over exactly (those are the key the app uses to match your lift history). Nothing in the app reads this new shape yet, it's pure groundwork.

how I tested it

Six new tests prove the converter keeps every slot in the right order, carries every day label across, points at the right "next" slot whether the program is fresh / partly done / finished, and survives a full save→load→convert round-trip without error (the guard against stranding a live program). Full build + the program-model test suite pass.

iOS-only, additive, no server deploy, no data migration, saved programs are untouched. (562, part of 558 / ADR-0030.)

Removed the fake "12-week calendar" from the program

The app pretended your program ran on a fixed 12-week calendar with named blocks (weeks 1–4 "accumulation", 5–8 "intensification", 9–11…

the problem

The app pretended your program ran on a fixed 12-week calendar with named blocks (weeks 1–4 "accumulation", 5–8 "intensification", 9–11 "peaking", week 12 "deload") and even guessed which weekdays you'd train (Mon/Wed/Thu/Sat). None of that was real, the coach actually decides intensity per lift based on how that lift is progressing, not on a calendar. The fake calendar showed up in three places: the data the coach was handed, the coach's instructions, and the Program tab ("WEEK 3 / 12" with a phase progress bar). It was confusing scaffolding that the next round of work needs gone before building on top.

what I changed

Stripped the calendar fiction everywhere: no more hardcoded week→phase table, no more invented weekday slots (days are just numbered slots now), no fake "where this week sits" volume ramp, and the coach's instructions no longer mention a global phase or 12-week framework, each lift uses its own phase, which is the only real clock. On the Program tab the "WEEK X / 12" hero and the ACCUM→INTENS→PEAK→DL bar are replaced with a plain "PROGRAM" title (I kept the honest "X / Y sessions, Z% complete" progress), and the date-driven "current week" highlight + auto-scroll are gone. The per-lift coaching engine and all saved programs are untouched, this only removes a display/labeling layer.

how I tested it

App + tests build clean. Searched the whole codebase to confirm none of the removed pieces are referenced anywhere. Updated the tests that checked the old calendar fields (and added guards that the removed fields no longer ship). The program still generates and the existing day-labels are preserved.

iOS-only; no server deploy. First piece of the deeper program-generation rework. (561, part of 558 / ADR-0030.)