Hi all,
I’ve been reading this forum a lot over the last few weeks and it changed how I built a personal project, so I wanted to share it and ask a few questions. It is a hobby project for my own training. It isn’t a product, there’s nothing to sell, and I’m not looking for users.
About me
I’m an amateur triathlete working towards a 70.3 in 2027. I train with a Forerunner 255 and a chest strap. I run and swim by heart rate and pace, and I only have power on the indoor trainer. Strength sessions are logged in a phone app that exports a .fit, which I upload with Intervals Companion.
What it is
It’s a coach built on Claude Code with several agents. I wrote two MCP servers for it: one for Garmin Connect (mostly wellness and device data) and one for intervals.icu. intervals.icu is the source of truth for the calendar and for training load (CTL/ATL/form). The agents read from it, and the planned sessions are written back to it so they reach the watch through the normal Garmin sync. Rules, athlete profile and decision thresholds live in text files. The calculations (form projection, workout checks, block analysis) run in small scripts, so the language model interprets the numbers and doesn’t recompute them. Cheap models fetch data, a mid-tier model analyses, and the strongest model only steps in when a decision moves or drops a key session.
Garmin Connect ----> Garmin MCP ------+
|
FR255 --> Garmin --> intervals.icu <--+--> intervals.icu MCP
^ ^ |
| | v
planned workouts | Claude Code agents
(Garmin sync) | (fetch -> analyse -> decide)
| |
+---- events, notes, --+
comments (writes)
The flows
/checkin is a quick check before a session. It reads the morning wellness and the current form, and answers in four lines with a traffic light: green, go; yellow, adjust; red, swap or rest. Anything other than green goes to a stronger “decision” agent instead of being answered by the fast one.
/analiza is post-session feedback. It splits the activity into blocks (warm-up, each rep, cool-down) from the planned workout and the laps in the .fit, compares each block with similar past blocks, asks me at most two questions when something looks off, and leaves a short coach comment on the activity.
/semana is the weekly cycle. It reviews the last week (adherence, load, flags), plans the next one, writes the events to the intervals.icu calendar, and saves a plan and a report I can audit later.
/ajustar-plan is for when life happens: a missed session, travel, feeling unwell. It replans the rest of the week, checks the result against the weekly rules, and only then rewrites the calendar.
/mejora runs every four weeks. It looks at trends (efficiency, decoupling, adherence per sport), checks whether thresholds still look right, and makes proposals. Those proposals are challenged by a separate agent before I see them, and nothing changes the rules without my OK.
/bloque handles phase changes (after a race, a new block, a forced break). Three agents draft different versions of the next block, a “judge” agent compares them, and I approve the one that becomes the long-term plan.
morning after session weekly / 4-weekly
+------------------+ +-------------------+ +---------------------+
| /checkin | | /analiza | | /semana /mejora |
| wellness + form | | blocks vs past | | review -> plan -> |
| -> green/yel/red | | -> comment + log | | write calendar |
+--------+---------+ +---------+---------+ +----------+----------+
| not green | |
v v v
decision agent ----------> shared memory files <------- /bloque, /ajustar-plan
What I learned here and changed
Things attributed to david are his words in the linked threads. The rest is my own testing or what other users said.
- Idempotent calendar writes. david explained that bulk event creation with upsert on external_id updates instead of duplicating, and that bulk-delete accepts external_ids ( Uploading planned workouts to Intervals.icu ). Every event the coach writes now has a deterministic external_id (week, date, sport and priority). Rewriting a week or retrying after a partial failure no longer leaves duplicates, and events I add by hand have no external_id, so the coach never overwrites them. I tested this on my own calendar and it behaved as described.
- Week and block notes. Following [DONE] Training Plan Periodisation Planner , the weekly plan now writes a NOTE with for_week (the week’s goal and key sessions), and a phase gets a multi-week note with an end date. In my tests both persisted through the API. I still have to check visually that the block band shows on the fitness chart.
- Which form to decide on. From what david wrote about how CTL/ATL are computed for the day ( Calendar and fitness updates , Form (TSB) as of yesterday or today's? , FITNESS PAGE - a guide to getting started ), the pre-session check now uses yesterday’s CTL minus yesterday’s ATL. Before, it used today’s record, which already includes the day’s decay and anything done earlier that day.
- recalcHrZones. The OpenAPI lists this flag on the sport settings PUT, so the MCP can send it when a threshold changes. I’ve only confirmed that the API accepts both values without the threshold changing. I haven’t yet tested what it does to zones on a real threshold change.
- HR and pace targets in one workout. I had a hard rule against mixing them, because of older reports about planned load and matching. In a quick test a workout with HR steps and pace steps parses fine, and its planned load is close to the HR-only version. I haven’t tried it on the watch or checked how it pairs with the completed activity, so the rule stays for now. I also noted david’s comment that HR steps need Instant HR to reach the watch as targets ( Power and HR targets for Garmin workouts ), and I still need to verify that on the 255.
plan week ---> lint workouts ---> upsert by external_id ---> re-read event
(one target (no duplicates, (parsed steps?
type, explicit manual events load set?)
target, pool untouched)
length...)
Questions for the community
- API gaps. For a setup like this, is there anything you’d add? For example, compliance isn’t in the API (it’s computed in the client), and so far I only read the weekly totals of mixed-sport load.
- Known issues with upsert by external_id or with for_week / multi-week notes that I should watch for, especially around Garmin sync of updated events.
- Mixed load. How do you handle load across run (HR/pace), swim (pace) and indoor-only power? And how do you count strength in fitness and fatigue: do you leave the default WeightTraining settings or change them?
- Mixed HR + pace workouts. Has anyone confirmed recently, on a Forerunner, that a workout with both target types shows correctly on the watch and pairs with the activity?
- Similar tools. I’ve seen several MCP and coach projects mentioned here. How do you validate workouts before writing them, and do you keep calculations out of the model as well? Any public repos or Claude Code skills you’d point me to?
Happy to answer questions about any part of it. Thanks for all the detailed answers in this forum; most of these changes came from reading them.