<?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: Samar Nathani</title>
    <description>The latest articles on DEV Community by Samar Nathani (@sammysncar).</description>
    <link>https://dev.to/sammysncar</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%2F4154449%2F34e021b0-1bc4-4da1-b80b-d2dfb0e6f8b0.jpg</url>
      <title>DEV Community: Samar Nathani</title>
      <link>https://dev.to/sammysncar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sammysncar"/>
    <language>en</language>
    <item>
      <title>One Real Docstring a Night: A Local Open-Weight Model That Keeps a Streak Honest</title>
      <dc:creator>Samar Nathani</dc:creator>
      <pubDate>Sun, 25 Oct 2026 02:07:16 +0000</pubDate>
      <link>https://dev.to/sammysncar/one-real-docstring-a-night-a-local-open-weight-model-that-keeps-a-streak-honest-2j5a</link>
      <guid>https://dev.to/sammysncar/one-real-docstring-a-night-a-local-open-weight-model-that-keeps-a-streak-honest-2j5a</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;streakkeeper&lt;/strong&gt; is a nightly GitHub Action that makes one small, real documentation improvement to one of my repositories, then stops.&lt;/p&gt;

&lt;p&gt;It is scheduled for 22:30 every night. It wakes up, asks GitHub whether I have already contributed today, picks a repository, shallow-clones it, and walks the Python syntax tree for a docstring or README section that is missing or says nothing. A local open-weight model drafts the text. A second model reads the same code and decides whether the draft is true. If it is, the script proves the code did not change, commits one file, and goes back to sleep.&lt;/p&gt;

&lt;p&gt;The effort it saves is not typing. It is the ten minutes every evening when I think "I should commit something", open a repo, stare at it, and close it again. That loop is gone, and what replaces it is a change a reader would actually want.&lt;/p&gt;

&lt;p&gt;What it will not do is manufacture a contribution. No empty commits, no whitespace, no generated files. If nothing passes review, the run ends and nothing is committed. The tool is allowed to do nothing. It is not allowed to invent work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The demo is the schedule itself: &lt;a href="https://github.com/SammySN-car/streakkeeper/actions/workflows/nightly.yml" rel="noopener noreferrer"&gt;the workflow&lt;/a&gt;, and &lt;a href="https://github.com/SammySN-car/jev-deliberation-judge/commit/bda52ac" rel="noopener noreferrer"&gt;a commit it made on its own&lt;/a&gt; on a placeholder CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Looking at SammySN-car/jev-deliberation-judge
  review rejected cli: The proposed text asserts that the implementation was completed, whereas the code only states it is implemented in M5 without indicating completion status.
Committed src/deliberation_judge/cli.py to SammySN-car/jev-deliberation-judge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three lines, the whole loop: it looked, the reviewer threw out the first draft, it tried the next place, and it committed a docstring.&lt;/p&gt;

&lt;p&gt;The commit is one line added and nothing else:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; def main() -&amp;gt; None:
&lt;span class="gi"&gt;+    """Raises a SystemExit upon execution. The exit message indicates that the judge CLI is not implemented yet. This message also references milestone M5."""
&lt;/span&gt;     raise SystemExit("judge CLI not implemented yet (milestone M5)")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A dry run, from a separate dispatch, stops one step earlier and shows the text before it is written:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Looking at SammySN-car/devpulse
Would update SammySN-car/devpulse/src/devpulse/__init__.py:1
  reviewed: The proposed text accurately describes the __version__ assignment without introducing unsupported claims, markdown formatting, or speculative language.
  --- new text ---
  Defines the module's version number. The variable __version__ is assigned the string value "0.1.0". This variable provides a specific version identifier for the package.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The part worth reading is the reviewer's sentence. It rules out "markdown formatting" and "speculative language" by name, because those were the two ways the first drafts went wrong: backticks inside docstrings, and hedged verbs like "likely".&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SammySN-car" rel="noopener noreferrer"&gt;
        SammySN-car
      &lt;/a&gt; / &lt;a href="https://github.com/SammySN-car/streakkeeper" rel="noopener noreferrer"&gt;
        streakkeeper
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      One small, real documentation improvement per night, so a contribution streak survives busy days.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;streakkeeper&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Makes one small, real documentation improvement to one of your repositories
every night, so a contribution streak survives the days you are too busy to
commit.&lt;/p&gt;
&lt;p&gt;A placeholder commit keeps the streak alive and makes the history worse. This
does the opposite: it finds a docstring or a README section that is missing or
says nothing, writes it properly, proves the code did not change, and commits
that one file.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What happens each night&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gate.&lt;/strong&gt; Ask GitHub how many contributions you have today, in your own
timezone. If it is more than zero, stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate.&lt;/strong&gt; Start from a different repository each night, then shallow-clone
one of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan.&lt;/strong&gt; Walk the Python syntax tree and list the places where
documentation is missing or too thin: modules, classes, functions, methods
and README sections that are a heading and nothing else. This step is
deterministic and uses no model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank.&lt;/strong&gt; A small…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SammySN-car/streakkeeper" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Eight steps, and only three of them involve a model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gate.&lt;/strong&gt; Ask GitHub how many contributions I have today, in my timezone. If it is more than zero, stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate.&lt;/strong&gt; Start from a different repository each night (&lt;code&gt;day.toordinal() % len(repos)&lt;/code&gt;), so no single project takes all the attention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan.&lt;/strong&gt; Walk the Python syntax tree and list modules, classes, functions and README sections whose documentation is missing or shorter than forty characters. Deterministic, no model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank.&lt;/strong&gt; The local model orders those candidates and keeps the best fifteen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write.&lt;/strong&gt; The same local model drafts the text for the best one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review.&lt;/strong&gt; A different model reads the code and the draft and returns &lt;code&gt;{"ok": true|false, "reason": "..."}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify.&lt;/strong&gt; Strip every docstring from both versions of the file and compare &lt;code&gt;ast.dump&lt;/code&gt;. If anything else differs, refuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit.&lt;/strong&gt; One file, one commit, one push.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The split is the design. The scan decides &lt;em&gt;where&lt;/em&gt; to look, so a model can never invent a place to write. The model writes, and a different model judges. Nothing in the pipeline is both author and approver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The models.&lt;/strong&gt; Ranking and writing run on &lt;code&gt;gemma4:e4b&lt;/code&gt; through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, inside the GitHub runner: 5.5 GB of model cache, restored in about forty seconds, in a job that takes about five minutes end to end. Review runs on &lt;code&gt;gemini-3.5-flash&lt;/code&gt; with thinking disabled, falling back to &lt;code&gt;gemini-3.1-flash-lite&lt;/code&gt;, because a busy reviewer should not be able to block the night. An unclear verdict counts as a rejection, never as approval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proof.&lt;/strong&gt; &lt;code&gt;verify.code_unchanged()&lt;/code&gt; parses both versions, removes every docstring, and compares the syntax trees. Comments are not in the tree, so a comment-only edit passes. A docstring edit passes. Anything else fails and the run exits non-zero. The code cannot change by construction, not by good behaviour.&lt;/p&gt;

