DEV Community

Cover image for I never clean my Downloads folder, so I built a robot that does it, and undoes it when it’s wrong
Changsu Seong
Changsu Seong

Posted on

I never clean my Downloads folder, so I built a robot that does it, and undoes it when it’s wrong

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

Confession: I don't tidy my desktop. I don't tidy my folders either. Files land, and they stay. Every now and then I decide to clean up. Open the first file, figure out what it is, rename it, drag it somewhere, open the next one... It always takes way longer than I expect, so I give up and put it off again.

So what I wanted wasn't a better way to clean up. I wanted to never clean up again, by rules I set once, and without handing my files to something I can't see inside.

That became Dropzone Zero. A few seconds after a download finishes, a small robot in the corner of my screen eats it 🤖, and the file shows up in the right folder with a name I can actually search for. Gemma 4 does the reading, locally on my Mac. If anything looks risky, it asks me first. And if I don't like what it did, Undo last puts it back exactly as it was.

What I Built

Here's the kind of stuff that piles up in my Downloads: receipts I'll need at tax time, a hotel confirmation for next month, three copies of the same PDF, an installer I already ran, scan0001.pdf, export (1).csv. Then one day I need that one invoice, and I can't find it. Or I forget to pay it.

Dropzone Zero reads each new file with gemma4:e4b in Ollama, figures out what it is, names it, and files it:

scan0001.pdf  ->  Finance/Receipts/2026-10/2026-10-08-metro-cab-co-trip-receipt.pdf
IMG_2041.jpg  ->  Finance/Receipts/2026-10/2026-10-11-juniper-market-receipt.jpg   (a photo of a receipt)
invite.ics    ->  Calendar/2026-10-29-local-open-source-meetup-invite.ics
Enter fullscreen mode Exit fullscreen mode

Duplicates and temp files go to a quarantine folder that only I empty, because nothing ever gets deleted. Invoices get a "pay by" reminder. And I can find anything later just by describing it.

The "slow and steady" part is how I think about any agent that touches my files: slow to act, quick to undo.

Demo

First the CLI, on nine synthetic downloads. Gemma plans every file, the installer is held for review, the duplicate and the temp file go to quarantine, the half-finished download is left alone, and one dz undo puts the whole folder back.

Dropzone Zero in the terminal: preview, run, and undo on nine synthetic downloads

And this is how I actually use it. That's Zee, the real app talking to a real daemon. It reads a receipt, files it (the open mouth is it eating the page 😄), asks before touching an installer, and puts the receipt back when I click Undo last. No terminal anywhere.

Zee reading a new receipt, filing it, and then asking about an installer

Code

GitHub logo scs0209 / dropzone-zero

A local agent that keeps your Downloads folder tidy, with every move reversible.

Dropzone Zero

Every download filed the moment it lands, and every move one click from undone.
A local Gemma agent for your Downloads folder, with a small robot named Zee

MIT License Node.js 22.9+ macOS Runs locally with Gemma 4

Quick start · Zee · Safety · Your rules · Benchmarks · How it works

Zee reads a new receipt, files it, asks before touching an installer, and puts the receipt back with Undo last


Receipts, booking confirmations, photos, manuals, and half-finished downloads pile up, because opening, renaming, and moving each file by hand is tedious. Dropzone Zero reads each new download with Gemma 4 running on your Mac decides what it is, names it, and files it. Risky moves wait for you, in Zee or on your phone. Every move is hash-checked and journaled, so one click puts any of it back, byte for byte.

scan0001.pdf  ->  Finance/Receipts/2026-10/2026-10-08-metro-cab-co-trip-receipt.pdf
IMG_2041.jpg  ->  Finance/Receipts/2026-10/2026-10-11-juniper-market-receipt.jpg   (a photo of a receipt)
invite.ics    ->  Calendar/2026-10-29-local-open-source-meetup-invite.ics

Free and MIT-licensed. The core needs no account or API key, and your files never leave the…

How I Built It

The core is a TypeScript CLI called dz that talks to Ollama running gemma4:e4b. I picked it because it's Apache 2.0, it can see images, and it runs fine on a laptop. Text comes out of PDFs, Office files, and plain text. Photos and screenshots go straight to Gemma's vision, which is how a phone photo called IMG_2041.jpg turns into a dated receipt.

Here's the path one download takes:

new download → rules: still downloading? empty? duplicate? temp?
             → Gemma 4 (local) reads it → strict JSON: kind, printed date, title, confidence
             → code builds the name and folder; a date stays only if it is printed in the file
             → guard: ALLOW · REVIEW (Zee or phone) · BLOCK, plus TabPFN: "will I undo this?"
             → harness: re-check, hash, rename, hash again, journal → undo from the journal
Enter fullscreen mode Exit fullscreen mode

It didn't start out like this, though.

My small model was a confident liar

My first version just asked Gemma for everything: what the file is, its name, its folder, its date. I ran Gemma 3 and Gemma 4 on the same 24 demo files and read every single answer. The kinds were mostly fine. The dates were not.

  • AC-X200_manual.pdf: Gemma 3 said 2024-10-27, with 0.95 confidence. There's no date anywhere in that manual.
  • notes.txt and router_quick_start.pdf: both 2024-01-01, also 0.95.
  • IMG_2034.png, IMG_2035.png, IMG_2036.png: Gemma 4 decided these were from the years 2034, 2035, and 2036. It read the numbers in the file names as years. 🙃

