<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhinav Sankarshana Dasu</title>
    <description>The latest articles on DEV Community by Abhinav Sankarshana Dasu (@abhinav412).</description>
    <link>https://dev.to/abhinav412</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4154542%2F1cfba8b5-cccd-4475-b93e-b3bc602096c1.png</url>
      <title>DEV Community: Abhinav Sankarshana Dasu</title>
      <link>https://dev.to/abhinav412</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhinav412"/>
    <language>en</language>
    <item>
      <title>haunt: the ghost in your git history — a local Gemma 2 whisper for your repo's graveyard</title>
      <dc:creator>Abhinav Sankarshana Dasu</dc:creator>
      <pubDate>Sat, 31 Oct 2026 09:13:13 +0000</pubDate>
      <link>https://dev.to/abhinav412/haunt-the-ghost-in-your-git-history-a-local-gemma-2-whisper-for-your-repos-graveyard-3ndg</link>
      <guid>https://dev.to/abhinav412/haunt-the-ghost-in-your-git-history-a-local-gemma-2-whisper-for-your-repos-graveyard-3ndg</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I Built
&lt;/h1&gt;

&lt;p&gt;Every repository is quietly becoming a graveyard. Files nobody remembers writing. Tests nobody dares delete. TODOs aging like gravestones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;haunt&lt;/strong&gt; is a ghost for that graveyard — and it lives in git, not in a terminal pasted on top. Two small rituals, entirely offline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;          &lt;span class="c"&gt;# then, inside any git repository:&lt;/span&gt;
haunt &lt;span class="nb"&gt;install&lt;/span&gt;          &lt;span class="c"&gt;# the whisper joins your post-commit hook&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"tomb, again"&lt;/span&gt;
&lt;span class="c"&gt;#   🕯️ Ah, the echoes of lost brilliance in those dusty files, a symphony of&lt;/span&gt;
&lt;span class="c"&gt;#      potential forever unplayed. The phantom hand of progress reaches out&lt;/span&gt;
&lt;span class="c"&gt;#      to erase... but will it leave behind anything but shadows?&lt;/span&gt;

haunt scan             &lt;span class="c"&gt;# which files are the most haunted, and why&lt;/span&gt;
haunt report &lt;span class="nt"&gt;--html&lt;/span&gt;    &lt;span class="c"&gt;# a narrated HAUNTINGS.md of the worst hauntings&lt;/span&gt;
haunt persona ancestor &lt;span class="c"&gt;# swap the voice: victorian, poltergeist, ancestor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three design choices make it a &lt;em&gt;ghost in the machine&lt;/em&gt; rather than an AI plugin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The deterministic score comes first.&lt;/strong&gt; Every tracked file gets a haunting score computed from &lt;code&gt;git log&lt;/code&gt; alone — no model, fully explainable:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   dormant  = min(age_days/365, 1) × min(size/500, 1)        (w 0.35)
   restless = min(churn_90d/20, 1)                            (w 0.25)
   orphaned = 1 if every author inactive 180d+                (w 0.25)
   debt     = min((TODO FIXME HACK XXX)/10, 1)               (w 0.15)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scores name their ghosts: 🫥 &lt;strong&gt;Orphan spirit&lt;/strong&gt; (all authors gone), 🕸️ &lt;strong&gt;Sleeper&lt;/strong&gt; (old, quiet), ⚡ &lt;strong&gt;Poltergeist&lt;/strong&gt; (high churn, many hands, no owner), 🕯️ &lt;strong&gt;Revenant&lt;/strong&gt; (dormant for months, then suddenly touched).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model only narrates; it never reads your code.&lt;/strong&gt; Gemma 2 2B (open weights, via Ollama) receives &lt;em&gt;metrics and commit subjects&lt;/em&gt; — never source files (&lt;code&gt;send_snippets&lt;/code&gt; is off by default). A 2B model can't review code, so haunt never asks it to. It asks for what a 2B model is actually good at: voice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The whispers never block a commit.&lt;/strong&gt; One generate attempt inside a time budget; if the model is cold, busy, or absent, a deterministic template keyed to the commit hash takes over — same commit, same line, forever. No retries, no queues. The hook always exits 0.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  The demo (all captured live, CPU-only laptop, today)
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;haunt scan&lt;/code&gt; on &lt;a href="https://github.com/pallets/click" rel="noopener noreferrer"&gt;pallets/click&lt;/a&gt; — a 14-year-old, very-much-alive open-source CLI library — finds &lt;strong&gt;175 haunted files&lt;/strong&gt;. The top haunting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score   type             age    churn90  authors  debt  loc  file
0.59    🫥 Orphan spirit  354d   0        0/13     0     782  tests/test_context.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thirteen authors. &lt;strong&gt;All of them inactive.&lt;/strong&gt; 354 days without a commit. The file has a funeral attended by no one. The full tally: &lt;strong&gt;84 orphan spirits, 81 sleepers, 10 poltergeists&lt;/strong&gt; — a repo at rest that nobody would call haunted until you read the blame.&lt;/p&gt;

