# FinZ: a first-paycheck desk that runs Gemma on the laptop

> Source: <https://dev.to/sudeepsarmah/finz-a-first-paycheck-desk-that-runs-gemma-on-the-laptop-a9b>
> Published: 2026-10-18 18:43:42+00:00

*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle](https://dev.to/challenges/hacktoberfest-week2-2026-10-12)*

FinZ is a money desk for a first real paycheck. You write the note the way you would text a friend.

`$14 matcha after a bad standup, felt like a treat.`

Gemma, running on your computer through Ollama, turns that into an amount, a direction, a short label, and a feeling. You can change any of those before it is saved. The note hits the desk only when you say so.

The desk is one pay period. Cash in, cash out, what's left. In the sample below, take-home is $2,800 every other week. The matcha landed at $16, the paycheck got dropped in, and $2,784 was left. The feeling mix is one survival note and one treat. Leftover cash fills dreams smallest first, so an $800 fridge is full and "Japan with my sister" sits at $1,984 of $4,000. Her reason for the trip is on the card: she turns 30, and the point is to be in the room.

Open the dream and FinZ will do two more jobs. A money mood is a short read of the notes already on the desk, held up against that dream. A starter split is three buckets for money she might set aside later: an emergency buffer, cash, and a broad index fund. The page says "not financial advice" before the button and again on the sketch. There are no tickers, and nobody places a trade.

The person who would pay for this is in their first year of getting paid, and wants that note read on their own machine. The job is small on purpose. Read a messy line, show the period, move leftover cash toward a named dream, and sketch a boring split when they ask. After `ollama pull gemma3:4b`, those model calls cost nothing, so the thing for sale is the desk. A metered API on every note would eat a product this small before anyone paid for it.

The screenshots are FinZ and Gemma on the same laptop. That is the path where the Mastra agent, the weights, and the ledger share a machine.

The public desk is [finz-omega.vercel.app](https://finz-omega.vercel.app). The page is hosted. Gemma runs on the visitor's computer. The guide at [finz-omega.vercel.app/guide](https://finz-omega.vercel.app/guide) is the setup: install Ollama, pull `gemma3:4b`, then start Ollama so the browser is allowed to reach localhost (`OLLAMA_ORIGINS`). A note can then be read on that machine, and the ledger for the demo stays in the browser.

The money mood and the starter split are the Mastra agent. They run when you start FinZ yourself, next to Ollama:

```
ollama pull gemma3:4b
npm install
npm run dev
```

Open [http://localhost:47291](http://localhost:47291). The repo README has the same steps, including the smaller `gemma3:1b` switch if the laptop is tight on memory.

*Cash in $2,800, cash out $16. Surplus fills the smaller dream first.*

*Gemma's read was titled "Sister's Trip Still Calling." It names the $2,800 paycheck, then the trip, and it signs off "FinZ, on this laptop."*

*Emergency plus cash is 75%. The index is 25%. A dream happening this year has to come out that way.*

Live site: [https://finz-omega.vercel.app](https://finz-omega.vercel.app)

A local-first money desk for the first real paycheck. Dreams and feelings up front, a spreadsheet nowhere. Gemma reads a messy note on your laptop. Mastra is the agent harness. Income, expenses, and dreams stay in a SQLite file on the machine.

You need Node.js 22 or newer (FinZ uses the built-in `node:sqlite`) and [Ollama](https://ollama.com/download).

Pull the model FinZ runs here:

```
ollama pull gemma3:4b
```

If Ollama did not start a background service (common when systemd is not running), start it in another terminal:

```
ollama serve
```

Then:

```
npm install
npm run dev
```

Open [http://localhost:47291](http://localhost:47291). The dev server binds `0.0.0.0:47291`. The first page explains the desk. Start Gemma is a plain-language setup guide at `/guide`. Onboarding includes a currency (USD, INR, EUR, or GBP).

`gemma3:1b` was the first try. It parsed a matcha note, then invented a purchase when…

The agent is one Mastra `Agent` in `lib/agent.ts`. The model is whatever `OLLAMA_MODEL` says. The default is `gemma3:4b`.

``` js
export const finzAgent = new Agent({
  id: "finz",
  name: "FinZ",
  instructions: `You are FinZ, the money friend on someone's laptop. They just got a first real paycheck.
Be warm, specific, and a little irreverent. Short sentences. No shame. No corporate tone. No hashtags. No bullet lectures.
Never invent amounts, entries, or dreams that are not in the prompt.
You are not a financial advisor. Allocation sketches only use three buckets: an emergency buffer, cash, and a broad index fund. Never name a ticker, fund company, coin, or individual stock.
When asked for JSON, reply with one JSON object and no markdown.`,
  model: ollama.chat(MODEL_ID, { think: false }),
});
```

Instructions are not a guarantee. A small model will still invent a purchase, so a finished reflection has to mention the dream, mention a real entry label, and only use dollar amounts that were actually logged. The sketch has its own rule for a dream happening this year:

``` js
const safe = allocation.emergencyPercent + allocation.cashPercent;
if (
  dreams.some((dream) => dream.timeline === "this year") &&
  allocation.indexPercent >= safe
) {
  return "A this-year dream needs emergency plus cash to be greater than the index percent.";
}
```

Fail the check and FinZ asks Gemma once more, with the reason in the prompt. Fail again and the request errors. The bad sketch is not saved.

The open-weight model is Google's Gemma 3, tag `gemma3:4b`, served by [Ollama](https://ollama.com) at `127.0.0.1:11434`. The agent harness is [Mastra](https://mastra.ai) (`@mastra/core`), connected with `ollama-ai-provider-v2`. I did not fine-tune anything. The model is stock. The behavior comes from the prompt, a loose JSON schema, and the checks above.

Three calls:

I tried `gemma3:1b` first. It parsed the matcha note correctly, then invented a purchase when I asked for a mood. `gemma3:4b` stayed on the entries that were actually there. That is the bar. The smaller tag is still a switch, `OLLAMA_MODEL=gemma3:1b`, for a laptop that cannot hold the 4B weights. The harness stays put.

Around the model, the app is Next.js 16, TypeScript, and Tailwind. Amounts are integer cents. Onboarding asks for a name, a currency (USD, INR, EUR, or GBP), take-home, how often it lands, how much movement you can stand, and one to three dreams. The note box will finish the word under the cursor from a local list, so `spe` can become `spent`. That completion is the word list.

Run it yourself and Node 22's built-in `node:sqlite` stores the profile, the dreams, and the notes in `data/finz.db`. The hosted demo cannot keep that file, so its copy of the ledger lives in the browser, and a note that the server cannot read falls back to a direct call from the page to local Ollama. The Mastra path, the one with the repair step, is the app and Ollama on the same computer. A 4B model on a laptop is allowed to be slow: each call can take up to 150 seconds a step.

This desk is pointless if the note leaves the laptop. The dream in the screenshot is a sister turning 30, and the wish is to be in the room rather than the group chat. That sentence, next to a paycheck, is a bad thing to drop into a request log someone else owns. A closed API can publish a retention policy. Ollama on localhost is a stronger promise: the weights are already on the machine, and the call is to `127.0.0.1`.

A side hustle this small also dies if every note has a meter on it. Reading a line, writing two sentences, and sketching three numbers will not support a token bill. One `ollama pull` and the marginal cost of a mood is electricity. I can charge for the desk, or wait until someone asks to pay, and a vendor's price change does not decide whether the project still runs.

I needed to be able to throw the model out. `gemma3:1b` lied about a purchase. I pointed the same Mastra agent at `gemma3:4b` and kept the checks. A closed default model would have left me waiting on someone else's changelog, or paying up a SKU, to fix a failure I could already see. The check that rejects an invented dollar amount is there because an open model will wander, and I get to decide what counts as a finished answer.

Nothing in that loop needs an account, a key, or a network I trust. Pull the weights once. On a plane, on cafe wifi, or on a laptop that has never held an API key, FinZ still reads the note.

`gemma3:4b` through Ollama. The 4B tag replaced 1B because the smaller one invented a purchase.`id: "finz"`, is the harness over that local model. It asks for JSON, and it gets one repair step when a reflection or a split fails the check.