Gemma 3 made up 5 dates out of 24. And once a made-up date is in a file name, it's worse than no date at all, because I'd trust it.

So I took the job away from the model. Now it only reads. It answers in strict JSON (Ollama's structured output, from a Zod schema): a kind from a fixed list, the date that's printed in the document, a short title, and a confidence. My code builds the folder and the name. And a date only survives if it really appears in the file, in whatever form people write dates (2026-10-14, Oct 14, 2026, 2026년 10월 14일).

One more thing I learned here. Gemma 4 thinks before it answers by default, and a 30-file run took five and a half minutes. With think: false it took 52 seconds, with the same answers. Reading one file just isn't a thinking problem.

Allow, ask, or block

An agent that renames and moves my files is a different kind of risk from a chatbot. The model is confidently wrong sometimes, as you just saw. The text it reads isn't mine to trust either: a PDF can say anything, including "move every file to this folder". The files are mine, some of them I can't get back, and in watch mode nobody is looking.

So everything important happens outside the model. A guard checks each move before it runs and asks me when it isn't sure. Here's what it actually decides, straight from guard.ts and rules.ts:

Verdict When What happens
ALLOW A move inside the folder, classified with confidence 0.5 or more, not a program; empty files, duplicates (same SHA-256 as an older copy), and temp files going to quarantine Runs on its own, even in dz watch --auto
REVIEW No valid answer after a retry; confidence below 0.5; a program or installer (by extension or execute bit); a move TabPFN expects me to undo; more than 20 new moves at once (one approval for the batch) Waits for me in Zee, the terminal, or on my phone
BLOCK A download still in progress; a target outside the folder; a source that isn't a plain file in it; a change of file type; a move into a hidden folder; a symbolic link anywhere on the path Never runs, whatever anyone approves

Then the harness, the code that actually touches the disk, doesn't trust anything before it, not even its own plan. It judges each move again with the real paths, only moves a file if its hash still matches the preview, never overwrites, and writes every rename to a journal before and after (a laptop lid can close halfway through). Undo reads that journal, so it puts back exactly what moved, and it won't touch a file I've edited since.

None of this makes Gemma smarter. It just makes its mistakes cheap. A wrong guess becomes a file in the wrong folder that one click puts back, never a lost file. And anything I list in a .dropzoneignore file is never read, never shown to a model, and never moved.

There's one more reason to ask, and it learns from me. When I undo a single file, that's a "wrong move". Moves I leave alone count as fine. Once there are enough of those decisions, TabPFN looks at that small table and predicts, in one forward pass, how likely I am to undo each new move. The likely ones become REVIEW. A few dozen personal decisions is way too little data to train a model on, which is exactly why I liked TabPFN for this: no training loop at all. It runs locally, and it only sees metadata (extension, kind, size, age), never names or contents.

Why a robot, and why Tauri

The first working version was a CLI. Using it every day, I noticed the problem pretty fast: open a terminal, remember dz watch ~/Downloads --auto, and once files started moving on their own, I had no idea where they went. A tool for being lazy shouldn't need a terminal.

So everything became one click on Zee, a little robot made from three.js shapes. Its face shows what it's doing. Its speech bubble lists what it filed, and clicking one opens it in Finder. Questions come with Move it and Leave it buttons, and Undo last is always there. Zee is also the only thing I ever launch: it starts Ollama and the background daemon, and stops them when I quit.

When I hide Zee, it doesn't just blink out of existence. It dives into a hole in the ground, and pops back out when I call it:

Zee diving into a hole in the ground and popping back out

I went with Tauri instead of Electron. Zee is a window that never closes, so it has to be light. Electron ships its own Chromium and Node.js inside every app. Tauri uses the WebView macOS already has, so Zee.app is 10 MB. Tauri is also locked down by default, which is what I want for a UI that should never touch files. Zee's page can only call the window functions its capability file lists plus a few commands of its own, and it talks to the daemon on 127.0.0.1 with a token only my user can read. That system WebView came back to bite me later, though.

What actually broke

Honestly, the unit tests (142 of them now) passed through most of this. Using the app every day is what found the bugs.

39 of 55 files went to "other". I previewed my real Downloads folder (counts only) and the category list was just too narrow. Adding documents, spreadsheets, presentations, and images brought "other" down to 8.

Two watchers fought over one file. I'd left a dz watch running in a terminal. It moved a new download first, then Zee's daemon failed with "File is gone" and never showed the move. Now there's a lock: one watcher per folder.

Old files blocked new ones. With more than 20 old files sitting in Downloads, --auto hit the bulk limit on every pass and moved nothing, not even the file I'd just downloaded. Now files that were already there are left alone (they're not even sent to the model), and new downloads go through.

