Download the beta

AskFry · claims → proofs

Verify

Read-only · never changes your files

For the reader who assumes marketing is lying. This page covers AskFry, the app that connects to your accounts. Each claim maps to something you can check yourself: what runs where, a test you run yourself, and the complete list of network calls.

claim 1 · find searches between your apps

Architecture

Local sources are read into a local index. Connected accounts are queried live through read-only APIs and receive search terms. Search works with no model; an optional local model reads the results and helps answer. There is no cloud AI.

AskFry architecture Sources on the left flow into a local index on disk, then a local model, then answers. Every arrow is read-only. There is no cloud AI component. your computer Gmail Google Drive Slack browser history local files GitHub read-only · GET / search only local index SQLite + vectors on your disk local model Qwen3 · GGUF · CPU/GPU runs on your computer answers with the path, the source, the why audit.jsonl every read, appended
Every arrow from a source is a read. AI runs only on your computer; there is no cloud AI component.
index
SQLite FTS5 + semantic vectors (multilingual-e5-small) in AskFry's data folder, readable only by your user. Delete the folder, the index is gone.
local model
Qwen3 1.7B (Qwen3 4B Instruct 2507 upgrade), Q4_K_M GGUF, in-process. Downloaded on click, sha256-pinned, unloaded after 120 s idle. Search works without it.
remote sources
Queried live through read-only APIs. Shown, not archived. Each labelled when it answers.
audit
Every source access is logged, and the log cannot be switched off. Blocked outbound attempts are logged too.

claim 2 · local

The Paranoid mode test

One switch, one search, no extra tools.

  1. Finish onboarding first, so the one disclosed model download is done.

    the embedding model downloads once, right after onboarding

  2. Turn on Paranoid mode. AskFry now refuses every outbound call at the network layer, before a connection opens.

    Settings → Privacy · or memo cloud paranoid on

  3. Check the chip next to the search bar.

    PARANOID MODE · ON

  4. Search for something in a local folder or in your browser history.

    default hotkey ⌘⇧Space

  5. Results come from your files, your history and the local index. Connected accounts are live services with no local copy, so they sit this one out.

    search needs no model; the optional local model runs on your computer

  6. Open the result. It is the file on your disk or the page in your history.

    Enter or click opens it

  7. Read the audit log. Every source access is there, with counts and no payloads. Any refused call is logged as blocked-outbound.

    audit.jsonl, in AskFry's data folder

Second opinion: ask the OS which processes hold connections while you search. Empty is the expected result.

$ lsof -i -P -n | grep -i -e fry -e memo
# expected: no output while searching with Paranoid mode on

Little Snitch or LuLu show the same. Paranoid mode refuses everything at the network layer, downloads and update check included. With nothing connected, an automated test asserts that a local search makes zero outbound attempts.

claim 3 · local · the complete list

Every network call AskFry can make

One network layer. Each call declares a purpose and its allowed hosts; a process-wide guard refuses anything else, from our code or a dependency, and logs the refusal. A static test fails the build if any file outside that layer touches the network. This table is the registry, in words.

hosts the engine can ever contact

api.github.com · api.notion.com · eu.i.posthog.com · github.com · gmail.googleapis.com · huggingface.co · oauth2.googleapis.com · slack.com · www.googleapis.com

update host · desktop shell only · on click

askfry.com

The engine never contacts it. The desktop shell asks askfry.com/updates/latest.json only when you click Update, and downloads the signed package from askfry.com only after a second click. Row 10.

Runtime calls

