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.

kai hero image

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, gpxpy for Garmin; everything else is stdlib and curl
  • Storage: plain markdown files. No database.

Source

github.com/rubengarciam/kai

Changelog

2.5.0 - 2026-10-01

  • Added: garmin_activity_files.py reads TCX files (#36). parse, query and analyze accept .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. analyze gives 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 tcx and 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.py loaded Chart.js from cdn.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 in skills/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 fit saved a ZIP archive under a .fit name (#15). Garmin sends the original file zipped, so parse, query and analyze failed with “Invalid .FIT File Header”. The FIT file is now unpacked on download, and parse/query/analyze also accept a ZIP-wrapped FIT, so files an earlier version left on disk work without re-downloading.
  • Fixed: download failed if --output-dir didn’t exist. It is now created (mode 700).
  • 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.md renamed to CREDENTIALS.md. It held Kai’s own credential-handling rules, but GitHub treats the SECURITY.md filename as the repository’s vulnerability-reporting policy. A real SECURITY.md with a reporting policy replaces it (#24). See upgrading if you copied this workspace elsewhere.

2.3.1 - 2026-09-29

  • Fixed: tyre-mileage.sh read a tyre’s fitted_date as 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.py adds 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 to CONTRIBUTING.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-maintenance skill. 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 garminconnect 0.3.x and Python 3.12+, and everyone logs in once more. login --password is removed and passwords are no longer read from config.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.md and skill symlinks); runtime-neutral README.

1.0.0 - 2026-09-28

  • First release: Kai’s instructions and four skills (endurance coach, TrainingPeaks, Garmin, Strava).