Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 13 min read

I hired a sloth to tidy my Downloads, and it only touches what I have forgotten

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Built with Google Gemma. What I Built A Downloads folder is where files go to be forgotten. final.pdf, fina

I hired a sloth to tidy my Downloads, and it only touches what I have forgotten

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

Built with Google Gemma.

What I Built

A Downloads folder is where files go to be forgotten. final.pdf, final(2).pdf, Screenshot 2026-03-18 at 19.11.33.png, an installer for a program you already installed. Nobody wants to open each one just to learn what it is.

The usual fix is a tidy script, and a tidy script has a flaw. It moves the PDF you downloaded this morning and are about to open. So I wrote the opposite: sloth-sort only touches files I have ignored for a week. The lazier I am, the more it tidies. Anything I touched recently, it leaves alone, because I might still be using it.

That is the whole idea, and it is enforced in code, not in a prompt. A file counts as touched if it was modified or opened in the last 7 days. Those files are never read, never planned, never moved. For everything else, a small open-weight model (Gemma 4 E2B, 4-bit, running on a laptop CPU through llama.cpp) reads the start of each file, picks one of my folders for it, and moves it. It is slow on purpose: lowest CPU priority, four threads, one file at a time. It never deletes or overwrites anything, and sloth undo puts every file back, byte for byte.

The setup is one sloth.toml: your folder names, one line each, and the rule.

[settings]
ignored_for = "7d"        # only touch files neither opened nor modified for 7 days

[folders.invoices-receipts]
desc = "Bills, invoices, receipts, payment confirmations and tax paperwork"

[folders.installers]
desc = "Software installers and setup packages"
ext = [".exe", ".msi", ".dmg", ".deb", ".appimage"]   # the baseline's guess; the model gets to disagree

Then sloth plan, look at the one-page report, sloth apply. Or sloth undo. --ignored-for 36h or --ignored-for 0 change the rule if you are less lazy than me.

Demo

Code (MIT):

GitHub logo xexpertai / sloth-sort

A lazy Downloads tidier: a local open model (Gemma) files only what you have ignored for 7 days, with full undo.

sloth-sort

A lazy folder tidier that only touches what you have forgotten. sloth-sort only touches files I have ignored for a week. The lazier I am, the more it tidies; anything I touched recently, it leaves alone, because I might still be using it. A file counts as touched if it was modified or opened in the last 7 days (--ignored-for 7d, changeable). For the forgotten files, a 2B open-weight model (Gemma 4 E2B, 4-bit, via llama.cpp) reads each one, picks one of your folders, and moves it, slowly and at the lowest CPU priority. It never overwrites or deletes anything, and sloth undo puts every file back, byte for byte.

Built by Harsha Shinde for the DEV Hacktoberfest Open-Source AI Challenge, Week 3 ("Slow and Steady"). AI-assisted: the code was written with Claude Code (the AI agent also wrote the demo generator and the labels for it)…




There is no live site. The model needs a CPU and a 2.7 GB file, and I would rather say so than fake a hosted page. What you can do in about ten minutes is generate the same fictional folder and watch it work:

git clone https://github.com/xexpertai/sloth-sort && cd sloth-sort   # setup steps are in the README
.venv/bin/python eval/make_downloads_folder.py /tmp/demo/Downloads   # 72 fictional files with old and recent dates
.venv/bin/sloth plan /tmp/demo/Downloads --config eval/downloads.toml
.venv/bin/sloth report /tmp/demo/Downloads    # opens as one HTML page
.venv/bin/sloth apply /tmp/demo/Downloads --config eval/downloads.toml
.venv/bin/sloth undo  /tmp/demo/Downloads

First, the true story: my real Downloads folder

My real Downloads on this laptop is small: 7 files. I ran sloth on a copy of it (made with cp -p, so the dates stayed the same; I never touched the original) with the settings the repo ships with: the 7-day rule and the final prompt. Here is what it did, with the names left out because they are mine:

What the file is Last touched What sloth did
a video (.mp4) 2 days ago left alone, under the 7-day rule
another video (.mp4) 3 days ago left alone, under the 7-day rule
a Windows desktop.ini now left alone (sloth always skips it)
a browser installer (.exe) 8.5 days ago installers, high confidence
a phone-style photo (.jpg) 8.7 days ago screenshots, high confidence
a screenshot (.png) 9 days ago screenshots, high confidence
another screenshot (.png) 9 days ago screenshots, high confidence

So the 7-day rule did what it is for: the two things I worked on this week were not read, not planned, not moved. The other 4 were planned in 50.7 seconds (about 13 seconds a file), apply moved them in 0.12 seconds, and undo put them back. I checked the SHA-256 of all 7 files before and after: identical.

Sloth moved 4 files (an installer, a phone photo and two screenshots) and left 3 alone. I haven't graded them; the photo landing in screenshots looks wrong to me. It went to screenshots with "high" confidence, from its name alone, which is the blind-name habit from bug two below. So there is no accuracy number for the real folder.

