The short version: your training data stays on your computer. We never see it. There is no Enduragent account, no hosted training database, and no behavioral analytics. Official releases send one minimal installation heartbeat, described below, with no athlete data or message content in it. If you run /feedback, the text you type is stored on a project Worker so we can read it. That command is the only athlete-authored content we keep.
This website
These are static pages. They set no cookies, run no analytics, load no tracking pixels, and have no sign-up, login, or contact form. Nothing you do here is recorded by us. /feedback is a command in the app, not a form on this site.
Like any website, these pages are served by a hosting provider, and that provider may keep standard server access logs (IP address, timestamp, requested URL, user agent) for operational and security purposes, under its own retention policy. We do not use those logs to build a profile of you, and we do not combine them with anything else.
Links from this site to GitHub, the npm registry, Railway, intervals.icu, or any model provider take you to services we do not run. Those services have their own privacy policies.
The app: where your data lives
Enduragent runs entirely on your machine — the desktop app on macOS, or the cycling-coach command-line tool on macOS and Linux (including in a container you host yourself). Everything it knows about you is written to your own disk, in a directory you control.
On the desktop, the default home is ~/.enduragent (override it with the ENDURAGENT_HOME environment variable). It is created with owner-only permissions and contains:
- config/config.yaml — your provider and model choice, intervals.icu athlete id, timezone, and data directory.
- config/auth-profiles.json — OAuth access and refresh tokens, if you sign in with a ChatGPT subscription. Owner-read-only, rotated automatically.
- config/daemon.token and a port file — the bearer token and port used by the app's own UI to talk to its local coaching service over loopback only.
- config/device-id — a local identifier used to mark which machine authored a record inside your own database. It is never transmitted anywhere.
- store/store.db — the derived athlete database (SQLite). App-private; not meant to be placed in a file-sync folder.
- archive/ — compressed, immutable copies of the raw inputs the app read: intervals.icu API responses and any ride files you imported.
- transcripts/ — append-only conversation transcripts, one file per chat.
- sessions/ — live session state. Resetting a conversation renames the file to a timestamped archive instead of deleting it, so old chats are kept until you remove them.
- memory/MEMORY.md and dated daily notes — your long-term memory: goals, injury history, preferences. Plain Markdown you can open, edit, or delete in any text editor.
- plans/current-plan.json — your active training plan.
The command-line tool uses the same layout in its own data directory: ~/.cycling-coach for existing installs, ~/.enduragent/cycling for fresh ones, or wherever CYCLING_COACH_HOME points. In a container or on Railway, the same layout sits on a persistent volume you own, mounted at /data.
The app: where your API keys live
Your API keys are credentials, and they are stored as such.
On the desktop, each provider key and your intervals.icu key are encrypted at rest, one file per slot, under ~/Library/Application Support/Enduragent/credentials-v1/. Encryption uses the operating system's own secure storage, with the key held in the macOS Keychain. The directory and files are owner-only, and that is verified on every read. If secure storage is unavailable, or the operating system only offers a plaintext fallback, the app refuses to store the key at all rather than writing it in the clear. It also refuses to read a credential through a symlink or from a file with the wrong permissions.
On the command line you choose the storage yourself during setup: plain text in config.yaml, the macOS login Keychain, the 1Password CLI, or a reference to any external resolver you already run (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Bitwarden, an age-encrypted file, and so on). Environment variables take precedence over external references, which take precedence over plain YAML. Container deployments normally inject secrets as environment variables.
Minimal installation telemetry
Enduragent has no behavioral analytics, feature tracking, uploaded crash reports, or session recording. No Sentry, PostHog, Mixpanel, Amplitude, Segment, Datadog, or OpenTelemetry. We do not know which features you use, which commands you run, what you ask the coach, or whether the app crashed.
Official Desktop releases and the self-hosted Telegram bot send the installation heartbeat described below. It is used to estimate active installations, not to build a user profile. Separately, if you run /feedback, the text you type is sent to the mailbox in section 8.
What leaves your machine
Local-first does not mean nothing ever goes out. An AI coach has to talk to a model, and a training coach has to read your training data. Here is the complete list, who receives what, and when.
1. Your model provider, at your direction
When you send a message or run a coaching command, the app sends a request directly to the model provider you configured, using your own API key, billed to your own account. That request contains the coaching instructions, your stored memory (goals, injury notes, preferences), the conversation so far, and the results of any data lookups the coach made, which includes your real training data: Fitness, Fatigue, Form, Load and Intensity values, ride history, FTP, weight, power and heart-rate zones, and wellness values such as HRV, resting heart rate and sleep.
This is the largest outbound flow in the product, and it is worth being blunt about it: the provider you pick sees your conversations and the training numbers included in them. Their handling of that data is governed by their own privacy policy and terms, not ours. Pick accordingly.
Depending on your choice, the destination is the official API of Anthropic (Claude), OpenAI (GPT), Google (Gemini), DeepSeek, Qwen via Alibaba Cloud DashScope, MiniMax, Kimi by Moonshot AI, Z.AI (GLM), or OpenRouter. No request is sent to a provider you have not configured, and nothing is routed through us.
2. ChatGPT subscription sign-in, only if you choose it
Instead of an API key you can sign in with an existing ChatGPT Plus, Pro, Business, Edu or Enterprise subscription. If you do, the app opens a standard OAuth 2.0 authorization flow in your browser against OpenAI's identity service, with the callback served on your own machine at a loopback address. After that, coaching requests, the same payload described above, go to the ChatGPT backend on your subscription's quota. The resulting tokens are stored locally, as described earlier. This path is governed by OpenAI's terms and privacy policy.
3. intervals.icu, with your own API key
If you connect intervals.icu, the app makes authenticated requests to intervals.icu using the API key you pasted in, to read your athlete settings, FTP, zones and weight, your activities, wellness records, activity streams, power and heart-rate curves, and bulk activity exports. This happens when you sync and when a coaching turn needs fresh numbers.
The app writes to intervals.icu only when you ask the coach to schedule training: it creates workout events on your own calendar. intervals.icu may then forward those workouts to the devices and platforms you have linked there: Garmin, Wahoo, Hammerhead, COROS, Suunto, Zwift. That onward sync is intervals.icu's, not ours; we never contact those platforms. intervals.icu's own privacy policy governs everything it holds.
4. Telegram, only in bot mode
The command-line tool can run as a Telegram bot that you host yourself. In that mode every message in and out passes through Telegram's servers, as any Telegram message does, and is subject to Telegram's privacy policy. Access is limited to a sender allowlist you control, so people you have not added are dropped before the coach ever sees them. This mode does not exist in the desktop app or in plain terminal chat.
5. Update checks and release notes
The desktop app checks for updates at launch and every six hours, over HTTPS, against a plain GitHub release feed configured when the app was built. That update request carries no athlete data, no credentials, and no Enduragent installation identifier. Updates are never installed behind your back: the app only installs when you choose to restart, and it will not install a prerelease or a downgrade.
When you explicitly ask the desktop app what's new, it makes one request to the public npm registry for the latest published version and one to the GitHub API for that version's release notes. Nothing about you is attached.
Official Desktop releases separately send an installation heartbeat to ping.enduragent.icu at startup and on a daily timer, limited to at most once in any 24 hours across restarts. It contains the product name, app version, platform channel (macOS or Windows), and a random UUID generated once and stored in the app's preferences directory. It contains no athlete data, message content, credentials, configuration, hardware identifier, or feature activity. Set ENDURAGENT_NO_USAGE_PING=1 before launch to disable it without disabling GitHub update checks.
The self-hosted Telegram bot makes its own background version check to the same endpoint at startup and every 24 hours. It sends the binary name, running version, install channel (npm or container), and its own random installation UUID. Set CYCLING_COACH_NO_UPDATE_CHECK=1 to switch it off entirely; manual /whatsnew and /update commands never initiate telemetry.
The project stores the four heartbeat fields, a count, and the time the request was received in Cloudflare Analytics Engine for up to three months to estimate active installations. Like any HTTPS service, Cloudflare can receive ordinary network metadata such as an IP address and user agent under its own policies. The project does not add that network metadata to the heartbeat dataset or combine it with athlete data.
6. Ride files you import
Ride files you import (.fit, .tcx, .gpx) are parsed entirely on your machine. They are never uploaded to any server we operate. If you later ask the coach about a ride, the relevant numbers are included in the prompt sent to your chosen model provider, exactly as described in section 1.
7. Links you click
Links in the app (GitHub releases, the Railway template page) open in your normal browser as ordinary navigations. The app attaches nothing to them.
8. /feedback you send
When you run /feedback in the app and send a message, the app POSTs that text to feedback.enduragent.icu. This is opt-in: nothing is sent unless you run the command. The coach model does not receive this path; the note is for the Enduragent authors.
We store the words after /feedback, the channel (telegram, desktop, or ios), a random submission UUID the app generated, and the time we received it. We do not store training data, conversation transcripts, API keys, intervals.icu athlete ids, ride files, the installation heartbeat UUID, IP address, or user-agent. We do not use the note to train a model. We do not write it to the installation-heartbeat dataset.
The operator reads the mailbox with Cloudflare wrangler or the dashboard, not a public list. Rows are kept until the operator deletes them, and in any case not longer than twelve months.
That is the complete list. Nothing else leaves your machine.
What the Enduragent authors receive
There is no Enduragent account, no hosted training database, and no behavioral analytics. We do not receive your training data, transcripts, or credentials. The exception is /feedback: if you send it, we store the typed text, channel, submission id, and receive time, as described in section 8. The heartbeat payload still contains only a product name, version, channel, and random installation UUID, and you can turn it off.
Where the software is self-hosted (a container, Railway, a VPS), you are the operator of that instance. The hosting provider you choose is your relationship, under their terms and privacy policy.
Children
Enduragent is not designed for or directed at children. It is a training tool for adults, installed and configured by the person using it.
Deleting your data
Your product data is stored locally and can be deleted locally. Heartbeat records and /feedback notes already received by the project are not removed when you delete local files. Heartbeats expire within three months. Feedback notes are deleted by the operator after reading, and in any case within twelve months.
- Delete individual pieces — memory files, transcripts, session archives and plans are ordinary files. Open the data directory and remove what you want gone.
- Delete all local files — remove the app's home directory (~/.enduragent, or your ENDURAGENT_HOME / CYCLING_COACH_HOME path, or ~/.cycling-coach for older command-line installs). For Desktop, also remove ~/Library/Application Support/Enduragent on macOS or %LOCALAPPDATA%\Enduragent on Windows; that directory contains Desktop preferences, the installation UUID, and encrypted credential files.
- Remove stored API keys — on the desktop, delete the credential files under ~/Library/Application Support/Enduragent/credentials-v1/; they are unreadable without the operating system key that stays in your Keychain. On the command line, remove them from wherever you chose to keep them: the config file, the login Keychain, your password manager, or your secret store.
- Uninstall — on macOS, turn off "Open at Login" first, quit the app, then delete it. For the command-line tool, uninstall the npm package or remove the container and its volume.
Data you already sent elsewhere is outside our reach: conversations held by your model provider, activities and workouts on intervals.icu, messages in Telegram, heartbeat records retained for up to three months, and /feedback notes in the project mailbox. Use each external service's own controls to delete its data; heartbeat records expire automatically. To retract a /feedback note, open a GitHub issue; the operator deletes the row.
Changes to this policy
If the product's data behavior changes, this page changes with it, and the effective date at the top moves. Material changes will also be noted in the release notes for the version that introduces them. The history of this page is public in the project repository, so you can see exactly what changed and when.
Contact
Questions, corrections, or something in this policy that does not match what the code actually does: open an issue on the project's GitHub repository. Product comments from inside the app go through /feedback once that command ships; that is not a substitute for a policy correction. The source is public, so if you would rather verify than take our word for it, read it.
See also the Terms of Use.