&lt;p&gt;And with the ancestor persona — built for Día de los Muertos, the week's truth that this Halloween stretch is also about honoring the past:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🪔 &lt;em&gt;"Their efforts bring warmth and joy to these old halls. May their spirits find solace in the stories shared through this code."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The poltergeist handles the noisy places:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚡ &lt;em&gt;"More flickering, more ghosts. Just don't break a fingernail."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Graceful degradation, demonstrated by literally killing Ollama mid-session and committing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🕯️ A chill passes through the repository. The commit is buried without ceremony.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same line twice, deterministically keyed to the commit hash — and the hook cost the commit nothing.&lt;/p&gt;

&lt;p&gt;And when the report narrates with Gemma — here is haunt's HAUNTINGS.md on that same repo, Victorians mourning real files:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;age 354d | churn90 0 | authors 0/13 | TODO/FIXME 0 | 782 loc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The pages flutter, whispering tales of what could have been. An echo of promises left unfulfilled, a spectral symphony in the grand ballroom of code. Time slips away like dust, leaving only emptiness where brilliance once resided. Give this ghost a purpose: delete, add a test, document it, or assign an owner to finally give it meaning, however fleeting."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model honestly ends with what the &lt;em&gt;metrics&lt;/em&gt; justify — every narration closes with a grounded directive, not vibes. On a lonely orphaned doc it got almost tender:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Dust motes dance in the flickering light, each one a memory of a lost chapter, a discarded dream. So many stories whispered between lines of code, now gathering dust like forgotten souls. A quiet voice asks: 'Shall I erase its echo or preserve its fragile remnants?' Name an owner for this forlorn document; at least grant it some solace."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How I Built It
&lt;/h1&gt;