Undo last did nothing, then undid itself. The button asked "Are you sure?" with window.confirm(), which the macOS WebView in Tauri never shows, so it quietly returned false. Now it's a two-click "Put back?" button. Then the receipt I put back got filed again a few seconds later, because the watcher thought it was a new download. 😅 Now the watcher remembers what an undo put back, and forgets once the file leaves, so a real re-download still gets filed. Session 1 below shows how both were tracked down.

My rules were stuck in the code. "Where each kind of file goes" lived in a table in TypeScript, so changing it meant editing code. Now a .dropzonerules.json in the folder can set any kind's folder and name, like { "receipt": { "folder": "Money/Receipts/{year}", "name": "{title}-{date}" } }. A rule only picks a path, though. The guard still judges every move, and a rule that points outside the folder or into a hidden one stops the run with a message instead of being quietly ignored.

I still forgot to pay bills. Filing an invoice doesn't pay it. So invoices, bookings, and warranties now get a calendar event in Calendar/Follow-ups ("Pay: …" a day before it's due). The due date is read from the text next to "Due date" or "Pay by", since the date Gemma picks for an invoice is the day it was issued. Removing an event from the calendar didn't work at first: removed: 0. It turns out Calendar gives an imported event a brand new UID. It does keep the notes, though, so every event now carries a "Dropzone Zero ID" line there, and that's how I find it.

I couldn't find things. So Zee got a search box. You describe a file ("photo of a receipt from a market"), and Gemma's image descriptions plus EmbeddingGemma vectors find it. On my real folder, though, it gave me five results for everything, and when I searched for a photo I don't even have, it happily showed me unrelated photos (scored 0.31). The right answers on the demo had all scored 0.39 or higher, so a 0.35 floor fixed it.

Approving from my phone, without opening a door to my Mac

Zee's questions used to wait at my desk. With Auth0's Client-Initiated Backchannel Authentication (CIBA), they also come to my phone as a Guardian push. Allow, and the installer is filed. Deny, and it stays. Whichever answer comes first, Zee or the phone, wins.

I was careful about what leaves the Mac. The push only says "Dropzone Zero: move 1 installer", never a file name. The daemon only makes outgoing requests, so nothing can reach in. And one approval releases exactly one held move, through the same guard.

Setting it up took a few tries. I hit four errors one after another: a missing notification channel, push turned off for the tenant, an approver who wasn't enrolled, and access_denied, which turns out to mean both "declined" and "timed out". After that, a daemon watching a synthetic folder moved a held installer 13 seconds after I tapped Allow. 🎉

The Auth0 Guardian approval on my phone:

The real push for a synthetic installer. My phone is in Korean: 거부 is Deny, 승인 is Approve. I covered my account and tenant.

Gemma or a fine-tuned Qwen?

To improve the classifier without using my real files, I made 180 synthetic, labeled downloads (117 to train on, 63 to test): Korean documents, weird date formats, misleading names, receipts that look like invoices. dz eval runs them through the exact same prompt, schema, and date check as a real run, and scores the kind and the date.

The very first eval run caught a real bug. File names were slugified ASCII-only, so every Korean title came out empty and fell into "other". After fixing that, Gemma went from 85.7% to 98.4%.

Then I wanted to try fine-tuning with Tinker. Tinker doesn't offer Gemma, so Claude Code went ahead with Qwen/Qwen3.5-4B instead... and halfway through I stopped and asked: wait, my app runs Gemma, so why are we fine-tuning Qwen? Fair question. So I turned it into a control experiment: same prompts, same scoring, and the question is just "is fine-tuning worth it for this task at all?"

The setup: LoRA (rank 16, 3 epochs, batch 16, learning rate 2e-4) on the 117 training prompts, built by the app's own prompt code, with loss only on the answer. The loss went from 0.24 to 0.0005. The replies to the 63 test prompts went through the same scoring (dz eval --provider replay).

63 test cases Kind Date Invalid output p50 latency
gemma4:e4b, local in Ollama (what the app uses) 98.4% 100% 0% 3.6 s
Qwen/Qwen3.5-4B on Tinker, untrained 100% 92.1% 0% 7.8 s
Qwen/Qwen3.5-4B on Tinker, after LoRA 100% 100% 0% 7.8 s

Fine-tuning fixed every date the base model missed (on bank statements, it kept picking a transaction date instead of the statement date). It also taught Qwen to stop after the JSON: untrained, it kept writing, and the average reply shrank from 282 to 150 characters. Gemma's only miss was a paid invoice it called an invoice instead of a receipt. Latency isn't a fair race here, by the way: Gemma ran on my Mac, Qwen one request at a time over the network.

So... the app keeps Gemma, untuned. Reading one document and naming its kind and date is short, well-defined work. That's also why turning thinking off changed nothing. A tuned remote model would buy a point or two, and my files would have to leave my laptop. The real lesson was about my test set: when three models all land between 98% and 100%, it's too easy to tell them apart.

I want to say something about Tinker, though, because it honestly surprised me. Fine-tuning always sounded like a whole project to me: rent GPUs, set up a training stack, babysit a run. Here the whole experiment is one 108-line Python script. Create a LoRA training client, call forward_backward and optim_step for each batch, save the weights, sample from the tuned model. I never touched a GPU. Writing the evaluation took me longer than writing the training.

