The Problem: Years of Running, Nothing to Show for It

The numbers were not flattering. VO2max went from 44 to 45 in nine weeks — statistically noise. My 10K time had been flat for months at around 63 minutes. The only thing improving was efficiency: my heart rate at 10K pace dropped from 160 to 152. I was getting better at running the same speed, while the ceiling didn’t move.

The diagnosis, once I actually looked at the data, was boring and common: years of monotone Zone 3 running. Every run “comfortably hard.” The grey zone. Too hard to build an aerobic base, too easy to push the ceiling. I knew the theory — polarize, run 80% genuinely easy, one genuinely hard session per week — but knowing the theory hadn’t changed my behavior in years.

What changed my behavior was building a coach that reads my actual data and refuses to flatter me.

The Setup: A Coach Made of Three Markdown Files and an MCP Connector

I’ve been feeding fitness data to AI before — a while back I built a vector-search engine over my Strava history. These days a Garmin MCP connector gives Claude direct access to activities, splits, HR zones, training readiness, the works. So the missing piece wasn’t data access. It was structure and memory. So the “coach” became a small project folder:

Hardlooptraining/
├── PROJECT_INSTRUCTIONS.md   # who the coach is, how it behaves
├── profiel.md                # my zones, known patterns, the goal
├── analyse.md                # living analysis doc, updated after each run
└── logs/
    ├── 2026-07-14.md         # one structured report per session
    ├── 2026-07-15.md
    └── 2026-07-17.md

The instructions file is where the actual coaching quality comes from. The key rules:

  • Analyze per lap, never just averages. Averages hide drift and zone distribution — which turned out to be the insight of the whole experiment.
  • HR is leading on easy days, never pace. No pace targets as the primary number.
  • If the data contradicts the plan, update analyse.md and say explicitly what changed and why.
  • Don’t play along when I try to convince myself a mediocre session was good.

That last rule matters more than it looks. A default LLM assistant is a validation machine. “Great job getting out there!” is worse than useless when your actual problem is a decade of comfortable self-deception at 145 bpm.

The workflow per session: I run, the coach pulls the activity from Garmin (activity → splits → HR-in-timezones), writes a structured report to logs/<date>.md, and updates the living analysis when something doesn’t match expectations. The files are the memory. Every new session starts with full context of every previous one.

What One Week of Per-Lap Data Exposed

Here’s what that structure produced in a single week. Three runs, three findings I’d been able to ignore for years.

Tuesday: The Drift I Never Saw

An easy Z2 run, target HR under 135. The average came out at 130 — looks perfect. Per lap, it was a different story:

LapPaceAvg HR
28:00119
48:14123
67:59143
77:54146

Same pace, start to finish. HR climbed from 119 to 146 — 27 beats of cardiac drift at constant effort. Nearly a third of my “easy” run was in Zone 3. The average of 130 hid all of it. That drift is the thin aerobic base made visible: the engine can’t sustain even a slow pace aerobically for a full hour.

Wednesday: The Interval Session That Failed Correctly

The plan said one hard session per week, so: 5×3 minutes hard, 2 minutes rest, target Zone 5 (174+). Result: peak HR in the intervals was 150. Not one block left Zone 3. Partly the heat (27–29°C, after a workday), but mostly something more interesting — the highest HR of the whole session (171) came after the intervals, during 13 minutes of continuous running at 6:00/km.

The coach’s conclusion, written straight into analyse.md: my HR rises slowly and needs sustained effort to climb. Short intervals with rest are the wrong instrument for my physiology — by the time HR approaches the target, the rest period arrives and resets it. Hard sessions were rewritten on the spot: 3×6 or 2×10 minute blocks, or a 20–25 minute tempo run, cool days only.

A session that fails and produces a permanent correction to the training plan is worth more than a session that vaguely succeeds.

Friday: Paying the Real Price of Zone 2

The rematch of Monday, with one rule change: when drift comes, sacrifice pace, not the HR ceiling. HR alarm at 135, walk when it fires.

It worked, and the result was humbling. At a constant HR of 129–133, my pace decayed from 8:26/km to 11:53/km. Power dropped from 255 to 197 watts at the same heart rate. That’s the same drift as Monday — but this time made visible in the pace column instead of hidden in the HR column.

So now I know my actual current Zone 2 cost: roughly 9:30–11:00/km including walk breaks once drift sets in after ~30 minutes. The 8:15/km I’d been calling “easy pace” for years was never easy. It just felt that way because I’d stopped noticing Zone 3.

The coach also caught a behavioral pattern I would have edited out of my own memory: my “cooldown” — the last 10 minutes — ran at 8:50/km with HR at 142–149. Faster and harder than the main block. Slow running feels wrong, so the body sneaks in “real running” at the end where it thinks nobody’s watching. Somebody was watching. New rule in the log: cooldown means walking. No exceptions.

Why This Works (and a Chat Window Doesn’t)

I’ve tried asking AI about training before. Generic answers, no memory, infinite encouragement. The difference here is entirely architectural, and it’s the same architecture I use for infrastructure:

Instrument everything. The Garmin MCP gives per-lap, per-zone, per-watt granularity. Averages are dashboards; laps are traces.

Log structured, append-only. Every session becomes a markdown report with a fixed format. Nothing relies on the model “remembering” — the files are the state.

Let data override the plan. analyse.md is explicitly a living document. When the intervals failed, the plan changed the same evening, with the reasoning written down. That’s a postmortem culture applied to training.

Alert on the right metric. The progress metric is no longer “10K time” or even “pace at HR 135.” It’s: how long can I hold 8:30/km while keeping HR under 135, before drift forces me to slow down? Right now: about 30 minutes. That number moving is the aerobic base thickening. Everything else is a lagging indicator.

If that sounds like GitOps for a human body — observe, diff against desired state, reconcile — that’s not an accident. It’s the only management loop I’ve ever seen actually work.

What’s Next

Sunday is the first long run under the new regime: 90 minutes on time (not distance — at the real Z2 cost, a distance target would balloon into a reckless duration jump), HR under 140, and a cooldown that consists of walking, because apparently I can’t be trusted otherwise.

The plan is boringly simple from here: weeks of genuinely easy running, one long-block hard session on cool days, and one number to watch — the drift point. If it moves from 30 minutes toward 45, the base is building. If it doesn’t, the coach will say so. Bluntly. That’s what I built it for.