← maximefrydman.com

Building an Agentic Training Coach with Claude Code

The why

I've played around with LLM / Agents / AI assisted coding but not really deep dived. This ends here and I only know one way to learn, build. This post won't go into software development practices but rather specific learnings from trying to create an agent that creates value from fuzzy real world context.

My healthy obsession - Problem statement

As I migrated from being an active SCUBA diving instructor to corporate cybersecurity, I had to reorganize my sports regimen to keep in shape. I eventually settled on various activities, originally very running centered but over time migrating to multi-sport to keep it fresh and exciting.

To track my progress I use a Garmin Fenix 8 and a HRM-600. This has been very helpful to get me back on track with its training programs, overall stats and gamification.

My life however is not always as predictable as Garmin expects and this led to some friction with training programs:

  • I want to decide on the day which activity I feel like doing - it should recommend an effort level aligned with my goals
  • When traveling, I don't have access to the same activities / equipment as at home - it should adapt to my context
  • My year has phases, holidays, work travel, home and rested... - it should nudge me coherently
  • Sometimes I'm tired, sometimes I went to a party - it should automatically move and shift things around
Life on the road
Life on the road

Before this project my solution was, take screen caps / exports from Garmin. Input into an LLM where I have over time defined my approach and look to align / insights. My general philosophy towards training is to keep active throughout the year, incremental improvements stack up over time and moving keeps me feeling good and mentally fresh.

So the perfect system is something that distills all of this into a package I can consume daily, weekly and monthly that gives me actionable short term steps and long term reward and accountability.

Garmin built a great ecosystem... but:

  • No good way of creating routines if you're outside of a training program - Manual calendar is friction heavy
  • Training programs are bad at identifying you accomplished the purpose of the training - Don't run a Z5 training because you did a Z5 swim session
  • Progress tracking is good but it requires me to go and look for it - The workload falls on me

Inputs, polling queues and interactions

After a bit of back and forth I settled on Telegram. It's easy to see why it has become such a common pattern for agentic / AI interaction. You get all the communication layer for free in an interface that supports all the features you need to add commands.

For the data I don't need to go very far as I already have everything in Home Assistant and that's accessible on my local network. To simplify and keep the freedom of a POC I'll run this as a container on my Proxmox lab.

What does the system need to know about me?

  • Garmin activity, sleep, habit tracking...

  • Position and zones pulled from Home Assistant companion app

  • Context and journaling provided by me haphazardly

    Journaling interaction, very light
    Journaling interaction, very light

What do I want from the system?

  • A message when I wake up with a plan for training today
  • This plan should be highly flexible and take all the above data into account
  • I can override the plan, it should adapt and extract the value
  • Summaries at the end of the week and the month for accountability and motivation
Useful output - compact, informational
Useful output - compact, informational

Predictions with a purpose:

  • If I've slept poorly, the plan should switch the training calendar around and place the easier training today or skip all together
  • If I'm full of energy, it's a great time to push me hard - bring harder sessions forward
  • Motivate me to do more if I'm low on volume - Load / Volume ratios, small deltas / increases
  • Motivate me to rest if I'm going too hard - Based on the combined metrics
Good predictions are easy to spot, they align with reality
Good predictions are easy to spot, they align with reality

Defining goals:

  • Balanced training Volume based on HR zones
  • VO2 Max Objective
  • Weight Objective
  • Science based recommendations

Architecture of a decision