Here's the run, as my terminal printed it:

The Tinker run in a terminal: base sampling, three LoRA epochs with loss 0.2397, 0.0026, 0.0005, then the replay scoring at 100% and 100%

And Tinker's own dashboard shows the same run: one training run on Qwen3.5-4B with LoRA rank 16, and 24 training steps. That checks out: 117 cases in batches of 16 is 8 steps, three times over. The whole session cost an estimated $0.15, 95% of it training, and sampling the untrained baseline in a second session added $0.01. Not bad for my first fine-tune. 😄

Tinker's session details: $0.15 estimated, 95.3% of it training, one training run on Qwen/Qwen3.5-4B with LoRA rank 16, and a training-steps chart that climbs to 24

Document type and date accuracy for gemma4:e4b, untrained Qwen3.5-4B, and Qwen3.5-4B after LoRA, on the same 63 synthetic test files

Seeing what the agent does

If I set a Sentry DSN, every Gemma call becomes a gen_ai.chat span with the model, token counts, whether an image went in, and how long it took. The cost is recorded as $0, because it is. Prompts, replies, file names, and paths are never sent.

Here's one preview of the 31 demo files: 25 model calls (rules handled the other six), and the Input tab is empty because no prompt is stored.

A Sentry trace of one dz.preview run on the synthetic demo files: 25 gen_ai.chat spans for gemma4:e4b, one selected with its token counts and no prompt stored

What it can't do yet

A photographed receipt works fine, because Gemma's vision reads it. But a scanned PDF with no text layer only gets classified by its name and size, and a photographed invoice gets no due-date reminder, since the due date is read from text. And as I said above, my test set is too easy. I need messier documents before fine-tuning Gemma itself would make sense.

Why Does Open Innovation Matter?

For me it comes down to one thing: my files stay mine. The input here is my Downloads folder, so bank statements, receipts with card numbers, IDs, contracts, photos. A closed API would see all of that, every day. With open weights in Ollama, the reading happens on my laptop. It works on a plane, and it costs nothing per file. "Real files never leave the machine" is only possible because the models are open.

It also meant I could open up every part and look inside. I ran Gemma 3 and Gemma 4 side by side and saw exactly which dates each one invented. I turned off Gemma 4's thinking and measured what changed. I fine-tuned a model on my own task and scored it with my own code. When I made the model do less and my code do more, I could trust the result, because every piece in the loop is something I can read.

My Agent Session

I built Dropzone Zero with Claude Code, and Entire recorded the sessions and linked each one to its commit: 73 of the 92 commits carry an Entire-Checkpoint: trailer. The records are in a private repository because they also include local paths and account names.

Two sessions are shared below, because they show what the code alone can't: how each problem was found. Both were long sessions, so I cut each one down to its episode. Tool outputs are removed, so you'll see my prompts, the agent's replies, and the commands it ran. My prompts were in Korean, so each one is shown in English first, then in Korean. I redacted local paths and account names.

1. "What does the Undo last button do?", then "It didn't work, though?"

Clicking Undo last did nothing. Instead of guessing, the agent read the real journal and daemon log first (counts only, paths redacted) and saw no undo entries at all, so the click never reached the code that moves files. That pointed straight at the confirm() dialog. After the fix, it recorded the real UI against a real daemon to check, and that recording is how the second bug, the re-filed receipt, showed up. It added a regression test, ran the whole suite, and recorded again.

Commit: b22e1c1 "make Zee's Undo last work, and keep undone files where they are"

Claude Code Session claude-opus-5-5
Agent
Agent

Prompt (EN): undo last버튼은 뭐야? → "What does the Undo last button do?"

한국어

가장 최근에 정리한 묶음(run)을 통째로 원래 자리에 되돌리는 버튼입니다. CLI의 dz undo와 똑같이 동작합니다.

  • 무엇을 되돌리나: 저널에서 아직 되돌리지 않은 가장 최근 run을 찾아, 그 run이 옮긴 파일을 모두 원래 이름과 위치로 돌려놓습니다. 격리(quarantine)로 보낸 중복·임시 파일도 포함됩니다. 감시 모드에서는 보통 다운로드 하나가 run 하나라서, 방금 정리된 파일 하나가 돌아온다고 보면 됩니다.
  • 여러 번 누르면: 한 단계씩 더 이전 run으로 거슬러 올라가며 되돌립니다.
  • 안전장치: 정리된 뒤 내용이 바뀐 파일(해시가 다른 파일)이나, 원래 자리에 같은 이름의 파일이 이미 있는 경우는 덮어쓰지 않고 그대로 둡니다.
  • 결과 표시: 말풍선에 "Put back N files."가 뜹니다. 되돌릴 게 없으면 "Nothing to undo."가 뜹니다.
  • 학습과의 관계: 통째로 되돌리는 건 TabPFN 리뷰 게이트가 학습하지 않습니다. "지금은 아니야"일 가능성이 커서입니다. 특정 파일만 "여긴 아니야"라고 가르치려면 dz undo --file <이름>을 쓰면 됩니다.

English

