Inspiration
People paste their bank statements into chatbots to ask one question: can I afford this? That hands a cloud company every merchant, amount and account they own.
We noticed something simple. Personal finance questions are just equations. "Can I buy this jacket?" "How long would my savings last if I quit?" "How much do I need to save per month?" Each one is a formula over a few numbers: savings, income, spending, a goal, a date.
A model doesn't need your numbers to write the equation. Only the solver needs them. So we split the job: the cloud writes the equation, your Mac solves it.
What it does
Blindfold is a finance agent you text over iMessage. It thinks blindfolded: the cloud sees symbols, your Mac sees the numbers.
- Can I afford this? It checks a purchase against your goal, your pace and your real monthly net, then suggests options: a cheaper price that keeps you on pace, or skipping a few delivery dinners.
- Goals and what-ifs. Ask "How long would I last if my income stops Jan 1?" and it shows the full breakdown.
- Who paid? Rules decide whose charge it is on a shared card. Apple Pay on her card is hers. A work-to-home ride is yours. When it's unsure, it asks, and you reply 1, 2 or 3.
- Nudges. "You've spent $450 on commute rides this month. Take the train a few days."
- Memory you control. Text "remember…", "memories" or "forget 2". Everything is stored on your Mac.
- A live ledger. A local dashboard shows income, spending and the exact bytes every cloud call sent.
How we built it
Every message runs through one pipeline.
- Symbolize. Plain code on the Mac swaps every merchant, amount, account, person and address for a symbol:
MERCHANT_3,Q_AMT_1,PERSON_1. - Classify. Laya, a small PII classifier running on the Mac, flags anything sensitive the rules missed.
- Understand and word. Luna, a small, fast model, reads the symbolized message and drafts the reply with placeholders only.
- Reason. Sol, the big model, gets a table of named variables with units and descriptions, never their values. It returns a plan of facts, equations and a reply template.
- Scan. A leak canary compares the exact outgoing bytes with every real value in the database. One match blocks the call.
- Solve. The Mac evaluates the equations on the real numbers, fills the placeholders and texts back.
For "if I quit Jan 1, how long would I last?", Sol writes:
$$\text{cash}_{\text{Jan 1}} = S + C + Q_1 + \bar{N}\cdot m$$
$$R = \frac{\text{cash}_{\text{Jan 1}}}{\bar{E}}$$
Here $S$ is savings, $C$ is checking, $Q_1$ is the pay still coming, $\bar{N}$ is average monthly net, $m$ is the months until the quit date and $\bar{E}$ is average monthly spending. Sol never learns what $S$ is. The Mac computes $R$.
Affordability is the same idea. With projected savings $S(D) = S_0 + \bar{N}\,\Delta t$ at the deadline $D$, goal $G$ and price $P$:
$$\text{verdict}(P)=\begin{cases}\text{yes} & S(D)-P \ge G\ \text{tight} & S(D)-P \ge 0.9\,G\ \text{hold off} & \text{otherwise}\end{cases}$$
The canary rule is strict:
$$\text{send} \iff \forall v \in V_{\text{db}}:\ \text{norm}(v) \not\subseteq \text{norm}(\text{bytes})$$
Stack: Bun, TypeScript, SQLite, iMessage on macOS, IMAP for receipts, and OpenAI for Luna and Sol. The math runs in a safe expression evaluator with unit checks and no eval.
Challenges we ran into
- Pattern matching failed. Our first version used rules to decide which math to run. Real messages broke it every time: "come Jan 1st I wanna quit", "6k in checking", "end of October". We rebuilt the core so the model reasons and the Mac computes.
- Models invent numbers. We banned digits, number words and
$in reply templates. Every number must come from a placeholder the Mac fills. - Our own canary caught us. It blocked our prompt because "capitals" normalized to a merchant word. It blocked
max_output_tokens: 3000because \$30.00 was a real charge. We made it skip numbers only at exact protocol fields. - Names leak. "Send my friend Priya the rent" went out in clear once. We added cue-word rules and a capitalized-name check, plus Laya as a backstop.
- Latency. Two privacy layers and two models add up. We cut the prompt and the variable table, and fall back to local answers on timeout.
- Real data is messy. Statements don't reconcile, Zelle memos hide names, and promo emails look like receipts.
Accomplishments that we're proud of
- The cloud never sees a real value unless you approve that one value.
- An adversarial red team ran 81 attacks against the exact request bytes. It found 34 leaks over six rounds. We fixed all 34, and every attack stays in the test suite.
- 1,100+ tests.
- Real data. It runs on real bank statements and hundreds of email receipts, with every statement reconciled.
- Every call is auditable. Each one lands in a local ledger with its exact payload and canary counts.
What we learned
- Privacy is a systems problem, not a model problem. Plain code you can read beats a model you have to trust.
- Equations are the right interface. A model reasons fine over $S$, $\bar{N}$ and $G$ without their values.
- Small models handle the talking. The big model handles the thinking.
- Test the bytes, not the intent. "It loads" is not "the guards hold."
What's next for Blindfold
- Run the same vault in a confidential VM with attestation, for people without a Mac.
- Live bank connections through Plaid.
- Push Laya and Luna fully on-device as Apple's models improve.
- Shared households: split goals and shared cards, with each person's data private from the other.
Built With
- apple-foundation-models
- applescript
- bun
- claude
- css
- gmail
- gpt-6-luna
- gpt-6.1-sol
- html
- imap
- imessage
- javascript
- laya
- macos
- openai
- python
- sqlite
- swift
- typescript
Log in or sign up for Devpost to join the conversation.