&lt;p&gt;Python 3.13, &lt;code&gt;typer&lt;/code&gt;, &lt;code&gt;httpx&lt;/code&gt;, and raw &lt;code&gt;git&lt;/code&gt; subprocesses. No GitPython, no LLM framework. &lt;a href="https://github.com/Abhinav412/haunt" rel="noopener noreferrer"&gt;The repo is here&lt;/a&gt;; MIT licensed; 24 tests pass, including a test that runs a &lt;em&gt;real&lt;/em&gt; &lt;code&gt;git commit&lt;/code&gt; end-to-end through the installed hook and asserts the whisper appears.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One-pass history.&lt;/strong&gt; A single &lt;code&gt;git log --format=&amp;lt;sep&amp;gt;%H&amp;lt;sep&amp;gt;%at&amp;lt;sep&amp;gt;%an&amp;lt;sep&amp;gt;%s&amp;lt;sep&amp;gt; --name-only&lt;/code&gt; feeds the entire scanner — records joined on &lt;code&gt;\x1e/\x1f&lt;/code&gt; unit separators. Small saga: Python's &lt;code&gt;str.splitlines()&lt;/code&gt; &lt;em&gt;will&lt;/em&gt; eat your &lt;code&gt;\x1e&lt;/code&gt; as a line boundary and silently shred every record; &lt;code&gt;split("\n")&lt;/code&gt; keeps the ghosts intact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 2 2B with intent.&lt;/strong&gt; &lt;code&gt;num_ctx&lt;/code&gt; pinned at 4096 — Ollama's silent default of 2048 clips the persona+stats prompt without telling you. &lt;code&gt;keep_alive=30m&lt;/code&gt; and an install-time warm-up so the first whisper fits its budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent hook surgery.&lt;/strong&gt; &lt;code&gt;haunt install&lt;/code&gt; writes a marker block (&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; HAUNT BEGIN … &amp;lt;&amp;lt;&amp;lt; HAUNT END&lt;/code&gt;) with an absolute interpreter path and a &lt;code&gt;PYTHONPATH&lt;/code&gt; fallback to &lt;code&gt;python -m haunt&lt;/code&gt; — the hook works in any repo without &lt;code&gt;pip install&lt;/code&gt; — and backs up any existing hook it finds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classification precedence found by tests, not vibes.&lt;/strong&gt; My first rule made Sleeper unreachable: any file old enough was already an orphan. Ship order is Orphan → Revenant → Poltergeist → Sleeper, with Sleeper firing at ~90 days quiet &lt;em&gt;while its author is still around&lt;/em&gt; — the exact limbo of files everyone assumes someone else maintains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three posts from the community shaped these calls: &lt;a href="https://dev.to/enjoy_kumawat"&gt;@enjoy_kumawat&lt;/a&gt;'s &lt;a href="https://dev.to/enjoy_kumawat/my-ai-commit-hook-got-a-timeout-fix-and-an-install-fix-it-had-never-once-actually-run-45pa"&gt;install-and-timeout postmortems&lt;/a&gt; (hooks that "ran" but never actually run), &lt;a href="https://dev.to/lowspecai/tokens-per-second-is-a-memory-bandwidth-number-measure-it-in-2-seconds-1omf"&gt;@lowspecai&lt;/a&gt; and &lt;a href="https://dev.to/studiomeyer_io/local-llms-in-2026-what-actually-works-on-consumer-hardware-50me"&gt;@studiomeyer_io&lt;/a&gt; on what local inference really costs on CPU, and &lt;a href="https://dev.to/bartrozycki/when-retries-timeouts-and-fallbacks-start-interacting-23gc"&gt;@bartrozycki&lt;/a&gt; on how retries and fallbacks compound into their own failure mode — which is why haunt has exactly one attempt and one fallback, no interaction between them.&lt;/p&gt;

&lt;p&gt;Then reality kept the honesty flowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My 8-second whisper budget was a siren song.&lt;/strong&gt; Measured cold truth: gemma2:2b decodes at ~5.4 tok/s on this CPU-only machine, so a 100-token whisper is ~18 seconds — the whisper path was &lt;em&gt;unreachable&lt;/em&gt;, always falling back. Defaults are now 25s/100 tokens, tuned to the lane the benchmarks above predicted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The probe gate did more harm than silence.&lt;/strong&gt; A 2-second "is Ollama alive" check flaked precisely when the server was busy decoding, silently downgrading real whispers to templates. Removed — generation carries its own timeout; the fallback covers everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emoji left silently.&lt;/strong&gt; On Windows' default cp1252 pipes, persona emoji raised &lt;code&gt;UnicodeEncodeError&lt;/code&gt; inside the &lt;em&gt;never-fail-a-commit&lt;/em&gt; catch-all — the line simply vanished. The CLI now bootstraps UTF-8 at startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;And while writing this post&lt;/strong&gt;, a commit ran while the model was busy narrating a report in the background — the whisper fell back to its template and the commit succeeded instantly. That's the contract working, not failing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why Open Innovation Matters
&lt;/h1&gt;