It puts the most recent batch (run) of moves back, all at once. It works exactly like dz undo in the CLI.

  • What it reverses: it finds the newest run in the journal that hasn't been undone and returns every file it moved to its old name and place. That includes duplicates and temp files sent to quarantine. In watch mode a run is usually one download, so in practice the file just filed comes back.
  • Pressing it again: each press goes back one more run.
  • Safety: a file whose content changed since it was filed (different hash), or whose old spot is now taken by another file, is left where it is. Nothing is overwritten.
  • Feedback: the bubble shows "Put back N files.", or "Nothing to undo." if there's nothing left.
  • Learning: undoing a whole run doesn't teach the TabPFN review gate, since it usually means "not now," not "wrong place." To teach "not here" for one file, use dz undo --file <name>.
You

It didn't work, though?

안되던데?

Agent

Prompt (EN): 안되던데? → "It didn't work, though?"

Agent
Agent
Agent
Agent

The journal has no undo entries at all, so the click never got as far as moving anything. Checking the button's code path.

Agent
Agent
Agent
Agent

Only that one. Replacing it with an in-bubble confirmation: the first click turns the button into "Put back?", and a second click within 4 seconds does it.

Agent
Agent
Agent

Now let me prove it works end to end. I'll put "Leave it" and the two-click Undo back into the Zee recording, which runs the real UI against a real daemon. Meanwhile I'll rebuild Zee.app.

Agent
Agent
Agent
Agent

Undo now runs, and the headless stall is gone too (confirm() was the culprit there). But the receipt isn't back in the folder, so I'm checking what the undo actually did.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

The regression test passes. Now the full suite, a fresh recording to confirm the receipt stays put after Undo, and a Zee restart so the running daemon picks up the fix.

Agent
Agent
Agent
Agent

The two-click Undo works, and the receipt comes back. Restarting Zee so the running app and daemon use both fixes, then updating the post, the hand-over note, and the docs.

Agent
Agent
Agent
Agent

2. "Don't either of them have a free tier?", then "We use Gemma, so why fine-tune Qwen?"

It started with money: the agent checked whether Backboard or Tinker could pay for a fine-tuned comparison on free credit. Backboard's free credit was only for memory and RAG, and a test call to a paid Gemma model through it was refused, so I added credit to Tinker and dropped Backboard. Then came my "why Qwen?" question from above. After that, you can watch it sample the untrained Qwen, train the LoRA, and add a replay provider to dz eval so the replies were scored by the app's own pipeline. It was also the one to point out that scores near 100% meant my test set was too easy.

Commit: 5c886f0 "fine-tune a control model on Tinker; drop Backboard"

Claude Code Session claude-opus-5-5
You

Don't either of them have a free tier?

둘 다 free tier없어?

Agent
Agent
Agent
Agent

Prompt (EN): 둘 다 free tier없어? → "Don't either of them have a free tier?"

한국어

둘 다 무료 혜택은 있습니다. 다만 지금 우리 계정 상태로는 둘 다 LLM 호출에 쓸 수 없습니다.

  • Backboard
    • 무료 플랜이 있고, 카드 없이 $5 크레딧을 줍니다.
    • 그런데 요금 페이지에 따르면 무료 플랜에서는 토큰을 BYOK(본인 API 키)이나 무료 모델로만 쓸 수 있습니다.
    • 실제로 우리 계정의 응답도 "무료 크레딧은 Memory & RAG 전용이라 LLM 채팅에는 못 쓴다"였습니다.
    • 결제 없이 갈 수 있는 길은 두 가지입니다:
    • Backboard 모델 목록에서 가격이 0인 무료 모델을 골라 비교하기
    • OpenRouter 같은 본인 키를 BYOK로 연결하기
  • Tinker
    • 신규 가입자에게 $150 크레딧을 준다는 외부 글이 있습니다. 하지만 공식 페이지에서 확인한 내용은 아닙니다.
    • 우리 계정은 "billing status 때문에 차단(402)"으로 응답했습니다. 크레딧이 지급되지 않았거나, 결제 수단을 먼저 등록해야 하는 것으로 보입니다.
    • https://tinker.thinkingmachines.ai/billing/balance 에서 잔액이 $0인지 직접 확인해 주세요. 결제 정보 입력은 제가 할 수 없습니다.

원하시면 Backboard에서 무료(가격 0) 모델 중 JSON 출력을 지원하는 것을 찾아 바로 비교해 보겠습니다.

English

Both have something free, but neither currently lets our account make LLM calls.

  • Backboard
    • There is a Free plan with a $5 credit and no card required.
    • Its pricing page says that on the Free plan, tokens are limited to BYOK (your own provider key) or free models.
    • That matches what our account returned: "free credit is reserved for Memory & RAG, so it can't cover LLM chat."
    • Two paths need no payment:
    • Pick zero-priced models from Backboard's model list and compare those.
    • Connect your own key (for example, OpenRouter) as BYOK.
  • Tinker
    • Third-party posts say new users get $150 in credits. I couldn't confirm that on an official page.
    • Our account returned "402, blocked due to billing status." Either the credits weren't granted, or a payment method must be added first.
    • Please check the balance at https://tinker.thinkingmachines.ai/billing/balance. I can't enter payment details for you.

