Kai
An AI Endurance Coach
Kai is an AI coaching agent for triathletes, runners, cyclists and swimmers. It’s not an app or a server — it’s a folder of plain markdown instructions and five skills that you point an agent runtime at and start chatting with.

Named after King Kai from Dragon Ball. Casual, analytic, occasionally sarcastic about your recovery metrics.
How it works
Kai is a set of files: identity, personality, operating manual, and skills. You bring your own AI runtime — OpenClaw, Claude Code, Codex, or Hermes Agent. The runtime reads the files and becomes your coach.
All data stays on your machine. Nothing is stored externally beyond the API calls to your training platforms and the language model.
Data sources
- TrainingPeaks — training load (CTL/ATL/TSB), planned vs completed workouts, personal records, weight
- Garmin Connect — sleep, HRV, Body Battery, training readiness, VO2 max, race predictions, resting HR
- Strava — lap splits, time-series streams (HR, pace, power, cadence), gear mileage
When sources overlap, Kai follows a priority: TrainingPeaks for load, Garmin for recovery, Strava for lap-level detail.
Capabilities
Workout analysis — Two-layer review: overall read first, then lap-by-lap breakdown. Reads the numbers before reading your comments. If the data disagrees with your perception, it says so.
Fitness and fatigue tracking — CTL, ATL, TSB, weekly TSS trends. Knows where your form sits relative to a target race.
Recovery monitoring — HRV trends, resting HR, sleep stages and scores, Body Battery, training readiness. Surfaces recovery data alongside training load.
Training plans — Periodized plans with base, build, peak, and taper phases. Friel 7-zone system for run, bike, and swim. Field test protocols when thresholds are missing. Race-day pacing, fueling, and taper plans.
Gear tracking — Live shoe and bike mileage from Strava. Per-wheelset tyre wear tracking with replacement alerts. Chain wax ledger with DUE SOON / DUE / OVERDUE statuses.
Nutrition and weight — Log food and weight in chat. Deficit recommendations paced to training load — checks tomorrow’s session before suggesting cuts.
Interactive dashboards — HTML charts for sleep, Body Battery, HRV, and activity trends via Chart.js.
Race predictions — Garmin-estimated finish times for 5K, 10K, half marathon, and marathon.
Dual role
Kai operates in one of two modes depending on your setup:
- Coach mode — no human coach. Kai builds and adapts your training plan, following a mandatory athlete-validation step before writing anything.
- Analyst mode — you have a human coach. Kai interprets data, spots trends, assesses recovery, and preps questions for your coach. Never writes plans or overrides the program.
Stack
- Runtime: any AI agent runtime (OpenClaw, Claude Code, Codex, Hermes Agent)
- Language: Python 3.12+, Bash
- Dependencies: minimal —
garminconnect,fitparse,gpxpyfor Garmin; everything else is stdlib andcurl - Storage: plain markdown files. No database.
Source
Changelog
2.5.0 - 2026-10-01
- Added:
garmin_activity_files.pyreads TCX files (#36).parse,queryandanalyzeaccept.tcx(any case) and return the same record fields as for FIT (timestamp, heart rate, distance, altitude, speed, power, cadence, position) plus a summary per lap, using only the standard library. This makes indoor rides uploaded by third-party apps analysable: their FIT files can’t be decoded, but their TCX exports carry the full data.analyzegives the same statistics from a TCX as from the FIT of the same activity. - Changed: when a FIT file can’t be decoded, the error now says to download the same activity with
--format tcxand parse that. - A TCX that declares a DTD or XML entities is refused (a TCX file never has them), which closes the usual XML entity-expansion trick.
2.4.0 - 2026-10-01
- Changed: Garmin dashboards no longer need the internet (#7).
garmin_chart.pyloaded Chart.js fromcdn.jsdelivr.net, so a dashboard was blank offline and depended on a third-party server. Chart.js 4.4.0 (the official build from npm, checked against the registry’s integrity hash, with its MIT licences) is now bundled inskills/garmin-health-analysis/assets/and embedded in each page, which also means a dashboard keeps working after you move or email it. If the bundled file is ever missing, the script warns and falls back to the CDN. Pages are about 200 KB larger.
2.3.2 - 2026-09-29
- Fixed:
garmin_activity_files.py download --format fitsaved a ZIP archive under a.fitname (#15). Garmin sends the original file zipped, soparse,queryandanalyzefailed with “Invalid .FIT File Header”. The FIT file is now unpacked on download, andparse/query/analyzealso accept a ZIP-wrapped FIT, so files an earlier version left on disk work without re-downloading. - Fixed:
downloadfailed if--output-dirdidn’t exist. It is now created (mode700). - Fixed:
.FIT(capitals), which devices and Garmin Express write, was rejected as an unsupported file type. - Changed: downloaded activity files are saved owner-only (
600) and never written through a symlink at the destination. They hold GPS tracks, and the default folder,/tmp, is shared. - Changed:
SECURITY.mdrenamed toCREDENTIALS.md. It held Kai’s own credential-handling rules, but GitHub treats theSECURITY.mdfilename as the repository’s vulnerability-reporting policy. A realSECURITY.mdwith a reporting policy replaces it (#24). See upgrading if you copied this workspace elsewhere.
2.3.1 - 2026-09-29
- Fixed:
tyre-mileage.shread a tyre’sfitted_dateas midnight UTC instead of the athlete’s own local date, so a ride on the local morning of the fit day (east of UTC) could be missed entirely, and a ride the evening before (west of UTC) could be wrongly counted (#22). It now compares each activity’s own local date, which Strava already provides, so no timezone configuration is needed.
2.3.0 - 2026-09-29
- Added: tyre ledger helpers (#16):
tyres.pyadds wheelsets and fits, replaces and retires tyre sets, so the tyre ledger no longer needs hand-editing. - Docs restructure: the README is now a short front door (381 → 123 lines); installation, per-agent setup, upgrading, features and architecture moved to
docs/, contributing toCONTRIBUTING.md. Added this changelog and GitHub pull request and issue templates.
2.2.1 - 2026-09-28
- Fixed: five Garmin scripts and the Garmin README still told users to run
login --password, a flag removed in v2.0. - Changed: skill docs use plain paths from the repo root instead of an OpenClaw-specific base-directory placeholder (#3).
- Added branch naming and PR conventions; more tests.
2.2.0 - 2026-09-28
- Changed: chain wax and tyre tracking moved out of the Strava skill into a new
gear-maintenanceskill. Ledgers in the old location still work, with a notice. See upgrading.
2.1.0 - 2026-09-28
- Added:
chain-wax.py, a chain wax ledger with km-since-wax reports and DUE SOON / DUE / OVERDUE statuses (#10).
2.0.0 - 2026-09-28
- Breaking: the Garmin skill needs
garminconnect0.3.x and Python 3.12+, and everyone logs in once more.login --passwordis removed and passwords are no longer read fromconfig.json. See upgrading. - Added: hidden password and MFA prompts,
--password-stdin,GARMIN_TOKEN_DIR, offline tests.
1.1.0 - 2026-09-28
- Added: Claude Code and Codex support (
CLAUDE.mdand skill symlinks); runtime-neutral README.
1.0.0 - 2026-09-28
- First release: Kai’s instructions and four skills (endurance coach, TrainingPeaks, Garmin, Strava).