&lt;p&gt;Seventy-one tests cover the scanner, the replacement logic, the parser for each model's answer, the review gate and the GitHub helpers. None of them need a network or a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because the part that does the work is open, and I needed to see inside it.&lt;/p&gt;

&lt;p&gt;The writer is an open-weight model running on the same machine as the tool. That is not a purity badge; it is what made the thing debuggable. When &lt;code&gt;gemma4:e4b&lt;/code&gt; kept wrapping text in backticks that do not belong in a Python docstring, I could read the raw JSON, tighten the prompt, and then strip backticks in code instead of hoping the next prompt would behave. When it hedged - "likely", "suggests" - I could watch it hedge again on the same input. A closed writer would have handed me a wrong docstring and no way to see why.&lt;/p&gt;

&lt;p&gt;It also costs nothing per night, runs on a free runner, and can be pointed at different models with two variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OLLAMA_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemma4:e4b                                 &lt;span class="c"&gt;# the writer&lt;/span&gt;
&lt;span class="nv"&gt;REVIEW_MODELS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemini-3.5-flash,gemini-3.1-flash-lite    &lt;span class="c"&gt;# the auditor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reviewer is the one closed component, and I want to be straight about that. It is a second opinion, not the worker, and it is replaceable. If no reviewer answers, the run stops and nothing is committed. The writing - the part that makes the project work - is open and local.&lt;/p&gt;

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

&lt;p&gt;After setup the tool asks nothing of me: one file, one commit, one push, about five minutes of compute, zero minutes of attention. Of those five minutes, roughly two and a half are the tool itself; the rest is installing Ollama and restoring the model cache.&lt;/p&gt;

&lt;p&gt;The honest accounting is that it has run for real twice. One commit landed. One was reverted, and that is the better story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The night it deleted a code block.&lt;/strong&gt; The first real run added a docstring to a README section and, in doing so, removed a fenced &lt;code&gt;bash&lt;/code&gt; block containing &lt;code&gt;ollama pull llama3.2&lt;/code&gt;. The cause was one line in the scanner:&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;# roughly how the scanner decided which lines were headings
&lt;/span&gt;&lt;span class="n"&gt;headings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A shell comment inside a fenced block - &lt;code&gt;# Runs on localhost:11434 by default&lt;/code&gt; - is not a heading, but that line said it was. The section was measured as ending at the comment, so its body looked twenty-eight characters long, and the writer replaced the body, fence included. The reviewer approved it, because the text accurately described the command that was about to disappear.&lt;/p&gt;