&lt;p&gt;Haunt's premise is that an AI reads your &lt;em&gt;private diffs&lt;/em&gt; to comment on them. Closed models make that premise toxic: your repo's history would live in someone else's logs forever. With &lt;strong&gt;Gemma's open weights on-device&lt;/strong&gt;, the only witness is a ghost, and it never leaves your machine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It works with the lights off.&lt;/strong&gt; Airplane mode, airgapped CI, a hotel with one dead outlet — haunt doesn't care; nothing in the pipeline calls home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero marginal cost makes it a habit.&lt;/strong&gt; A whisper on &lt;em&gt;every&lt;/em&gt; commit is viable only when inference is free. Gate every commit behind a $0.002 API bill and the ghost becomes a toy you disable in week one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The haunting can outlive the tool.&lt;/strong&gt; 1.6 GB — one &lt;code&gt;ollama pull gemma2:2b&lt;/code&gt; — and any other Ollama model slots in via one config line. Personas are TOML files; localize the ancestor voice for your team's history. And the fallback templates are auditable, first-party Python — even "no model" is an inspectable code path here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the open story in miniature: when the proprietary link dies, dignity dies with it. When the open link dies, you still get a whisper, just a quieter one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Honest Limits
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;CPU-only whisper takes ~18s; report narrations (~260 tokens) up to a minute each. Fine off to the side, too slow to be chatty.&lt;/li&gt;
&lt;li&gt;The score is a heuristic, not truth — churn isn't ghostliness. Every row prints its own metrics so you can argue with the ghost.&lt;/li&gt;
&lt;li&gt;Shallow clones thin out age and author data; haunt still scores, just blunter.&lt;/li&gt;
&lt;li&gt;Next up: a pre-push "last rites" summary, blame-aware ancestor naming (opt-in), and a GitHub Action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository is never truly empty. It is merely quiet. 🪦&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>python</category>
      <category>git</category>
    </item>
    <item>
      <title>sloth-tidy: the file organizer that files like you — and never repeats a mistake</title>
      <dc:creator>Abhinav Sankarshana Dasu</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:55:13 +0000</pubDate>
      <link>https://dev.to/abhinav412/sloth-tidy-the-file-organizer-that-files-like-you-and-never-repeats-a-mistake-1mgi</link>
      <guid>https://dev.to/abhinav412/sloth-tidy-the-file-organizer-that-files-like-you-and-never-repeats-a-mistake-1mgi</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What I Built
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;sloth-tidy&lt;/strong&gt; — a local file organizer that tidies a messy folder &lt;em&gt;overnight&lt;/em&gt;, files it the way &lt;strong&gt;you&lt;/strong&gt; file it (not the way a generic tool guesses), and gets better every time you tell it it's wrong.&lt;/p&gt;

&lt;p&gt;It is deliberately not the first "point a local LLM at Downloads" tool. Plenty shipped this month (&lt;a class="mentioned-user" href="https://dev.to/tahary"&gt;@tahary&lt;/a&gt;'s daemon-style SlothTidy, &lt;a class="mentioned-user" href="https://dev.to/sufian_khan"&gt;@sufian_khan&lt;/a&gt;'s Messimo, and more). In every one of them the same failure shows up after a few nights: the model files your invoices the way an &lt;em&gt;average&lt;/em&gt; user would, and it commits the same mistake on night 14 that it made on night 1. You unfile it, life moves on, the tool learns nothing.&lt;/p&gt;

