<?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: Sanketh Kumar</title>
    <description>The latest articles on DEV Community by Sanketh Kumar (@sank8).</description>
    <link>https://dev.to/sank8</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%2F2192163%2Ff047a549-1f5a-4c9d-ad32-3c3c1f0eed8e.jpeg</url>
      <title>DEV Community: Sanketh Kumar</title>
      <link>https://dev.to/sank8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sank8"/>
    <language>en</language>
    <item>
      <title>My 1B model renamed one file to another file's name — the fix was a provenance guard</title>
      <dc:creator>Sanketh Kumar</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:36:41 +0000</pubDate>
      <link>https://dev.to/sank8/my-1b-model-renamed-one-file-to-another-files-name-the-fix-was-a-provenance-guard-3ekm</link>
      <guid>https://dev.to/sank8/my-1b-model-renamed-one-file-to-another-files-name-the-fix-was-a-provenance-guard-3ekm</guid>
      <description>&lt;p&gt;My Downloads folder had &lt;strong&gt;599 loose files in it&lt;/strong&gt; — 101 PDFs, 79 spreadsheets, 129 images, and a folder full of things called &lt;code&gt;img.png&lt;/code&gt;, &lt;code&gt;fp.jpeg&lt;/code&gt; and &lt;code&gt;1762857107887_floop-plan-4.png&lt;/code&gt;. I had been closing that tab for months.&lt;/p&gt;

&lt;p&gt;So for this week's Hacktoberfest challenge ("build something with open-source AI that rewards laziness") I wrote &lt;strong&gt;Tidy Sloth&lt;/strong&gt;: a local file tidier that sorts a mess into folders and gives files names a human can actually read. Python standard library, Ollama, no cloud, no API key.&lt;/p&gt;

&lt;p&gt;It works. But the interesting part isn't the sorting. It's that a 1-billion-parameter model confidently renamed one file to &lt;em&gt;a completely different file's name&lt;/em&gt;, and the fix was not a better prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sloth is the architecture, not the mascot
&lt;/h2&gt;

&lt;p&gt;Sloths are slow because speed costs energy. A file tidier has the same economics: most files are obvious, and asking a language model about them is wasted work.&lt;/p&gt;

&lt;p&gt;So the pipeline spends tokens like they're calories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file ──▶ rules ──▶ confident? ──yes──▶ done, 0 tokens
                       │
                       no
                       ▼
              local model (batched) ──▶ category + name
                       │
                       ▼
             Python validates, then moves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extension rules&lt;/strong&gt; — &lt;code&gt;png → Images&lt;/code&gt;, &lt;code&gt;xlsx → Data&lt;/code&gt;, &lt;code&gt;zip → Archives&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose rules beat extension&lt;/strong&gt; — &lt;code&gt;Invoice-March.pdf&lt;/code&gt; goes to &lt;code&gt;Invoices&lt;/code&gt;, not &lt;code&gt;Documents&lt;/code&gt;. &lt;code&gt;Resume-Sanketh.pdf&lt;/code&gt; goes to &lt;code&gt;Resumes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A meaningful filename is enough&lt;/strong&gt; — &lt;code&gt;carprices.csv&lt;/code&gt; doesn't need a model to tell you it's data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only the leftovers go to &lt;code&gt;llama3.2:1b&lt;/code&gt;: the timestamps, the &lt;code&gt;img.png&lt;/code&gt;s, the &lt;code&gt;AC3.json&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;On my real folder:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;loose files&lt;/td&gt;
