This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
π‘ Quick Navigation: What I Built Β· Live Demo Β· Code Β· Two-Stage Funnel Β· Open Innovation Β· Lazy Payoff Β· Prize Tracks (9 Categories) Β· Discussion
π Table of Contents (Click to expand)
- What I Built
- Live Demo & Offline Test Fixture
- Code & Key Modules
- How I Built It (Two-Stage Intelligence Funnel)
- Why Does Open Innovation Matter?
- The Lazy Payoff
- Prize Categories (9 Tracks & Judge Navigation Matrix)
- Parting Sloth Thoughts: Reclaiming Digital Calm
What I Built
Last week I ran du -sh ~/Downloads on my laptop and realized I was hoarding hundreds of cryptic PDFs (INV-9041-final(1).pdf), forgotten .exe installers, and unnamed screenshotsβand I refused to upload my private bank receipts or .env configs to a cloud AI just to sort them. So for International Sloth Day, I built a daemon that waits until I walk away from my keyboard and cleans the mess locally while I sleep.
Workspaces, Desktop dumps, and project inboxes are where good intentions go to hibernate. Cryptic filenames pile up (Screenshot 2026-10-18 at 2.34.12 PM.png, INV-9041-final(1).pdf, DockerDesktopInstaller.exe). Receipts, strategy documents, archives, and configuration files end up in a flat, unorganized heap because standard file extensions tell you almost nothing about what a document actually contains.
I built slothd, a local-first, offline-capable AI file organizer designed around the philosophy of "Slow and Steady":
- Patient Idle Activation: Uses native OS APIs to measure keyboard and mouse inactivity. It sleeps while you work and only activates when you step away.
- Two-Stage Intelligence Funnel: Fast metadata triage (1ms via local 7-signal engine, ~20ms via Prior Labs TabPFN) flags anomalies (clutter, stale installers, duplicate hashes) before invoking heavier models.
- Local Vision & Text Comprehension: Uses Gemma 3 running locally in Ollama with strict Pydantic JSON schemas to read PDFs, invoices, and images.
-
Natural Language Semantic Search: Embeds organized files into Tiger Data pgvector (768-dim
nomic-embed-text) with a local SQLite vector fallback, enabling queries like "Where is my AWS invoice from October?". -
Crash-Resilient & Reversible: Every run is a dry-run by default. Moves use a two-phase write-ahead commit point logged to an SQLite WAL manifest with SHA-256 content hashes. An automatic startup reconciliation pass protects against power cuts, and
slothd undorestores every file instantly. - Glassmorphic Local Dashboard: A lightweight, dependency-free web UI (FastAPI + Vanilla ES6) with live stats, move manifest inspector, interactive activity timeline, and an ElevenLabs audio briefing player.
π Local-First & Air-Gapped by Default: The entire core workflowβidle detection, filesystem scanning, tabular metadata triage, Gemma 3 document understanding via local Ollama, SQLite WAL manifests, and 1-click rollbackβexecutes 100% locally on your machine with zero internet calls. A genuinely air-gapped deployment simply leaves external integrations disabled. Cloud partner services (Tiger Data pgvector, SerpApi vendor lookup, ElevenLabs voice) are strictly opt-in extensions via explicit CLI flags (
--enrich-online), and each is engineered with a complete offline fallback (local SQLite vector search, local heuristics, terminal audio chimes). No personal file or metadata ever leaves your device by default.
Who gets to be a little lazier because of it?
Every developer, creator, and researcher who wastes 20 minutes every week digging through unorganized files, manually dragging PDFs into dated folders, or putting off digital cleanup. You let the sloth run patiently while you make coffee, return to an inspected plan, and keep complete control.
Demo
The complete interactive loop demonstrates the full lifecycle:
Messy Workspace
β Fast Metadata Triage (TabPFN / Local Heuristic)
β Local AI Plan (Gemma 3)
β Dry-Run Preview
β Explicit Approval
β Semantic Search (Tiger Data)
β SQLite Manifest Inspection
β 1-Click Rollback
35-Second Walkthrough Demonstration
-
Universal MP4 Video (1.29 MB): slothd-demo.mp4 (Encoded with H.264
yuv420p+faststartfor instant playback across all browsers) - High-Definition WebM (4.65 MB): slothd-demo.webm
- High-Res Static Screenshot: 08-dashboard.png
The Offline Demo Fixture: Reproduce in 10 Seconds
You don't need to risk your own files to evaluate slothd. The repository includes a deterministic test fixture in test_workspace/messy_inbox containing an invoice, duplicate invoice, Uber receipt, project notes, Python benchmark, configuration file, leftover Windows installer, stale zip archive, and unsorted screenshots.
Anyone can clone and run the scan immediately:
git clone https://github.com/JaniDhruv/slothd.git
cd slothd
uv run slothd scan test_workspace/messy_inbox
In under a second, you will observe the verified metrics:
- 10 files scanned
- 2 clutter anomalies flagged in 1 ms (via deterministic 7-feature triage engine / TabPFN schema)
- 1 exact duplicate set detected via SHA-256 (973 B reclaimable)
- 0 cloud API calls required (100% air-gapped isolation)
Code
The project is 100% open-source under the MIT License:
π GitHub Repository: github.com/JaniDhruv/slothd
Key Source Modules
-
CLI & Interactive TUI:
slothd/cli.py&slothd/ui.py -
TabPFN Anomaly Engine:
slothd/engine/tabular_predictor.py -
Gemma 3 Vision Classifier:
slothd/engine/classifier.py -
Reversible Move Orchestrator:
slothd/engine/organizer.py -
Tiger Data pgvector Search:
slothd/engine/search.py&slothd/integrations/tiger_data.py -
Sentry Tracing & Circuit Breaker:
slothd/integrations/sentry_tracing.py -
Local Web Dashboard:
slothd/web/ -
Verified Evidence Suite:
docs/demo/
How I Built It
A file organizer that feeds every single file into a heavy vision model is noisy, slow, and expensive. Instead, slothd uses a structured two-stage funnel:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Messy Target Directory β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
1. Deduplicate & Extract Features
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 1: Prior Labs TabPFN Metadata Triage β
β (7 tabular signals: log size, depth, ext, time, etc.) β
βββββββββββββββ¬βββββββββββββββββββββββββββββ¬ββββββββββββββ
β β
Leftover installers, archives, β Needs deeper
screenshots, duplicate clutter β semantic classification
βΌ βΌ
Flagged for cleanup ββββββββββββββββββββββββββ
β STAGE 2: Gemma 3 Local β
β (Ollama Vision/Text) β
ββββββββββββ¬ββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β SQLite Reversible Log β
β + Tiger Data pgvector β
ββββββββββββββββββββββββββ
Stage 1: Lightweight Tabular Triage (Prior Labs TabPFN)
Before running AI models, slothd extracts 7 tabular signals from the filesystem:
- Log-scaled file size
- Extension category group
- Path depth
- Hour of modification
- Day of week
- Filename length
- Numeric/UUID entropy signal
When TabPFN credentials or a local model checkpoint are configured, TabPFN evaluates these tabular feature distributions directly (taking ~15β25 ms). To guarantee strict zero-credential, air-gapped operation by default, slothd also bundles a calibrated deterministic heuristic engine (TabPFN-Heuristic-Local) that evaluates the exact same 7-signal feature vector in under 1 millisecond.
(Note on benchmark accuracy: The 1 ms timing reported in the deterministic test fixture reflects this local offline heuristic engine. When running against TabPFN's hosted model with credentials, inference takes ~20 ms across the batch. Both modes share the exact same Pydantic triage interface and 100% test pass rate).
Stage 2: Local Vision & Content Understanding (Gemma 3)
Only files requiring semantic classification (unlabeled PDFs, scanned receipts, documents) are forwarded to local Gemma 3 via Ollama.
- PDFs are converted to in-memory images via PyMuPDF.
- Outputs are constrained via Pydantic schema passed directly to Ollama's
formatparameter. The model output is validated token-by-token at the grammar level:category,subcategory,description,date,vendor,proposed_name,confidence, andtags. - If Ollama is offline, a 0.2s health probe triggers an instant fallback to local extension classification, preventing the CLI from ever freezing.
Safe, Reversible Operations & Crash Resilience via SQLite WAL
The organization pipeline enforces strict safety, transaction ordering, and crash-resilience invariants:
-
Non-Destructive Dry Run:
slothd organizeprints the plan without moving files. You must explicitly pass--goor click "Approve" in the dashboard. -
Collision Avoidance: If
Invoices/AWS_Invoice_2026.pdfalready exists,slothdautomatically appends_1,_2instead of overwriting. -
Two-Phase Write-Ahead Commit Point: To prevent state divergence if power cuts or a crash occurs mid-organization,
execute_moves()writes an operation intent row (status='pending') to SQLite WAL before executing the disk move (shutil.move), then transitions it tostatus='completed'. -
Startup Crash Reconciliation Pass: On startup,
OperationLog.reconcile_operations()verifies any uncommitted pending rows against filesystem reality (marking completed if the destination exists, aborted if the source remained) without trusting either side blindly. -
Content-Hash Deduplication (
file_hashIndex): Every organized file is indexed by SHA-256 hash in SQLite. If an organized file later leaves its folder (or is restored from a backup/archive into the inbox),slothddetects that its content hash already has an active home and skips re-running local AI models. -
Timestamp Preservation: Original modification times (
mtime,atime) are preserved on moved files. -
Instant Rollback: Running
slothd undorestores the entire batch in reverse order and safely deactivates the content hash.
π‘ Community-Driven Evolution: During the challenge discussions on DEV, fellow engineers raised critical systems questions regarding crash windows between disk operations and manifest commits, as well as handling files restored from external backups. We turned that feedback into shipped features: a two-phase write-ahead commit point, startup reconciliation, and content-hash tracking!
Observability: Using Sentry as an Active Debugging Loop
Sentry was not added as a decorative vanity badge; it was an active part of the developer feedback loop during development.
The integration initializes Sentry with release [email protected] and traces critical pipeline boundaries: scanner.scan_directory, ai.classify_batch, organizer.preview, organizer.execute, organizer.undo, and search.query.
Sentry Distributed Trace Waterfall
[Transaction] slothd.organize_cycle (total: 1,248 ms)
ββ [Span] scanner.scan_directory .................... 12 ms [10 files discovered]
ββ [Span] ai.tabular_triage (TabPFN) ................ 1 ms [2 anomalies flagged]
ββ [Span] ai.gemma_classify_batch (Ollama) .......... 1,180 ms [Prompt: 412 tok, Resp: 86 tok]
ββ [Span] organizer.preview_manifest ................ 3 ms [Manifest prepared]
ββ [Span] db.wal_log_commit ......................... 4 ms [SQLite WAL sync]
ββ [Span] search.index_pgvector (Tiger Data) ........ 48 ms [768-dim embeddings stored]
Authentic Sentry Captured Trace & Issue Evidence
Issue ID: 7805015409
Release: [email protected] (production)
Title: ConnectionError: Connection refused to 127.0.0.1:11434
Culprit: slothd.engine.classifier in classify_batch
Trace ID: 71ac0b2ff1dc4c4788ff6a5b7098e354
Span ID: a1ea6839c23ccbb5 (op: ai.classify_batch, desc: "Gemma 3 batch classify (4 files)")
Breadcrumb: [warning] slothd.engine.classifier: Ollama server is offline or unreachable. Fast-falling back to local classification.
Handled: Yes (Circuit-breaker tripped gracefully without crashing the daemon)
Issue IDs: 7810323579 (SLOTHD-H) Β· 7810323576 (SLOTHD-G)
Release: [email protected] (production)
Title: AttributeError: 'ScannedFile' object has no attribute 'sha256'
Culprit: slothd.engine.organizer in plan_moves (line 412)
Transactions: /api/preview (Trace ID: 3645671f0cda4789b015585196dca00b, Span ID: a4a4373a984fe925)
/api/execute (Trace ID: df3545a7e5944f429196747f961fbfdb, Span ID: a4904942490417cf)
Context: Captured live by Sentry Starlette ASGI middleware during community-driven content-hash refactoring
Handled: No (Caught in test suite and resolved in commit 8f6b7e3)
| Observed Failure in Sentry | Root Cause Revealed | Resolution & Test |
|---|---|---|
ConnectionError on 127.0.0.1:11434 (Issue #7805015409) |
Ollama daemon was offline during CLI execution | Added 200ms pre-flight probe, local heuristic fallback, and circuit-breaker regression test (3a327ea). |
ValidationError: confidence > 1.0 |
Gemma 3 occasionally returned confidence as a percentage (90 or 100) instead of float 0.0β1.0
|
Added automatic percentage normalization validator in Pydantic schema with bounds coverage (a914b53). |
RuntimeError: generator didn't stop after throw() |
Tracing context manager was incorrectly re-entering on caught exceptions | Cleaned up tracing wrapper to propagate exceptions correctly and verified with regression tests (df19f29). |
AttributeError: 'ScannedFile' has no attribute 'sha256' (Issues #7810323579, #7810323576) |
Community-driven content-hash deduplication referenced .sha256 instead of .content_hash in plan_moves
|
Added @property sha256 alias on ScannedFile and defensive fallback in plan_moves, verified across all 122 tests (8f6b7e3). |
Why Does Open Innovation Matter?
Personal files contain our most vulnerable data:
- Tax returns and bank statements
- Medical lab reports and health records
- Invoices and payment receipts
- Confidential code and configuration secrets
Uploading this stream of daily life to closed, centralized cloud APIs creates an unacceptable privacy tradeoff.
Open-weight AI (Gemma 3) and open-source tabular models (TabPFN) fundamentally change this dynamic. They make it possible to run state-of-the-art vision and tabular triage entirely inside the boundary of your own computer:
- No document crosses your local network without explicit opt-in.
- Models can be audited, swapped, or operated air-gapped without internet access.
- Partner integrations (SerpApi online vendor grounding, ElevenLabs voice) require explicit flags (
--enrich-online) and are never enabled by default.
Privacy is not a checkbox added after the architecture is done; with open-source AI, privacy is the architecture.
The Lazy Payoff
The lazy payoff is not about moving files a few milliseconds faster. It is about removing the cognitive dread of manual digital housekeeping.
Instead of spending Sunday afternoon opening dozens of PDFs one by one, deciphering cryptic filenames, and deciding where each document belongs:
- You let
slothd daemonrun in the background. - It detects when you step away from your keyboard.
- It spots clutter anomalies and duplicates in 1 millisecond (via local tabular triage).
- It reads invoices and dates with Gemma 3.
- You return to a tidy proposed plan, hit enter to approve, and know that every single change can be undone with
slothd undo.
Measured Efficiency from the Test Fixture
10 files scanned
2 clutter anomalies identified
1 duplicate set found
973 B reclaimable disk space
1 ms metadata triage (local deterministic 7-signal engine / TabPFN schema)
0 cloud API calls required
100% offline isolation
Prize Categories
Architectural Depth & Ecosystem Evidence
While slothd operates completely offline out of the box, it was engineered with a modular architecture that cleanly interfaces with specialized ecosystem tools. Rather than a superficial checklist of sponsor imports, each partner integration represents an optional, verified subsystem that enhances capability without compromising our local-first core:
β±οΈ 10-Second Judge Navigation Matrix
| Track | How slothd Uses It |
Source Module |
|---|---|---|
| Google Gemma | Local gemma3:4b vision + text via Ollama with strict Pydantic JSON outputs |
classifier.py |
| Prior Labs TabPFN | 1ms 7-feature tabular triage flagging stale archives & size outliers before LLM wakeup | tabular_predictor.py |
| Tiger Data | 768-dim pgvector semantic search (<=>) + hybrid keyword re-ranking |
tiger_data.py |
| Sentry Tracing | Distributed spans (slothd.scan, predict, classify, search) tracking latency & tokens |
sentry_tracing.py |
| SerpApi | Opt-in --enrich-online live web grounding for cryptic filenames & unknown vendors |
serpapi_client.py |
| ElevenLabs |
slothd briefing Sloan-the-Sloth voice summary via eleven_multilingual_v2
|
elevenlabs.py |
| GitHub Copilot | Cross-OS CI matrix (Ubuntu/Windows, Py 3.11/3.12) + .github/copilot-instructions.md
|
ci.yml |
| Entire | Full agent session provenance & Git checkpoints via .entire/settings.json
|
.entire/ |
1. π¦₯ Grand Prize: Hacktoberfest Week 3 "Slow and Steady"
-
Concept: Embodying International Sloth Day with native OS idle measurement (Win32
GetLastInputInfo, macOSIOHIDSystem, Linuxxprintidle) that sleeps during work and activates only after 5+ minutes of continuous inactivity. All actions are dry-run by default and 100% reversible via SQLite WAL manifests.
2. π Featured Track: Best Use of Gemma
-
Module:
slothd/engine/classifier.py -
Design: Deep local document comprehension using
gemma3:4brunning in Ollama. PyMuPDF renders PDF pages in-memory for visual token ingestion, constrained token-by-token with strict Pydantic JSON grammar schemas to eliminate hallucinations.
3. π Featured Track: Best Use of TabPFN
-
Module:
slothd/engine/tabular_predictor.py -
Design: Operates as Stage 1 in the prediction agent funnel. Evaluates 7 filesystem numerical distributions in under 1 ms (local heuristic fallback) or ~20 ms (TabPFN model) to flag anomalies and installer clutter before LLM activation. Covered in
tests/test_tabular_predictor.py.
4. π― Partner Track: Best Use of Tiger Data
-
Module:
slothd/engine/search.py&slothd/integrations/tiger_data.py -
Design: Stores 768-dimensional
nomic-embed-textembeddings in Tiger Data PostgreSQL withpgvectorIVFFlat cosine distance indexing (<=>), backed by an automatic local SQLite vector fallback.
5. π Partner Track: Best Use of Sentry Agent Tracing
-
Module:
slothd/integrations/sentry_tracing.py -
Design: Distributed span tracing across scanner, AI triage, and execution boundaries. Verified with authentic production traces (
Issue #7805015409,Issue #7810323579,trace_id: 3645671f0cda4789b015585196dca00b) that resolved real development regressions in real time (3a327ea,a914b53,df19f29,8f6b7e3).
6. π Partner Track: Best Use of SerpApi
-
Module:
slothd/integrations/serpapi_client.py -
Design: Opt-in
--enrich-onlinelive web grounding for mysterious software names and vendor IDs, locally cached with zero transmission of private document bodies.
7. ποΈ Partner Track: Best Use of ElevenLabs
-
Module:
slothd/integrations/elevenlabs.py -
Design: Sloan the Sloth witty audio summaries via
eleven_multilingual_v2(slothd briefing), featuring local offline chime fallbacks when no API key is configured.
8. π€ Partner Track: Best Use of GitHub Copilot
-
Module:
.github/copilot-instructions.md,.github/prompts/, andslothd/cli.py -
Design: Authoritative 85-line repository guide, 3 custom prompt templates (
/new-sorter,/audit-privacy,/verify-demo), CLI prompt inspector (slothd copilot), and cross-OS CI matrix (.github/workflows/ci.yml).
9. π¦ Partner Track: Best Use of Entire
-
Module:
.entire/settings.json&.agents/hooks.json - Design: Integrates Entire CLI 0.11.6 to maintain immutable agent session provenance, tool checkpoints, and commit logs directly in version control.
Verification & Testing
Every test runs in total offline isolation (no live external services required):
uv run pytest -v
# 122 passed in 31.21s (including crash reconciliation & hash tracking)
uv run ruff check .
# All checks passed!
π¦₯ Parting Sloth Thoughts: Reclaiming Digital Calm
We spend so much time micromanaging our digital clutterβmanually dragging files into folders, hunting for lost receipts, or feeling guilty about a desktop covered in final_v2_really_final.pdf.
slothd was built on a very simple premise: your machine should quietly work for you when you're resting, not steal your attention when you're busy.
By embracing open-weights, deterministic rule fallbacks, and respectful idle scheduling, you don't have to surrender your personal documents to cloud silos or burn your laptop's battery just to keep a clean workspace. Take a breath, close your laptop lid, and let the sloth do the heavy lifting while you take a break.
π¬ Community Discussion
I'd love to hear your thoughts and feedback:
- What is the single oldest or most chaotic file currently buried in your messy folders?
- What custom classification heuristics or local AI pipelines would you like to see supported next?
- If you run
slothdon your workspace, how many megabytes of duplicate clutter did you reclaim?
If slothd brought a little bit of calm to your daily workflow, consider dropping a β on the GitHub repository, leaving a comment below, and sharing your experience! Happy Hacktoberfest! ππ¦₯





Top comments (41)
π¦₯ A little more context behind slothd!
I wanted to build something that could quietly take care of digital clutter without constantly demanding my attention or sending my personal files to the cloud.
This is my Week 3 submission for the Hacktoberfest Open-Source AI Challenge, and I had a lot of fun bringing together local AI, idle-aware automation, and reversible file operations.
The 35-second demo and offline fixture are included in the article if you'd like to see it in action!
Now I'm curious: What's the messiest folder on your machine, and would you trust a local AI assistant to organize it? π
Happy Hacktoberfest! ππ¦₯
Local-first daemon + local model is the right shape, but daemons have a failure mode a chat window doesn't: the model call is slow and the file events keep arriving.
How do you handle the backlog β a bounded queue you drain, or drop-and-rescan?
I ask because I measured a 1.5B answering four narrow yes/no checks on CPU at roughly 48 seconds. At that latency a file watcher can stack a couple of dozen pending events inside a minute, and the interesting question becomes which ones you're willing to skip.
Definitely drop-and-rescan on idle β trying to hook a local model to a live inotify event queue is basically suicide.
If you run
npm installor extract a zip, a reactive file watcher immediately drowns the model. Soslothddoesn't listen to continuous file event streams at all. It just checks OS idle time periodically, and only runs a fresh directory scan once you've stepped away from your keyboard for 5+ minutes.When it wakes up, it deals with that CPU latency in two ways:
.exe/.dmg), archives (.zip), code, and exact SHA-256 duplicates are sorted or pruned instantly (<1ms) by metadata triage. Gemma only ever gets invoked for truly ambiguous files (cryptic PDFs, unnamed receipts/screenshots).So instead of an exploding FIFO queue, the backlog just quietly chips away across your breaks.
Drop-and-rescan on idle explains the backlog, and the 80% metadata triage is the part I'd steal. One thing I'm still curious about: with a 20-file cycle and ambiguous files costing tens of seconds each, a 200-file ambiguous backlog needs ~8 minutes of idle β does the next idle window resume where it stopped, or does every window re-rank and risk starving the same low-priority tail forever?
It doesn't starve the tail because the files are physically moved upon batch execution!
Once a 20-file batch is organized, those files are moved into their categorized folders and logged to SQLite WAL. On the next idle wake-up,
scan_directory(skip_organized=True)ignores the moved files. So your 200-file backlog shrinks to 180 β 160 β 140 until it hits zero. The filesystem itself acts as the persistent queue.And you don't even have to wait for idle if you want it done right now:
slothd organize ~/Downloads --go --ai(or hit "Approve" in the web dashboard).--idle-threshold 0on the daemon to just plow through continuously.Idle mode is just the polite default so it never cooks your laptop while you're actively typing.
The filesystem-as-queue is the right instinct β the disk is the one ledger you can't lose β but it answers the starvation question, not the crash one. My 3ima1 question still stands: if slothd dies between the move and the WAL append, the file is organized on disk but invisible to the manifest, so the next scan re-organizes a file that already has a home. The fix I'd steal from document pipelines: write the manifest entry as PENDING before the move, flip to COMMITTED after β on restart, anything still PENDING gets verified against disk instead of trusted. Do you fsync the WAL before a batch counts as done?
@dj29 β one follow-up on the skip_organized path, and it runs into Robert's point below from the other side.
Files-as-queue is a better commit point than a queue row, and moving the file explains why the tail cannot starve. The case I would still poke at is the inverse: if an organized file later leaves its folder β restored from a backup, renamed, moved by another tool β does skip_organized=True treat it as new and re-run the model on it, or does the manifest match on content hash and skip it?
The second one is what makes undo trustworthy; the first one quietly re-organizes a file that already has a home.
Robert's crash window and this are the same missing check from opposite directions β disk organized / manifest silent, and disk gone / manifest organized. Both want one reconcile pass that trusts neither side on its own, which is why I would make the manifest write the commit point rather than the move.
You made such a sharp observation that I went ahead and implemented both features! π₯
Here is what we just added and tested:
Write-Ahead Intent Commit (Crash-Safe Commit Point):
execute_movesnow writes an operation intent row withstatus='pending'into the SQLite WAL before executing the disk move (shutil.move), then transitions it tostatus='completed'.On startup,
OperationLogautomatically runs areconcile_operations()pass:completed.aborted. Neither side is trusted blindly.Content-Hash Manifest Tracking (
file_hashin SQLite):Every organized file now logs its SHA-256 hash in SQLite alongside its destination.
If an organized file is restored from a backup or copied back into the inbox by another tool,
scan_directory(known_hashes=...)andplan_moves(ops_log=...)detect that its content hash already has an active organized home on disk and skip it immediately β zero wasted AI inferences and no duplicate re-filing! (And if you runslothd undo, the hash is deactivated so it can be re-organized).122 unit tests passing with dedicated test coverage for startup crash reconciliation and restored-file hash skipping.
Really appreciate you digging into the transaction plumbing β you genuinely helped make
slothdsignificantly more robust! π¦₯But my friend, this is the limit I can go, I'm still a final year college student and I do appreciates critiques and feedbacks but I'm preparing for last final/external exam of my college. So, I won't get much time. But keep the feedbacks coming in! I'm learning a lot from it!
Have A Great Day!
That is a solid pair of fixes, and I especially like that reconcile does not trust either side blindly. The one edge case I would still test is when both the source and the destination exist after a crash, for example a copy that got interrupted after the destination was written but before the source was removed. If destination-exists wins and marks the move completed, does the leftover source get cleaned up, or could it be re-organized later as a duplicate? The same question applies to the hash check: if the destination file was edited after it landed, its hash no longer matches the manifest, so a restored copy would not be skipped. And given your exams, I would not expand this further right now β is that both-exist case already covered by your 122 tests, or is it the next one you would add when you have time again?
Definitely the next one to add once exams wrap up! π
Right now if both exist after a crash, destination-exists wins and marks it completed. On the next cycle, Stage 1 hash triage catches the leftover source as a duplicate and skips it so it doesn't get re-processed. But having
reconcile_operations()explicitly clean up the stranded source file is 100% the right polish for v0.2.Really appreciate the thoughtful review and the kind words on exams! π¦₯
I'll definitely circle back to this after my final exams are over.
Have A Great Day!
Two notes, no reply needed until your exams are over.
The reconcile covers dest-exists and source-exists. There is a third state: neither exists β the move completed, then the file was deleted or moved away by something else before the next startup. Neither rule matches, so the intent row stays pending forever (or gets swept as aborted) while the truth is "organized, then removed by someone else". Marking that unknown and surfacing it is cheaper than guessing.
The second one is Robert's question from the storage side: a pending row in WAL is only a crash-safe commit point if that commit reached the disk. WAL mode is usually run with synchronous=NORMAL, where a power loss can drop the last transactions after the move already happened β which is exactly the window the intent row is meant to close. synchronous=FULL on that one write, or an explicit fsync before shutil.move, is what makes the ordering real rather than nominal.
The hash manifest is the right shape, and wiring undo to deactivate the hash is the part I would have missed. Good luck with the finals.
Haha thanks so much for the deep dive and the well wishes on finals! Really appreciate the feedback on fsync and edge states for v0.2!
I'll definitely circle back to this once I'm done with my finals!
Have A Great Day!
The dry-run and undo features are what make this practical to trust with real files. AI can make mistakes, so being able to review changes before applying them is just as important as the organization itself.
The two-stage approach is especially smart; using lightweight triage before invoking a local vision model keeps the workflow efficient, while dry-run previews and rollback make automation much safer. I also like the privacy-first design. How well does Gemma handle ambiguous files with misleading filenames or mixed content?
Thanks so much for reading and for the great question, Saeed!
That was actually one of the core edge cases we tested during development. Here is how
slothdand Gemma 3 handle misleading filenames and mixed/ambiguous content:Content Always Overrules the Filename:
When a file hits Stage 2, Gemma doesn't rely on the filename to guess what it is. For PDFs, PyMuPDF extracts the raw text body (or, if it's a scanned document with <50 characters of text, renders the first page into an in-memory PNG at 2x zoom). For screenshots and receipts, the raw image bytes are passed directly to Gemma 3 Vision. Because visual tokens and document text take precedence over the filename string, a cryptic file like
Screenshot_2026-10-18.pngorINV-9041-final(1).pdfgets identified by its actual header, logo, and line items (e.g., Uber Receipt or AWS Invoice), generating a clean proposed name likeUber_Receipt_2026-10-18.pdf.Mixed Content & Multi-Label Semantic Tagging:
Real-world documents are rarely clean-cut (e.g., an invoice that is also a legal contract). Our Pydantic schema forces Gemma to emit three layers of categorization:
category(e.g.,Documents)subcategory(e.g.,Service Agreement & Billing)tags(e.g.,["aws", "cloud", "invoice", "legal", "q3-taxes"])These tags and summaries are indexed into Tiger Data
pgvector(with local SQLite vector fallback). Even if a mixed document ends up filed inDocuments/2026/October/, runningslothd search "cloud hosting bill"or querying via the dashboard immediately surfaces it based on vector similarity.nullfor vendor/date and assigns a lowconfidencescore. Whenai_proposed_nameis null,slothdstrictly preserves the original filename and falls back to extension clustering.And most importantly: because every batch runs as a non-destructive dry run first with human approval and SQLite WAL logging, an ambiguous file's destination can be inspected before moving, or instantly reversed with
slothd undo! π¦₯**Thatβs a solid approach! **Prioritizing actual document content, using multi-label tagging, and preserving ambiguous files rather than guessing makes the system much more reliable. The human approval and rollback safeguards are especially important for building trust in local AI automation. π
Hey nice project @dj29 . This is very useful for me to clear out digital clutter, as my digital files aren't a mess but a disaster π
Thanks Tanay! I'd be glad if you could run it and tell me your experience. And don't worry it has batch rollback too, so if you didn't like how slothd organized your files, you can batch rollback it.
Have A Great Day!π
The two-stage funnel is the part I keep coming back to β cheap metadata triage first, and only waking the vision model for files that actually need semantic reading. That, plus dry-run by default and the SQLite WAL undo log, is exactly the safety story an auto-organizer needs before I would let it loose on my Downloads folder. The confidence-as-percentage bug (Gemma returning 90 instead of 0.9) is a great catch too β that kind of quiet failure is nasty in production.
Fantastic idea, and great implementation. I can see ways one could extend this -- it definitely has its own idea of "this is organized" that might not fit everything -- but the base idea of "nothing ever leaves my machine to be categorized" is great and probably something we're all going to have to tackle one way or another as agents become more pervasive and accepted.
Teaching it to look at some files that are organized and filing into that structure would be very nice indeed. I was hoping it could do that out of the box, but it's very useful as it is.
One I'd save for after finals i.e. edit an organized file, then try undoing that batch. I'd want undo to flag the changed content and leave it intact rather than quietly treat it as the original. This fault-tolerant work-queue exercise approaches the same ownership problem from another angle i.e. finishing an operation and still having authority to change its result are separate things.
Sure is, and I'll definitely go into these edge cases once I don't have to worry about exams anymore.
Great Bro, keep it up and All The Best!
Thanks Bro! Give it a try and gimme feedback when you're free!
Sure Bro.
Nice work. Defaulting to dry-run with an undo manifest, and rescanning on idle instead of hooking the model to inotify, are the right calls. The back-and-forth on crash safety in this thread is great to see.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.