Coach should remember what it can prove
INCREMNT is an iOS strength-training app that tells you what to lift next; this week its Coach became more useful between questions by carrying conversations forward, showing answers as they arrive, and keeping progress advice tied to evidence the app can actually verify.
- Ask Coach can resume a saved conversation with its full canonical transcript, search history, and continue from the latest answer.
- Free-form Coach replies can stream into the native iOS conversation while the request is still running, with Stop, Retry, and New Chat boundaries intact.
- Coach now remembers explicit goals, review dates, target weights, and strength-preservation constraints as user-visible facts.
- Health history adds daily activity and body-composition measures, while older exports still decode safely and percentage precision is preserved.
- Matched-month comparisons are rendered from validated session arithmetic instead of asking a language model to invent causes or strength claims.
Product: a Coach that can pick up the thread
The useful version of an AI coach isn’t the one that says the most. It’s the one that can continue a real training conversation without losing the athlete’s question, goal, or evidence.
That’s the product decision behind this week’s work. Open Coach History, search for an earlier question, and INCREMNT refreshes the canonical transcript before offering Continue. The next question is sent with the bounded context needed to preserve the thread, while the full transcript stays available locally and in history. A conversation that was updated on another device is reloaded rather than overwritten. If it can’t be refreshed, the app says so and gives you a retry path.
The details matter here. History is grouped by last activity, not just creation date, so a conversation you returned to today comes back to the top. A completed answer no longer waits through an artificial typing delay. The app also keeps a failed request distinct from the saved transcript, which means Retry doesn’t quietly duplicate a message or resurrect an abandoned chat.
Streaming makes the other half of the interaction visible. When the server negotiates an event stream, Coach shows the final prose as it arrives. The preview is deliberately provisional: structured decisions, tool arguments, reasoning, and artifacts stay private until the canonical answer is complete. Stop leaves the partial text marked as incomplete and preserves a separate follow-up draft. A late completion can’t replace a newer conversation. Existing JSON callers and older servers still work, so this is an additive transport change rather than a second request path.
That boundary is important for trust. Seeing words appear faster is pleasant, but a partial sentence isn’t an answer. The app only adds the answer to history after the checked completion arrives. On cancellation, retry, or New Chat, the provisional text is cleared from the next request’s context.
Coach also got better at remembering what the athlete actually said. Explicit weight goals can now carry a target, a review date, and a constraint such as preserving strength. A follow-up can update those facts or withdraw them; an unrelated strength goal remains intact. The goal is not to make memory feel magical. It’s to make the next review start from a fact the athlete can recognize and correct.
The evidence surface widened too. HealthKit exports now include steps, active energy, walking and running distance, stand minutes, body-fat percentage, and lean body mass alongside the existing measures. Older snapshots decode with empty arrays for the new fields, and body-fat fractions are converted to percentage points before rounding. That gives Coach more context without breaking the data already on a phone.
Building INCREMNT: continuity needs hard edges
The difficult part wasn’t adding a Continue button or opening an event stream. It was deciding which state is authoritative when several things happen at once.
For saved chats, the server owns the canonical transcript. Each new iOS request carries an opaque request ID and an expected prior message count. A delivery retry can receive the already-saved answer without another model call, while a request that belongs to a different question or account is rejected. The database upsert checks the account and transcript version, so two devices can’t silently replace each other’s work. On a conflict, the coordinator reloads the conversation and presents a recoverable state instead of applying a late response to the wrong chat.
The client has a matching generation guard. Every in-flight request is tied to a conversation ID and generation number. Switching chats, resuming history, stopping a request, or starting New Chat invalidates older work. That sounds like plumbing, but it is product behavior: a correct answer delivered to the wrong conversation is still a bug.
Streaming adds another state machine. The service negotiates Server-Sent Events on the existing request, emits delta events for the transient preview, and ends with the existing checked answer in completed. reset discards a draft before repair or fallback; error retains the incomplete preview while exposing the normal recovery flow. Heartbeats keep a quiet evidence-gathering request alive, and disconnects abort generation and clean up listeners. The provider’s rolling prompt-leak detector still runs before text is forwarded. In other words, the fast path did not get permission to skip the safety path.
The same principle shaped goal and progress advice. Canonical goal facts are extracted from explicit user statements, not from Coach’s suggestions. A target weight is not inferred from a requested amount of weight loss. A review date is stored as a date, not turned into a notification promise. For strength retention, the chart compares repeated observations of the same exercise and load basis, excludes warm-ups, and labels an empty comparison as insufficient evidence.
The final safeguard is deliberately boring: some answers are computed, not generated. When a request asks for a matched-month comparison, validated calendar ranges, session counts, and logged volume are rendered directly. The answer says what was logged and what that does not establish. It does not ask a model to explain a missed session, declare fat loss, or call a volume change a strength change. The tests run that computed answer through the same claim verifier used for ordinary Coach prose.
That trade-off costs a little expressive freedom. It buys a much clearer failure mode. If the evidence is thin, the product can say “not enough to conclude” and tell the athlete what to check next. A smaller answer that survives inspection is more useful than a fluent answer that quietly changes the question.
The implementation spans SwiftUI state, HealthKit normalization, the CLI’s typed decision graph, the sync service’s optimistic concurrency checks, and focused tests. The week’s Coach streaming verification covered 1,394 CLI tests, 575 sync-service tests, and 114 focused iOS tests, including controlled Stop and completion flows. Those are development checks, not a claim that every path is deployed or available to every customer.
Also moved forward
- Plan progress now has shared workout components and a clearer view of completed days, with focused logic and source tests around the new presentation.
- The reviewed Plan adjustment flow keeps proposed edits as explicit changesets until the athlete accepts them; the native store remains responsible for applying and undoing an accepted change.
- Sentry trace correlation and profiling hooks were added to make Coach and acquisition work easier to inspect without putting raw questions or answers into telemetry.
- iOS 1.6.8 build 443 was recorded for TestFlight review. Source verification and a build record are not the same as public release.
What’s next
The next proof is not a longer transcript or a more animated stream. It’s a Coach review that survives a return visit: the goal is still correct, the dated evidence is still present, and the next action can be checked against the following session. I’ll keep tightening those boundaries, especially where HealthKit data is missing or a plan change needs explicit acceptance.