&lt;td&gt;599&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resolved by rules&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;453 (75.6%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;needed the model&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model calls&lt;/td&gt;
&lt;td&gt;13 (batches of 12)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wall time&lt;/td&gt;
&lt;td&gt;14.2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;estimated manual sorting&lt;/td&gt;
&lt;td&gt;~80 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three quarters of the folder never touched the model. That's the whole trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the model lied
&lt;/h2&gt;

&lt;p&gt;Small models are not editors. I knew that. What I didn't expect was &lt;em&gt;how&lt;/em&gt; it failed.&lt;/p&gt;

&lt;p&gt;I batch a dozen filenames per prompt and ask for a category and a cleaner name for each. The model returned exactly twelve answers, in order, in valid JSON. The count matched. The JSON was clean. And it had silently shifted the associations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1786076876258_SoulTree-Brochure-1 (1).pdf  →  AC2.pdf
1786076876258_SoulTree-Brochure-1.pdf      →  Cost-Breakdown-6a61d9864a29092bf3f7159d-1.pdf
1748950125346_out-0.webp                   →  Aadhar-Card.webp
1764919659917_floop-plan-4.png             →  Copy-of-2024-07-TBC-Banner-Generic-2560x650-1-H1nZTAL.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those names belongs to a &lt;em&gt;different file in the same batch&lt;/em&gt;. The model didn't hallucinate a filename; it recycled a real one. Which is worse, because it looks plausible enough to ship.&lt;/p&gt;

&lt;p&gt;My first fix was to stop trusting the model's self-reported index and match answers positionally. That didn't help — the count was already right, so there was nothing to catch. I also stripped echoed extensions, which fixed &lt;code&gt;thing.pdf.pdf&lt;/code&gt; but not this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: a rename may only reuse the file's own words
&lt;/h2&gt;

&lt;p&gt;The rule I settled on is boring and it works. &lt;strong&gt;A proposed name is accepted only if it is built from words that were already in the original filename.&lt;/strong&gt;&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;provenance_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A rename may reorganise a file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s own words. Never import another&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;_compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SoulTree-Brochure-1.pdf&lt;/code&gt; → &lt;code&gt;AC2.pdf&lt;/code&gt; shares zero tokens with the source, so it's rejected and the file keeps its own name. &lt;code&gt;SoulaceNew2.png&lt;/code&gt; → &lt;code&gt;soulace-new-2&lt;/code&gt; shares everything, so it's accepted.&lt;/p&gt;

&lt;p&gt;The model may &lt;strong&gt;reorganise&lt;/strong&gt; a filename. It may never &lt;strong&gt;import&lt;/strong&gt; one.&lt;/p&gt;

&lt;p&gt;I put that and the other invariants into 15 unit tests, including the exact five bad pairs the model produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 -m unittest test_tidysloth
Ran 15 tests in 0.002s
OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the guard, the same 599-file run produced &lt;strong&gt;zero cross-row names&lt;/strong&gt; and zero doubled extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more rules that saved me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A name with no meaning is never given invented meaning.&lt;/strong&gt; Given &lt;code&gt;01.csv&lt;/code&gt;, the model happily suggested &lt;code&gt;project-proposal.csv&lt;/code&gt;. That's not tidying, that's fiction — and it's unfalsifiable, so you'd never notice it was wrong. Now &lt;code&gt;01.csv&lt;/code&gt; stays &lt;code&gt;01.csv&lt;/code&gt;. Only the category is decided by the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic noise removal beats model creativity.&lt;/strong&gt; &lt;code&gt;1762857107887_floop-plan-4.png&lt;/code&gt; became &lt;code&gt;floop-plan-4.png&lt;/code&gt; by stripping a leading 10+ digit message ID in Python — no tokens, no guessing, and it works whether or not Ollama is running. (With one edge case: &lt;code&gt;4446563839.pdf&lt;/code&gt; is &lt;em&gt;all&lt;/em&gt; digits, so stripping the "ID" would leave nothing. The strip only applies if a real name survives.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting is destructive, so the defaults are cowards
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;plan&lt;/code&gt; changes &lt;strong&gt;nothing&lt;/strong&gt;. It writes &lt;code&gt;plan.json&lt;/code&gt; and an HTML report you read in the browser first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apply&lt;/code&gt; moves files and writes a timestamped manifest.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;undo&lt;/code&gt; replays that manifest in reverse and removes the empty folders it created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no delete code path.&lt;/strong&gt; Collisions get renamed, never overwritten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tested apply and undo on a 40-file copy: 40 files into 10 folders, then all 40 restored and the folders cleaned up. I have deliberately &lt;strong&gt;not&lt;/strong&gt; run &lt;code&gt;apply&lt;/code&gt; on my real Downloads folder yet — the plan is sitting there for review, which is exactly the workflow I want from a tool that touches my files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhn83zz6cf8fykw27sjzz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhn83zz6cf8fykw27sjzz.png" alt="Tidy Sloth's generated plan report" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone reaching for a small model
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Match by position, not by index.&lt;/strong&gt; A model-supplied &lt;code&gt;i&lt;/code&gt; field is another generated token, not a fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A correct count proves nothing.&lt;/strong&gt; All twelve rows were there and correctly shaped; the mapping between rows and files was still corrupted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate provenance, not just format.&lt;/strong&gt; Schema validation would have passed every one of those bad renames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the model the smallest job that still helps.&lt;/strong&gt; Here, choosing between 15 folders. Names are mostly mechanical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 14 minutes is real, and it's slow — that's a 1B model on a laptop, and most of it is the model, not the rules. A bigger model would name better and cost more energy, which rather misses the point of a sloth.&lt;/p&gt;

&lt;p&gt;Code: &lt;strong&gt;&lt;a href="https://github.com/sank8-2/tidy-sloth" rel="noopener noreferrer"&gt;github.com/sank8-2/tidy-sloth&lt;/a&gt;&lt;/strong&gt; — MIT, one file, no dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:1b
python3 tidysloth.py scan  ~/Downloads   &lt;span class="c"&gt;# read-only summary&lt;/span&gt;
python3 tidysloth.py plan  ~/Downloads   &lt;span class="c"&gt;# proposals + report&lt;/span&gt;
python3 tidysloth.py apply ~/Downloads/.tidysloth/plan.json
python3 tidysloth.py undo  ~/Downloads/.tidysloth/manifest-&lt;span class="k"&gt;*&lt;/span&gt;.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with no model at all, which still produces a complete and useful plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 tidysloth.py plan ~/Downloads &lt;span class="nt"&gt;--no-model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>I built a listing writer that runs on the seller's own laptop — open-weight models, zero cloud</title>
      <dc:creator>Sanketh Kumar</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:41:35 +0000</pubDate>
      <link>https://dev.to/sank8/i-built-a-listing-writer-that-runs-on-the-sellers-own-laptop-open-weight-models-zero-cloud-1c27</link>
      <guid>https://dev.to/sank8/i-built-a-listing-writer-that-runs-on-the-sellers-own-laptop-open-weight-models-zero-cloud-1c27</guid>
      <description>&lt;p&gt;&lt;strong&gt;A reseller photographs forty things a week and then writes forty listings by hand.&lt;/strong&gt; That is the tedious part of the side hustle — not the sourcing, not the shipping, the copy. Existing tools fix it by charging per listing and shipping your inventory to somebody else's cloud. I wanted to see how far a &lt;em&gt;local&lt;/em&gt; open-weight model could get instead.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Listing Studio&lt;/strong&gt;: describe an item once and it writes a platform-shaped title, description, tags and price range for Etsy, eBay, Depop or Vinted — running entirely on your own machine.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/sank8-2/listing-studio" rel="noopener noreferrer"&gt;https://github.com/sank8-2/listing-studio&lt;/a&gt; (MIT)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo09occtwzt5s8jxw6xpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo09occtwzt5s8jxw6xpe.png" alt="Listing Studio writing a listing for a vintage wooden birdhouse" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who would actually pay for this
&lt;/h2&gt;

&lt;p&gt;The buyer is a one-person reseller: someone clearing a house, selling vintage finds, or running a small craft business. Their economics are small margins and large volume, and their two real costs with cloud listing tools are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-listing pricing.&lt;/strong&gt; When you list 200 items a month, a subscription or credit pack is a real line item. A local model costs electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handing over commercially sensitive data.&lt;/strong&gt; Your stock list, your supplier names and your margins are the business. Uploading them to generate ad copy is a strange trade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of those is a technical argument. They are the reason a local tool exists at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[item notes] --&amp;gt; B[Python: build prompt&amp;lt;br/&amp;gt;+ platform rules]
  B --&amp;gt; C[Ollama&amp;lt;br/&amp;gt;open-weight model]
  C --&amp;gt; D[extract JSON]
  D --&amp;gt; E[Python: clamp title,&amp;lt;br/&amp;gt;normalise tags, price band]
  E --&amp;gt; F[(SQLite history)]
  E --&amp;gt; G[listing + CSV export]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The pipeline has one deliberate rule: &lt;strong&gt;the model writes prose, Python decides every hard limit.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Etsy: 140-character title, 13 tags × 20 chars&lt;/li&gt;
&lt;li&gt;eBay: 80-character title, no tags&lt;/li&gt;
&lt;li&gt;Depop/Vinted: shorter titles, 5 tags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model is never asked to count characters, because small models cannot count. &lt;code&gt;finalise()&lt;/code&gt; clamps titles at a word boundary, lowercases and de-duplicates tags, caps their length, and reorders the price band if the model gets it backwards.&lt;/p&gt;

&lt;p&gt;It is stdlib Python and one HTML file. No pip install, no build step, no account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:1b
python3 app.py        &lt;span class="c"&gt;# → http://localhost:8004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it actually produced
&lt;/h2&gt;

&lt;p&gt;Input:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;item:&lt;/strong&gt; vintage wooden birdhouse · &lt;strong&gt;category:&lt;/strong&gt; garden decor · &lt;strong&gt;materials:&lt;/strong&gt; pine wood · &lt;strong&gt;condition:&lt;/strong&gt; good, some weathering · &lt;strong&gt;details:&lt;/strong&gt; roof lifts off for cleaning; hanging hook on the back; 28 cm tall&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First run, ~49 seconds on CPU, unedited:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title:&lt;/strong&gt; Vintage Weathered Pine Wood Birdhouse by Local Carpenter (56/140 chars)&lt;br&gt;
&lt;strong&gt;Tags (12 returned, 13 allowed):&lt;/strong&gt; garden decor · birdhouse · weathered pine wood · wooden birdhouse · hanging hook · pine wood birdhouse · local carpenter · handmade garden deco · garden accessory · wooden bird house · bird house for birds · decorative wooden &lt;em&gt;(clipped to Etsy's 20-char tag limit mid-phrase — you can see the limit doing its work)&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Price:&lt;/strong&gt; GBP 25–40&lt;br&gt;
&lt;strong&gt;Description:&lt;/strong&gt; &lt;em&gt;the model returned this one empty&lt;/em&gt; — so the deterministic fallback composer wrote it instead. That safety net is not a nice-to-have; it is the difference between an empty listing and a sellable one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then a second run on the identical input produced a shorter title (24 chars), only 8 tags, and a &lt;strong&gt;GBP 5–10&lt;/strong&gt; band — that is the screenshot above, and it is the honest headline: &lt;strong&gt;a 1B model is a drafting assistant, not an appraiser.&lt;/strong&gt; Two runs, a 4× spread on price. The seller still sets the price. What the tool removes is the blank page, and it does that at zero marginal cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that did not work
&lt;/h2&gt;

&lt;p&gt;I wanted the photo to be the input — point the camera at the item, get the listing. It is wired up, and it is &lt;strong&gt;not&lt;/strong&gt; the default, for reasons worth sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On this machine (Ollama 0.18.2, CPU-only) &lt;code&gt;gemma3:1b&lt;/code&gt; and &lt;code&gt;qwen2.5:1.5b&lt;/code&gt; both produced degenerate output — literal token loops like &lt;code&gt;1 1 1 1 1&lt;/code&gt; even at temperature 0. Re-pulling did not help. &lt;code&gt;llama3.2:1b&lt;/code&gt; worked fine, so the runtime is healthy and those blobs are not.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;moondream:latest&lt;/code&gt; described a portrait correctly but returned symbol salad on real product photos, at 130–229 seconds per image.&lt;/li&gt;
&lt;li&gt;With ~4 GB RAM free, holding a vision model and a text model at once made the model server return HTTP 500.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I shipped text-first and left the photo field in place behind &lt;code&gt;VISION_MODEL&lt;/code&gt;. &lt;strong&gt;The honest version of this project is less magical than the pitch, and it still saves the seller real time.&lt;/strong&gt; I would rather write that down than show a screenshot of a vision pipeline I never got working reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters here specifically
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The privacy claim is structural, not a policy.&lt;/strong&gt; There is no API key and no third-party endpoint; the app talks to &lt;code&gt;127.0.0.1:11434&lt;/code&gt;. You can verify it by pulling the network cable out and watching it keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model choice stays yours.&lt;/strong&gt; &lt;code&gt;WRITER_MODEL=llama3.1:8b&lt;/code&gt; is the entire upgrade path from "usable draft" to "genuinely good copy". No plan tier, no vendor deciding what you are allowed to run this month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The price cannot be changed out from under you.&lt;/strong&gt; A tool whose running cost is your own electricity does not get a pricing-page redesign that triples your bill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a business tool aimed at people with thin margins, those are not nice-to-haves. They are the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sank8-2/listing-studio
&lt;span class="nb"&gt;cd &lt;/span&gt;listing-studio &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ollama pull llama3.2:1b &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you sell things on the side: I would genuinely like to know whether the output survives contact with a real listing, and where the copy falls apart. That feedback is more useful to me than a star.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 2 — Side Hustle.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Touch Grass: an offline terrace stargazing guide on open-weight Gemma</title>
      <dc:creator>Sanketh Kumar</dc:creator>
      <pubDate>Sun, 11 Oct 2026 06:13:37 +0000</pubDate>
      <link>https://dev.to/sank8/touch-grass-an-offline-terrace-stargazing-guide-on-open-weight-gemma-3i19</link>
      <guid>https://dev.to/sank8/touch-grass-an-offline-terrace-stargazing-guide-on-open-weight-gemma-3i19</guid>
      <description>&lt;p&gt;For the Touch Grass theme I built something that makes the screen the shortest part of the experience: a stargazing guide for Bengaluru terraces that tells you what's actually above the horizon &lt;em&gt;right now&lt;/em&gt; — then sends you outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terrace Sky Spotter&lt;/strong&gt; — a web app (vanilla HTML/JS + zero-dependency Python server, stdlib only) in two halves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic astronomy (code, not AI):&lt;/strong&gt; 16 anchor stars from open catalogs (Yale Bright Star Catalogue / HYG, public domain) + real Julian-date → sidereal-time math computes altitude/azimuth for 12.97°N 77.59°E. No GPS, no network. Tap any constellation for its finder hint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight narration (local Gemma 3 1B via Ollama):&lt;/strong&gt; a "Narrate my tour" button produces the night's viewing order (setting constellations first), star-hop instructions, myths, and practical tips — plus per-constellation "how do I find it?" answers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since the first version I added the three features that make it a real observing tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌙 &lt;strong&gt;Moon phase + washout filter&lt;/strong&gt; — a synodic model computes illumination (validated against the Mar 3 2026 lunar eclipse); the naked-eye limit slides from ~mag 5.5 to ~mag 2.3 and faint targets get a 🌕 washed-out badge.&lt;/li&gt;
&lt;li&gt;🪐 &lt;strong&gt;Planets&lt;/strong&gt; — low-precision orbital math adds Venus, Mars, Jupiter and Saturn (verified by the Venus-elongation invariant). Saturn at 42° in the east is currently the best thing in the evening sky.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🕘 &lt;strong&gt;Time picker&lt;/strong&gt; — plan any evening ahead, not just right now.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; run &lt;code&gt;python3 app.py&lt;/code&gt;, open &lt;code&gt;http://localhost:8002&lt;/code&gt; (needs Ollama + &lt;code&gt;gemma3:1b&lt;/code&gt;; tour narrates in ~30s on CPU)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/sank8-2/terrace-sky-spotter" rel="noopener noreferrer"&gt;https://github.com/sank8-2/terrace-sky-spotter&lt;/a&gt; (MIT)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;My roommate Nagaraj and me — terrace sessions in Bengaluru, phone brightness down, eyes up. I took it outside on a bright moonlit night and gazed for real: the moonlight washed out the faint stuff, exactly as the app's washout filter now predicts — the Pegasus square, Vega and Altair punched through while Pisces vanished. That field night is literally what the 🌕 badge is built from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters here
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Works with no signal.&lt;/strong&gt; The catalog is bundled data and the model runs on-device. A closed-API guide dies the moment the bars disappear — exactly where you need it most: terraces, trails, villages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location stays yours.&lt;/strong&gt; Stargazing needs your coordinates; here they never leave the laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free + swappable.&lt;/strong&gt; One env var changes the model tomorrow.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest part: don't let the model invent the sky
&lt;/h2&gt;

&lt;p&gt;Testing caught the key failure mode: asked about Cassiopeia while it was below the horizon, the model cheerfully described it as visible "high up". So the app never lets Gemma state positions — the deterministic math owns &lt;em&gt;what's up and where&lt;/em&gt;, and the model only narrates &lt;em&gt;how to find&lt;/em&gt; what code already verified. Below-horizon queries get an explicit "not up right now, here's when to catch it" path. Same architecture as my weekend entry's allergy guardrail: code enforces the safety property, the model supplies the words.&lt;/p&gt;

&lt;p&gt;Also entering &lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — narrated end-to-end by open-weight &lt;code&gt;gemma3:1b&lt;/code&gt;, running locally.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Build for a Friend: a local-first meal planner on open-weight Gemma</title>
      <dc:creator>Sanketh Kumar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 03:33:46 +0000</pubDate>
      <link>https://dev.to/sank8/build-for-a-friend-a-local-first-meal-planner-on-open-weight-gemma-59po</link>
      <guid>https://dev.to/sank8/build-for-a-friend-a-local-first-meal-planner-on-open-weight-gemma-59po</guid>
      <description>&lt;p&gt;My roommate Nagaraj needed high-protein vegetarian food on a ₹2000/week budget — while strictly avoiding peanuts and lactose. So for the Build for a Friend theme, I built him a meal planner that runs &lt;strong&gt;entirely on his laptop&lt;/strong&gt;: no accounts, no API keys, no cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Friend Meal Planner&lt;/strong&gt; — a web app (vanilla HTML/JS + a zero-dependency Python server, stdlib only) that generates a 7-day Indian meal plan with a grocery list and budget check. You fill in your friend's profile — diet, allergies, dislikes, budget — and the plan streams in, token by token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcc3dcjujxb823726qd5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcc3dcjujxb823726qd5g.png" alt="Meal Planner" width="789" height="994"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; run &lt;code&gt;python3 app.py&lt;/code&gt; and open &lt;code&gt;http://localhost:8000&lt;/code&gt; (needs Ollama + &lt;code&gt;gemma3:1b&lt;/code&gt; pulled; ~40s per plan on CPU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/sank8-2/friend-meal-planner" rel="noopener noreferrer"&gt;https://github.com/sank8-2/friend-meal-planner&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;Nagaraj, my roommate in Bengaluru. Hostel-style cooking, 30-minute recipes, ingredients from local markets, everything priced in ₹. I handed him the first plan — his verdict: &lt;em&gt;"now that's cooking"&lt;/em&gt; (he also immediately asked for a recipe mode and non-veg mode, so that's next).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters here
&lt;/h2&gt;

&lt;p&gt;Open-weight AI is what makes this project &lt;em&gt;possible&lt;/em&gt;, not just cheaper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Health data stays home.&lt;/strong&gt; Allergies and eating habits are sensitive. With a local open-weight model, none of it touches a server anyone else controls — a closed API would mean shipping my friend's health profile to a third party on every generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline.&lt;/strong&gt; Hostel Wi-Fi dies constantly; the planner doesn't care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free forever.&lt;/strong&gt; No per-token billing against a student budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable.&lt;/strong&gt; Gemma 3 1B today, something bigger tomorrow via one env var — no vendor lock-in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest part: small models slip, so I built a guardrail
&lt;/h2&gt;

&lt;p&gt;Testing caught something important: the 1B model kept suggesting paneer and yogurt to my &lt;em&gt;lactose-allergic&lt;/em&gt; friend, despite explicit instructions. Rather than hide that, I added a &lt;strong&gt;deterministic allergy guardrail&lt;/strong&gt; — every plan is scanned line-by-line against an allergen keyword map (lactose → milk, curd, paneer, ghee…) and an ⚠️ Allergy Check section is appended flagging risky dishes for review. Prompt engineering sets the intent; code enforces the safety property. That's the architecture I'd defend: never let a probabilistic model be the last word on food safety.&lt;/p&gt;

&lt;p&gt;This also enters the &lt;strong&gt;Best Use of Gemma&lt;/strong&gt; category — the whole app is Gemma-powered, running the open-weight &lt;code&gt;gemma3:1b&lt;/code&gt; model locally through Ollama.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>weekendchallenge</category>
      <category>devchallenge</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GoFr!?</title>
      <dc:creator>Sanketh Kumar</dc:creator>
      <pubDate>Wed, 23 Oct 2024 05:06:09 +0000</pubDate>
      <link>https://dev.to/sank8/gofr-2h4m</link>
      <guid>https://dev.to/sank8/gofr-2h4m</guid>
      <description>&lt;p&gt;Consider GoFr to be your own microservice helper. This framework, which is based on the robust Go language, handles the menial duties so you can concentrate on creating incredible applications.&lt;/p&gt;

&lt;p&gt;Important attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With built-in middleware, there's no need to start from scratch. GoFr saves you time and effort by including necessary middlewares like authentication, error handling, and logging.&lt;/li&gt;
&lt;li&gt;Support for Datastores, File Systems, and Pub/Sub: Easily connect to your preferred file systems, messaging apps, and databases. You're insured by GoFr.
Integrated Monitoring: Use the integrated tracing, monitoring, and logging features of GoFr to closely monitor your microservices.&lt;/li&gt;
&lt;li&gt;Opinionated Approach: GoFr adheres to a precise and dependable framework that directs you toward best practices and steers clear of typical errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Opt for GoFr?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quicker Development: Focus more on creating amazing features and less on boilerplate.&lt;/li&gt;
&lt;li&gt;Enhanced Performance: GoFr creates high-performance microservices by utilizing Go's capabilities.&lt;/li&gt;
&lt;li&gt;Enhanced Reliability: GoFr assists you in developing dependable and strong applications with integrated monitoring and error management.&lt;/li&gt;
&lt;li&gt;Simplified Deployment: GoFr's Kubernetes design simplifies the deployment and management of your microservices.&lt;/li&gt;
&lt;li&gt;Are you prepared to accelerate the development of microservices? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://gofr.dev/" rel="noopener noreferrer"&gt;Try GoFr&lt;/a&gt; and see the difference for yourself.&lt;/p&gt;

</description>
      <category>go</category>
      <category>gofr</category>
    </item>
  </channel>
</rss>