&lt;p&gt;I reverted the commit within minutes. The fix is a fence-aware scan, plus a refusal to replace any section containing a code block, with tests for both. That is the lesson of the week: a tool that does nothing is better than a tool that does damage, and only a real run could have found this. Every dry run was clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The commit that would not have counted.&lt;/strong&gt; The first version committed as &lt;code&gt;streakkeeper &amp;lt;streakkeeper@users.noreply.github.com&amp;gt;&lt;/code&gt;. GitHub credits a commit to your streak only when its author email is associated with your account, so the tool would have worked perfectly and kept no streak at all. A dry run cannot catch that either, because a dry run never commits. It now derives the account's no-reply address from the API and commits as me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; - &lt;code&gt;gemma4:e4b&lt;/code&gt; ranks the candidates and writes the documentation, locally through Ollama. Without it there is no text and no commit.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>A Local Job Agent That Reads Your Code to Find Your Level (and the 4B Model That Invented a Basketball League)</title>
      <dc:creator>Samar Nathani</dc:creator>
      <pubDate>Sat, 17 Oct 2026 23:17:47 +0000</pubDate>
      <link>https://dev.to/sammysncar/a-local-job-agent-that-reads-your-code-to-find-your-level-and-the-4b-model-that-invented-a-130k</link>
      <guid>https://dev.to/sammysncar/a-local-job-agent-that-reads-your-code-to-find-your-level-and-the-4b-model-that-invented-a-130k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;freelance_search&lt;/strong&gt; is a job agent that runs entirely on my machine. On a schedule it crawls five job sources, discards whatever is a bad fit for &lt;em&gt;me&lt;/em&gt; specifically, and posts a shortlist of 20–30 links to Discord. No feed to scroll, no tabs to keep open, no applications wasted on roles I am not senior enough for or cannot legally take.&lt;/p&gt;

&lt;p&gt;The problem it solves: if you are a freelancer, intern, or junior developer job-hunting across several boards, you redo the same triage by hand every day. You open five sites, skim a hundred listings, and mentally discard the ones in the wrong country, the ones titled "Staff Engineer," and the ones whose links already 404'd. That is an hour of low-grade work a machine should be doing.&lt;/p&gt;

&lt;p&gt;Who would pay for it: anyone stuck in that loop. The job-search tooling market exists because people pay for leverage when they are competing against hundreds of applicants. This automates the triage step, on your own hardware, without shipping your profile to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F4wgxatnqilsu4gksvg5d.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.amazonaws.com%2Fuploads%2Farticles%2F4wgxatnqilsu4gksvg5d.png" alt="Discord digest — a numbered list of job links" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SammySN-car" rel="noopener noreferrer"&gt;
        SammySN-car
      &lt;/a&gt; / &lt;a href="https://github.com/SammySN-car/freelance_search" rel="noopener noreferrer"&gt;
        freelance_search
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Daily freelance job scout — SerpApi + free feeds, scored by a local Gemma model, shortlist delivered to Discord
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;freelance_search&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/SammySN-car/freelance_search/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/SammySN-car/freelance_search/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;freelance_search&lt;/code&gt; is a daily opportunity scout that anyone can point at
their own career stage: it searches SerpApi Google Jobs and free remote-job
feeds for freelance work, internships, and entry-level jobs, scores each new
listing with a local Ollama Gemma model against &lt;strong&gt;your&lt;/strong&gt; profile, and posts a
ranked list of up to 25 links to Discord each morning. A job is persisted as
shown only after Discord confirms delivery, so the same job is never shown
twice.&lt;/p&gt;
&lt;p&gt;Everything it optimizes for comes from your own configuration — there is no
hardcoded location or seniority bias in the code.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quickstart&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Requirements: Windows, Python 3.11+, and &lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; running
locally.&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python &lt;span class="pl-k"&gt;-&lt;/span&gt;m venv .venv
.\.venv\Scripts\Activate.ps1
python &lt;span class="pl-k"&gt;-&lt;/span&gt;m pip install &lt;span class="pl-k"&gt;-&lt;/span&gt;e &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;.[dev]&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
ollama pull gemma3:4b
&lt;span class="pl-c1"&gt;Copy-Item&lt;/span&gt; .env.example .env
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Edit .env: Discord + SerpApi values, then your PROFILE,&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; LOCATION, and EXPERIENCE_LEVEL (see Customization below).&lt;/span&gt;
python &lt;span class="pl-k"&gt;-&lt;/span&gt;m&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SammySN-car/freelance_search" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Two open-weight models, both running locally through &lt;strong&gt;Ollama&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mxbai-embed-large&lt;/code&gt;&lt;/strong&gt; — embeds my profile and every listing into a shared vector space. Dense cosine similarity &lt;em&gt;ranks&lt;/em&gt; candidates; it never discards them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gemma3:4b&lt;/code&gt;&lt;/strong&gt; — the judge. It reads the top candidates and scores each one 0–10 against my profile, with a one-line reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The retrieval funnel, per run: crawl roughly 700 raw listings from five sources (Arbeitnow, Jobicy, remoteOK, We Work Remotely, and SerpApi's &lt;code&gt;google_jobs&lt;/code&gt; engine) → embed → rank by similarity → hand the top 50 to Gemma for scoring → keep everything above a floor → post the best 20–30 as a links-only digest.&lt;/p&gt;