#PurposeEndpointsTriggerPayload (outbound)Opt state
1 Slack: message search, people lookup, connection check
POST https://slack.com/api/search.messages
POST https://slack.com/api/users.list
POST https://slack.com/api/auth.test (connect check)
A search runs while a Slack user token is configured and Slack is neither disabled nor paused; users.list only during identity resolution; auth.test when you connect or open Sources to identify an existing connection (the label is cached). Each connected identity is searched independently.
search.messages: a query of ≤6 keywords, ≤3 person-name variants, from:, after:/before: day bounds, has:link; ≤4 calls per search, ≤2 pages each
users.list: ≤3 pages of 200 during identity resolution (no query text)
auth.test: the token only
Opt-in
Opt-in (connect a token)
2 Google Drive: file search, Google Docs excerpt, token refresh
GET https://www.googleapis.com/drive/v3/files
GET https://www.googleapis.com/drive/v3/files/{id}/export (text/plain)
POST https://oauth2.googleapis.com/token (access-token refresh)
A search runs while the keychain holds Drive OAuth tokens and a Google client is configured, and Drive is neither disabled nor paused; identity resolution reads the owners of the 100 most recently modified files.
files.list q= built from ≤6 keywords / person variants / media hints / owner email / time bounds
files.export text/plain of ≤5 Google Docs under 1 MiB per search (20 KiB read)
Token refresh: the refresh token + client credentials
Opt-in
Opt-in (OAuth consent)
3 Gmail: message search, metadata/body fetch, token refresh
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
GET https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}
POST https://oauth2.googleapis.com/token (access-token refresh)
A search runs while the keychain holds Gmail OAuth tokens and a Google client is configured, and Gmail is neither disabled nor paused; identity resolution runs one list (10 results) + ≤6 metadata gets.
Short content phrases may first be sent as a subject:"phrase" query to find precise title matches
messages.list q= with ≤6 keywords, a from:/to: group, filename:/domain group, after:/before:; ≤25 ids
messages.get metadata for ≤12 candidates; full text/plain body (20 KiB cap) for the first 5 only
Token refresh: the refresh token + client credentials
Opt-in
Opt-in (OAuth consent)
4 Google OAuth consent (Drive, Gmail): code exchange, client check and account label
https://accounts.google.com/o/oauth2/v2/auth — opened in the SYSTEM BROWSER, not by this process
redirect to a loopback listener http://127.0.0.1:<port>/callback (this process listens, never dials out)
POST https://oauth2.googleapis.com/token (authorization-code exchange; client check on save)
GET …/drive/v3/about or …/gmail/v1/users/me/profile (account email for the confirmation line)
Connect Drive or Gmail from setup or Settings → Sources → Sign in with Google. The listener closes when the flow ends. The client check runs once when a Google client id/secret is saved in Sources. Opening Sources resolves missing account labels with read-only profile requests; labels are cached in the keychain.
Consent URL: client_id, scopes drive.readonly and gmail.readonly, random state, PKCE S256 challenge
Code exchange: the one-time code + PKCE verifier + client id/secret. Tokens go to the OS keychain, never to disk or logs
Client check: the client id/secret + a deliberately invalid code; nothing is stored unless the client is real
User action
User action
5 Notion (in development, not a live source yet): page search, people lookup, connection check
POST https://api.notion.com/v1/search
GET https://api.notion.com/v1/users
GET https://api.notion.com/v1/users/me (connect check)
A search runs while a Notion integration token is configured and Notion is neither disabled nor paused; /v1/users only during identity resolution; /v1/users/me on connect or when opening Sources (the label is cached).
search.query for ≤3 sub-searches (≤4 keywords / person / media hint), filter: page. No page content is ever fetched: titles and metadata only
/v1/users: ≤3 pages of 100 (no query text)
Opt-in
Opt-in (token)
6 GitHub: code / issue / repo search, restricted to repositories owned by the connected account
GET https://api.github.com/search/code, /search/issues, /search/repositories
GET https://api.github.com/user/repos (owner affiliation)
GET https://api.github.com/user (connect check and account scope)
GET https://api.github.com/user/installations (permission check)
A search runs while a GitHub token is configured and GitHub is neither disabled nor paused (≤3 requests in flight); /user identifies each connected account before search (labels are cached). A failure never permits a public search. Some searches can still time out; a timeout returns no GitHub results for that search.
q= of ≤2 content words (plus a fallback); account/repository scope kept on each request; per_page ≤50
Own-repo listing: ≤2 pages of 100
Opt-in
Opt-in (token)
7 Download the local brain (Qwen3 1.7B default / Qwen3 4B Instruct 2507 upgrade, Q4_K_M GGUF)
GET https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf
GET https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q4_K_M.gguf
The user clicks Download (Settings → Models, or onboarding step 2). Never automatic. An interrupted download resumes with an HTTP Range request.
GET only, plus Range: bytes=<offset>- on resume. sha256 and size pinned; a mismatch is discarded. Hugging Face redirects to its LFS CDN inside the same call.
User action
User action
8 Download the embedding model (multilingual-e5-small, int8 ONNX, ~135 MB)
GET https://huggingface.co/Xenova/multilingual-e5-small/resolve/main/onnx/model_quantized.onnx
GET …/tokenizer.json, …/config.json, …/tokenizer_config.json
AUTOMATICALLY, once, right after onboarding completes: the semantic index of the ticked folders starts in the background and needs the model. Onboarding states this (size, checksum, disk budget) before the user finishes. Also on the first index build if the model is missing. Never re-downloaded once verified.
GET only. Four files, each sha256-pinned; the model is used only after every hash matches.
Automatic after onboarding
Automatic after onboarding (disclosed there); otherwise user action
9 Download a voice model (whisper.cpp ggml base / small)
GET https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin
GET https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin
The user clicks Download voice model (Settings → Hotkey & voice / Models, or onboarding step 2). Never automatic. Resumable.
GET only, plus Range: on resume. sha256 and size pinned.
User action
User action
10 Desktop app update check and signed download (Tauri updater)
GET https://askfry.com/updates/latest.json (no newer version = no update)
GET the signed package link on askfry.com named in latest.json, only after the install click
The user clicks Update at the bottom of the sidebar, or Check now in Settings › Updates & About. Never on launch, never on a timer. Installing is a separate, second click; the package is minisign-verified against the public key shipped in the app, whatever host serves it. Refused while Paranoid mode is on. Made by the desktop shell, not the engine: askfry.com is the update host, not an engine host.
GET only. The updater sends its User-Agent and the current version/target; nothing about the user or their data. Each check/install is written to the audit log.
User action
User action (Update button)
11 GitHub desktop device authorization and token refresh
System browser: https://github.com/login/device
POST https://github.com/login/device/code
POST https://github.com/login/oauth/access_token
The user clicks Connect GitHub; refresh only before an enabled GitHub search when its token expires.
Public client id, device code or refresh token; no search query or source content.
Opt-in
Opt-in (Connect)
12 Slack desktop PKCE authorization and token refresh
System browser: https://slack.com/oauth/v2/authorize (public client id, user scopes, random state, PKCE challenge)
Local callback: http://localhost:47839/callback
POST https://slack.com/api/oauth.v2.access
The user clicks Connect Slack; refresh only before an enabled Slack search when its token expires.
Public client id, authorization code and PKCE verifier or refresh token. No client secret, query or source content.
Opt-in
Opt-in (Connect)
13 Anonymous usage stats (since 1.0.65; on by default, opt-out)
POST https://eu.i.posthog.com/batch/ (PostHog, EU region)
Only after the first-launch consent screen, and only while stats are on. Batches of up to 50 allowlisted events, at most once a minute. Never sent while stats are off or Paranoid mode is on.
Allowlisted events and fields only: counts, ranges, source types, error codes, app version, OS, chip type
A random device ID, not linked to email, accounts or hardware. Client IP discarded, GeoIP off, no person profiles
Never search text, file names, paths, contents, emails, messages, account names or tokens
Opt-out
Opt-out (first-launch screen, Settings › Privacy › Usage stats)