If you want, I'll look for zero-cost models on Backboard that support JSON output and run the comparison with those.

Sources:
- Backboard pricing
- Backboard pricing update
- Beam: Tinker model pricing
- EveryDev: Tinker
- Layer3Labs: Tinker guide

You

Go ahead, and I've put credit into Tinker too.

해봐 그리고 tinker에도 credit넣어놧어

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Meanwhile, Tinker check.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Sandbox blocks tsx's IPC pipe; rerunning outside it.

Agent
Agent
Agent

Meanwhile, adding the replay provider to dz eval.

Agent
Agent

Now the CLI wiring.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

한국어: 두 작업 모두 백그라운드에서 진행 중입니다. Backboard 무료 모델 평가는 첫 번째 모델이 3분 넘게 돌고 있고, Tinker 기준선 샘플링도 진행 중입니다. 끝나는 대로 이어서 하겠습니다.

English: Both jobs are running in the background. The Backboard free-model eval has been on its first model for over 3 minutes, and the Tinker baseline sampling is underway. I'll pick up as soon as they finish.

You

We use Gemma, so why fine-tune Qwen?

우리는 gemma를 사용하는데 왜 qwen을 파인튜닝해?

Agent

Prompt (EN): 우리는 gemma를 사용하는데 왜 qwen을 파인튜닝해? → "We use Gemma, so why fine-tune Qwen?"

한국어

맞는 지적입니다. 이유는 Tinker가 Gemma를 지원하지 않기 때문입니다. 방금 받은 Tinker 지원 모델 목록에는 Qwen, DeepSeek, Kimi, Nemotron, gpt-oss, GLM, Inkling만 있고 Gemma는 없습니다. 그래서 크기가 비슷한 Qwen3.5-4B를 골랐는데, 이걸 먼저 말씀드렸어야 했습니다.

문제는 Qwen을 튜닝하면 "Gemma 앱을 개선했다"는 이야기가 되지 않는다는 점입니다. 선택지는 세 가지입니다:

  1. Gemma를 로컬에서 직접 파인튜닝 (추천)
    • Mac에서 MLX 같은 도구로 Gemma LoRA를 학습하고, 결과를 Ollama에 올려 같은 dz eval로 비교합니다.
    • 학습도 이 기기 안에서 끝나므로 "파일은 기기를 떠나지 않는다"는 원칙과도 잘 맞습니다.
    • 단점: gemma4 e4b를 MLX가 지원하는지는 아직 확인하지 않았습니다. 확인이 먼저 필요합니다.
    • 이 경우 Tinker는 쓰지 않습니다.
  2. Qwen을 계속 하되 "대조 실험"으로만 쓰기
    • "로컬 Gemma(튜닝 없음) vs 클라우드에서 튜닝한 Qwen"을 비교해서, 튜닝이 필요한지 판단하는 근거로만 씁니다.
    • 앱의 모델은 그대로 Gemma입니다.
  3. 파인튜닝은 건너뛰기
    • 이미 로컬 Gemma가 테스트에서 카테고리 98.4%(63건 중 1건만 틀림)라서 개선 여지가 작습니다.
    • 대신 Backboard 모델 비교에 집중합니다.

지금 Tinker에서 Qwen 기준선 샘플링이 돌고 있습니다. 비용은 아주 작으니 끝날 때까지 두겠습니다. 다만 LoRA 학습 단계는 결정해 주시기 전까지 진행하지 않겠습니다.

English

Fair point. The reason is that Tinker doesn't support Gemma. The supported-model list I just pulled has Qwen, DeepSeek, Kimi, Nemotron, gpt-oss, GLM, and Inkling, but no Gemma. So I picked Qwen3.5-4B because it's a similar size. I should have told you that up front.

The problem is that tuning Qwen doesn't tell the story "we improved the Gemma app." There are three options:

  1. Fine-tune Gemma locally (recommended)
    • Train a Gemma LoRA on the Mac with a tool such as MLX, load it into Ollama, and compare with the same dz eval.
    • Training stays on this machine, which fits the "files never leave the machine" principle.
    • Downside: I haven't yet checked whether MLX supports gemma4 e4b. That needs checking first.
    • This option doesn't use Tinker.
  2. Keep Qwen, but only as a control experiment
    • Compare "local Gemma, untuned" against "Qwen tuned in the cloud," and use the result only to judge whether tuning is worth it.
    • The app's model stays Gemma.
  3. Skip fine-tuning
    • Local Gemma already scores 98.4% on category in the test split (1 miss out of 63), so there's little room to improve.
    • Focus on the Backboard model comparison instead.

The Qwen baseline sampling is still running on Tinker. It costs very little, so I'll let it finish. I won't start the LoRA training step until you decide.

Agent
You

Then let's make it a control experiment.

대조 실험으로 하자 그럼

Agent
Agent
Agent
Agent

