Privacy promise¶
Quill is local-first. This page is the short, blunt version of what that means in practice. If anything here turns out to be wrong, file a bug — drift between this page and the shipping behavior is itself a bug.
The one-line version¶
A default install of Quill never sends your audio, your transcripts, or any usage telemetry off your machine. Period. Cloud STT and cloud LLM endpoints exist, but they are opt-in and surface a startup warning the moment they're configured.
What runs locally, by default¶
| Component | Where it runs | What it touches |
|---|---|---|
Audio capture (quill-audio) |
Your CPU | Microphone stream → in-memory ring buffer. Never written to disk. |
Speech-to-text (quill-stt) |
Your CPU/GPU (Metal on Apple Silicon) | whisper.cpp model loaded from ~/Library/Caches/quill/models/ or ~/.cache/quill/models/. |
Polish pass (quill-polish) |
Your CPU/GPU | Embedded llama.cpp against a verified GGUF model in Quill's model cache. |
Text injection (quill-inject) |
Your CPU | Accessibility API (macOS) or enigo/clipboard (Linux). |
Daemon (quill-daemon) |
Your CPU | Owns the global hotkey, drives the pipeline, exposes a Unix socket for IPC. The IPC socket is chmod 0600 so only your user can talk to it. |
The Whisper model is downloaded once from the public whisper.cpp ggml mirror. The embedded polish GGUF is downloaded once from the pinned HuggingFace repo and verified by SHA-256 before use. After download, no further network calls are made by these components.
What never leaves your machine, ever¶
- Your microphone audio. Captured into memory, fed to whisper.cpp, then dropped. Not buffered, not written to disk, not retained beyond the dictation session.
- Your raw transcripts. Held in memory through the polish + inject stages, then dropped. Starting in alpha.4, the daemon does not even print transcripts to its own stdout at the default tracing level — printing requires the explicit
--emit-transcriptsflag orQUILL_EMIT_TRANSCRIPTS=1env var. The GUI sets this env var when it spawns the daemon as a child so the Activity log keeps working; CLI users get the privacy default. Either way, the data stays on your box. - Your polished transcripts. Same path as raw — held in memory, injected, dropped. The GUI Activity log writes a local history file at
~/Library/Application Support/quill/history.json(macOS) or~/.local/share/quill/history.json(Linux). That file never leaves your machine.
What can cross a network boundary — only if you opt in¶
Quill has explicit opt-in escape hatches. Each one fires a startup warning so you can't enable them by accident:
Remote polish endpoint¶
Set polish_backend = "remote" and a non-loopback polish_endpoint in settings. Your transcripts will be sent to that endpoint for the polish pass.
Safeguards:
- The daemon logs a
tracing::warn!at backend-resolution time when the endpoint resolves to a non-loopback host. - The
is_localcheck is strict — it parses the endpoint as a URL and checkshost_str()exactly againstlocalhost,127.0.0.0/8, and::1. Substring tricks likehttp://localhost.evil.comare rejected. - Recommended use: a polish model hosted on your own LAN (e.g. a beefier desktop running an Ollama-compatible endpoint on
192.168.1.42:11434).
Cloud STT (planned, not in alpha)¶
Cloud Whisper-large is on the roadmap. When it ships, it'll have the same opt-in surface: explicit settings change, startup warning, no defaults that route off-box.
Update check¶
Quill polls latest.json on the CorvidLabs release host on startup to see if there's a newer version. This is a GET request with no payload. No machine identifiers, no user agents beyond the default reqwest user-agent, no headers from your Quill install. If the check fails, the app shrugs and moves on — you're never blocked on it.
You can disable this in Settings → Updates if you prefer.
Crash reports + telemetry¶
Crash reports and usage telemetry are opt-in and default OFF. No Sentry, no PostHog, no custom phone-home runs silently. If you opt in, crash reports are scoped to redacted diagnostics; transcript content is never included.
Why we wrote this page¶
Dictation tools that send your audio to the cloud usually justify it with "we need it to improve our models." That justification doesn't apply to Quill, because the model we use (Whisper) is already trained, open-source, and good enough for the use case. The polish model is local too. We don't need your data to make the product work, so we don't take it.
If we ever change our minds about that, we will say so loudly, in this exact page, before the change ships — and the default will stay local-first.
Verifying the claims¶
We can't make you trust us, but we can make it easy to verify.
- The source code is in this repository. Read
crates/quill-daemon/,crates/quill-audio/,crates/quill-stt/,crates/quill-polish/, andcrates/quill-inject/. There's no obfuscation. - Run
lsof -i -nP -p $(pgrep quill-daemon)while a dictation is in flight. On the default embedded backend, the daemon should not need a polish TCP socket at all; the expected persistent socket is the Unix-domain IPC socket under your runtime directory. Remote/system polish is opt-in and should be obvious in Settings and logs. - Run
tcpdump -i any -nn 'not (net 127.0.0.0/8 or net ::1/128)' &and dictate something. You should see zero packets attributable to Quill, with the sole exception of the once-per-startup update check (a single HTTPS GET to the release host). - Read the daemon spec at
specs/daemon/daemon.spec.md— it's the source of truth for what the daemon will and won't do.
If you find a way Quill leaks data that contradicts this page, that's a P0 bug. File it.