Telemetry
Rapid-MLX ships opt-in, anonymous usage telemetry with a
short-and-boring wire format you can audit before enabling. No
analytics SDKs, no third-party trackers, no ads — and never will
be. This page is a byte-level transcription of what the code in
vllm_mlx/telemetry/
actually does.
rapid-mlx telemetry enable, or set
RAPID_MLX_TELEMETRY=1. Every event site checks
is_enabled() before constructing a payload, so a code
path that would have populated a field can't leak by mistake
when telemetry is disabled.
Controls
Everything is rapid-mlx telemetry:
$ rapid-mlx telemetry status # enabled/disabled + why + file paths $ rapid-mlx telemetry enable $ rapid-mlx telemetry disable $ rapid-mlx telemetry preview # exact JSON that would leave the machine $ rapid-mlx telemetry reset # wipe consent + client-id, next interactive run re-prompts
Env-var override: RAPID_MLX_TELEMETRY=1 or =0
wins over the consent file for the current process. Per-run:
rapid-mlx --no-telemetry <subcmd>.
Where the state lives
~/.rapid-mlx/telemetry-consent.yaml— the y/n answer + the version that recorded it.~/.rapid-mlx/telemetry-client-id— random UUID identifying this install; rotate any time withtelemetry reset.
What we collect (only after you opt in)
Platform fingerprint (per event)
| Field | Meaning | Redaction |
|---|---|---|
os | OS family — darwin, linux. | Lowercase family only. |
os_version | Major.minor only, e.g. 25.5. | Patch number dropped — soft fingerprint. |
arch | arm64. | — |
chip | Apple Silicon brand, e.g. Apple M3 Ultra. | From sysctl machdep.cpu.brand_string; no serial. |
memory_gb | Total RAM rounded to nearest GB. | Exact bytes never sent — rounded via bucket_memory_gb(). |
python_version | Major.minor only, e.g. 3.12. | Micro number dropped. |
Session events (session_start / session_end)
| Field | Meaning |
|---|---|
subcommand | Which subcmd you ran — serve, chat, bench, agents, doctor, models. |
duration_seconds | Session length (only on session_end; null on start). |
models_loaded | HuggingFace repo IDs only (e.g. mlx-community/Qwen3.5-9B-4bit), or <local> for anything that wasn't an org/name repo. |
flag_names | Names of CLI flags you passed, sorted + de-duped. Never values — --api-key sk-XXX becomes the literal string "api-key"; the value is never even read. |
Request events (Phase 2 — future)
The RequestPayload dataclass exists in the schema so
reviewers can audit its shape today, but Phase 1 does not populate
request events. When per-request instrumentation lands (behind the
same consent gate), it will carry: endpoint (e.g.
/v1/chat/completions), model alias, whether streaming
was on, whether a tool call was used, bucketed prompt / completion
token counts (6 fixed buckets — "0-256",
"256-1k", "1k-4k", "4k-16k",
"16k-64k", "64k+"), bucketed TTFT ms and
tokens-per-second, and the HTTP status.
Error events
When rapid-mlx crashes with telemetry on, we send a small error
fingerprint. Fields: category (one of
model_load_failure, oom,
tool_parse, shutdown_traceback),
fingerprint (16-hex prefix of a SHA-256 built from the
bare exception class name + basename:function:lineno per
frame — never str(exc), never full paths, never
third-party module paths), and phase
(startup / request / shutdown).
Envelope fields (every event)
schema_version— currently1; bumped only on backwards-incompatible field changes.client_id— random UUID from~/.rapid-mlx/telemetry-client-id.session_id— per-process random UUID, sosession_startandsession_endcan be grouped without correlating across runs.rapid_mlx_version— e.g."0.10.3".event—session_start/session_end/request/error.timestamp— ISO-8601 UTC with a"Z"suffix, second precision, no timezone.
What we never collect, ever
- Prompts. Nothing you type reaches our servers.
- Generated text. Not one token.
- File paths. Local model paths render as the literal string
<local>. Traceback frames use file basename only. - API keys. Flag values are never even read —
hash_flag_names()only extracts the flag name after--. - Flag values in general. Whatever you pass after
--foostays on your machine. - Exception messages.
fingerprint_traceback()intentionally excludesstr(exc)because messages routinely contain user input ("could not open /Users/alice/secret.txt"). - Third-party module identity. Bare exception class name only — not
__module__— so the fingerprint doesn't reveal which packages you have installed. - Anything from before you opted in. The
_just_collected_consentlatch suppresses lifecycle emit for the same invocation that just collected consent — honouring the "nothing from before this prompt" promise. - Anything from a session you opted out of.
is_enabled()is checked before any payload is constructed.
About your IP
Any HTTPS request reveals your IP to the receiver at the network
layer — we can't change that. What we control is what we
record: the receiving Cloudflare Worker never writes your
IP to the stored event. Your IP is used only for a transient
per-minute rate-limit counter keyed on sha256(IP), and
the raw IP is discarded the same request.
Preview your payload
The single most useful audit tool:
$ rapid-mlx telemetry preview
Prints the exact JSON that would leave your machine on a
session_start event — real platform info + a fixed
dummy session_id. Sample from
rapid-mlx 0.10.3 on M3 Ultra 256 GB:
{
"schema_version": 1,
"client_id": "b2292c64-de82-495d-b2b8-877d488f6537",
"session_id": "preview-0000000000000000",
"rapid_mlx_version": "0.10.3",
"platform": {
"os": "darwin",
"os_version": "25.5",
"arch": "arm64",
"chip": "Apple M3 Ultra",
"memory_gb": 256,
"python_version": "3.12"
},
"event": "session_start",
"timestamp": "2026-07-07T19:42:11Z",
"session": {
"subcommand": "serve",
"duration_seconds": null,
"models_loaded": [
"mlx-community/Qwen3.5-9B-4bit"
],
"engine": "",
"flag_names": [
"port",
"host"
]
}
}
The engine field is a schema v1 back-compat slot with
no runtime information — Round 4 removed live emission because
BatchedEngine is the only engine. It stays in the
dataclass in its original positional slot so external callers
constructing SessionPayload against schema v1 don't
break.
First-run disclosure
The disclosure prompt fires at most once per machine — only for
interactive subcommands (serve, chat,
agents, bench, doctor,
pull, rm, upgrade), only when
stdin + stdout are both TTYs, only when the consent file has never
been written, and only when --no-telemetry is not set
and RAPID_MLX_TELEMETRY is unset. Read-only /
one-shot subcommands (version, help,
models, ps, info,
telemetry itself) skip the prompt entirely.
Default answer is no. Interrupted answers count as decline but are not recorded, so the next interactive run re-prompts.
Failure suppression
A telemetry bug can never crash your serve or
chat. Every emit helper wraps the body in
a broad except, and the consent prompt itself only swallows the
failure modes it actually expects (OSError,
EOFError, KeyboardInterrupt,
UnicodeError). Programming errors
(AttributeError, TypeError) propagate so
they get noticed in development.