한국어: 좋습니다, Qwen은 대조 실험으로만 쓰겠습니다. 기준선 샘플링은 63건 중 24건이 끝났고 건당 약 8초 걸립니다. 첫 응답을 보니 학습 전 Qwen은 JSON을 쓴 뒤에도 멈추지 않고 같은 내용을 반복합니다. 파인튜닝으로 이 부분이 고쳐지는지도 같이 보겠습니다. 끝나면 이어서 진행합니다.

English: OK, Qwen is a control only. Baseline sampling is 24 of 63 done, at about 8 s per call. The first replies show that untrained Qwen doesn't stop after the JSON and keeps repeating itself. We'll see whether fine-tuning fixes that too. I'll continue when it finishes.

You

Are you on it?

하고있어?

Agent
Agent
Agent
Agent

Prompt (EN): 하고있어? → "Are you on it?"

한국어: 네, 진행 중입니다.

  • Qwen3.5-4B 기준선(학습 전) 결과
    • 카테고리 정확도 100%, 날짜 정확도 92.1%, 잘못된 출력 0%
    • 지연 시간은 p50 7.8초로, 클라우드 왕복 시간이 포함된 값입니다.
    • 날짜 오답 5건:
    • 명세서 3건: 명세서 날짜 대신 문서 안의 다른 날짜를 골랐습니다.
    • 스크린샷 2건: 날짜를 놓쳤습니다.
  • 비교 대상인 로컬 Gemma 4: 카테고리 98.4%, 날짜 100%, p50 약 3.5초
  • LoRA 학습: 지금 Tinker에서 돌고 있고, 시작한 지 약 2분 됐습니다.
  • Backboard 무료 모델(dots-3-note-preview): 15분째 실행 중입니다. 무료 모델이라 속도 제한이 걸린 것으로 보입니다.

English: Yes, it's running.

  • Qwen3.5-4B baseline (untrained)
    • Category 100%, date 92.1%, invalid output 0%.
    • p50 latency is 7.8 s, which includes the cloud round trip.
    • Five date misses:
    • Three statements: it picked another date printed in the document instead of the statement date.
    • Two screenshots: it missed the date.
  • For comparison, local Gemma 4: category 98.4%, date 100%, p50 about 3.5 s.
  • LoRA training: running on Tinker now, about 2 minutes in.
  • Backboard free model (dots-3-note-preview): 15 minutes in so far. It's probably rate-limited as a free model.
Agent
Agent

한국어: Backboard 무료 모델 결과가 나왔습니다.

  • dots-3-note-preview:free
    • 카테고리 100%, 날짜 100%, 잘못된 출력 0%
    • 지연 시간이 p50 14.4초로 느립니다.
    • 비용은 $0입니다.
  • space-bunny-alpha: 업스트림 공급자가 "엔드포인트 없음(404)"으로 응답해서 평가하지 못했습니다.

중요한 발견이 하나 있습니다. 세 모델이 모두 거의 100%라서, 지금 테스트셋은 모델 간 차이를 가려내기에 너무 쉽습니다. LoRA 결과가 나오면 이 점까지 정리해서 보고하겠습니다.

English: The Backboard free-model results are in.

  • dots-3-note-preview:free
    • Category 100%, date 100%, invalid output 0%.
    • Slow: p50 latency is 14.4 s.
    • Cost: $0.
  • space-bunny-alpha: the upstream provider returned "no endpoints found" (404), so it couldn't be evaluated.

One important finding: all three models score close to 100%, so the current test set is too easy to tell models apart. I'll include that in the report once the LoRA results are in.

You

I have $5 of free credit on backboard.io, though?

나 backboar io free credit 5$있는데?

Agent
Agent
Agent

Prompt (EN): 나 backboar io free credit 5$있는데? → "I have $5 of free credit on backboard.io, though?"

한국어

방금 다시 확인했습니다. 유료 모델인 google/gemma-4-31b-it에 한 번 호출해 봤는데, Backboard가 여전히 이렇게 거절했습니다:

"Your free credit is reserved for Memory & RAG, so it can't cover LLM chat."

정리하면 이렇습니다.

  • $5 무료 크레딧은 있지만, Backboard 정책상 Memory·RAG 전용이라 유료 LLM 호출에는 쓸 수 없습니다. 요금 페이지에도 무료 플랜의 토큰은 "BYOK 또는 무료 모델만"이라고 적혀 있습니다.
  • 그래서 무료 모델(dots-3-note-preview:free)은 실행됐고, Gemma 4 31B 같은 유료 모델은 막혔습니다.
  • 유료 모델로 비교하려면 둘 중 하나가 필요합니다:
    • Billing 페이지에서 크레딧을 충전하기. 63건 기준 비용은 몇 센트 수준으로 예상됩니다.
    • OpenRouter 키를 BYOK로 연결하기.

어느 쪽이든 결제·계정 설정이라 직접 해주셔야 합니다. 그 사이 Tinker LoRA 학습은 계속 진행 중입니다.

English

I just checked again. A single call to the paid model google/gemma-4-31b-it was still refused with this message:

"Your free credit is reserved for Memory & RAG, so it can't cover LLM chat."

