Domestique - a free, open source, science based, self hosted cycling training app

I’ve been building Domestique, a free and open-source cycling training planner, and I’d love feedback or contributions from people who actually train with power.

GitHub: GitHub - platypus45/domestique: Adaptive cycling training planner: 7 science-grounded injury-prevention guardrails close the loop between planned and actual training. 3,054 ZWO workouts, 622 routes, hardware-agnostic. · GitHub

The itch I was scratching: most “smart” planners stop at a dashboard — they show you CTL/ATL/TSB and leave the thinking to you. I wanted something that closes the loop: build the plan, ride it, import the ride, and have it mutate tomorrow’s prescription from what the ride actually showed — TSS overshoot, polarisation breach, DFA α1, aerobic decoupling, monotony, eFTP drift, daily-wellness.

What it does:

- Plans from your goal and your event. Pick an FTP or VO2max focus and it schedules more of the work that actually moves that number (threshold/sweet-spot vs VO2/30-15). Set a target event and it builds backwards from the date — a long-ride progression sized to the event’s distance + elevation, a fitness target that auto-lowers if the date’s too soon (so it never prescribes an impossible ramp), and climbing-specific work for hilly routes.

- ~4,200 structured workouts, copyright-free, content-classified (every file run through a classifier so the type/duration always matches the title), plus 600+ real-world route courses.

- Fully science-based. Every threshold and guardrail traces to the literature (Seiler, Rønnestad, Coggan, Gabbett, Mujika…), cited inline in the README — if you disagree with a number, the paper is right there.

- Computes DFA α1 in-ride — your aerobic threshold (HRVT1) straight from your chest strap’s RR/HRV data, with Malik artifact rejection and a per-ride α1-over-time chart. No lab, no ramp test.

- Reads your physiology, not just TSS: DFA α1 + HRV thresholds, intensity distribution, aerobic decoupling, monotony — and a morning readiness score that folds in your Garmin sleep + overnight HRV (synced through intervals.icu’s wellness).

- Integrated with [intervals.icu](https://intervals.icu) — one API key, no separate account; it pulls your rides, wellness, Garmin sleep + resting HRV automatically.

- Hardware-agnostic. Generate a ZWO or FIT; load it into a trainer app (MyWhoosh / Tacx / Zwift / Golden Cheetah) or push it straight onto a Garmin / Wahoo / Hammerhead; ride; import the FIT back to close the loop.

- Multiple profiles possible, localhost-only. No telemetry, no cloud, no subscription. Your data stays on your machine.

It’s free and open-source (MIT), and I’d genuinely welcome contributors — sports scientists, cyclists, and devs alike. The training logic is grounded in the literature (Seiler, Rønnestad, Coggan, Gabbett…) with inline citations in the README, so if you disagree with a threshold, the receipts are right there to argue with.

Honest disclosure: it’s a personal project, macOS + Windows, and a lot of it was built with heavy AI assistance — so it’s improved fast but I’m sure there are rough edges. That’s exactly what I’m hoping you’ll find.

Happy to answer anything about the methodology or take feature requests / PRs.

Question: suppose I had peak fitness some time in the past with PRs, high FTP test etc. Could it look at the data leading up to that date and replicate the training, rest etc?

Will you post some screenshots of what the UI looks like? It’s hard to get a sense of how one would interact / use this without that.

Sounds insane as a private project! Congrats

Was this vibecoded?

No security policy, no security tests, unsigned and remote access permissions. No thanks.

The Claude folder is a giveaway

Yes fully built using Claude. But dont underestimate the amount of manual work, shaving, re-iterating.

The server binds 127.0.0.1 (localhost-only) at launcher.py:114 and launcher.py:181 — nothing on the network can reach the local API, and there’s no network.server entitlement. What the app does have is outbound access (it calls intervals.icu to sync, using a stored API key). Those are different things.

i added security notes to the readme

what kind of security tests do you want?

there are at least a dozen that have popped up on Intervals.icu since the start of the year. I think because intervals.icu makes their api easily accessible for 3rd party apps and AI has improved enough to allow people to churn out apps and lean on AI for the training methodologies/plans/adaptations.

I think it’s going to take some time to sort out which apps really add value and stick around.

100% Free, open source, locally hosted training cycling planner. Science based.

What’s new since the last post, grouped:

Train by heart rate — no power meter needed. The biggest addition. A workout-targets mode that prescribes every session in bpm: LTHR-anchored ranges for steady work, RPE for short maximal efforts (HR lags too much there, and the app tells you so instead of pretending). LTHR syncs continuously from intervals.icu with a manual override, zones are customizable, and no-power rides get TRIMP-based load so CTL/TSB stay honest.

Your plan lands on your head unit by itself. Sign-in is proper OAuth now (was an API key), and planned workouts push one-way to your intervals.icu calendar — Garmin, Wahoo and MyWhoosh pull them automatically. It only ever touches its own entries; your races and manual entries are safe. FIT workout export (with %FTP targets) also works for TrainingPeaks / Vekta, and FIT import is drag-and-drop with multi-activity days.

A workout library you can actually trust. This was the deepest work. Every file is now classified by what its intervals contain, and matching is gated on that content — a sweet-spot day can’t hand you a disguised VO2 session, a recovery day can’t serve hidden threshold. The library grew from ~3,000 to 4,200+ files, ~1,500 got proper warm-ups/cool-downs, mislabeled “hard work hidden as easy” files were re-classified in two audit waves, physiologically dangerous prescriptions are screened out, and ~600 structural variants (ladders through threshold/VO2/sweet spot, tempo intervals, endurance-with-strides, Rønnestad 30/15s) that the sampler could never reach are now in rotation. Every build phase guarantees the classic four shapes: threshold, VO2max, sweet spot, over-unders.

The time you enter is a hard limit. Tester-reported: 60-minute days could get 90-minute sessions through side doors (variety swaps, load redistribution). Every selection path now respects your per-day availability, workouts duration-match the slot, and the per-day Reshuffle shows a preview with accept/decline — and offers genuinely different alternatives instead of repeating itself.

Plans got personal. A phase-split editor (adjust base/build/peak/taper weeks within safe limits), plan styles with a custom intensity-mix template, opt-in block periodization (concentrated blocks with guaranteed complementary work), set up races intermittent on any plan with automatic mini-tapers and openers, swap any day’s training type, and races visible on the calendar with a longer pre-race taper. Starting mid-season works now: backdate the start, or let it scan recent rides and propose which week you’re on. Plans also survive app upgrades (a 2.x→3.x upgrade used to be able to resurface an old plan — fixed, with automatic restore).

Adaptation got teeth. Miss a hard session and the remaining week re-fits around it — freed stimulus is redistributed only where 48-hour spacing and weekly hard-day caps allow, never as a catch-up spike. The weekly recalculation now carries the full variety contract (it used to quietly skip some guarantees). Post-ride execution scoring grades how you actually rode the prescription; FTP/LTHR retest nudges fire when your numbers look stale; a mid-cycle FTP test is injected at the build1→build2 boundary automatically.

FTP testing your way. Choose ramp or 20-minute — a side-by-side chooser lays out each protocol’s trade-offs honestly. The computed FTP reads the effort itself, so a warm-up sprint can’t inflate a ramp result.

Analysis tab. Power curve with CP/W′ that loads instantly and hydrates ride data in the background with a live progress bar (previously it could hang — worst long-standing bug, now dead), a fatigue-resistance index (power decay after kJ of work), sharpened DFA α1 threshold estimation, and short-interval targets sanity-checked against your measured sprint power — advisory only, because the W′ literature doesn’t support prescribing off it, and the README says exactly why.