DEV Community

James Ngandu
James Ngandu

Posted on

Dozer: a folder tidier that works while you sleep

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.

Dozer's plan view: 13 files to file, 6 left alone, 4 needed the model

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/ or Duplicates/.
  • Renames only the files whose names carry no information. a1b2c3d4e5f6a7b8.pdf became 2026-10-08_certificate-of-participation.pdf. kasha-hardware-invoice.pdf was 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, and undo puts 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
Enter fullscreen mode Exit fullscreen mode

Or against your real folder:

make scan ROOT=~/Downloads
make night ROOT=~/Downloads
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The six files still in the root are the point.

The morning note

Code

GitHub logo mukuvi / dozer

A folder tidier that works while you sleep, with Gemma 3 1B running locally.

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.HEIC download (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:

Building Dozer: a local folder tidier for Hacktoberfest Week 3
You

Proceed with the Hacktoberfest Week 3 challenge (Slow and Steady). Build something with open-source AI that rewards laziness.

Agent

Plan: a local folder tidier called Dozer. Gemma 3 1B through llama.cpp decides where a file belongs; deterministic rules handle the obvious cases; anything below 0.75 confidence is left exactly where it is. Python + FastAPI, JSONL log, undo, and a paced overnight mode.

Agent

First I checked the hardware. An i5-5200U with 4 threads, and the machine was already loaded. A quick llama-bench run showed 3.81 t/s prompt and 0.19 t/s generation. Loading the 806 MB model per file with llama-cli would make the tool unusable, so the model layer became a warm llama-server with prefix caching instead.

Agent

The core is plan.py. Extension and text rules run first, so most files never reach the model. Rules also produce the safe filenames: only names with no information (scan_0042, IMG_2301, a 16-character hash) get renamed, and only when the model or a rule has a title to offer. Duplicates are compared by SHA-256 and moved to Duplicates/ instead of being deleted.

Agent

Then the first real scan. The model called a book chapter (doc_8831.pdf, 'Chapter 3: Trees and Recursion') a Document at 95% confidence, and its 'why' text was boilerplate. I did not reach for a bigger model. I added one rule: text with 'Chapter N', 'Table of Contents' or an ISBN is Reading. That is the lesson the post is built around.

Agent

I also tried letting the model write the morning note. It produced: "I left 6 files, including the one named 'Project Alpha'" - there is no Project Alpha in the folder. It invented file names. The note became a deterministic template, and the model is now trusted with exactly one decision: the category.

Agent

Final run on the 19-file sample folder: 13 filed, 6 left alone, 9 folders, 3 renames, 4 files needed the model. Paced at 2 seconds per file, the pass took 24 seconds. A fresh ambiguous file costs about 28 seconds on this laptop, and answers are cached by content hash so the next scan is instant.

Agent

Shipped: 27 tests passing, dashboard screenshots from a real run, README, MIT license, render.yaml for the hosted demo. The repo is github.com/mukuvi/dozer and the Week 3 post is on DEV with the #hf26challenge tag.

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 through llama.cpp.
  • Best Use of Render: render.yaml deploys 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)