&lt;p&gt;sloth-tidy is built around three mechanisms that close that loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Habit profiling.&lt;/strong&gt; Before classifying anything, a read-only scanner walks the folders you &lt;em&gt;already organize yourself&lt;/em&gt; — &lt;code&gt;~/Documents&lt;/code&gt;, whatever you keep tidy — and learns your taxonomy, your naming style, and your filename habits. If your folder is called &lt;code&gt;Work &amp;amp; Career&lt;/code&gt;, the model is never even &lt;em&gt;offered&lt;/em&gt; the choice &lt;code&gt;Work&lt;/code&gt;; the allowed enum in the prompt is built from your real folder names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo-as-feedback.&lt;/strong&gt; &lt;code&gt;sloth-tidy undo&lt;/code&gt; doesn't just reverse a run — every reversal is recorded as a &lt;em&gt;correction&lt;/em&gt;, and the next run's classify prompt carries your recent corrections: "the user rejected this filing; do not repeat it." Fix it once, never again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-model consensus.&lt;/strong&gt; Any file the primary model (&lt;code&gt;gemma3:4b&lt;/code&gt;) tags below 0.7 confidence also gets a second opinion (&lt;code&gt;qwen2.5:3b&lt;/code&gt;). Agree → file it with the weaker vote's confidence. Disagree → one richer reconsider pass with both opinions in context; still shaky → honest &lt;code&gt;Unsorted&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And because this thing is going to move files while you sleep, safety isn't an afterthought — it's the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run default.&lt;/strong&gt; Nothing moves unless you explicitly pass &lt;code&gt;--apply&lt;/code&gt;. The preview is computed by the exact same destination math as apply, so the table you approve is what happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move-only.&lt;/strong&gt; &lt;code&gt;shutil.move&lt;/code&gt; is the only filesystem verb in the codebase. No delete, no overwrite, anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journal-first.&lt;/strong&gt; Every move is recorded &lt;code&gt;pending&lt;/code&gt; in an SQLite journal &lt;em&gt;before&lt;/em&gt; the move and marked &lt;code&gt;done&lt;/code&gt; (with the final destination) after. A crash can only leave a pending row — undo refuses those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never overwrites.&lt;/strong&gt; Collisions get &lt;code&gt;-2&lt;/code&gt;, &lt;code&gt;-3&lt;/code&gt;, … suffixes — during apply, &lt;em&gt;and&lt;/em&gt; during undo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo forever.&lt;/strong&gt; &lt;code&gt;sloth-tidy undo&lt;/code&gt; reverses the last run (or any run by ID) newest-first. A vanished destination is logged honestly as &lt;code&gt;skipped_missing&lt;/code&gt;; a re-filled source spot gets the file restored &lt;em&gt;beside&lt;/em&gt; it, never through it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Code
&lt;/h1&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Abhinav412/sloth-tidy" rel="noopener noreferrer"&gt;https://github.com/Abhinav412/sloth-tidy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fastest repro is three commands — a generated messy folder, previewed, then applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python tests/make_samples.py demo/messy-inbox   &lt;span class="c"&gt;# 16 messy files incl. a dupe pair&lt;/span&gt;
sloth-tidy run &lt;span class="nt"&gt;--config&lt;/span&gt; demo/config.yaml        &lt;span class="c"&gt;# dry-run plan table — nothing moved&lt;/span&gt;
sloth-tidy run &lt;span class="nt"&gt;--config&lt;/span&gt; demo/config.yaml &lt;span class="nt"&gt;--apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;…and &lt;code&gt;sloth-tidy digest&lt;/code&gt; in the morning, &lt;code&gt;sloth-tidy undo&lt;/code&gt; when the weekend-you disagrees with the overnight-you (that disagreement becomes the training data the next run consumes).&lt;/p&gt;

&lt;p&gt;The part I'm most convinced by is the journal contract. A planned move is two-phase:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mover.py — journal-first move: pending BEFORE the filesystem feels it
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_meta&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;planned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;target_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;move_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_pending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;planned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... then the move, then mark_done(move_id, final_dst)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Undo lives on the other side of the same coin, and writes the correction&lt;br&gt;
memory the next run consumes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mover.py — undo_run: never combine a restoration and a lesson
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                   &lt;span class="c1"&gt;# newest-first
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_skipped_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# honest, not a crash
&lt;/span&gt;        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;restored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unique_destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# src free -&amp;gt; src, occupied -&amp;gt; src-2
&lt;/span&gt;    &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restored&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_undone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_correction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;restored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The category enum is enforced at inference time with Ollama's &lt;code&gt;format=&amp;lt;json&lt;br&gt;
schema&amp;gt;&lt;/code&gt; — schema-constrained decoding, not "please return JSON" hoping:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# classify.py — the model physically cannot invent a category
&lt;/span&gt;&lt;span class="n"&gt;category_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category_schema&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;category_schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Safety has its own test file (&lt;code&gt;tests/test_safety.py&lt;/code&gt;): never-overwrite, journal&lt;br&gt;
round-trip, undo reversibility, corrections dedupe, consensus ladders.&lt;/p&gt;
&lt;h1&gt;
  
  
  How I Built It
&lt;/h1&gt;

