sports-coach/

Sports Coach

AI coaching system built on years of training history, combining a structured knowledge base with live performance data to replace a human coach.

Solo builder·Personal
Live

Stack

AI
Claude CodeClaude SonnetAnthropic SDKVoyage AI
Data
PythonSQLiteNumPy
Backend
FastAPI
Frontend
ReactPWA
Infra
Hetznersystemd

About

Built into the sports-data app, this coaching system replaces a human coach with a knowledge base drawn from years of documented training history. It combines that knowledge with live performance data from Strava (activity tracking), WHOOP (recovery and sleep) and intervals.icu (training load analysis). Everything surfaces through three tabs: a training calendar, a periodisation view, and a chat with Fred, an AI coach running on Claude Sonnet.

Each metric Fred reports is computed inside the app’s own tools, tuned to be fast and cheap in production. In parallel, I have been building a governed semantic layer over the same data, where each metric is defined once and an AI can only query the definitions that exist rather than recompute them its own way. It stays an experiment for now, deliberately downstream of the live system, but it points at where the definitions should eventually sit. I wrote up what that showed in one definition of a metric, even for the AI.

The story

Two years into a coaching relationship built around specific cycling objectives, the collaboration with my coach had run its course. The programming was good, the progression was real. But the cost had become significant and the rhythm had plateaued into a comfortable routine that was no longer pushing forward.

The question then became whether the best parts of the training plan I had followed could be preserved, and improved, by building something different instead of hiring another coach. The structure of training blocks and weekly sessions was worth keeping. The time saved by not having to design the programme yourself was worth keeping. What a human coach could not provide was the ability to handle multiple data sources simultaneously and explain every programming decision in full, at any level of detail.

Building the knowledge base

I started with documentation rather than code. Over several sessions, I extracted more than two years of training built around my recent objectives and structured it into a folder of Markdown files, closer to a formal onboarding than a quick reference document. Previous experience as a competitive athlete and as a trainer made it possible to articulate what the methodology actually was, including the parts that had never been written down before.

coach/
├── context.md                 # Shared framework, cycling zones, block model, progressions, philosophy
├── calendar.md                # Race calendar, races ranked by priority across the season
├── persona.md                 # Fred's identity, voice, responsibilities and explicit guardrails
├── athlete-profile.md         # Motivations, psychological profile, training preferences
├── questions.md               # Coach-athlete calibration questionnaire
├── workflow.md                # How plan updates flow between Fred, Claude Code, and the app
├── blocks/
│   ├── periodisation.md       # Past and planned blocks with their rationale
│   └── decision-log.md        # Structural changes to planned sessions, what replaced them and why
├── history/
│   ├── complete-blocks.md     # 22 training blocks with session details and rationale
│   ├── cycling-sessions.md    # ~280 cycling sessions with perceived effort and normalised power
│   └── session-library.md     # Cycling session library: intervals, zones, progressions
├── running/
│   ├── running-framework.md   # Running zones, paces and prescription rules
│   ├── session-library.md     # Running session library
│   └── history/               # Reference preparations, ~180 running sessions, best efforts by season
└── race-strategies/           # Pre-race analyses and race preparation plans

Each file serves a distinct function. persona.md describes what Fred knows and what he must not do. The list includes prescribing a professional-style taper (a sharp cut in training before a race), fixating on a single day’s recovery reading, or dropping sessions without a solid reason. These guardrails are written into the file explicitly, because the restrictions matter as much as the knowledge. context.md holds the reasoning layer: coaching philosophy, training principles, the logic of each block type. The operational data (sessions, dates, power targets) lives separately in a SQLite database. The distinction is deliberate: structured data goes where it can be queried, natural language goes where it can be read, by a human or a model.

history/ records what was done and how it performed, and blocks/ holds the plan being built, so the plan can change without disturbing the reference material. The decision log exists because Fred, faced with a changed session and no record of why, would otherwise invent a reason. Running came later, with its own framework, session library and history, so Fred coaches both disciplines. When a question could apply to either, he asks which one is meant or answers for both.