Derived from AskFry's network registry document, 2026-09-28; row 10 updated 2026-10-21 for 1.0.67. When the registry changes, this table is regenerated with it.

Latent calls, closed

  • node-llama-cpp: clone/build llama.cpp when no prebuilt binary matchesgithub.com (git clone), api.github.com (release lookup)CLOSED: build pinned to never, download skipped. A missing binary is a typed error and search stays at Tier 0.

Installer-time and external

  • Windows installer: WebView2 bootstrapper downloadMicrosoft WebView2 bootstrapper (Microsoft CDN)The installer runs on a Windows machine without WebView2. Installer-time, never app-time.
  • Opening a result in the system browserany http(s) URI of a result, opened by the OS default browserThe user presses Enter or clicks a result. The app itself makes no request.

Things checked that make no call

  • Usage stats: anonymous, allowlisted counts, opt-out (row 13), never your searches, files or content. No session replay, no autocapture, no ad or third-party SDKs: no sentry, segment, amplitude, mixpanel or bugsnag, and no PostHog SDK (the app posts the allowlisted events itself).
  • License checks: none. Deep-search metering is a local JSON file.
  • Voice: audio is written to a temp WAV and passed to the bundled whisper-cli; no network.
  • Browser history: local SQLite copies only. Local files: filesystem walk, ripgrep subprocess, local SQLite index.
  • Keychain: OS keychain; no network. Engine ↔ shell: JSON-RPC over stdio, no sockets. The only listening network socket ever opened is the temporary loopback sign-in callback, closed after the flow.
  • AI apps over MCP (since 1.0.68): the AI app starts Fry's MCP server over stdio; it opens no port and makes no network call. It reaches the running app over local IPC, not the network: a Unix domain socket (mcp.sock, owner-only 0600) on Mac or a named pipe on Windows, with no host or port, open only while an AI app may search and Paranoid mode is off. A search asks the same sources as the search bar, so it adds no host.
  • External AI services: AskFry calls none. All inference is local. An AI app you connect over MCP sends what it receives to its own AI provider, unless that AI runs locally.