&lt;p&gt;The part worth writing up is how the agent decides what "a bad fit" means, because I got it wrong twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First I tried keyword filtering.&lt;/strong&gt; Drop any listing whose text mentions "senior," or whose location field names a country that is not mine. It worked, and it was a mistake. It threw away good listings that used the word incidentally, and it hid listings that described senior work without the title. Filtering on keywords is information loss. So I split the problem in two: dense retrieval handles &lt;em&gt;relevance&lt;/em&gt; and discards nothing, and the only hard drops are the two things a keyword genuinely settles — a stated foreign location, and a seniority title that contradicts my level. Both come from my &lt;code&gt;.env&lt;/code&gt;, so the agent carries no hardcoded bias toward any country or any seniority. It filters for me, not for a hypothetical average user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, I had it read my GitHub to work out my level.&lt;/strong&gt; Telling the model "you are matching for an intermediate developer" is a guess I typed in. Instead the agent lists my repos, pulls real source from each, and asks Gemma to name a level — but only after citing the specific files that prove it:&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;# Instruction goes AFTER the code: with a large code context, gemma3:4b follows
# recency and ignores format rules placed before it (verified locally).
&lt;/span&gt;&lt;span class="n"&gt;GITHUB_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CODE:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{code}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Now answer based ONLY on the code above, output exactly two lines:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Level: beginner|intermediate|senior&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Evidence: &amp;lt;one line citing at least two specific repo or file names as proof&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Judge what the code demonstrates: completeness, structure, testing, and complexity. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ignore stars and popularity. Output only those two lines, nothing else.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That comment is the whole post, really. Here is why it is there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because I could only fix this bug &lt;em&gt;because&lt;/em&gt; the model is open-weight and running on my machine.&lt;/p&gt;

&lt;p&gt;The first version of that GitHub prompt asked Gemma for a JSON object — &lt;code&gt;{"level": ..., "evidence": ...}&lt;/code&gt; — with the instruction placed at the top, before the code. On a large code context, &lt;code&gt;gemma3:4b&lt;/code&gt; under JSON mode collapsed. Feed it roughly 63,000 characters and it returned an empty &lt;code&gt;{}&lt;/code&gt;. Feed it roughly 30,000 and it invented an entire &lt;strong&gt;basketball league&lt;/strong&gt; as its JSON response — a structure with nothing to do with the code, confidently emitted.&lt;/p&gt;

&lt;p&gt;A closed API would have hidden every step of that. I would have gotten a wrong answer with no way to see why, no way to re-run the exact prompt and watch it fail differently at different context sizes, and no cheap way to iterate through five variations. Running it locally, I reproduced the failure on demand, bisected it by prompt length, and found the two things that actually mattered: &lt;strong&gt;put the instruction after the code&lt;/strong&gt; (the model follows recency, not position), and &lt;strong&gt;ask for two plain-text lines instead of JSON&lt;/strong&gt; (the parser takes the first &lt;code&gt;Level:&lt;/code&gt; and &lt;code&gt;Evidence:&lt;/code&gt; lines and ignores whatever else is echoed back). One retry, and it has held ever since.&lt;/p&gt;

