This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 3, Slow and Steady.
What I Built
SlothBox watches the folders you choose on a Windows PC, such as Downloads and Screenshots. When a new file lands, a local Gemma model reads it and says what it is: a receipt, a screenshot, a document, or something else. SlothBox then gives the file a clear name, moves it into a sorted folder, and, if it is a receipt, writes a row in a monthly ledger. A page you open on your phone over home Wi-Fi sends photos into the same pipeline. Every move can be undone from the dashboard or the tray.
Nothing leaves the PC. Gemma runs in Docker on your own GPU, the database is a local Postgres, and there is no account.
Who gets lazier
Anyone with a Downloads folder they have stopped looking at, a screenshot pile named Screenshot 2026-10-14 213055.png, or a wallet of paper receipts they mean to type into a spreadsheet one day.
Gemma never writes a file name. It returns fields, and plain code builds the name from a template:
| Type | Template | Example |
|---|---|---|
| Receipt | {date}_{Vendor}_{amount}{CUR} |
2026-10-20_Jio_599INR.jpg |
| Screenshot | {file date}_{title} |
2026-10-20_reddit-thread-on-dotnet-10.png |
| Document | {title} |
dotnet-10-release-notes.pdf |
| Other | original name, unchanged | ChromeSetup.exe |
An installer keeps its name because renaming it would help nobody.
The rules that make it safe to leave running
- Files that were already in a folder when you added it are never touched. "Tidy them now" is a separate click, and "Undo since…" puts everything back.
- A file goes to Needs review instead of being guessed when Gemma's answer is not valid, when the type is unknown, when a receipt has no date or amount, or when the file could not be read (HEIC, scanned PDF, password-protected). Review is keyboard-only if you want it: a digit picks the category, Enter files it.
- Every move is logged before it starts and finished through a temp file with a hash check, so a crash or a power cut leaves either the original or the copy, never neither.
- Cloud-synced folders are copy-only. SlothBox never deletes from a folder that something else also owns.
Demo
SlothBox runs on your own PC, so there is no public deployment. Three screens from the build:
Today. The meter counts what was filed this week and turns it into minutes you did not spend. The two rows are test files: a synthetic text receipt filed under Receipts / Shopping, and a HEIC file that went to review and was filed by hand.
Needs review. SlothBox cannot read HEIC yet, so it asks instead of guessing. 1 to 9 picks a category, Enter files it, S skips, O opens the file.
Receipts. The ledger for October, with the total per currency and per category, and a CSV export. The one row is the synthetic receipt from the test above; Gemma read the date, amount and payment method and missed the vendor.
Code
SlothBox
A local file butler for Windows. SlothBox watches the folders you choose, such as Downloads and Screenshots. A local AI model (Gemma, running in Docker on your own PC) reads each new file. SlothBox then gives the file a clear name and files it in a sorted folder. Receipts go into a monthly ledger, every move can be undone, and you can search your files by words or by meaning. A small upload page lets your phone send photos over your home Wi-Fi. Files never leave the PC.
What you need
- Windows 10 or 11, 64-bit.
- Docker Desktop with Linux containers.
- An NVIDIA GPU with at least 8 GB of video memory is recommended. Without one, Gemma runs on the CPU and is much slower.
- About 17 GB of free disk space for Docker: about 10 GB of images and 7 GB of…
How I Built It
The open AI part
Two Gemma models through Ollama, both running in a Docker container on the PC:
-
Gemma 4 E4B (
gemma4:e4b, 6.6 GB) reads each file. Images are sent as base64, PDFs and Office files as extracted text, and the model answers in a fixed JSON schema: type, category, title, and for receipts the vendor, date, amount, currency and payment method. Temperature 0, thinking off, one retry on a timeout or on malformed output. It fits entirely in 8 GB of VRAM, so the GPU runs one file at a time with nothing offloaded to RAM. -
EmbeddingGemma (
embeddinggemma, 768 dimensions) turns the text Gemma wrote about each file into a vector, on the CPU, so Gemma keeps the GPU to itself.
The model proposes; code decides. The category must be one of the user's own list, or it becomes Other. A currency that is not a three-letter code becomes null. An amount of ten billion or more becomes null, because the ledger column is numeric(12,2) and a misread would otherwise fail the insert. Control characters are stripped before anything reaches Postgres.
Storage and search: Postgres + pgvector
Everything that is not a file lives in one Postgres database: the queue, the action log, the receipts ledger, the categories and the search index. Search is one SQL query that merges the top 50 keyword matches (tsvector, websearch_to_tsquery) with the top 50 meaning matches (cosine distance on the EmbeddingGemma vector) using reciprocal rank fusion with k = 60. Each hit reports whether it matched by words, by meaning, or both, and the UI shows that label.
Two things in that query were not in any tutorial I read:
- pgvector's default
hnsw.ef_searchis 40, which is belowLIMIT 50, so withoutSET LOCAL hnsw.ef_search = 100the meaning branch silently returned at most 40 rows. - With a filter such as "receipts only", the HNSW index picks its nearest rows first and applies the filter after, so a narrow filter could return no meaning matches at all.
SET LOCAL hnsw.iterative_scan = strict_orderfixes that.
The test for the second point is the most measured line in the repo. The index graph is built with Postgres's internal random generator, which no SQL setting can seed, so the same 20 seeded receipts come back as 20 matches about four runs in five and as few as three in the worst of 600 runs. The test now asserts what the spec can justify, at least one, and the spec records the numbers.
The stack
A .NET 10 tray app hosts an ASP.NET Core server on 127.0.0.1:5080, with the React dashboard built into the exe. A second listener on port 5081 serves only the phone page and its upload. Postgres and Ollama run from one compose file. The release is a single self-contained SlothBox.exe, so the PC that runs it needs no .NET install.
The engine has 768 tests and the UI has 139, all on synthetic files and a throwaway Postgres container, so no real file of mine is in the repo.
The bug I am glad a review found
Undo looked simple: the action log says the file went from A to B, so move it back. A review of the first safety batch found that a stale undo, one requested after the file had already been refiled somewhere else, could build a "delete the copy" step whose copy and original were the same path, which would have deleted the only copy of the file. Three guards went in: undo refuses unless the action is still the file's newest live one, the action log re-checks that under a row lock, and the mover refuses any delete whose copy and original are the same path. Each guard has a test that failed before it existed.
How the work was split
I used AI for most of the code. Claude (Opus 5.5, through Claude Code, on the laptop and in cloud sessions) wrote the engine and most of the UI from a design spec and a task plan, test first, one batch at a time, each batch ending with a report and a review. OpenAI's Codex (GPT-6 Astra) built the first four UI batches. My part was choosing the problem, writing the rules into the spec, deciding every open question the agents raised, merging the branches, running the checks, and running the app on my own machine against a real phone and real folders.
Why Does Open Innovation Matter?
Downloads folders are full of things nobody would upload on purpose: bank statements, ID scans, chat screenshots, receipts with a home address on them. A closed vision API would mean sending every one of those files to someone else's server, paying per file, and needing internet before a screenshot could be filed.
With open-weight models, the files never leave the PC. Once the two models are downloaded, a file costs nothing to process, and the whole thing works with Wi-Fi off, apart from the phone page, which needs the local network and nothing else.
Open also means the model is a setting. The spec has a day-one rule: if gemma4:e4b does not fit the GPU or takes more than ten seconds per file, switch to the quantised build or to gemma4:e2b. That is one line in settings.json and a pull in Ollama, not a vendor negotiation. The same goes for the embedding model, with a re-embed of the index.
And it means the limits can be measured and published. The receipt fields Gemma gets wrong, the search matches the index misses, the seconds each file takes: all of that is visible, repeatable, and free to re-run.
The Lazy Payoff
The dashboard has a meter. The formula is shown in the UI under "How we count" and it is an estimate, stated here plainly: a receipt counts as 30 seconds you did not spend typing it into a spreadsheet, a screenshot or a document as 15 seconds you did not spend renaming and moving it, anything else as 5 seconds. The per-type seconds can be changed in Settings.
I did not get SlothBox onto my real Downloads folder before the deadline. The week went into the build, and the plan's "run it on real folders from day one" step is the one that slipped. So the numbers below come from a test folder and the phone page, and they are small:
- Files filed: 3. One photo uploaded from the phone, one synthetic text receipt, one HEIC filed from review.
- Receipts logged: 1, ₹120.00. Gemma read the date, the amount and "paid in cash" correctly and missed the vendor, so the file is
2026-10-26_120INR.txtinstead of2026-10-26_SampleStore_120INR.txt. - Duplicates caught: 0.
- Minutes saved by the formula: under one.
- Seconds per file: 28 s for the first file after a start, while Gemma loads into the GPU; about 2 s per call once it is warm. The spec's target of under ten seconds per file holds only once the model is loaded.
That is one receipt, not an accuracy figure. The honest version is: the pipeline works end to end, and the measurement of how often Gemma gets a receipt right is the first thing to do after the deadline.
Prize Categories
- Best Use of Gemma. Gemma 4 E4B reads every file; EmbeddingGemma indexes it.
Credits
- Models: Gemma 4 and EmbeddingGemma by Google, under the Gemma Terms of Use, served by Ollama.
- Database: PostgreSQL with pgvector.
- .NET libraries: Npgsql and EF Core, PdfPig, SkiaSharp, OllamaSharp, Testcontainers.
- UI: React, Vite, Mock Service Worker.
- The hybrid search is the same idea I used in my earlier
rag-semantic-searchproject; the SlothBox code was written fresh.
If you have a Downloads folder you have given up on, point SlothBox at a copy of it and tell me what it got wrong. Open an issue on the repo.

Top comments (0)