With all cloud features off, the calls are:

"All cloud features off" means nothing connected: no source tokens or OAuth grants. AI always runs locally.

  1. Zero calls during search, indexing over an already-downloaded model, voice, onboarding scan, audit, recall, graph and metering. An automated test asserts this against the network guard.
  2. One automatic download, disclosed in onboarding: the embedding model (~135 MB, huggingface.co, sha256-pinned) right after onboarding completes, because the semantic index starts then. It never repeats once verified.
  3. Downloads on click only: the local brain and the voice model (huggingface.co, sha256-pinned, resumable).
  4. Update check on click only, to askfry.com: the Update button in the sidebar, or Check now in Settings › Updates & About. Nothing on launch, nothing on a timer.
  5. Installer only (Windows): the WebView2 bootstrapper when WebView2 is absent.
  6. Usage stats (row 13, since 1.0.65) are separate from cloud features: on by default, opt-out on the first-launch screen or in Settings › Privacy, sent only after that screen, and never in Paranoid mode.
  7. Paranoid mode refuses all of the above at the network layer, downloads, update check and usage stats included.

claim 4 · usage stats · since 1.0.65

What AskFry collects, and what it never collects

AskFry sends anonymous, allowlisted usage counts to help us improve it, unless you opt out. None in Paranoid mode. Never your searches, files or content. A first-launch screen asks first, with the box pre-checked: “Help improve Fry: anonymous stats only, never your searches or content”. Settings › Privacy turns it off at any time, and “See what we send” lists every event and field, plus what is queued on the device. Nothing is recorded or sent before that screen, while stats are off, or in Paranoid mode.

AskFry collects

  • Install, first launch and onboarding
  • A daily active ping (days since install, in ranges)
  • Number of searches, search speed (rounded), number of results (in ranges) and whether there were zero
  • Which result position was opened, and its source type
  • Which source types are connected (for example Gmail, Slack, local files; no account names)
  • Which section is used (Find, Clean, Move, History, Settings) and which action
  • Connector error codes
  • Crashes, as an error type plus an anonymous grouping code (no stack traces, paths or messages)
  • App version, OS and chip type
  • Update success or failure

AskFry never collects

  • Search text
  • File names, paths or contents
  • Emails or messages
  • Account names or tokens
  • IP address or location
  • Screen recordings

Only allowlisted events and fields can be sent; the app drops anything else, and tests enforce it. A random ID made on the device, not linked to email, accounts or hardware, replaced when you turn stats off and on again. Sent to PostHog’s EU region with the client IP discarded, GeoIP off and no person profiles. No session replay, no autocapture, no ad or third-party SDKs. Network row 13.

claim 5 · AI apps (MCP) · since 1.0.68

What an AI app can reach through AskFry

Fry is a local MCP server for the AI apps you allow. It is off by default, nothing works in Paranoid mode, and search needs Fry to be running. Three read-only tools: search, rewrite_query and get_recall_profile, which shares aggregates only. Set up under Recall › Connect an AI app.

  1. The AI app starts Fry's MCP server and talks to it over stdio.

    no network port · the MCP server makes no network call

  2. The MCP server hands the request to the running Fry app over a local IPC socket: a Unix domain socket on Mac, a named pipe on Windows.

    mcp.sock in Fry's data folder, owner-only (0600) · no host, no port · open only while an AI app may search and Paranoid mode is off

  3. Fry checks every guard again: Paranoid mode, “Allow AI apps to use Fry”, the app and its key, “Allow search”, the rate limit.

    off by default · search is a separate opt-in per app · 10 searches a minute, 2 at a time

  4. Fry writes the audit row, then searches the same sources its search bar asks.

    the row has the app, the result count, the limit and the filter names, never the query text or results · no row, no reply · no new host

  5. The AI app receives up to 20 results: title, source, date, link and a snippet of 300 characters or fewer.

    read-only · no write, move, delete or send

  6. The AI app sends what it receives to its AI provider, unless the AI runs locally, for example with Ollama.

    this step is outside AskFry: check your AI app's own policy

An AI app's searches do not teach Fry new words and are not in your recent searches. Turn the switch off, turn a permission off, or remove the app, and the next request is refused. The audit log is in Settings › Privacy › Audit log, source “Recall sharing”.

