Everblue
Everblue is a chat-first AI journal for iPhone: everblue.me. People write about their lives in it, so I treat every privacy line in the app as a technical claim that the code has to back up.
Privacy, in code
- The journal stays on the phone, and so does retrieval. MiniLM embeddings through ONNX, BM25 and recency decay with MMR re-ranking, all in SQLite. The index and the embeddings never leave the device. No vector database, because at this corpus size it would be pure overhead.
- The companion has run entirely on-device on Apple's Foundation Models, with a prompt chain fitted to the 4k context and the grading checks ported to on-device JavaScript.
- What leaves the phone is listed exactly. Chat and photos the user adds go to model providers through OpenRouter. Photos pass an on-device sensitive-content check and are EXIF-stripped first, and the backend passes requests along without keeping them.
- Zero data retention is treated as configuration, not a promise. A flag for ZDR routing stays off until it's verified in the provider dashboard, and the app reads its privacy wording from a backend endpoint that reports the real posture, falling back to the cautious version.
Quality
- Every prompt change goes through an eval harness that runs the production pipeline on synthetic days: a bar of 92% on generation quality and 100% on no-fabrication.
- A recall guard re-rolls any memory reply containing words it can't trace back to the journal, so the companion can't present an invented memory as real.
The decision I'm proudest of
Photo captioning first ran fully on-device: about 500MB of Florence-2 in Core ML, a custom Expo config plugin and a Swift bridge, after debugging four separate out-of-memory crashes on 4GB iPhones. It worked. Then honest accounting showed the bundle wasn't earning its cost, so I replaced it with a gated backend service and cut about 1.1GB from the app.
Stack: React Native / Expo, Django on Heroku, PostgreSQL, RevenueCat, GitHub Actions.
Write-up: Don't let your app promise more privacy than your config delivers