Writing these files changed more than the system. Going back over several years of blocks put names on progression patterns I had applied for a long time, and confirmed which orderings had consistently worked. The documentation was as much an audit of previous coaching relationships as a foundation for what came next.

How Fred works

Fred runs on Claude Sonnet. Every question he receives is answered against a context assembled from four layers, two always in front of him and two fetched only when a question needs them.

Always loaded   Knowledge base (coach/ files)   ~60,000 tokens   cached
Always loaded   Live snapshot                   ~7,600 tokens    rebuilt on every request
On demand       Session libraries               ~18,000 tokens   retrieved by relevance
On demand       Six data tools                  exact figures    called by Fred

Always present: the stable knowledge. The files that describe the method, the athlete, the plan and the training history, for cycling and running, are loaded as a single cached block of around 60,000 tokens. This block is served through the Anthropic prompt cache. The first request reads it in full. As long as questions keep coming within five minutes of each other, the following requests read it from cache at a tenth of the price, $0.30 per million tokens instead of $3. The files are cached by modification time, so any change synced to the server is picked up on the next request with no restart.

Always present: the fresh snapshot. Rebuilt from scratch on every request, never cached, because it changes constantly. It has three parts. The short-term view holds weight, recovery, the last two weeks of sessions, the current and next blocks, the planned sessions up to the next race, and the latest entries from the Journal. The Journal is a page in the app where I log dated events that affect training, such as illness, fatigue, travel or a race, with their impact. The comparative view sets the last 30 days against the same month a year ago, and this season against the previous one. The daily view is a 30-day table with one row per day of recovery, sleep and distance. This layer is roughly 7,600 tokens, and it carries what just happened.

Fetched on demand: the session libraries. The cycling and running session libraries stay out of the permanent block. Together they hold around 18,000 tokens of exact interval prescriptions and session variants, and a question rarely needs more than a few of them. Fred retrieves them by relevance instead. The libraries are split into around 60 self-contained chunks, each indexed with Voyage AI. When a question looks like a lookup, the exact intervals of a named session for instance, it is matched against the closest chunks, and the top five above a threshold are added to the snapshot. If no chunk is relevant, nothing is added. The retrieval is best-effort, so if the index or the Voyage AI key is missing, Fred answers without it rather than failing. A simple index file on the server is enough for around 60 chunks, so there is no dedicated search database.

Fetched on demand: the tools. This layer is different in nature. The three layers above are text handed to Fred. The tools let Fred go and fetch exact figures himself, for anything beyond the 30-day daily window. When a question needs a precise number, Fred issues a tool call, the server runs it, the result comes back, and he continues, up to seven turns inside a single answer. There are six tools:

  • query_metric aggregates any of 20 metrics, from recovery and sleep to power and training load, over any date range.
  • get_planned_sessions returns what was or is planned over a date range, so Fred can set it against what was actually done.
  • get_activity_detail returns the full breakdown of one session by date.
  • list_sessions returns a filtered and sorted list of sessions over a date range.
  • get_activity_breakdown returns power, heart rate and pace segment by segment, lap by lap or kilometre by kilometre for a Strava activity.
  • get_best_efforts returns my best sustained efforts over standard durations and distances, the session that set each record, and a comparison between seasons.

Both on-demand layers exist because they answer different questions. The session libraries are prose, and retrieval by relevance is good at prose: the shape of a session, the wording of a progression. It is poor at the magnitude of a number, at arithmetic, and at filtering by date, which is the gap the tools cover. A question about how a session type is built is answered by retrieval. A question about the actual watts held last Tuesday is answered by a tool call.

What this costs. A typical request costs about five cents. A question that triggers a tool adds one to three cents, one that triggers retrieval adds a fraction of a cent. The full context, roughly 60,000 cached tokens plus the live snapshot, sits far below the model’s one-million-token capacity, so nothing competes for attention and nothing is truncated.

The three tabs

Fred is the tactical interface. Quick prompts cover the most common questions: current form, readiness for the next objective, the week’s programme, what the next block is building towards. Responses are grounded in the numbers. For a question such as “am I on track for my next race?”, the answer compares my fitness with the same point last year and weighs how much fatigue I am carrying. It also cites the power from recent sessions and the sessions that remain before the event. The most recent conversations are kept, so a thread can be picked up again later.