&lt;p&gt;Everything runs on &lt;strong&gt;open-weight models through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt;,&lt;br&gt;
on your laptop, with design choices tuned for small open-weight models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema-constrained prompts.&lt;/strong&gt; The pydantic &lt;code&gt;Decision&lt;/code&gt; model (flat, 4 fields —
category, new_name, summary, confidence) is compiled to a JSON schema with your
category enum injected, passed as Ollama's &lt;code&gt;format&lt;/code&gt;. Temperature 0. Invalid or
off-enum output gets exactly one corrective retry that carries the real
validation error, then a deterministic &lt;code&gt;Unsorted&lt;/code&gt; fallback. Hard cap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First profile, then classify.&lt;/strong&gt; &lt;code&gt;sloth-tidy profile&lt;/code&gt; walks your organized
directories once, caches a &lt;code&gt;HabitProfile&lt;/code&gt; (categories, naming examples, style
rules), and it merges into the prompt ahead of any manual &lt;code&gt;config.yaml&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consensus ladder, bounded.&lt;/strong&gt; File → model A. If A is confident, no second
call — obvious files shouldn't pay for two votes. Anywhere else, model B votes;
agreement takes the minimum confidence (honest number), disagreement triggers
one reconsider pass with both opinions in context, and a persistent
disagreement lands in &lt;code&gt;Unsorted/&lt;/code&gt; — original filename untouched, because if we
don't trust the category we shouldn't trust the invented name either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digest from journal, not from disk.&lt;/strong&gt; The 2-line morning summary is built
from what the journal &lt;em&gt;recorded&lt;/em&gt;, so what you read is exactly what happened —
even if users touched files since.&lt;/li&gt;
&lt;li&gt;No surprises in memory: corrections dedupe by &lt;code&gt;(src, dst, wrong_category)&lt;/code&gt;,
so repetitive undo cycles never balloon the prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h1&gt;

&lt;p&gt;For this project, open is the whole trust story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing leaves the laptop.&lt;/strong&gt; File contents — invoices, contracts, personal
scans — are snippets sent to a localhost daemon. A cloud API means shipping
your private papers to someone else's quota-policy. Here there is no network
path at all in the classification loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run forever.&lt;/strong&gt; An overnight organizer only creates
change if you leave it running — with 4B/3B open models on Ollama, that's
electricity, not a subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The habit profile is the product, and it's only possible locally.&lt;/strong&gt; A closed
service can't build "your" taxonomy, because they'd have to walk your folders.
Mine reads them once, per cache day, and keeps the walk fully offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable models.&lt;/strong&gt; The config lists models, not an account: put any two
local models in &lt;code&gt;models:&lt;/code&gt; — tonight's consensus pair changes with two lines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  My Agent Session
&lt;/h1&gt;

