Reference · rapid-mlx 0.10.3 · ← Back to README

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.

Default state. Telemetry is off by default. It only turns on if you type "y" at the first-run disclosure prompt, run 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

What we collect (only after you opt in)

Platform fingerprint (per event)

FieldMeaningRedaction
osOS family — darwin, linux.Lowercase family only.
os_versionMajor.minor only, e.g. 25.5.Patch number dropped — soft fingerprint.
archarm64.
chipApple Silicon brand, e.g. Apple M3 Ultra.From sysctl machdep.cpu.brand_string; no serial.
memory_gbTotal RAM rounded to nearest GB.Exact bytes never sent — rounded via bucket_memory_gb().
python_versionMajor.minor only, e.g. 3.12.Micro number dropped.

Session events (session_start / session_end)

FieldMeaning
subcommandWhich subcmd you ran — serve, chat, bench, agents, doctor, models.
duration_secondsSession length (only on session_end; null on start).
models_loadedHuggingFace repo IDs only (e.g. mlx-community/Qwen3.5-9B-4bit), or <local> for anything that wasn't an org/name repo.
flag_namesNames 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)

What we never collect, ever

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.

Next steps