Fred, the AI coach tab

Calendar replaces TrainingPeaks, the training-planning platform widely used by coaches. A monthly grid shows past and upcoming sessions, colour-coded by type and intensity, with a weekly summary panel on the right covering distance, duration, and training load. Completed sessions pull from Strava. Upcoming sessions come from the SQLite database on the VPS. Sessions can be added from templates, moved or deleted directly in the app. The app links out to Strava for session detail rather than duplicating it.

Calendar tab

Periodisation makes the plan legible. Each training block appears in sequence from the current one through the full planning horizon, with its duration, its goal, and its sessions. The reasoning is written out: why this block now, why this sequence, what it builds towards. The full arc of a season is visible in one place for the first time.

Periodisation tab

The weekly review

Every Monday, Fred also writes a review of the past week, sent by email. Three analyses run in parallel over the week’s data, a final pass writes the review from their findings without calling any tool, and automatic checks verify the figures and the style before it goes out.

Consultative vs executive

Fred and Claude Code serve different functions.

Fred reads, reasons, and advises. He cannot modify the plan files, commit a change to Git, rebuild and deploy the app, or update his own system prompt, and his tools open the database in read-only mode.

Claude Code does all of that. It can rely on Fred to propose that a block should be adjusted, then turn that proposal into updated files, a redeployed app, and a committed change.

Fred now has the full methodology, the history, and the race calendar. Claude Code has write access. Keeping reasoning and execution in separate interfaces means no unreviewed change reaches the plan.

First test: GF Piccolo Ventoux 2026

The first meaningful test of a race plan built entirely by the system was the GF Piccolo Ventoux on 6 June 2026, a gran fondo (an amateur mass-start road race) of 77km with 2,100m of climbing. I had ridden the same event in 2025.

In 2026, the system broke the 2025 race down section by section and compared my fitness and fatigue before each edition. Its conclusion was to arrive rested rather than at peak training load, even with 23% less training volume in the build-up. I followed the plan, and the result was a 5% improvement in average power and 17th place out of more than 500 starters. For an athlete with several years of competitive experience at similar events, that margin is significant.

Limits

The system works because of what preceded it: years of competitive experience, a clean and consistent dataset, and prior coaching that established the methodology being documented. It is not a shortcut for someone starting from nothing. The preparation time is real, the domain expertise required is real, and the ongoing work of auditing outputs and updating files as the season evolves is what keeps it accurate over time.

The retrieval and tool layers make clear what the system still cannot do. Beyond roughly three questions a minute, the retrieval layer is throttled by the Voyage AI free tier, and Fred answers without the library detail rather than stalling. The tools return simple aggregates rather than correlations, so Fred cannot relate recovery to power on his own. He compares planned and completed sessions by reasoning over both lists, since no tool matches them, and he keeps no memory from one conversation to the next.

Why it matters professionally

The pattern this project demonstrates applies directly in a RevOps or business systems context. Take a domain with accumulated expertise, structure that knowledge deliberately, connect live operational data to it, and build an interface designed around how it will actually be used.

The system works because the subject matter was understood deeply first, and skipping that step is the most common failure in business: an AI layer deployed over generic or poorly structured knowledge produces generic outputs. Specific guardrails, a coherent hierarchy across files and continuous auditing of results all belong to systems design.

The two-tier architecture, with Fred as a reasoning interface and Claude Code as an execution interface, has direct equivalents in a professional context. A sales intelligence layer surfaces context while a CRM records actions, and a reporting layer explains while a pipeline transforms. Keeping those concerns separate is what makes each one maintainable.

The way Fred assembles context is a transferable pattern in its own right. Stable reference material stays in cache, cheap and always present. Fast-moving data is rebuilt fresh on every request. Anything too large or too precise to carry by default is fetched on demand, by relevance or by an explicit tool call. That is the same design a production assistant needs over a CRM or a data warehouse: keep the durable context cheap, and pull the live and exact figures only when a question asks for them. The split between what is always present and what is fetched on demand is how you keep a language model accurate and affordable against a large and constantly changing body of data.