&lt;p&gt;The full three-day build — design decisions, the journal-first safety contract,&lt;br&gt;
the consensus ladder, every test fix — is saved and embeddable:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;sloth-tidy: building a habit-mirroring local file organizer (3-day build)&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build the sloth-tidy project per ARCHITECTURE.md: a local Ollama-powered overnight file organizer with habit-mirroring, corrections memory, and two-model consensus for the Hacktoberfest Week 3 'Slow and Steady' challenge (due Oct 25).&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Research note: the Week 3 field already has 15+ entries including a same-named SlothTidy daemon. The open lane is habit-mirroring: profile what folders/naming the user already maintains, feed that into the classify prompt, and make every wrong filing a correction the model never repeats.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writemodelspy"&gt;write models.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pydantic contracts: Decision (flat 4 fields = the Ollama JSON schema), FileIn...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;pydantic contracts: Decision (flat 4 fields = the Ollama JSON schema), FileInfo, HabitProfile, Correction&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; validators inject allowed_categories via context&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writeprofilerpy"&gt;write profiler.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;read-only walk of the user's organized folders -&amp;gt; categories, naming examples...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;read-only walk of the user's organized folders -&amp;gt; categories, naming examples, style summary, JSON cache&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; noise/Windows filters included&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writescannerpy"&gt;write scanner.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;top-level scan with skip rules + mtime-ordered SHA-256 dupe pairing&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;top-level scan with skip rules + mtime-ordered SHA-256 dupe pairing&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; 1 partial download and 2-dupe pair detected on demo folder&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writeclassifypy"&gt;write classify.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;schema-constrained Ollama call + bounded 1-retry ladder + deterministic Unsor...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;schema-constrained Ollama call + bounded 1-retry ladder + deterministic Unsorted fallback&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; category enum injected into Ollama format schema&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-shellpytesttests"&gt;shell pytest tests/&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;python -m pytest tests/&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;python -m pytest tests/&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;42 passed (Day 1 suite green)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;go&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Day 2 = the safety layer. Journal-first contract: every planned move is recorded 'pending' in SQLite BEFORE the filesystem sees it, marked 'done' after. Undo only touches 'done' rows, newest-first.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writejournalpy"&gt;write journal.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;runs/moves/corrections tables + WAL, two-phase status marks&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;runs/moves/corrections tables + WAL, two-phase status marks&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writemoverpy"&gt;write mover.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;shared target math for preview AND apply, unique_destination -2/-3 suffixes, ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;shared target math for preview AND apply, unique_destination -2/-3 suffixes, apply_plan journal-first, no overwrite ever&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; broken-symlink case handled via lexists&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editclassifypy"&gt;edit classify.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;two-model consensus: solo when A confident, B votes below 0.7, disagreement -...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;two-model consensus: solo when A confident, B votes below 0.7, disagreement -&amp;gt; one reconsider pass, dead-B degrades to solo-A&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;edited; consensus_min wired from settings&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writedigestpy"&gt;write digest.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;2-line digest + move table built from journal truth, not from disk&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;2-line digest + move table built from journal truth, not from disk&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; write_digest lands in dest/digest.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-shellpytesttests"&gt;shell pytest tests/&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;python -m pytest tests/&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;python -m pytest tests/&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;61 passed, exit 0 — safety suite covers never-overwrite, journal roundtrip, undo reversibility, corrections dedupe, consensus paths&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-shellcliliveloop"&gt;shell CLI live loop&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;run (dry) -&amp;gt; run --apply -&amp;gt; undo -&amp;gt; digest on demo folder&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;run (dry) -&amp;gt; run --apply -&amp;gt; undo -&amp;gt; digest on demo folder&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;14 files journaled run 1; undo restored 14/14 byte-identical; 16 corrections written; digest reprinted&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Found and fixed during the loop: dead model B initially punished the file with a fabricated disagreement — now B's death degrades to solo-with-A. Undo consolidated into mover.undo_run so CLI and tests share one code path.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;go ahead with day 3&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Day 3: README (lead with habit-mirroring + corrections memory, not file-tidying), MIT license, .gitignore, demo walkthrough, and the #hf26challenge submission draft following the challenge template.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writereadmemd"&gt;write README.md&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;invariant table (dry-run default, move-only, journal-first, never-overwrite, ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;invariant table (dry-run default, move-only, journal-first, never-overwrite, undo forever), scheduling via schtasks + cron, honest limitations&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;created; config defaults verified against code&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-gatewaycreatearticle"&gt;gateway create_article&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;DEV draft staged (not published), tags devchallenge/hf26challenge/python/ai&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;DEV draft staged (not published), tags devchallenge/hf26challenge/python/ai&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;draft id 4898757 staged; unterminated HTML comment fixed via update_article&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;repo url set, publish the draft along with saving the agent session&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Submitting this session to DEV, embedding it into the draft's 'My Agent Session' section, and publishing on the user's explicit go-ahead.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        11 of 13 messages
          · some content was hidden by the author
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h1&gt;
  
  
  The Lazy Payoff
&lt;/h1&gt;

&lt;p&gt;The stall: my Downloads folder is the eternal triage list — every few days, ten&lt;br&gt;
minutes of poking, re-filing and "where does this even belong". The payoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~16 files nightly, and after two undo cycles the same mistake never comes back&lt;/li&gt;
&lt;li&gt;One command, scheduled, no UI, no paid API&lt;/li&gt;
&lt;li&gt;The real payoff is qualitative: it's the first organizer I trust unattended,
precisely &lt;em&gt;because&lt;/em&gt; a bad filing costs one undo command and zero future
nights — the undo handles it, the memory remembers it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Prize Categories
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; (featured, $200) — &lt;code&gt;gemma3:4b&lt;/code&gt; is the primary classifier
in the consensus pair: schema-constrained by habit profile, corrected by user
memory, second-opinioned by a smaller model.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>python</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
