This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady.
What I Built
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.
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.
It is for anyone whose Downloads folder has become the place where decisions go to wait. You set it going before bed, and the only thing waiting for you in the morning is a short note.
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 minute 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.
Code
Dozer
A folder tidier that works while you sleep, with an open-weight model at the core.
Dozer reads your Downloads one file at a time with Gemma 3 1B running through
llama.cpp on your own machine. It files invoices, documents, installers, photos
duplicates and the rest into folders, renames the files that have useless names, and
leaves everything it is not sure about exactly where it is.
No API key. No upload. No account.
Write-up: Dozer: a folder tidier that works while you sleep.
What it actually does
- Files the mess in one folder, one level deep.
- Renames only files whose names carry no information (
scan_0042.pdf,IMG_2301.HEICdownload (3).pdf). A name a human chose is left alone. - Keeps identical files in
Duplicates/instead of deleting anything. - Leaves files alone when it is not sure, when the file is empty, when the type is unknown, or…
MIT licensed, 27 tests that run without a model. make test on a clean checkout.
How I Built It
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 also 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 Does Open Innovation Matter?
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:
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.
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.
Built for Hacktoberfest 2026. No API key, no upload, no account, and nothing leaves the machine.


Top comments (0)