skeptical faq

Questions a careful person asks

How is this different from a screen recorder?

A screen recorder keeps an archive of everything you saw and heard. AskFry records nothing. It searches what already exists: your files, your browser history, the read-only APIs of the apps you connect. The only thing it builds is an index of local text, in a folder you can delete. No screen capture, no audio archive, no timeline. Voice input is transcribed on your device.

What if the company changes hands?

A store of your life outlives a company's plans and goes where the company goes. AskFry's answer: store as little as possible, only on your disk, with no licence server and none of your searches or content on a server of ours. Local search needs no server at all, and keeps working with Paranoid mode on.

Why should I believe "local"?

Do not believe it; check it. A local store of screenshots of everything, readable by any process, is not the same thing. AskFry's index holds text you already have, in your home folder, in a folder only your user can read. And the network layer is enumerable: the table above is every host the process can reach, a zero-outbound local search is checked by an automated test, and Paranoid mode is a hard refusal. Little Snitch will show you what this page says.

What do you see about me?

From the app, only if you leave them on: anonymous, allowlisted usage counts (on by default, opt-out, none in Paranoid mode). Never your searches, files or content. The list of what AskFry collects and never collects is below. There is no licence server. This website stores the email you submit, when you submitted it, and the page and campaign parameters that came with the visit. It also counts a few anonymous page events. No third-party analytics.

Your searches, the results you open and your Recall profile never reach us. If something breaks, tell us, and share the log if you choose.

Is it open source?

Not today. The code is private, and no licence decision has been published. Until then, the checks on this page are the ones you can run yourself.

What are the OAuth scopes?

Exactly these. None permits writing. Google, Slack and GitHub connect through a sign-in in your browser; a token you paste also works. Tokens are stored in the OS keychain. Public Google sign-in still needs Google's verification and is not available yet. The Notion connector is in development and is not a live source yet; its calls stay in the network table above because the code can make them.

SourceAuthExact scopesHow you connectWhat is readOS permission
Local files None
filesystem read of the folders you tick
n/a Default roots ~/Documents, ~/Desktop, ~/Downloads. Filename walk (50k entries cap), content search via ripgrep (5 MiB/file, 8 s), optional local hybrid index (.txt/.md/code/.pdf/.docx, 2 MB text cap; media files metadata-only). Symlinks never followed; node_modules, .git, Library, .Trash, dist, .cache pruned. None for the default folders.
Browser history (Chrome, Edge, Brave, Firefox) None
read-only copy of the History / places.sqlite database
n/a The history database is copied to a temp dir and opened read-only; url/title LIKE match; ≤25 rows per profile. The live database is never opened or locked. None.
Browser history (Safari) None
read-only copy of ~/Library/Safari/History.db
n/a Same as above, Safari schema. Full Disk Access for Fry.app (macOS). Without it the copy fails and the app shows the exact System Settings path. Since 1.0.67 the app id is com.askfry.fry (was co.quantumlabs.fry), so grant it again once after moving from an earlier build.
Slack User token, from Connect Slack (browser sign-in with PKCE) or pasted by hand
search:read
users:read
Connect Slack in the app, or paste a user token. Stored in the OS keychain. Message search results, and the people list for name resolution. Nothing is posted. None.
Google Drive OAuth 2.0, desktop loopback flow with PKCE
https://www.googleapis.com/auth/drive.readonly
Sign in with Google in your browser, with a configured Google client. Public Google sign-in awaits Google's verification. Tokens stored in the OS keychain. File list matches; text/plain export of ≤5 Google Docs under 1 MiB per search (20 KiB read). Other file types: name and metadata only. None.
Gmail OAuth 2.0, same flow and client as Drive
https://www.googleapis.com/auth/gmail.readonly
Same Google sign-in as Drive. Tokens stored in the OS keychain. ≤25 message ids per search, metadata for ≤12, full text/plain body (20 KiB cap) for the first 5 only. None.
GitHub Desktop device sign-in (GitHub App), or a fine-grained read-only token pasted by hand
read-only, repositories owned by the connected account
Connect GitHub in the app (sign-in in your browser), or paste a read-only token. Stored in the OS keychain. Code, issue/PR and repository search results, only in repositories the connected account owns. Starred repositories and gists are excluded. Nothing is written. None.

Derived from AskFry's source inventory and network registry, 2026-09-28.

beta

Checked enough? Get the beta.

Download the beta