Cade is a personal running coach that runs on your own training data. It pulls your runs from Strava and Garmin, stores them in one place, and lets you ask questions in plain English. Every answer cites your real data so it never makes up numbers.
Cade has two halves. The data tools (sync, query, reports) run on their own with just Python and a Strava account. The coach that turns that data into an answer needs a model, and you bring your own: point it at a local model on your machine (Ollama, LM Studio, Hermes) or connect a hosted service (Claude or OpenAI). Nothing in this repo bundles a model.
- Pulls runs from Strava (REST API) and Garmin (.fit files)
- Merges them into one clean database and removes duplicates
- Computes weekly mileage, pace trends, heart rate zones, personal records, and a training-load number (ACWR)
- Indexes everything for search so you can ask questions like "how have my long runs been trending?"
- Answers through a CLI coach (local model, Claude, or OpenAI) that only uses your real data
Strava API ─┐
├─> pipeline ─> activities.json ─┬─> analytics (weekly stats)
Garmin .fit ─┘ (merge + │
dedupe) └─> rag index (search)
│
query.py <─────┘
(numbers + search)
│
coach.chat
(query.py bundle ─>
your local / Claude /
OpenAI model)
You need Python 3.10+ and a Strava account.
-
Install into a virtual environment:
python -m venv .venv .venv/Scripts/activate # macOS/Linux: source .venv/bin/activate pip install -e . -
Add your Strava API keys. Copy the example file and fill in your Client ID and Secret from https://www.strava.com/settings/api:
cp .env.example .env -
(Optional) Drop any Garmin
.fitfiles into thegarmin data/folder.
Pull your data and build the search index:
python -m coach.pipeline --sync
Get the raw data bundle for a question (JSON: computed stats + the most relevant runs, no model involved):
python -m coach.query "show me my hardest runs in the last 4 weeks"
Make a progress report:
python -m coach.reports --save
coach.chat is the coach that answers in plain English. It pulls the same
grounded bundle as coach.query and hands it to a model you choose. Set a
provider in .env (see .env.example) or pass it on the command line:
# a local model
python -m coach.chat "how are my long runs trending?" --provider local --model hermes3
# Claude, hosted
python -m coach.chat "am I on track for the marathon?" --provider anthropic
# OpenAI, hosted
python -m coach.chat "how's my weekly mileage?" --provider openai
Local models (Ollama, LM Studio, vLLM, Hermes) all speak the OpenAI-compatible
API, so --provider local just needs a --base-url (Ollama's default is
http://localhost:11434/v1, already set in .env.example). Because the coach
only ever sees the retrieved slice of your data, not the whole history, even a
small local model stays grounded.
See the assembled prompt without calling any model:
python -m coach.chat "how am I doing?" --dry-run
You can also use the coach as a subagent inside Claude Code, which supplies the model for you:
@running-coach how is my marathon training going?
running/
src/coach/
models.py data models for a run and a week
strava_client.py pulls runs from strava
garmin_client.py reads garmin .fit files
pipeline.py merges the sources and saves them
analytics.py weekly stats, trends, PRs, training load
rag.py embeddings + vector store for search
query.py builds the grounded data bundle (stats + retrieved docs)
chat.py the coach CLI: bundle -> prompt -> your chosen model
llm.py provider backends (local / Claude / OpenAI)
prompts.py the coaching system prompt
reports.py makes a markdown progress report
data/ your runs + the search index (not committed)
plans/ training plans
progress_notes/ weekly notes
garmin data/ raw .fit files
docs/ design notes
.claude/agents/ the coach agent definition
See ARCHITECTURE.md and docs/ for the deeper design decisions.