How I Built Vault, a Money Agent That Never Leaves Your Laptop
Code: https://github.com/RajdeepKushwaha5/vault
Demo video: https://drive.google.com/drive/u/0/folders/1elL9cQ86p_-D2-Jt_lULuX42pzNLlSKj
Build time: about 30 minutes. Everything below is real and runnable.

A friend of mine got a notification last month that a fitness app had charged her $44. She had not opened it since January. She had been paying it every single month, quietly, for almost a year. Five hundred dollars, gone, for a thing she used twice.
The annoying part is that nothing was hidden. Her bank knew about the charge. Her inbox had the original sign-up receipt. Her phone knew she had not opened the app in months. Every fact needed to catch this existed. They just lived in three different places that do not talk to each other, so the money kept leaving.
I wanted to build the thing that connects those three facts. But there is a catch that kills most ideas like this: to do it, an app would need your bank statement. And there is no chance I am uploading my bank login to some random startup’s cloud, and neither should you.
So I gave myself a harder rule for the hackathon. Build a money agent that finds the leaks and never sends a single byte off your machine. I called it Vault.
Here is exactly how I built it, with the real SQL and the real source spec, so you can build your own.
One honest note up front. I am a student. I do not have a giant pile of subscriptions to dramatize. So Vault ships with a realistic sample bank statement, real merchant names and real 2026 prices, so anyone can try it. The local-first story is true no matter whose data it is, and saying that out loud reads better than faking the stakes.
The idea, and the trick that makes it safe
The idea is one sentence: take three things that normally live apart, your bank statement, your inbox receipts, and your app usage, turn each into a SQL table, and JOIN them to find money you are wasting.
The trick is the part most people miss. Everyone thinks of Coral as a way to query SaaS APIs in the cloud. But Coral also reads local files as first-class SQL tables. That one capability is the entire reason Vault is safe. The join runs on your disk. Nothing is uploaded, because there is nowhere to upload it to.
Act 1: Turn three boring files into a database
Vault keeps three local JSONL files in a data/ folder. I describe them to Coral in one manifest. The magic line is backend: jsonl, and each table just points at a file.
name: vault
dsl_version: 3
backend: jsonl
description: Reads three LOCAL JSONL files as SQL tables so Coral can join
money, sign-ups, and actual usage to find leaking spend. Nothing leaves your machine.
tables:
- name: transactions # your bank: one row per charge, negative = money out
source: { location: "file:///path/to/vault/data/", glob: "transactions.jsonl" }
columns:
- { name: txn_id, type: Utf8 }
- { name: date, type: Utf8 }
- { name: merchant, type: Utf8 }
- { name: amount, type: Float64 }
- { name: category, type: Utf8 }
- name: receipts # your inbox: what you signed up for and the listed price
source: { location: "file:///path/to/vault/data/", glob: "receipts.jsonl" }
columns:
- { name: merchant, type: Utf8 }
- { name: service, type: Utf8 }
- { name: category, type: Utf8 }
- { name: list_price, type: Float64 }
- { name: billing_cycle, type: Utf8 } # 'monthly' or 'annual'
- { name: signup_date, type: Utf8 }
- { name: cancel_url, type: Utf8 }
- name: usage # your phone: when you last opened each service
source: { location: "file:///path/to/vault/data/", glob: "usage.jsonl" }
columns:
- { name: service, type: Utf8 }
- { name: merchant, type: Utf8 }
- { name: last_used_date, type: Utf8 }
- { name: sessions_last_30d, type: Int64 }
Register it, then read from it:
coral source add --file sources/vault/manifest.yaml
coral sql --format json "SELECT merchant, amount FROM vault.transactions LIMIT 3"
Try this. That command reads rows straight off your disk. No server, no database process, no network call. The moment it returns rows, you have a SQL engine sitting on top of your own files, and that alone is a small superpower.
Act 2: The query that catches my friend’s $44
A “forgotten subscription” is one you are charged for every month but have not actually used in ages. That fact does not exist in any one file. It only appears when all three agree. This is Vault’s killer query, the real one from the project:
SELECT
r.service,
r.list_price AS monthly,
ROUND(r.list_price * 12, 2) AS annual_waste,
u.last_used_date,
u.sessions_last_30d,
r.cancel_url
FROM vault.receipts r
JOIN vault.usage u ON u.service = r.service
JOIN vault.transactions t ON t.merchant = r.merchant AND t.category = 'subscription'
WHERE r.billing_cycle = 'monthly'
AND CAST(u.last_used_date AS DATE) < CAST(CURRENT_DATE AS TIMESTAMP) - INTERVAL '60 days'
GROUP BY r.service, r.list_price, u.last_used_date, u.sessions_last_30d, r.cancel_url
ORDER BY annual_waste DESC
Read it slowly, because the whole product is in these few lines. The receipt says you subscribed. The transaction says you are still being charged. The usage says you have not opened it in over sixty days. Three sources, one row, real money. On the sample statement this alone surfaces over a thousand dollars a year. My friend’s fitness app would have been the top row.
Coral gotcha. Coral returns dates as strings, so cast before doing date math: CAST(u.last_used_date AS DATE). You will use that pattern in every time-based query.
Act 3: Once you can join, you catch everything
The beautiful thing is that the same three tables, joined differently, catch every other kind of leak. These are the actual detectors Vault ships.
The silent price hike. Compare the first charge to the latest charge for the same merchant:
WITH latest AS (
SELECT merchant, MAX(date) AS last_date
FROM vault.transactions WHERE category = 'subscription' GROUP BY merchant
),
first_charge AS (
SELECT merchant, MIN(date) AS first_date
FROM vault.transactions WHERE category = 'subscription' GROUP BY merchant
)
SELECT t_now.merchant,
ABS(t_old.amount) AS was_paying,
ABS(t_now.amount) AS now_paying,
ROUND((ABS(t_now.amount) - ABS(t_old.amount))*12, 2) AS yearly_increase
FROM latest l
JOIN first_charge f ON f.merchant = l.merchant
JOIN vault.transactions t_now ON t_now.merchant = l.merchant AND t_now.date = l.last_date
JOIN vault.transactions t_old ON t_old.merchant = f.merchant AND t_old.date = f.first_date
WHERE ABS(t_now.amount) > ABS(t_old.amount)
ORDER BY yearly_increase DESC
The duplicate. Two music apps, two cloud drives, paying for the same thing twice:
SELECT category,
COUNT(*) AS services,
ROUND(SUM(list_price)*12, 2) AS annual_total,
ROUND(MIN(list_price)*12, 2) AS annual_saving_if_drop_one
FROM vault.receipts
WHERE billing_cycle = 'monthly'
GROUP BY category
HAVING COUNT(*) > 1
ORDER BY annual_total DESC
The trial about to bite. A recent sign-up with a $0 charge that is days away from becoming real money:
SELECT r.service, r.signup_date, r.list_price AS converts_to, r.cancel_url
FROM vault.receipts r
JOIN vault.transactions t ON t.merchant = r.merchant
WHERE t.amount = 0.0
AND CAST(r.signup_date AS DATE) >= CAST(CURRENT_DATE AS TIMESTAMP) - INTERVAL '30 days'
ORDER BY r.signup_date DESC
One detail I am proud of: a growing cloud bill gets flagged to review, not called waste, because rising usage can be legitimate. When you are dealing with someone’s money, being careful about what you claim is the difference between trustworthy and annoying.
Act 4: A thin backend that proves every number
The backend reimplements nothing. It runs Coral and attaches proof. Every number on the screen has a query behind it that you can open and read.
import subprocess, json
def coral_query(sql: str) -> dict:
proc = subprocess.run([CORAL_BIN, "sql", "--format", "json", sql],
capture_output=True, text=True, timeout=40)
ok = proc.returncode == 0
return {"rows": json.loads(proc.stdout) if ok else [],
"sql": sql, "status": "ok" if ok else "error"}
The detectors run in parallel so the page loads fast, even though each one is a live Coral query reading from disk. And because Coral is SELECT only, the app can physically only read. When the data is your bank statement, “it literally cannot write anything” is a feature worth more than any dashboard.
Act 5: The part that wins, making “it stays local” provable
Anyone can print the word “private” on a landing page. I wanted to prove it on camera. So Vault has a Privacy panel with a button that runs a live query with the network off and reports how many external calls it made.
Read 84 rows in 12 ms with 0 external calls. Offline OK.
You turn your Wi-Fi off, you click the button, and it still works. That is not a feature I bolted on at the end. It is the entire reason Vault is safe to point at real financial data. You would never hand your bank login to a random app. With Coral reading local files, you never have to.
Vault also stays in its lane on purpose. When you decide to cancel something, it drafts the cancellation email and gives you the direct cancel link, but it never sends anything and never cancels anything itself. It drafts. You decide.
Act 6: Run the whole thing
# generate the realistic sample data
python scripts/seed_data.py
# backend
cd backend && pip install -r requirements.txt && uvicorn main:app --port 8002
# frontend
cd frontend && npm install && npm run dev # http://localhost:5176
Open the app. The big number up top is the yearly waste it found. Click any leak card and the exact Coral SQL that found it drops down underneath it. That single gesture, a dollar amount turning into the query that proves it, is the whole product in one click.
How the story ends
My friend’s $44 app was the spark, but the thing I actually built is bigger than catching one forgotten subscription. It is a pattern: point Coral at your own files, join them, and let an AI work on your most private data without that data ever leaving your laptop.
That is the lesson I would hand you. Everyone reaches for Coral to query APIs in the cloud. Point it at local files instead and you flip the entire risk model of AI tools. “Let an AI help with my finances” usually means “upload my finances somewhere.” With Coral it means “the AI runs on my machine and the data never moves.” For money, health, personal notes, anything you would not paste into a chatbot, that is the whole game.
Build the manifest, write the three-way join, and watch a thousand dollars of forgotten spend appear out of three boring files. Then turn your Wi-Fi off and watch it still work.
See it run: https://drive.google.com/drive/u/0/folders/1elL9cQ86p_-D2-Jt_lULuX42pzNLlSKj
Grab the code and build your own: https://github.com/RajdeepKushwaha5/vault
That is Vault. Your money, your machine, nothing uploaded.