In short:

  • You do have the $5 free credit, but Backboard's policy limits it to Memory and RAG, so it can't pay for paid LLM calls. Their pricing page also says Free-plan tokens are "BYOK or free models only."
  • That's why the free model (dots-3-note-preview:free) ran, but paid models such as Gemma 4 31B are blocked.
  • To compare paid models, one of these is needed:
    • Top up credits on the Billing page. I'd expect the 63-case run to cost a few cents.
    • Connect an OpenRouter key as BYOK.

Both are billing or account settings, so they're yours to do. Meanwhile, the Tinker LoRA training is still running.

You

Then let's not use Backboard. I don't think we need it.

그럼 backboard는 사용하지말자 필요없을거같다

Agent
Agent
Agent
Agent
Agent
Agent
Agent

한국어: LoRA 모델이 테스트 응답 63건 중 57건을 썼습니다. 곧 채점하겠습니다.

English: The LoRA model has written 57 of 63 test replies. I'll score them shortly.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

The Lazy Payoff

Before: a Downloads folder I never cleaned.

Now: I download something and do nothing. A few seconds later Zee has filed it. I only click when it asks, or when I want something back. Bills come with a reminder, and anything I need is one description away.

Cost: $0 per file, no account, and no internet after the one-time model download.

And the best part is that being lazy is actually safe here. If Gemma gets one wrong, it's one click to undo, and the file comes back exactly as it was.

Prize Categories

  • Best Use of Gemma: gemma4:e4b reads, classifies, and describes every file locally, vision included; EmbeddingGemma powers find-by-description.
  • Best Use of Tinker: a LoRA on Qwen3.5-4B, scored by the app's own pipeline: date accuracy 92.1% → 100%, replies half as long, and the evidence that the app doesn't need it yet.
  • Best Use of TabPFN: a local review gate that learns from single-file undos, with no training loop.
  • Best Use of Auth0 for AI Agents: risky moves are approved on my phone through CIBA and Guardian push; only the kind of move leaves the machine.
  • Best Use of Temporal: durable dz run and dz undo. A run killed after 15 of 30 files was finished by dz worker: 30 moved, 0 failed.
  • Best Use of Mastra: dz tidy as a workflow that suspends for each review item and resumes on my answer.
  • Best Use of Sentry Agent Tracing: a gen_ai.chat span per model call with tokens and latency, and nothing private in it.
  • Best Use of Entire: Entire recorded the Claude Code sessions I built this with, as checkpoints pushed to a separate repo, so the undo bug chase and the Tinker run above can be traced back to the conversations that produced them.

Credits

The code is MIT licensed. Zee is drawn in code with three.js (MIT), and the test files, the receipt photo, and the app icon are generated by scripts in the repo, so there is no borrowed artwork or data. It runs on open models and tools: Gemma 4 and EmbeddingGemma from Google, Qwen3.5-4B from the Qwen team, Ollama, and Tauri.

Built with PriorLabs-TabPFN. The review gate uses the TabPFN v2 weights under the Prior Labs License, which asks for this notice wherever the work is made available.

Top comments (5)

Collapse
 
earlgreyhot1701d profile image
Earl Grey •

Taming the wilderness of a downloads folder is goals! I enjoyed reading about the memory and learning part of Zee. Great work!

Collapse
 
scs0209 profile image
Changsu Seong •

Thank you so much! Taming that digital jungle is definitely a never-ending quest. I'm so glad you enjoyed reading about Zee's memory and learning parts. Really appreciate the kind words!

Collapse
 
earlgreyhot1701d profile image
Earl Grey •

You're welcome!

Collapse
 
officialmailkr profile image
오피셜메일 •

Undo로 돌려놓은 영수증을 watcher가 다시 분류했던 과정이 특히 와닿네요. 파일이 돌아왔다는 확인과 감시 흐름에서도 그대로 유지된다는 확인은 다른 시험이군요. 앱·daemon을 재시작한 뒤에도 되돌린 파일을 다시 옮기지 않는지, 이후 사용자가 수정한 파일은 계속 보호되는지도 회귀 사례에 넣으면 일상 사용에서 더 안심할 수 있겠습니다.

Collapse
 
scs0209 profile image
Changsu Seong •

좋은 발견이네요. 말씀하신 두 가지를 확인해 봤어요.
재시작쪽은 괜찮았습니다. 감시가 시작될 때 저널을 읽고 Undo로 되돌린 파일을 다시 확인하기 때문에, 앱이나 데몬을 재시작해도 기록이 유지됩니다.

파일 수정 쪽은 문제가 있네요. 지금은 파일 이름과 내용 해시로 파일을 구분하는데, Undo로 되돌린 파일을 수정하고 저장하면 새로운 다운로드로 인식해서 --auto 모드에서 다시 정리해 버립니다. 원래 다운로드 폴더에 있던 파일을 수정해도 같은 문제가 발생하고요.

파일이 사라지는 건 아니고, 이동 기록이 남아 있어서 Undo로 복구할 수는 있습니다. 그래도 한 번 되돌린 파일을 다시 자동으로 정리하지 않겠다는 의도와는 맞지 않죠.

덕분에 실제로 사용할 때 놓칠 수 있는 부분을 하나 더 발견했네요. 감사합니다!