1. Do the math in code. Rolling baselines, deltas, acute:chronic workload ratio, deviation flags — all of it gets computed in code, into a metrics.json, before the model ever sees the data. The model never derives a number from raw JSON; We all know LLMs are bad at math but it was tempting to try. Great way to spend tokens for garbage, unreproducible data. (don't ask how I know)

2. Doctrine files, some things can't change. The actual training rules live in base_goals.md. For me this includes all my hard data like heart zones, training objectives, training philosophy etc... It's become a long file of hard facts and goals. It also sets rules for changes (move training a day forward / a day back, be canceled) with strict boundaries. Without this set in stone baseline the LLM started going off-script and drifting really fast into incoherence.

3. Memory tiering - Not everything is important forever. As some things are set in stone, some lose importance over time. How I felt last week is important for the monthly suggestions but is irrelevant for training today. I landed on 7 days rolling windows of data for daily suggestions and 30 days for the weekly, 90 days for the monthly.

4. Memory goes both ways. The agent studies what I did yesterday compared to what it recommended I should do. For this I also need a log of what the recommendations were so we can check for adherence or if there was deviation from the intent. Otherwise the coherence of the plan falls off a cliff; also the accountability of predictions and adherence.

With the 4 concepts above and a lot of tweaking I arrived at a result that strikes a balance. I was surprised how long it took me to fine tune this. Getting all the data from Garmin, embellishing, communications... took me 2 hours. Getting a relevant suggestion that stayed coherent over time took days.

Balanced prompting is an art I don't think I really understood how much of a balancing act prompting can be in certain cases. When you ask for code fixes the tone, style... is less important than the results. But this is a training coach, it should be peppy, but ideally not kill me.

Too mushy, I'd never get out of bed
Too mushy, I'd never get out of bed

At the same time you look to anchor the prediction on the data otherwise it's useless. But even there you can't be too strict otherwise you get a dashboard not a recommendation engine. Compare this with Conditional Access Policies agent where any deviation from the RAG is a crime.

So strict it can't say anything but the data
So strict it can't say anything but the data

Below is a sample of base_goals.md, using Claude to write these is functional but it needs human intervention for the nuances. I do love the drama of it, Severity Doctrine, Suppressed Readiness, the plan and the laziness just don't sound as exciting.

Training-through doctrine (counterweight to the readiness gates — read
alongside the Severity doctrine)
---
The gates in Coaching Behavior #1 and the Severity doctrine exist to
catch real risk, not to give ambiguity an easy out. Suppressed readiness
moves or shrinks a session; it does not delete one by default.

Hard-day floor: suppressed readiness (fired deviation flags, low HRV,
RHR drift) MOVES the week's quality session to another day within the
week, or downgrades it one notch (4×4 → 3×3, hold speed rather than add
it, or switch to pace-based execution instead of HR-based). It cancels
ONLY on an actual hard blocker per the Severity doctrine: active
migraine, illness signs, or HRV low AND still falling versus the prior
morning. A week should almost never close with zero quality attempts —
"no session fit this week" is a planning failure, not a neutral outcome.

Transient suppressions are noise, not injury: alcohol logged the prior
evening, a single short night, or heat (per lifestyle/context log) do
not suppress training — train as planned in the evening slot. Switch to
pace targets instead of HR that day (HR is unreliable under these
conditions), and the prescription includes hydration/electrolytes and a
nap if feasible. Say so plainly: this is a known, temporary drag on the
numbers, not a signal to protect against.

Easy days are never cancelled for readiness: a red morning still gets
Z1-Z2 movement (rucking, easy spin, walk) unless a hard blocker is
active. "Rest" is a prescription reserved for hard blockers or a planned
deload — never the default answer to ambiguity.

Lessons learned

  • Over-specifying the output contract produced a dashboard, not a coach. My first instinct was a rule like "every claim carries its datum." What I got back was prose that just restated metrics at me. Early I over corrected trying to limit the scope of decisions. This made it useless.
  • Tone is a design parameter, not a nicety. Banning cheer-leading language produced an auditor instead, technically correct (it's the best kind of correct), but exhausting to read every morning. The actual fix was earned optimism: positive statements have to cite data, exactly like any other claim does. Neither "always encouraging" nor "always clinical" was right; the rule had to be about evidence.
  • LLMs default to risk-aversion on health topics, and that compounds with conservative data. The system measured fatigue exquisitely and fitness barely at all, so its recommendation was always "rest." Fixing that took two things: an architectural fix (adding real outcome metrics, not just strain ones) and a /override that lets me decide (human in the loop, more on that later), logs that decision, and scores it later so the thresholds themselves can be calibrated against what actually happened, not just against caution. So a line between "I want to do what I want" and "it should enter the system so as to not break tomorrow".
  • The human-in-the-loop I did a lot of back and forth within Claude code, plan reviews, architecture decisions... This is not what's prescribed. Now we have loop prompting. But I'd say it's fundamental at the beginning to get a feeling for what works and what doesn't.

The final lesson is, pure vibe coding is now 100% feasible. I didn't touch a single line of code. Bugs happened, testing was introduced, it managed to always work its way out. I did choose Python so I could jump in if needed and review but never had to.

But I still found ample room for improvement by catching bad ideas early:

  • Don't pull data from Garmin every 2 seconds
  • Don't create token devouring loops to analyse if I ran yesterday which is a boolean in your dataset

The final product

Ok so am I a happy user of my own product? Yes! It actually does what I wanted it to do and actually fits my usage. I settled on a few modifications, and there is room for a V2.0.

It replaced my manual review with Claude and gives me a nice nudge to get moving. The load tracking is very accurate and helps me keep productive without a lot of guesswork. It's also generally very low friction.

What I would change?

  • Prompting can be improved. It's spot on for tone but still conservative. Maybe some roleplay and a personality
  • Try some different models, we are running Opus 4.8 right now and new options got released, not sure it's worth the cost
  • I need a working long term memory for certain items that don't quite fit the current model
  • I need more commands, specific cases where I want something now and not on a Cron schedule

The main field test was 2 months of traveling where it helped me create a new routine and track progress and actually keep my productive rating on Garmin (if you don't have a Garmin watch you can't understand how hard it can be XD).

Next Steps

Next I want to find a bigger project with more constraints to test both drift when the context becomes massive, vibe coding versus design oriented prompting and loop engineering.

I'll stick to my bad analogy for now. LLMs are a tool. Like a screwdriver it works best when there is someone holding it. When you chuck the screwdriver at the job it doesn't perform as well autonomously.

That being said, the next project will explore more daring loop engineering and lower the supervision based on the lessons learned. (I expect the next lessons learned will be token costly).


share: linkedin twitter

subscribe via atom