7 files is a small test. It shows the rule and the undo working on real data. It does not show how good the model is. For that I made a bigger, fictional folder.

Bigger stress test (fictional 72-file folder)

All the numbers from here down (except the "What I got wrong" notes about the 7-file run) come from a fictional Downloads folder that the AI coding agent generated for this entry, with invented shops, invented trips and generated placeholder images. It is not my files.

How I Built It

Scale test

I also planned 920 of 1,000 free-licence files (dry run, stopped early on purpose): 4.84 files a minute (about 13 s a file), the 7-day rule held (283 left alone), and the model left 448 of 637 eligible files alone. No labels, so no accuracy claim. Numbers: eval/results/scale/scale-summary.json.

Credits: Wikimedia Commons file authors and Project Gutenberg; licence and author for every non-generated file are in eval/results/scale/SOURCES.csv. Raw files are not in the repo.

The folder: 72 files, 14 of them "in use"

The generator (eval/make_downloads_folder.py) builds 72 files in seven kinds: invoices (PDF, text, email), screenshots (placeholder PNGs with phone-style and desktop-style names), installers (random bytes with a binary header), manuals and papers, boarding passes and hotel bookings, memes (JPG, PNG, GIF with names like image(3).png), and three notes that fit nowhere. Names are deliberately useless: final.pdf, copy of copy.txt, x.pdf.

Each file gets a date. 58 were last modified 10 to 204 days ago. 14 are "active": 11 modified in the last 6 days (a boarding pass for this week, an installer from this morning, a paper "to read tonight") and 3 that were modified long ago but opened in the last few days, like a router manual that is 95 days old and was opened a day and a half ago. Those three are the interesting ones. If sloth only looked at modification time, it would grab a file you are literally reading.

What the sloth did, and what it saved me

On the 58 forgotten files, the baseline and the model compare like this. The baseline is what a normal tidy script does: match on the extension (.pdf is a bill, .png is a screenshot, .jpg and .gif are memes), with no age rule.

Results table: extension rules versus sloth on the 58 forgotten files, and the 14 active files each approach moved

58 forgotten files Right moves Wrong moves Left, should have moved Left, correctly
Extension rules, no model 31 16 8 3
sloth, first prompt 46 8 1 3
sloth, final prompt 42 0 13 3

And on the 14 fictional files "in use": the extension script would have moved 14 of 14. sloth moved 0 of 14. That is the point of the tool, and it is the number I trust most, because it comes from a rule in code and not from the model being clever.

The time cost, measured: in the end-to-end run, sloth plan took 9 minutes 56 seconds of wall clock for 58 files (about 10.3 seconds per file, at nice 19), sloth apply moved 42 files in 0.09 seconds, and sloth undo restored all 72 files with identical checksums (e2e-before.sha and e2e-after.sha in the evidence). I did not time anyone sorting this folder by hand, so I am not going to invent a minutes-saved figure.

Bug one: reading a file counts as opening it

The 7-day rule uses the later of "last modified" and "last opened". My first version read each file for the model with a normal open(). On a normal Linux filesystem, reading a file updates its access time. So the sorter itself made every file look freshly opened, and after one plan, a second plan found nothing to do. The rule was eating itself.

I wrote a short experiment and saved the output (atime-regression.log):

file last opened/modified 30.0 days ago; days since atime: 30.0
after a plain open()+read():   days since atime = 0.0 -> the file now looks fresh
after open(O_NOATIME)+read():  days since atime = 30.0 -> still forgotten

The fix is to open files with O_NOATIME whenever sloth reads them for the model or hashes them for the undo ledger. There is a regression test (test_planning_does_not_make_files_look_fresh) that ages a file 30 days, plans, hashes, and checks it is still 30 days old. The same log shows cp -a has the same side effect on the source folder, so the end-to-end script hashes with a helper that avoids touching access times too.

Bug two: a model that cannot see an image still sounds sure

The first full run scored 46 right and 8 wrong. Every wrong move was the same mistake. Eight memes went into screenshots, all with "high" confidence. Here is the model's own reason for cat_laptop.png:

The file name 'cat_laptop.png' strongly suggests it is an image, and the 'screenshots' folder is the most appropriate...

It cannot see the picture. Gemma 4 E2B in this setup reads text, not pixels, so for an image it has only the file name. And "an image" plus a folder called screenshots is enough for it to say "high". A confidence guard does not help when the model is confidently wrong.

The fix is a rule in the prompt for files with nothing to read: you cannot see inside this file, go by the name only, and answer leave unless the name itself says where it belongs. A name like Screenshot 2026-03-18 at 19.11.33.png does. image(3).png does not. The result is in the second table: 8 wrong moves became 0. The price is also in the table: 13 files that should have moved were left where they were, 12 of them memes and one a browser archive. For a lazy tidier that trade is fine. A file left alone costs you nothing, and a file in the wrong folder costs you a hunt later.

I should say clearly that I wrote this prompt after seeing these 8 errors on this same folder, so "0 wrong" is not a score on unseen files.

Bug three: the same settings, two different answers

