This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady.
The folder is a to-do list nobody wrote
My Downloads folder is not a storage problem. It is a stack of small decisions I keep postponing. Every file asks a small question: is this an invoice, a screenshot, something I should keep at all? Answering forty of them is not hard. It is just relentless.
So for Slow and Steady I did not build another assistant that talks. I built one that decides where a file goes, and one that knows when not to decide.
What I Built
Dozer reads a folder one level deep, one file at a time, with Gemma 3 1B running on the same machine through llama.cpp. Then it:
- Files each file into
Invoices/,Documents/,Reading/,Photos/,Screenshots/,Installers/,Archives/,Code/orDuplicates/. - Renames only the files whose names carry no information.
a1b2c3d4e5f6a7b8.pdfbecame2026-10-08_certificate-of-participation.pdf.kasha-hardware-invoice.pdfwas left exactly as it was. - Leaves a file alone when it is not sure, when it is empty, when the type is unknown, or when you touched it in the last ten minutes.
- Never deletes and never overwrites. A name collision gets a
-2. - Writes every move to
data/runs.jsonl, andundoputs the whole run back. - Works at a pace you choose, with a budget, so an interrupted night resumes where it stopped.
Demo
The whole thing is three commands:
make install && make models && make llama make demo # dashboard on a throwaway copy Or against your real folder:
make scan ROOT=~/Downloads make night ROOT=~/Downloads This is a real scan of the 19-file folder that ships in samples/, trimmed to keep it readable:
Dozer · /tmp/dozer-demo 19 files · 13 to file · 6 left alone · 9 folders asked the local model about 4 of 19 files, 4 answered from cache DOCUMENTS/ 2026-10-08_certificate-of-participation.pdf from a1b2c3d4e5f6a7b8.pdf · 95% · The text is a certificate, which is a document. 2026-09-22_tenancy-agreement.pdf from download (3).pdf · 80% · the text reads like a tenancy agreement random_notes.txt 80% · the text reads like meeting notes INVOICES/ 2026-10-12_tax-invoice.pdf from scan_0042.pdf · 85% · the text reads like a tax invoice statement-oct.pdf 85% · the text reads like an account statement READING/ doc_8831.pdf 80% · the text reads like a book LEFT ALONE data_dump.csv - I'm not sure what this is. empty.txt - empty file notes.txt - This file contains a list of vegetables and spices. It's not a standard document type. recent-scan.pdf - touched 1 minutes ago, still in use ubuntu-notes.md - The text is too short to determine the category. weird.xyz - not a type Dozer knows Then the overnight pass:
$ make night ROOT=/tmp/dozer-demo PACE=2 [1/13] a1b2c3d4e5f6a7b8.pdf -> Documents/2026-10-08_certificate-of-participation.pdf [2/13] doc_8831.pdf -> Reading/doc_8831.pdf [3/13] download (3).pdf -> Documents/2026-09-22_tenancy-agreement.pdf ... [13/13] VSCodeUserSetup-x64-1.94.2.exe -> Installers/VSCodeUserSetup-x64-1.94.2.exe MORNING NOTE I left 6 files alone. I filed 13 files into 3 Documents, 2 Invoices, 2 Reading. 4 of them needed the model and the rest were rules. The pass took 24 seconds and nothing left this machine. And the folder afterwards:
/tmp/dozer-demo ├── Archives/ node-v22.11.0-linux-x64.tar.xz ├── Code/ scrape.py ├── Documents/ 2026-09-22_tenancy-agreement.pdf │ 2026-10-08_certificate-of-participation.pdf │ random_notes.txt ├── Duplicates/ IMG_20260918_0712 (1).heic ├── Installers/ VSCodeUserSetup-x64-1.94.2.exe ├── Invoices/ 2026-10-12_tax-invoice.pdf │ statement-oct.pdf ├── Photos/ IMG_20260918_0712.heic ├── Reading/ doc_8831.pdf │ thinking-fast-and-slow.epub ├── Screenshots/ Screenshot 2026-10-20 at 09.14.02.png ├── data_dump.csv ├── empty.txt ├── notes.txt ├── recent-scan.pdf ├── ubuntu-notes.md └── weird.xyz The six files still in the root are the point.
The lazy payoff
Thirteen files moved without me. Three names got better. One duplicate pair got separated, and neither copy was deleted.
The numbers are less flattering than a pitch. The six files left alone are six decisions I still have to make. Dozer did not remove the work. It batched it and moved it to a moment when I might feel like doing it. What it did remove was the interruption: you open the folder for one file and leave twenty minutes later with nothing decided.
Machine time: 24 seconds, paced at two seconds per file. Human time: one glance at a morning note.
How it works
flowchart LR A[Folder, one level deep] --> B[hash + short text preview] B --> C{extension or text rule?} C -->|yes| D[category + confidence] C -->|no| E[Gemma 3 1B in a warm llama-server] E --> D D --> F{confidence >= 0.75?} F -->|yes| G[folder + safe name] F -->|no| H[left alone, with the reason] G --> I[apply one file at a time] I --> J[(data/runs.jsonl)] J --> K[undo] Three decisions make a 1B model trustworthy enough to move files.
Rules first. An .exe is an installer. A document with Tax Invoice and Amount due in the text is an invoice. In the scan above, 15 of 19 files never reached the model. Rules are free, instant and predictable, and they leave the model only the cases that are actually ambiguous.
A confidence floor. Below 0.75 Dozer does nothing and says why. data_dump.csv is still in the folder because the model answered other at 0.0 confidence and said "I'm not sure what this is." That is a correct answer. A tidier that guesses is worse than a folder.
A warm server, not a subprocess per file. Every call goes to one llama-server on localhost, with the prompt prefix cached. On this 2015 dual-core laptop the difference is large. Loading the model per file costs more than the answer, and the shared prefix means the instructions are evaluated once per run instead of once per file. A fresh ambiguous file takes about thirty seconds here. The first one in a run also pays for loading 806 MB.
Model answers are cached by content hash, so a second pass over a folder you already scanned costs nothing. That is why the demo above says "4 needed the model, 4 answered from cache."
The safety rules are simple. No symlinks, no subfolders, no deletion, no overwrite, and no rename of a name a person wrote. undo reverses the last run in order and refuses to overwrite a file you put back yourself.
What it got wrong
Three things, and they are the interesting part.
It called a book chapter a document. doc_8831.pdf starts with "Chapter 3: Trees and Recursion", and the model filed it under Documents at 95% confidence. The fix was one rule, not a bigger model or a longer prompt: text with Chapter N, Table of Contents or an ISBN is reading. Rules are how you correct a small model.
The kind field was noisy. Asked about a certificate, it returned kind: "tax invoice". The prompt's examples were doing more work than the instruction, so I removed them.
It invented file names in the morning note. I wanted the model to write the note you see at 7am. It produced "I left 6 files, including the one named 'Project Alpha'". There is no Project Alpha in the folder. The note is a template now.
That is the rule I ended up with. The model answers one narrow question, says how sure it is, and code checks the answer. It does not get to tell me what happened.
Why open innovation matters here
The folder never leaves the machine. Downloads is where bank statements, contracts, ID scans and photos of your kitchen end up. A closed sorter has to upload them to read them. Running the weights locally means there is no trust boundary to audit, because there is no boundary.
It works with no network. After the one-time model download, the scanner, the rules, the model and the log are all on disk. On a metered or unreliable connection that matters more than any feature.
You can change the sorter. Swapping Gemma for a bigger model, a different quantisation, or a model fine-tuned on your own vendor names is a path and an environment variable. The prompt is a string in the repo. When it behaved badly I could read it and fix it the same afternoon.
It costs nothing per file. No per-document price, no rate limit, no key that expires. The run above cost nothing, which matters most for the person with a full folder and no budget for another subscription.
My Agent Session
I built this with an agent, and the session is saved. It includes the benchmark that pushed me from a subprocess per file to a warm server, and the moment the model invented a file name in the morning note:
Prize Categories
- Best Use of Gemma: Gemma 3 1B (
Q4_K_M, 806 MB) does every ambiguous classification, running locally throughllama.cpp. - Best Use of Render:
render.yamldeploys the dashboard as a hosted demo. The free instance cannot hold a 0.8 GB model, so the hosted demo runs the rule engine only, on a throwaway copy of the sample folder, and the UI says so. The model path is the one you run at home.
Code
https://github.com/mukuvi/dozer (MIT licensed). make test runs 27 tests without a model.
Built for Hacktoberfest 2026. No API key, no upload, no account, and nothing leaves the machine.

