Inspiration
Melissa Stone didn’t need another budgeting app to tell her she had overspent. She needed an empty budget category to feel as real as reaching into a cash envelope and finding nothing left.
She had tried app after app. She could build a budget, assign every dollar, and know exactly what her money needed to cover. But between ADHD and a busy life, that plan was hard to stick to while she was actually spending money. When checking out, the money was either in her account or it wasn’t. The fact that some of it was supposed to cover groceries for another two weeks didn’t change whether her card worked.
Traditional budgeting apps would later sort everything into categories and show her where she had gone over. But, the money was already gone. Now she just had a colorful reminder.
What worked best for her was the old cash envelope system. You put your grocery money in an envelope. When you went shopping, you took money out. You could see what was left and feel it getting thinner. You counted the bills, handed them over, and came home with less. Eventually, you reached in and there was nothing there.
You could still take money from another envelope, but you had to make that choice. You had to reach for the one marked “rent” and physically take it out. The tradeoff was right there in your hands. There is research behind why that feels different. Researchers call the discomfort of parting with money the “pain of paying.” Studies have found that electronic payments can feel less painful than handing over cash, and a large review of research found that people tend to spend more with cashless payment methods.
Melissa needed the “pain of paying” to help control impulsive spending and take a proactive approach to her budget. But with bills paid online, purchases made through apps, and so much of everyday life moving away from cash, keeping her money in physical envelopes wasn’t practical anymore.
So, how do we recreate the small but useful "pain of paying" hesitation that comes with watching your money leave?
The idea is to make taking money out part of the purchase. Before spending, you pull money from the bucket it belongs to. A shopping trip might draw from groceries, household supplies, and personal spending, because the laundry detergent and the thing you absolutely did not go to the store for are two different decisions. You see what you are taking, where it is coming from, and what will be left. And when a bucket is empty, you cannot keep pulling money from it.
But there also needed to be protections in place to keep her accountable when she did try to pull from the other buckets on impulse. A purchase could become a “need” remarkably quickly when she was the one making the case. And an ordinary budgeting app offers no pushback.
So, Jason had the idea that we could also create a moment to ask those questions before buying: “Do I need this? Do I want it? If I want it, do I have money set aside for that?” And incentivize sticking to the plan. Jason’s idea added another layer: a moment to question the purchase, some pushback when she tried to borrow from another bucket, and an incentive to leave that money where she had planned for it to go.
Together, those ideas became “Buckets.” Melissa knew how to make a budget. She needed one that could survive a shopping trip with her. One that made taking money from next week’s groceries feel like taking money from next week’s groceries, even when her card would have gone through just fine. And one that would help keep her accountable along the way, before an impulse became a purchase and the money was gone, leaving her budgeting app to document the damage.
What it does
First, we built "the pause." The first version of Buckets focuses on the moment when a purchase is still a decision. It questions the label, challenges the very convincing argument you may have already made to yourself, and creates a little hesitation before you spend. We want to get that interaction right with enough pushback to make you think, without becoming something you immediately learn to ignore.
You log a purchase and tag it, one at a time, as a "want" or a "need." That tag is the claim Buckets examines.
An NVIDIA Nemotron 3 Nano model running on Nebius Token Factory reads each tagged purchase against your own spending history, and it is allowed to disagree with you. When it does, it does not reclassify anything. It asks you one question.
But, an app that silently overrules you is insulting, and an app that believes every stated reason is useless. So Bucket asks, listens, and then decides. You answer in a sentence. Nemotron 3 Ultra reads your answer, and if it holds, Bucket concedes and says what convinced it. If it does not, Bucket keeps its verdict and explains why, kindly and specifically. Either way you can override it with one tap, and both the model's call and yours are recorded.
Before reconsidering, Bucket can research alternative and more cost effective options. When you say "the old one broke, I need this," nothing in a model's weights knows whether that particular thing can be repaired for a tenth of the price. A Tavily search finds out, and Ultra weighs your explanation against what came back.
"Wants" over a threshold park in a "Sleep On It" tray for 24 hours. When one is released unbought, a coin flies into savings and the hero number ticks up. That money is called deferred, never saved, until the item ages out unbought. Buckets will not claim a saving it has not proven.
Every contested purchase is written down and published: the tag, the verdict, the question, your answer, whatever was looked up, and whether your answer changed the model's mind.
How we built it
The verdict engine is an isolated module. It imports nothing from the UI, never reads storage, and a lint rule enforces that boundary. History is passed in, the provider sits behind an adapter interface, and model selection happens inside the engine where nothing else can reach it.
Two tiers, routed on disagreement. Nemotron 3 Nano 30B A3B screens every tagged purchase. Nemotron 3 Ultra 550B A55B adjudicates. Most tiered systems escalate on task difficulty: hard prompt to the big model, easy prompt to the small one. Bucket escalates on human disagreement. Ultra is only ever called because a person pushed back. A purchase you and the model agree about never touches the expensive tier, and the ledger records the split per verdict.
Nothing self-reported. Whether the model agrees with you is computed by comparing its verdict to your tag, never read from a field the model filled in. Whether your explanation was accepted is derived the same way. A model that answers "want" while claiming it was persuaded is ignored, and there is a test for exactly that.
A spend ceiling in dollars, failing closed. The demo has no login by design, so anyone can trigger paid inference. Before every Token Factory call the engine asks whether the budget allows it, counting verdicts, tokens and estimated cost. If the ledger cannot be read at all, it reports the ceiling reached and serves a cached verdict rather than risking unmetered spend. The app stays fully usable either way.
A lookup can only ever add a fact. No Tavily key, no sensible query, an empty result, a provider error and a timeout all decide the verdict exactly as the code did before the search existed. Nothing turns into a harsher answer because a third party was slow.
Stack: TypeScript, React 19, TanStack Start, Vite, Tailwind, Cloudflare Workers and D1. 93 checks across two suites, all of which run with no API key and no network, because a test that can spend money is a test nobody runs.
Challenges we ran into
A silent truncation that looks like a refusal. Set max_tokens too low and Token Factory returns HTTP 200 with a successful-looking response whose content is an empty string. No error, no warning. finish_reason says length, but a caller that checks res.ok and then parses content sees a blank and concludes the model declined or that its own prompt was bad. At 256 we got nothing; at 4096 we got the answer. This is the one that would have cost us a day.
We were building around a model that does not exist. Our plan named Llama-3_1-Nemotron-Ultra-253B-v1. GET /v1/models returns no such id. The flagship is a 550B MoE with roughly 55B active, every id on the platform is namespaced nvidia/, and several widely-repeated ids in circulation do not resolve as written. Every cost and latency conclusion we had drawn from "253B" was about a model that was never there.
We nearly shipped an architecture sized on a toy prompt, and the correction is the most useful thing we learned. Our first probe used 80 tokens and no history, and produced Nano at 985 ms and Ultra at 1204 ms. From that we concluded Ultra was barely slower and that warnings about reasoning models taking seconds were overblown. Both conclusions were wrong. Re-measured with a realistic prompt carrying eight history items, Nano runs about 2.1 seconds and Ultra about 3.6, because completion tokens scale with how much there is to think about. Then the running app showed worse: two consecutive Nano calls of near-identical shape came back at 1964 ms and 3796 ms, a 1.9x spread on one tier, with Nano's slow case overlapping Ultra's typical case.
The plan had said "cheap model inline, expensive model async." The real rule is that no model call blocks the purchase flow. The verdict is fired without being awaited and lands when it lands, so the half-second interception beat the product exists to create survives a model having a slow day. Our instinct to build async from the start was right even though one of the numbers behind it was not.
Nano's reasoning is billed and invisible. On the toy prompt it spent 256 completion tokens emitting no content at all, and needed 263 to produce a 143-character answer: roughly 220 tokens of hidden work before the first visible character. It does not report completion_tokens_details, so that spend cannot be attributed. The cheap tier is the one you can least account for.
There is no published per-token pricing. Token Factory does not expose rates through the API and Nebius does not publish them, so our spend ceiling runs on third-party blended estimates deliberately set above the real figures. We would rather be wrong in the safe direction, but an app that enforces its own budget should not have to guess what it is being charged.
The hardest problem was not technical. Writing a model that disagrees with someone about their own money, without being insufferable, took more iteration than any of the above. Bucket never mentions what you can or cannot afford, never moralises, never compares you to anyone, and never invokes your family. A money app that makes someone feel small has failed no matter how accurate it is.
Everything above is measured, written down and re-runnable: docs/token-factory-findings.md, reproduced with node scripts/probe-token-factory.mjs. It includes the sections where we were wrong first.q
Accomplishments that we're proud of
Publishing the corrections to our own conclusions. docs/token-factory-findings.md contains a section that opens "this section was initially wrong, and the correction is the most useful thing in this document." We had measured on a toy prompt, drawn an architectural conclusion from it, and been wrong about both tiers. Rather than quietly fixing the number we left the error in, with the measurement that disproved it, because a findings document that only contains things we got right is marketing. The probe script is committed and anyone can re-run it.
Escalating on human disagreement rather than task complexity. Most tiered systems send hard prompts to the big model and easy ones to the small one. Bucket sends the expensive model only the purchases a person has pushed back on. It is a routing policy we have not seen elsewhere, it is cheap to explain, and it makes the 550B model earn its place.
Publishing the exchange log unfiltered, including the misses. Every contested purchase is recorded with the question, the answer and whether that answer changed the verdict, and the challenges that collapsed the moment someone explained themselves ship through the same code path as the ones that held. A hit rate you can only see the good half of is not evidence of anything.
Not sending the user's explanation to the search API. It is the most personal text in the app. The query is built from the purchase label alone, plus a single yes/no read on whether something broke, and there is a committed test asserting that a sentence about somebody's private life never reaches a third party. We gave up better search results for it.
Being precise about where the savings come from. Ultra costs 24.6x a Nano call while using only 1.25x the tokens. So "we saved N tokens by screening with Nano" would be false as stated, and we say so in the README instead of using the easier sentence.
Calling it deferred instead of saved. It is a weaker claim and it costs us on paper, and it is the only honest one until the thing ages out unbought.
What we learned
Derived beats self-reported, every time. Every boolean we asked a model to restate came back inconsistent eventually; every boolean we computed from its own answer did not.
A fallback must never be able to make a product harsher. Our first version of the lookup recorded "searched, 0 results" as though something had been learned, and a test caught it. We tightened the engine rather than relaxing the test.
Honest failure paths are a feature. Every way the reconsideration can fail, including no key, budget exhausted, ceiling tripped and provider down, accepts the person's explanation. A guard that silently hardened into "no" would turn the question into theatre, which is the one thing this product cannot be.
And the product lesson, which surprised us: asking is more persuasive than declaring. A model that announces "that's a want" gets argued with. A model that asks what makes this one different gets answered.
What's next for Bucket
Next, we want to bring the "envelope" all the way back. The next phase adds the actual buckets: money you set aside in advance and pull from before making a purchase. One shopping trip could draw from groceries, household supplies, and personal spending. An empty bucket would stop you instead of quietly turning red after the money was gone. The first version asks whether you should make the purchase. The next phase makes you decide where the money will come from and what spending it will leave behind. Jason already has an early PayPal integration working, but it is not part of this demo. We chose to build the hesitation and accountability first, then connect them to real money.
Make the accuracy claim testable. Melissa is developing a rubric of want-versus-need scenarios. The next step is to commit that evaluation set to the repository, along with a command that scores both tiers against it and shows which scenarios each one gets wrong. Anyone should be able to rerun the evaluation rather than take our word for the results.
Find out whether the pause changes anything. We need real users beyond the two of us. Getting someone to reconsider a purchase is one thing; finding out whether that changes their spending over time requires testing. We have not measured that impact at scale yet.
Longer term, put Buckets where the money already lives. A bank could license Buckets and connect it to its customers’ transaction history, accounts, and existing cards. That could give the model more context for its questions and allow the buckets themselves to hold money before a card is ever swiped. The envelope would become part of how the money moves, with the pause and accountability built into spending it.
Built With
- cloudflare
- framer-motion
- nebius
- nemotron
- node.js
- nvidia
- radix-ui
- react
- sqlite
- tailwind-css
- tanstack
- tavily
- typescript
- vite
- zod

Log in or sign up for Devpost to join the conversation.