Enduragent / Privacy

Privacy Policy

Effective October 2026.

Enduragent is a free, open-source, local-first AI cycling coach. This policy covers three things: this website, the software you install on your own machine, and the Enduragent iPhone app.

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. The iPhone app differs in a few places, set out in The iPhone app.

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. On the iPhone, Apple may share crash reports with us, as described in The iPhone app.

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.

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 for the desktop app and the command-line tool. Nothing else leaves your machine.

The iPhone app

The Enduragent iPhone app runs the coach on the phone. There is no coach server. This section covers what the app keeps and what leaves the phone.

Where your data lives. The conversation, memory, saved plan and the app's other records are stored on the iPhone. The records that follow you to your other devices are kept in your own iCloud, in the app's CloudKit private database, which belongs to your Apple Account and which we cannot read. Training data stays in intervals.icu. The app reads it when a turn needs it and keeps no copy of it, apart from what appears in the conversation.

Where your keys live. Your intervals.icu key and the model key for your access method are stored in the iOS Keychain, which iCloud Keychain syncs to your other devices. They are never written to the app's files or to its iCloud database.

What leaves the phone, and where it goes:

  • The model, with Credits. With Credits, each message goes from the phone to OpenRouter with your memory, the conversation so far, and the training data the turn needs, as in section 1. OpenRouter passes it to the provider that runs the model; OpenRouter's and that provider's privacy policies govern it. It is sent with a key the Enduragent Credits service made for you on the authors' OpenRouter account, capped at the credits you have, so OpenRouter bills the authors, not you. The Credits service never receives those messages. It keeps a random Credits id the app made and a record of the key it made for you, until you use Delete everything (see Deleting your data). When the app asks for the free starter credits, it also sends an Apple DeviceCheck token, so they are given once per iPhone. The iPhone app does not sell credits.
  • The model, with your own OpenRouter account. If you sign in with your own OpenRouter account, you sign in on OpenRouter's page, and the key it returns is stored in the Keychain. Each message, with your memory, the conversation so far, and the training data the turn needs, goes from the phone to OpenRouter under your account, billed to you. The Credits service is not involved. OpenRouter passes each message to the provider that runs the model; OpenRouter's and that provider's privacy policies govern it.
  • intervals.icu. The app reads your training data from intervals.icu with your key when a turn needs it, and adds, changes or removes workouts on your calendar only after you confirm the change in its preview, as in section 3.
  • Model list. The app downloads the list of models it can offer from api.enduragent.icu. That request carries no athlete data, key or identifier.
  • Feedback. When you send a note from Settings or with /feedback, the app sends only the note, the channel ios and a random id to feedback.enduragent.icu, which records when it arrived, as in section 8. Nothing is attached: no conversation, memory, training data, or Diagnostics file.
  • The Diagnostics file. The Diagnostics file in Settings is made on the phone. It holds the app version, the phone model and iOS version, which access method you use, the connection state, your language choice, counts of what the app has saved, and the last 14 days of the app's problems and model usage. It holds no keys, messages, Credits id, intervals.icu athlete id or training data. It leaves the phone only when you share it, and only to where you send it.
  • Updates. The app checks for no updates and sends no telemetry, heartbeat or crash report of its own. Apple's TestFlight and App Store deliver updates and show each version's notes. If you let iOS or TestFlight share crash reports with app developers, Apple passes them to us under Apple's privacy policy.

Apple runs iCloud, iCloud Keychain, the App Store and TestFlight under its own privacy policy. Nothing else leaves the phone.

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 iPhone app adds one more exception, the Credits path: with Credits, OpenRouter bills the authors for your messages, but the Credits service never receives them. It keeps a random Credits id and a record of your key, as described in The iPhone app. 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

On the desktop and the command line, 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.
  • Delete everything in the iPhone app — Settings › Your data › Delete everything. If you have used Credits, the app first asks the Credits service to delete your Credits record: your Credits id, your credits balance and the record of the key it made for you. The service also revokes that key at OpenRouter, so unused credits are lost. If the Credits service cannot be reached or does not confirm, the app deletes nothing on the iPhone or in iCloud and asks you to try again later. Rarely, the service has already started deleting your Credits record; trying again finishes it. Once the service confirms, the app deletes its records from the iPhone and from your iCloud, its files, and its keys from the Keychain and iCloud Keychain. If iCloud does not confirm the deletion within about a minute, the app tells you. It finishes deleting your iCloud copy the next time you open the app while the iPhone is online and signed in to iCloud. If you delete the app before then, delete its data in iOS Settings under iCloud storage.
  • Other devices — if you use the app on another device with the same Apple Account, that device removes the synced records the next time you open the app there, and its keys go when iCloud Keychain syncs. What that device keeps only on itself, such as drafts and its Diagnostics file, stays until you use Delete everything there or delete the app there.
  • What stays after Delete everything — the Credits service keeps a one-way code made from your Credits id, which cannot be turned back into the id, and the time of the deletion, so a deleted id cannot be used again. If the authors banned your Credits id for abuse, it also keeps that ban under the same code. It keeps both without a time limit. Apple keeps a DeviceCheck flag for your iPhone, set when the Credits service gave the free starter credits or blocked a banned athlete, so starter credits are given once per iPhone, even after a deletion. OpenRouter keeps its own records of the revoked key and its usage on the authors' OpenRouter account, under OpenRouter's privacy policy; the key was named with your Credits id. If you signed in with your own OpenRouter account, the app deletes only its copy of the key; the key works until you delete it on OpenRouter. Your intervals.icu key works until you change it on intervals.icu, and your workouts stay there.
  • If the Credits service cannot confirm your iPhone — for example because the app lost its Credits key, the service deletes nothing. The app says so and offers to delete everything on the iPhone and in your iCloud anyway. Your Credits record then stays on the Credits service without a time limit. No one holds its key, so no one can use it, and because the app deleted your Credits id, we cannot tell which record was yours.
  • Deleting only the iPhone app — removes its records from that iPhone only. Your records stay in your iCloud until you delete the app's data in iOS Settings under iCloud storage, and your keys and Credits id stay in the Keychain and in iCloud Keychain. The Credits service keeps your Credits id and key record. Use Delete everything first if you want them gone.

Data you already sent elsewhere is outside our reach: conversations held by your model provider (for the iPhone app with Credits, OpenRouter and the provider it passed them to; those messages went through the authors' OpenRouter account, so you have no OpenRouter controls for them), 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 in the desktop app and Feedback in the iPhone app's Settings; neither is a substitute for a policy correction, because a note carries no way to reply. The source is public, so if you would rather verify than take our word for it, read it.

See also the Terms of Use.