&lt;p&gt;That is the case for open innovation in a single bug report. An open, local model turned a silent wrong answer into a debuggable one. It also means my code, my job history, and my profile never leave my machine — which, for a tool that reads your source repositories to judge your skill level, is not a nice-to-have. It is the only version of this I would be willing to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;Me. It has been running against my own profile daily through the entire build, which is how the basketball-league failure got caught in the first place — a staged demo would never have pushed a 63k-character context. No one else has used it yet, and nobody has paid for it. &lt;em&gt;(Cut this section if you would rather not include a self-described first user.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — &lt;code&gt;gemma3:4b&lt;/code&gt; is both the judge and the level-detector. It runs locally through Ollama and is what makes the ranking work at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of SerpApi&lt;/strong&gt; — the &lt;code&gt;google_jobs&lt;/code&gt; engine is one of the five sources, surfacing Google-indexed roles that never appear on the boards directly.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I drove an AI agent to build a local-first outdoor quest game in a day</title>
      <dc:creator>Samar Nathani</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:35:46 +0000</pubDate>
      <link>https://dev.to/sammysncar/i-drove-an-ai-agent-to-build-a-local-first-outdoor-quest-game-in-a-day-i0d</link>
      <guid>https://dev.to/sammysncar/i-drove-an-ai-agent-to-build-a-local-first-outdoor-quest-game-in-a-day-i0d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;SYSTEM://GRASS&lt;/strong&gt; is a local-first outdoor quest game. An AI "System" assigns you one outdoor micro-quest per day, judges your photo evidence, narrates the verdict in a dry System voice, and tracks your rank from F to S — with real XP penalties for ignoring it.&lt;/p&gt;

&lt;p&gt;The loop is deliberately short: open the app, see today's quest, go outside, snap 1-3 photos, submit. Screen time is seconds; the rest happens outdoors. Miss a quest by the deadline and you drop a rank step. The System remembers.&lt;/p&gt;

&lt;p&gt;The phone is a dumb terminal. The brain — quest generation, judgment, narration — runs on a laptop via Ollama. Built for Hacktoberfest 2026 Week 1 ("Touch Grass").&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/GnXzD2FJOoc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A short screen-recorded walkthrough of the core loop: the quest loads, evidence is captured and submitted, and the System returns a verdict with its narration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One honest note: this demo only really covers the single main path — one quest taken, submitted, and judged. The offline buffer and the rest of the loop are in the code and test suite, but I didn't have the energy to film them. I'd been awake most of the night building this and just didn't have the strength to step outside or do a longer take. The photos in the demo are indoor shots for the same reason. The quest itself is outdoor-oriented; the recording just shows the judgment loop working.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SammySN-car" rel="noopener noreferrer"&gt;
        SammySN-car
      &lt;/a&gt; / &lt;a href="https://github.com/SammySN-car/touchgrass" rel="noopener noreferrer"&gt;
        touchgrass
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Hacktoberfest 2026 Open-Source AI Challenge Week 1 (Touch Grass) - scratch repo, design TBD
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SYSTEM://GRASS&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;local-first outdoor quest game&lt;/strong&gt;. An AI "System" assigns you one outdoor
micro-quest per day, judges your photo evidence, narrates the verdict, and
tracks your rank from F to S - with real penalties for ignoring it.&lt;/p&gt;
&lt;p&gt;Built for the &lt;strong&gt;Hacktoberfest 2026 Open-Source AI Challenge: Week 1
("Touch Grass")&lt;/strong&gt;. Submission post tagged &lt;code&gt;#hf26challenge&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What makes it different&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% local.&lt;/strong&gt; Quest generation and judgment run on your own machine via
Ollama (Gemma). No cloud, no accounts, no API keys. Your photos never leave
your laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone = terminal.&lt;/strong&gt; A mobile web app on your LAN captures evidence; the
brain stays home. The screen is the shortest part of the experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consequences, not confetti.&lt;/strong&gt; Ranks, XP, streaks, demotions. The System
is harsh but recovery is always one quest away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System-window aesthetic.&lt;/strong&gt; Isekai-inspired interface. Original names and
lore - not affiliated with any rights holder.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Docs&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/SammySN-car/touchgrass/docs/design.md" rel="noopener noreferrer"&gt;docs/design.md&lt;/a&gt; - frozen…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SammySN-car/touchgrass" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Python 3.13 + FastAPI + SQLite backend; vanilla JS/CSS frontend (no build step); Ollama + Gemma for narration. &lt;strong&gt;61 tests green&lt;/strong&gt;, ~2,200 lines, 20 commits.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The open-source AI at the core is &lt;code&gt;gemma3:4b&lt;/code&gt; running locally through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/strong&gt; No API key, no per-call bill, no request leaves the machine.&lt;/p&gt;

&lt;p&gt;I built this by &lt;strong&gt;driving an AI coding agent&lt;/strong&gt;, task by task, over a single day. I froze a design doc, split the work into ten tasks with exact contracts, and let the agent implement each one against a failing test first (TDD), committing after every green. I reviewed every diff.&lt;/p&gt;

&lt;p&gt;Two design decisions worth calling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two-tier judge.&lt;/strong&gt; Tier 1 is deterministic rules (photo count, freshness vs. quest issue time, deadline, file-size sanity) that produce the skeleton PASS/FAIL — &lt;em&gt;never&lt;/em&gt; depending on the model. Tier 2 is Gemma narration: quest text + evidence metadata into a dry System-voice line. If the model contradicts the rules verdict, the model's line is discarded and a canned bank line is used. The model narrates; it never judges. A hallucination should never cost you XP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline-first evidence.&lt;/strong&gt; Photos go into an IndexedDB buffer the instant you capture them — no spinner, no network. The buffer flushes on the &lt;code&gt;online&lt;/code&gt; event, on app load, and on a manual tap. Rows are deleted only on a &lt;code&gt;200&lt;/code&gt; or a &lt;code&gt;409&lt;/code&gt;. A &lt;code&gt;409&lt;/code&gt; means the quest was already judged — the UI shows the existing verdict and clears the buffer, never an error dialog.&lt;/p&gt;

&lt;p&gt;The Ollama client falls back &lt;code&gt;gemma3:4b&lt;/code&gt; -&amp;gt; &lt;code&gt;llama3.2&lt;/code&gt; -&amp;gt; &lt;code&gt;qwen2.5:7b&lt;/code&gt;. Change one env var (&lt;code&gt;GRASS_MODEL&lt;/code&gt;) and the System gets a new brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;This project is &lt;em&gt;made of&lt;/em&gt; open pieces, and each one earns it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It runs where the data lives.&lt;/strong&gt; Quest text and verdict narration happen on my laptop. Photos never leave the machine. A closed API would mean sending evidence photos to a third party, every day, forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works with no signal.&lt;/strong&gt; After the model is pulled once, the full loop works on a plane, on a train, behind a captive portal. Evidence is buffered in IndexedDB and flushed when you're back on the LAN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing per decision.&lt;/strong&gt; No inference bill, no rate limit, no API key sitting in a &lt;code&gt;.env&lt;/code&gt; file that can leak. Here it's electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could swap the model in one line.&lt;/strong&gt; The fallback chain (&lt;code&gt;gemma3:4b&lt;/code&gt; -&amp;gt; &lt;code&gt;llama3.2&lt;/code&gt; -&amp;gt; &lt;code&gt;qwen2.5:7b&lt;/code&gt;) is one env var. Because everything behind the judge is an open interface, that decision was &lt;em&gt;mine to measure&lt;/em&gt;, not a vendor's roadmap.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest tradeoff of open here is latency — Gemma narration takes ~10 seconds. For a game whose whole premise is &lt;em&gt;leaving your screen&lt;/em&gt;, that's not a bug; it's a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honesty section
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v1 does not understand pixels.&lt;/strong&gt; The 4GB VRAM budget means the judge reads &lt;em&gt;metadata&lt;/em&gt; (count, timestamp, file size), not image content. Owned honestly, not hand-waved. &lt;strong&gt;Planned next (not in this build):&lt;/strong&gt; add a CNN verification tier that reuses a pretrained vision model — e.g. MobileNet/ResNet via torchvision, or a local vision model through Ollama — to catch wrong or irrelevant photos before they reach the narration layer. This submission ships without it; it's the next step. The deterministic rules stay authoritative; the CNN would be a gatekeeper, not a judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The roasts are a feature, not the marketed identity.&lt;/strong&gt; The README and this post pitch the loop and the local-first story. The System's meanness is a discovery, not the headline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No IP borrowed.&lt;/strong&gt; Original lore and names. Not affiliated with any rights holder.&lt;/li&gt;
&lt;/ul&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;ollama pull gemma3:4b
pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn pillow
uvicorn app.main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;code&gt;http://&amp;lt;laptop-ip&amp;gt;:8000&lt;/code&gt; on your phone (same Wi-Fi).&lt;/p&gt;
&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Entering &lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — SYSTEM://GRASS runs &lt;code&gt;gemma3:4b&lt;/code&gt; locally through Ollama as the core model. Gemma is the only thing that speaks; it powers both the per-quest text and the verdict narration, entirely on-device.&lt;/p&gt;

&lt;p&gt;Also automatically in the running for the &lt;strong&gt;overall Week 1 prize&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The full build session — including the parts that went wrong (a 2-hour subagent wall-clock burn, a missing-table 500, and my own bad call that a saved DEV draft was a "phantom") — is saved as a DevRelay agent session:&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="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;SYSTEM://GRASS — building a local-first outdoor quest game&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 SYSTEM://GRASS — a local-first outdoor quest game — as my Hacktoberfest 2026 Week 1 entry. Local Ollama brain, phone as dumb terminal, offline-first photo evidence, a two-tier judge where Gemma narrates but never overrides rules.&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;Froze design docs (design.md, challenge-week1.md) and a 10-section build plan. Split into tasks 1-10 with exact contracts; each task = failing test first, then implementation, then commit. Ledgered all deferred minors. Tasks 1-8 approved (db, ollama_client, quests, judge, images, service, FastAPI). Approved deviation: FastAPI handlers use async def (Python 3.13). 61 tests green.&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-task"&gt;task&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;subagent implement Task 9 frontend (static/index.html, style.css, app.js + In...&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;subagent implement Task 9 frontend (static/index.html, style.css, app.js + IndexedDB buffer)&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;Subagent ran ~2h and was CANCELLED with code written but never committed. Lesson ledgered: cap browser/UI implementers at write+commit; live Playwright verification stays with the controller.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Recovered Task 9: controller committed 2e51620, reconstructed the task report, added bb40f78 static mount so frontend + API share origin (no CORS). Controller ran the Playwright drill.&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;What did we do so far?&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;Verified the offline buffer live: capture in airplane mode -&amp;gt; IndexedDB 'pending' store holds the blob -&amp;gt; back online -&amp;gt; flush on the 'online' event -&amp;gt; buffer cleared on 200. Verified the 409 path: second submit returns 409, buffered row is cleared, existing verdict is shown, and no error dialog appears. Only console noise is a favicon 404. Fixed /api/state 500 (OperationalError: no such table: quests) by calling db.init_db(). Committed 883bd2f to gitignore grass.db + evidence/.&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-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pytest -q&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 agent-session-terminal"&gt;&lt;code&gt;$ pytest -q&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&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Diagnosed the DEV draft confusion: I had wrongly called the draft a 'phantom' because public GET /api/articles/{id} 404s unpublished drafts. Author-scoped /api/articles/me/unpublished confirmed the draft (id 4831075) exists. Found DevRelay CLI bugs: 'articles create' silently truncated the body and dropped tags, and 'api' never forwards the request body. Pivoted to pasting the post manually.&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;ok draft saved&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;Restructured the post to the official Hacktoberfest Week 1 template (What I Built / Demo / Code / How I Built It / Why Does Open Innovation Matter? / My Agent Session / Prize Categories). Saved this agent session to DEV for the My Agent Session section.&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;
        6 of 6 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Note:&lt;/strong&gt; this is a curated slice of the session, not the full transcript. The complete conversation ran for hours and included every design decision, instruction, and course correction — too long to embed here. What's above captures the milestones, the parts that went wrong, and the recovery. The build itself lives in the &lt;a href="https://github.com/SammySN-car/touchgrass" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; Built by driving an AI coding agent. I set the architecture, froze the design, split the work into task contracts, reviewed every diff, and made all product decisions; the agent implemented large parts of the code under that direction. The AI's role was substantial and deliberate, not incidental.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>devpulse: A Local-AI News Digest Built for One Person</title>
      <dc:creator>Samar Nathani</dc:creator>
      <pubDate>Mon, 05 Oct 2026 00:17:33 +0000</pubDate>
      <link>https://dev.to/sammysncar/devpulse-a-local-ai-news-digest-built-for-one-person-1g54</link>
      <guid>https://dev.to/sammysncar/devpulse-a-local-ai-news-digest-built-for-one-person-1g54</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;devpulse&lt;/strong&gt; is a Discord bot built for my friend &lt;strong&gt;Soham&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;His problem is simple, and you've probably felt it too: everything moves too fast now. Releases ship while you're looking the other way, the AI news cycle turns over before lunch, and Soham kept missing the things he actually wanted to see — new versions of the tools he uses, interesting projects before everyone else finds them. Keeping up had quietly become a part-time job nobody applied for.&lt;/p&gt;

&lt;p&gt;So devpulse does the scanning for him. Every morning it pulls &lt;strong&gt;rising GitHub repositories, new releases from our shared watchlist, Hacker News, and dev.to&lt;/strong&gt;, judges every single item with a model running &lt;strong&gt;on my laptop&lt;/strong&gt;, and posts one clean message to our Discord:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TOP 5 FOR YOU&lt;/strong&gt; — ranked by relevance and quality, with a two-line verdict on each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLEEPER PICK&lt;/strong&gt; — high quality, low attention: the thing worth finding before it's everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NEW RELEASES&lt;/strong&gt; — just the watchlist repos, linked straight to the release page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One message. No algorithm, no ads, no forty tabs. It lands at 7 AM, before either of us is fully awake, and the watchlist is just one config line — easy to repoint whenever Soham starts following something new.&lt;/p&gt;

&lt;p&gt;I handed him the live bot in our Discord. His response, from the channel:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;demnnn this looks pretty good , for a project made in a day this is pretty good , if you optimize it more then this can be fire and if you want to you can deploy it also so evryne will be able to use it but yeah personally ill be using this aand will wait for more updates from you&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The whole thing in action&lt;/strong&gt; - 2:40, no voiceover, just the sequence: refresh, offline cache, the crash with the terminal visible, recovery, &lt;code&gt;/sleeper&lt;/code&gt;, then a friend's reaction in the channel before one last run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/MDPMhYyqOjs" rel="noopener noreferrer"&gt;https://youtu.be/MDPMhYyqOjs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It fails honestly too.&lt;/strong&gt; When I stopped Ollama mid-day and ran &lt;code&gt;/refresh&lt;/code&gt;, the bot didn't pretend things were fine — it posted a skip message, recorded the failed run, and picked the work back up automatically when the model returned.&lt;/p&gt;

&lt;p&gt;The terminal tells the same story from the inside: every pending item logged &lt;code&gt;judge failed for '&amp;lt;item&amp;gt;': [WinError 10061] No connection could be made because the target machine actively refused it&lt;/code&gt;, while Discord only ever saw the honest skip message.&lt;/p&gt;

&lt;p&gt;Want to poke it yourself: join the server and try &lt;code&gt;/sleeper&lt;/code&gt;, &lt;code&gt;/releases&lt;/code&gt;, &lt;code&gt;/dig fastapi&lt;/code&gt;, or &lt;code&gt;/status&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SammySN-car" rel="noopener noreferrer"&gt;
        SammySN-car
      &lt;/a&gt; / &lt;a href="https://github.com/SammySN-car/devpulse" rel="noopener noreferrer"&gt;
        devpulse
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Discord bot: local qwen2.5:7b judges fresh tech news into a daily digest with sleeper picks. Built for Hacktoberfest 2026 Weekend Challenge.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;devpulse&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Discord bot that gathers what is moving in tech - new releases, rising GitHub
repos, Show HN posts, dev.to articles - and judges every item with an open-weight
model (qwen2.5:7b) running &lt;strong&gt;locally&lt;/strong&gt; through Ollama. Each day it posts one
digest: the top 5 items for your stack, one sleeper pick (high quality, low hype
scored by arithmetic, not vibes), and new releases from your watchlist.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why open&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Everything runs on-device: collectors call free public APIs, the judge runs on
your own machine, and results live in a local SQLite file. No cloud inference, no
subscription, nothing you fetch leaves the machine.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Install Python 3.11+ and &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;; then &lt;code&gt;ollama pull qwen2.5:7b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a Discord application, invite the bot, copy the token&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python -m pip install -e ".[dev]"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and fill in token, channel id, owner id, watchlist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devpulse&lt;/code&gt; - the digest posts at DIGEST_TIME…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SammySN-car/devpulse" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Python 3.13 · discord.py · httpx · SQLite · pytest · ruff · GitHub Actions CI (Ubuntu + Windows). &lt;strong&gt;61 tests&lt;/strong&gt;, hermetic by default (no network, no model), plus one live marker test that does a real model round-trip when I ask for it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The open-source AI at the core is &lt;code&gt;qwen2.5:7b&lt;/code&gt; running locally through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/strong&gt; No API key, no per-call bill, no request leaves the machine. The model reads each item and returns JSON: a relevance score, a quality score, and a two-line verdict ("what it actually is; worth clicking yes/no").&lt;/p&gt;

&lt;p&gt;What the architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub / HN / dev.to (free sources)
        │  collect
        ▼
  normalizer ── URL identity, dedupe, within-source engagement percentile
        │
        ▼
    SQLite ── items + judgments + digest ledger
        │  pending items only (never re-judges)
        ▼
  qwen2.5:7b via Ollama ── sequential, JSON mode, temperature 0.2
        │                    keep_alive: 0 → model evicted after each batch
        ▼
  composer ── TOP 5, sleeper pick, releases, 2000-char Discord limit
        │
        ▼
   Discord ── 5 slash commands + a 07:00 scheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details I care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A RAM guard trips before anything can hurt the machine.&lt;/strong&gt; If free memory drops below 1500 MB, judging stops cleanly, the reason is surfaced in the digest, and the run is recorded — leftovers are retried automatically the next day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The sleeper pick is a formula, not an opinion:&lt;/strong&gt; &lt;code&gt;quality × (1 − engagement_percentile)&lt;/code&gt;, with an eligibility gate (quality ≥ 7, engagement bottom 40%). A q8 repo at 14% engagement ranks &lt;em&gt;below&lt;/em&gt; a q7 repo at 0% — high signal, no attention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judging failures never lie.&lt;/strong&gt; One retry, then the item stays pending; if &lt;em&gt;every&lt;/em&gt; judgment fails, the digest says so instead of pretending it's a quiet news day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model sleeps after every batch&lt;/strong&gt; (&lt;code&gt;keep_alive: 0&lt;/code&gt;) — a 4.7 GB resident model on a normal laptop is not a tradeoff I was willing to make 24/7.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Process, since judges asked to see it:&lt;/strong&gt; I wrote the spec first, cut it into 13 tasks, and implemented each with tests written before code, followed by an independent spec review and quality review per task. The final whole-branch review caught a real production bug the test suite couldn't see: the SQLite connection was created on the main thread but digests run on a worker thread (&lt;code&gt;asyncio.to_thread&lt;/code&gt;) — every scheduled run in production would have failed with &lt;code&gt;SQLite objects created in a thread can only be used in that thread&lt;/code&gt;, while all 48 tests at the time stayed green. Fixed with a reentrant lock across every connection path, plus a cross-thread regression test. CI runs on Ubuntu and Windows, and a live round-trip test proved the real model answers and gets evicted afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;This project is &lt;em&gt;made of&lt;/em&gt; open pieces, and each one earns it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It runs where the data lives.&lt;/strong&gt; The digest is a reading habit — who and what someone follows is a profile. Here, fetches happen locally, judgments happen locally, and storage is a file on disk. A closed API would mean sending every title and description I care about to a third party, every morning, forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs with no internet.&lt;/strong&gt; After the model is pulled once, judging works on a plane, on a train, behind a captive portal. The only thing that needs the network is the news collection itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing per decision.&lt;/strong&gt; 33 items judged in 4.5 minutes. On a closed API that's 33 calls, 33 bills, rate limits, and an API key sitting in a &lt;code&gt;.env&lt;/code&gt; file that can leak. Here it's electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could swap the model in one line.&lt;/strong&gt; I ran a spike comparing &lt;code&gt;llama3.2&lt;/code&gt; (3B) against &lt;code&gt;qwen2.5:7b&lt;/code&gt; before committing: the small model echoed titles back as "verdicts" on 6 of 15 items; qwen produced real evaluations with a yes/no. Because everything behind the judge is an open interface, that decision was &lt;em&gt;mine to measure&lt;/em&gt;, not a vendor's roadmap. The spike table is in the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where open beat closed outright:&lt;/strong&gt; resilience. When the model was down mid-run, my bot degraded into an honest skip message and retried tomorrow. Try getting that failure semantics from a vendor status page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest tradeoff of open here is speed — a 4.5-minute batch is not a streaming UI. For one digest a day, who cares.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Hacktoberfest 2026</title>
      <dc:creator>Samar Nathani</dc:creator>
      <pubDate>Thu, 01 Oct 2026 12:24:04 +0000</pubDate>
      <link>https://dev.to/sammysncar/hacktoberfest-2026-4pg3</link>
      <guid>https://dev.to/sammysncar/hacktoberfest-2026-4pg3</guid>
      <description>&lt;p&gt;Hey everyone!&lt;/p&gt;

&lt;p&gt;Just signed up for Hacktoberfest this year. Figured I’d give it a shot and see what I come across.&lt;/p&gt;

&lt;p&gt;Looking forward to finding some cool stuff to work on and having fun with it. :)&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
