Activity retrospective
After a key session you want more than “nice ride”. This recipe makes the assistant pull one activity apart — intervals, splits, time-in-zone, decoupling — and turn it into two takeaways, while being honest about Strava-imported activities that come back nearly empty. Split rows are source-labelled and may include aligned average heart rate, power, cadence, and positive elevation gain when coverage is sufficient; missing channels stay omitted.
For logged carbohydrate evidence, missing-log coverage, and source-labelled grams-per-hour calculations without nutrition targets, use the dedicated Fueling review recipe.
When to use this
- After an interval session, to check whether the hard parts held their targets.
- After a long endurance ride or run, to see aerobic durability (drift, decoupling).
- As a post-race debrief.
The recipe
You can name an activity by ID or just describe it — the assistant will look it up.
Compare repeated workouts
When you already know the ordered activity IDs for the same workout, use the read-only compute_workout_progression analyzer instead of asking the assistant to match sessions by title or date. Supply at least two IDs in the order you want compared, and pass an exact planned event_id only when the activity’s prescription is not already linked. The analyzer reports prescribed/completed structure, target-family adherence, stability, duration/recovery deltas, source-labelled feel/RPE, and optional wellness fields. It keeps mixed sports, missing intervals/prescriptions, unsafe lap sources, and missing readiness explicit; it does not assign a progression score, recommend extra intensity/repetitions/recovery changes, or write the calendar.
{
"activities": [
{"activity_id": "ride-2026-05-01", "label": "first session"},
{"activity_id": "ride-2026-05-15", "label": "repeat session"}
],
"include_readiness": true
}Treat comparison.comparable_pair_count, row/pair status, reasons, _meta.source_tools, and _meta.insufficient_sample as part of the evidence. A not_comparable or insufficient_evidence result is a data boundary, not a coaching verdict. For example, a mixed-sport pair or a missing prescription should be reported as unavailable evidence rather than silently paired.
This analyzer is read-only: it does not choose the next workout, increase intensity, shorten recovery, add repetitions, assign a progression/readiness score, or write calendar events. Do not use workout names to discover the sequence. For relative dates like “last Sunday,” first resolve the athlete-local date window with get_activities, choose the matching activity IDs, and then pass those explicit IDs to the analyzer.
Give me a detailed retrospective of my most recent hard ride. Use icuvisor
with my intervals.icu data.
1. If I gave an activity ID, use it. Otherwise list my recent activities or
the requested athlete-local date window, pick the one I described, and tell
me which activity ID you chose.
2. Get the activity details: sport, local start time, duration, distance,
load, tags, activity fueling (`carbs_ingested_g`, `carbs_used_g`),
and source/device. If I ask about an activity custom field such as VO2Max,
request its field code explicitly with `custom_fields` instead of assuming
it is present in default activity rows.
3. Get the intervals or laps with get_activity_intervals, check
`_meta.interval_source`, `_meta.auto_lap_suspected`, and
`_meta.interval_source_caveat`, and get the per-km or per-mile splits.
Treat split `provenance` and `distance_basis` as part of the evidence: a
device lap or workout interval is not automatically an exact fixed-distance
row. Use split `_meta.data_availability` for pauses, missing channels, and
insufficient coverage; do not fetch or average raw streams in chat.
4. Get the time-in-zone for the session.
5. Get the extended metrics, and report only the ones actually present
(decoupling, IF, VI, normalized power, interval `dfa_alpha1`, RPE, feel).
If I ask for AlphaHRV, route only verified interval `dfa_alpha1` from
upstream `average_dfa_a1`; do not join daily wellness `hrv`/`hrvSDNN`,
infer readiness, or treat `device_name` as DFA provenance. If I mean an
athlete-defined field, pass its exact code in `custom_fields`; preserve
that code and read `_meta.custom_field_provenance` and
`_meta.data_availability` without inventing a unit, device, algorithm, or
physiological interpretation.
6. If `get_activities`/`get_activity_details` returns `hypoxic_training_caveat`,
or `get_extended_metrics` returns `_meta.hypoxic_training_caveat`, quote its
provenance and wording. If I explicitly said the session used an altitude
tent/chamber or reduced oxygen exposure, mention that CTL/ATL/Form still use
logged `training_load` and do not add a hypoxia multiplier.
Then give me:
- What kind of session this was and how it went overall.
- A table with one row per work interval: target vs actual power, average
HR, duration, any interval `custom_fields` that are relevant to the prompt
(for example manually-entered lactate), and whether it held. If the activity
has only laps and no structured intervals, say so and summarize the laps instead;
if there is one unknown/collapsed interval row, say it is not proof of no
intervals and use `compute_activity_segment_stats` over explicit time or
distance bounds before judging sprint or anaerobic execution.
- How the hard parts held up — pacing, fade, heart-rate drift, decoupling.
- Two concrete takeaways for next time.
Rules: if this activity was imported from Strava and its fields are blank,
tell me that up front and analyze only what is actually there. Quote a
decoupling, NP, IF, or VI figure only when a tool returned it, and name the
source tool inline. Do not compute or estimate metrics intervals.icu did not
provide. For hypoxic training, require explicit provenance from the user or from
activity name/notes/tags/custom fields; do not treat altitude, elevation gain, or
SpO2 alone as proof. Keep the answer under about 400 words, leading with the
interval table.Split routing and caveats
get_activity_splits keeps the existing manual_intervals and
virtual_streams sources while adding provenance, distance_basis, and
optional metric fields. Virtual rows require cumulative distance/time coverage
from zero and include pause time; structured, manual-added, unknown, and device
rows retain their upstream distance basis. A pool Swim request may omit
split_unit to use 100m only when the matching Swim sport setting says
SECS_100M and the distance stream explicitly proves meters. Yards, unknown
pace units, missing settings, or ambiguous distance metadata safely fall back
to km/mi (or return a user error for explicit 100m).
What icuvisor does
| Step | Tool | Why |
|---|---|---|
| 1 | get_activities | Finds the activity and its ID when you describe it; terse rows include upstream tags when intervals.icu returns them. |
| 2 | get_activity_details | Sport, timing, load, tags, activity fueling grams, device, Strava-import flag. |
| 3 | get_activity_intervals and get_activity_splits | Per-rep and per-distance breakdown. Check interval provenance/caveats before treating rows as reps; a single unknown/collapsed row can be an averaged import, not proof of no interval work. get_activity_intervals also includes scalar interval custom_fields such as lactate when upstream returns them. |
| 4 | get_activity_histogram | Time-in-zone distribution for the session. |
| 5 | get_extended_metrics | Decoupling, IF, VI — only those upstream actually fitted. |
For a specific surge, climb, distance-bounded split, or sprint/anaerobic workout that appears as one averaged interval row, compute_activity_segment_stats computes mean/median/p90, NP/IF, drift, or decoupling over an explicit time or distance range. For relative requests like “last 10 km”, first use get_activity_details to get the activity distance, convert it to meters, and pass explicit bounds such as start_distance_m = total_distance_m - 10000 and end_distance_m = total_distance_m. Do not fetch raw streams and average them in chat.
For “which sustained climbs were in this activity?” or a comparison of its climb segments, route to get_climb_segments rather than reducing raw streams in chat. Use bounded parameters, for example {"activity_id":"a123","min_grade_percent":3,"min_elevation_gain_m":30,"max_gap_distance_m":100,"max_bridged_elevation_loss_m":5}. The read-only analyzer returns concise distance, gain, grade, and available time/HR/power metrics; it reports missing or noisy altitude evidence instead of inventing climbs and does not make coaching or physiology claims.
Hypoxic-training caveat
Some athletes do workouts in reduced-oxygen environments such as altitude tents,
altitude chambers, or other explicitly logged hypoxic setups. icuvisor surfaces a
hypoxic_training_caveat on activity rows, and _meta.hypoxic_training_caveat
from get_extended_metrics, only when explicit evidence is present in the user
request or exposed activity name/notes/tags/custom fields. Do not infer hypoxic
stress from altitude, elevation gain, or a low SpO2 value by itself.
When the caveat appears, keep the interpretation conservative: CTL, ATL, Form,
and projections are based on logged training_load; icuvisor does not change TSS
or apply a hypoxia multiplier. If load is power-based, it may under-represent
extra physiological strain from reduced oxygen. If load is HR-based, it may
capture some acute cardiovascular response, but it is still not a complete
hypoxic-stress model. Use HR, RPE, feel, and recovery trends as supporting
context only.
A good answer looks like
Session: Tuesday VO2 ride, 1h 12m, 38.4 km, load 95, tags
vo2andtrainer, recorded on a Wahoo head unit (get_activity_details) — direct upload, full data. Fueling fields show 72 g carbs ingested and 138 g used.The work: 5 x 4 min intervals (
get_activity_intervals). Targets held well for reps 1-4 (308-314 W, target 310 W) but rep 5 faded to 291 W. Recoveries were on target.Aerobic response: Pw:HR decoupling 6.1% across the work intervals (
get_extended_metrics) — acceptable for a VO2 session. Time-in-zone was 41% Z5+ during the work blocks (get_activity_histogram).Takeaways: (1) The rep-5 fade suggests the session was at the right edge of doable — hold this format rather than adding a sixth rep. (2) Decoupling stayed controlled, so durability is not the limiter here; the ceiling is repeatability at VO2 power.
Variations
- Lactate test: “Analyze this interval session and include the lactate values I entered on each rep. Use
custom_fields.lactatefromget_activity_intervalswhen present.” - Activity custom field: “For this ride, include my activity custom field
vo2max_est. Passcustom_fields: ["vo2max_est"]toget_activity_details, preserve the exact code and source metadata, and state when the value is absent, null, or malformed. If I ask whether it is improving over time, switch toanalyze_correlationwithmetric_x: "custom:vo2max_est"plus the samecustom_fieldsselection over a date range; do not assign a unit or physiology from the name alone.” - AlphaHRV/DFA request: “Use
get_extended_metricsand report intervaldfa_alpha1only when sourced fromaverage_dfa_a1. If it is absent, null, malformed, or the interval source is restricted, report the_meta.data_availabilityreason. Do not rename an activity custom fieldalpha_hrv, join wellness HRV, or infer readiness, threshold, medical, or training conclusions.” - Race debrief: “…this was a race — focus on pacing discipline and where I lost time.”
- First-vs-last distance comparison: “Compare the first 10 km with the last 10 km of this run. Use
get_activity_detailsfor total distance, then callcompute_activity_segment_statswith explicit distance bounds (0..10000 mandtotal_distance_m-10000..total_distance_m) for averagevelocity_smooth,wattsif available, andheart_rate. Convert velocity to pace in the final answer; do not reduce raw streams in chat.” - Compare two sessions: “Compare activity A and activity B — same workout, two weeks apart. Did the hard parts improve?”
- A Strava import specifically: “Analyze my most recent Strava-imported activity. Identify genuine Strava imports by the
sourcefield — a Garmin or Wahoo device means a native upload, not a Strava import — state the blank-field policy first, and confirm the blank payload withget_activity_detailsrather than inferring it from the list.” - Leave a note: add “If write tools are enabled, append a one-line summary as a comment on the activity.” This uses
add_activity_message.
Why this prompt works
- Look-it-up step. Letting the assistant resolve the activity with
get_activities— including athlete-local date windows for phrases like “last Sunday” — means you never paste raw data or raw IDs, which is what overruns the context window. - “Report only the ones actually present.” Extended metrics vary by device and sport. This line stops the assistant inventing a decoupling figure for a run with no power.
- Strava callout. Strava-imported activities return blank fields by policy. Naming this up front turns a confusing “your power was 0 W” into an honest “this was a Strava import, so power is unavailable”.