The problem
For a cloud assistant, every promise about privacy runs against the company's own interest. The cheapest path to serve is the one that keeps your data, the product improves when it keeps more, and the user is asked to trust a policy that can be revised without shipping any code.
I did not want to write a better policy. I wanted an arrangement where the promise survives the company changing its mind.
The decision
Run the model on the phone by default, and price accordingly.
- On-device is free and unlimited, because it costs nothing to serve. Nothing to meter, nothing to log, no reason to build the pipe that would log it.
- The cloud is metered, because it costs money per call. That is the premium tier, and it is the only thing being sold.
- At zero credits it falls back to the device rather than blocking. Running out of money degrades the answer, never the access.
The incentive lands the right way round. The cheapest thing for the user is the private thing, and the company only earns when it does the expensive one.
Making the guarantee mechanical
A free tier that "makes no network calls" is a claim about every line of code in the binary, including code nobody on the team wrote. So it is asserted by a test, not by a review.
- A network trap. The suite registers a URL protocol that fails the run on any outbound request. Not any request to the model provider: any request at all, analytics included. The realistic threat was never the model call. It is a crash reporter or a telemetry ping from a dependency constructed at startup, and no review of the chat path catches that.
- A receipt on every cloud message. The exact string handed to the transport is stored and shown on tap. Local turns render nothing, so silence means it stayed on the phone. It cannot disagree with what happened, because it is what happened.
- A fail-closed gate. The dispatch path waits for the scrubber and rethrows if it is unavailable. Sending unscrubbed text is not a degraded mode, it is an error.
- A leakage odometer that only ratchets up, so a single careless turn cannot be averaged away by a hundred careful ones.
The receipt is the piece I would keep if I could keep only one. It costs one stored string per message and it converts the category's standard unfalsifiable promise into something a suspicious user can check for themselves, indefinitely, without trusting me.
What is not true yet. There is no automatic content-sensitivity routing: which path a message takes is a setting the user chooses, so "it escalates to the cloud when it needs to" has no answer today. And if the anonymizer times out, the raw text goes to a deterministic scrub and that output ships, so "only a rewrite is ever sent" is true most of the time rather than always. The app is pre-launch and has not been released.
What I would do differently
- Test the gate's failure, not its success. An audit found that a refactor moving one line below the dispatch call would have left the whole suite green while putting raw text on the wire. The fix was to turn the CI environment's weakness into the test: continuous integration never has the on-device model available, which is exactly the state needed to prove the refusal fires.
- Fit the odometer thresholds to data. They are hand-tuned round numbers sitting on top of an eval corpus that could calibrate them properly. A privacy meter with invented gradations is a worse version of an honest one.
- Decide the routing question before writing the marketing copy. Auto-routing is the feature a reader assumes from the positioning, and the parts that would carry it are staged and waiting on a single decision function. The gap between what the page implies and what the build does is the thing that costs trust, not the missing feature.