After the prompt change I ran the whole thing end to end with the same settings, and I ran the eval a second time. Both gave the same result: 42 right, 0 wrong, 13 left.

That was not the whole story in my first version of this entry. Before 21 October I had the same two runs with brand-named installers in the demo folder, and they disagreed: the eval run had 1 wrong (when_the_build_passes.jpg went to screenshots with high confidence) and the end-to-end run had 0 wrong. I then renamed every demo installer to a generic name (no brand names anywhere in the demo files, the video or the results) and re-ran everything for real. The first-prompt row moved by one file as well (47/8/0 before, 46/8/1 now). Both sets of results are real; the earlier ones are in the git history.

So the model is not fully deterministic here. Across the runs I have, the final prompt made between 0 and 1 wrong move in 58 files, and I would not claim anything finer from a folder this size. The saved files are downloads-eval-run2-blindname-prompt.json and e2e-score.json.

Why it is slow on purpose

The model server runs with four threads under nice, and the sorter runs at nice 19. --pace 60 makes it rest a minute between files, if you want the sloth literally. But the real slowness is the week of waiting. The CPU priority is just courtesy.

Undo is the feature that lets me be lazy

I would not run a tidier on a real folder if I could not reverse it. Every move goes into a ledger with the file's SHA-256. undo replays it backwards and refuses a file if the moved copy has been edited since. The code, not the model, owns the dangerous parts:

  • the model answers with grammar-constrained JSON: a folder from my list, or leave
  • low confidence means the file stays put, and a file the model cannot read as text moves only on high confidence
  • it never overwrites (a clash gets a -1), skips symlinks and dotfiles, and skips every file touched in the last 7 days
  • if a file changed between plan and apply, that file is refused

What I got wrong

  • The real-folder run is only 7 files and I have not labelled the 4 moves, so it is a demo of the rule and of undo, not a score.
  • An earlier test on a different folder is in eval/results/round1/. Its labels and the strict rule for it were written by the agent, not by me.
  • I trusted "high confidence" at first. In the eight meme cases it meant nothing.
  • The rule counts access time, and access time is only as honest as the filesystem. On a noatime mount, opening a file does not count, so only modification time protects you. I tested on ext4 with the default relatime, nowhere else.
  • The prompt fix and the numbers come from the same 72 files. They are not a held-out test.
  • The reasons the model writes are sometimes cut off mid-sentence, because I capped its answer at 120 tokens. The report page now ends them at the last whole word with "...".
  • scripts/install-timer.sh sets up a nightly systemd timer. I wrote it and did not install it on my machine for this entry, so I will not tell you it works.

What it can't do

  • It reads only the first 1,200 characters of each file. A long document that changes subject halfway is sorted by its start.
  • Images, audio and video are sorted by name only, and only when the name is clear.
  • It is a 2B model. It is sometimes wrong in a confident voice. That is why undo exists.
  • I tested English files only, and no files were in subfolders.
  • I did not test a closed model, so I cannot say how it would do on the same 72 files.

The Lazy Payoff

The payoff I can measure, on the fictional folder: 14 files "in use" were left alone, 42 forgotten files were filed, and it took 9 minutes 56 seconds of machine time that I did not have to sit through, plus a single apply that took less than a second. The payoff I cannot measure yet: how long a person takes to sort 58 old files by hand. Nobody timed it, so the post makes no claim about it.

Why Open Innovation Matters

A Downloads folder is private: bills, tickets, screenshots of things I would not paste into a chat box. With an open model, the file names and the first 1,200 characters of each file never leave the machine, and I did not need an account or a per-file fee to find out whether the idea works. The model is one 2.7 GB file that I can swap by changing a name in a script. A closed model might read the memes better, because several of them are the kind of thing a vision model would get right. I did not test that. For a tool that runs on every folder I own, forever, "free, private, and I can replace it" matters more to me than the last few points of accuracy.

My Agent Session

This project was built with Claude Code as the coding agent. I decided the idea. The agent wrote the code, the fictional-folder generator, the labels, the evaluation scripts and the first draft of this post, and it ran the evaluations you see above. I reviewed the logic and the results. The 7-day rule and the access-time fix were both made in that agent session, and the log in the evidence folder shows the bug that led to the fix.

Prize Categories

  • Best Use of Gemma: Gemma 4 E2B is the whole decision step for forgotten files: it reads each one and picks a folder under a JSON schema, on a laptop CPU with no network. The code around it (the 7-day rule, the blind-name prompt, the confidence guard, never-overwrite, the undo ledger) exists because a 2B model is confidently wrong in specific places, and the tables above show where.

AI assistance: the code, the evaluation scripts, the demo folder generator, the screenshots and a first draft of this post were produced with an AI coding agent (Claude Code). I checked the logic and reviewed the results. The video narration is an AI voice (open-source Kokoro).

Demo data: the 72 files in the big tables are fictional, made by eval/make_downloads_folder.py. The 7-file section is my real Downloads, names left out. Post by Harsha Shinde.

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.