<?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: Vinks Goyal</title>
    <description>The latest articles on DEV Community by Vinks Goyal (@vinksgoyal).</description>
    <link>https://dev.to/vinksgoyal</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%2F4152525%2F686420db-c567-4ba8-bd07-bb8f35b38fbe.jpg</url>
      <title>DEV Community: Vinks Goyal</title>
      <link>https://dev.to/vinksgoyal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vinksgoyal"/>
    <language>en</language>
    <item>
      <title>Gnosk — Know what your code knows</title>
      <dc:creator>Vinks Goyal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:24:11 +0000</pubDate>
      <link>https://dev.to/vinksgoyal/gnosk-know-what-your-code-knows-cgd</link>
      <guid>https://dev.to/vinksgoyal/gnosk-know-what-your-code-knows-cgd</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;Gnosk is an offline PII scanner for freelance developers. You point it at a folder. It scans every file in milliseconds and reports where personal data lives — Aadhaar numbers, PAN cards, Indian phone numbers, emails, twelve provider API-key shapes — with the exact file path, line number, and a confidence score. Then it writes a redacted markdown inventory you can hand to a client without a second thought.&lt;/p&gt;

&lt;p&gt;The problem it solves is legal, not technical. Freelancers in India are increasingly being asked by clients to sign data-protection clauses under the DPDP Act. Most of them have no idea what personal data their project actually touches. Enterprise DLP tools cost more than a freelancer's monthly income. Cloud-based scanners want you to upload the exact data you're trying to protect. Both of those answers are non-starters.&lt;/p&gt;

&lt;p&gt;Gnosk is the third option. Deterministic validators run first — regex, Verhoeff checksums, provider-format patterns — and a local Gemma 2B model runs only on the findings the rules can't decide. Nothing leaves the machine. You can unplug the network cable and the scan still works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/vinksgoyal/gnosk" rel="noopener noreferrer"&gt;https://github.com/vinksgoyal/gnosk&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Tests:&lt;/strong&gt; 215 passing, ruff clean&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;br&gt;
&lt;strong&gt;Platform:&lt;/strong&gt; Linux and macOS. Windows requires WSL — documented, not polished.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;Four things to watch for in the demo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Triage before the model.&lt;/strong&gt; A scan produces five findings. Four are resolved by rules in ten milliseconds. One goes to the model. The AI sees one item, not five.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world scan.&lt;/strong&gt; The same tool against my Downloads folder: 127 files, 6 findings, 0.13 seconds. Two of the findings were my own email in my own resume — sitting in a folder I'd forgotten about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redaction.&lt;/strong&gt; The report shows &lt;code&gt;23********24&lt;/code&gt;, not &lt;code&gt;234567890124&lt;/code&gt;. Two characters at each end, asterisks in the middle. The report cannot become the leak it's warning you about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; Airplane mode on. Scan still runs.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/vinksgoyal/gnosk" rel="noopener noreferrer"&gt;https://github.com/vinksgoyal/gnosk&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vinksgoyal/gnosk
&lt;span class="nb"&gt;cd &lt;/span&gt;gnosk
./install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gnosk scan ./my-project             &lt;span class="c"&gt;# quick scan&lt;/span&gt;
gnosk scan ./my-project &lt;span class="nt"&gt;--classify&lt;/span&gt;  &lt;span class="c"&gt;# add local model verdicts&lt;/span&gt;
gnosk report ./my-project           &lt;span class="c"&gt;# write a client-ready inventory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Python 3.11+, typer for the CLI, rich for terminal output, questionary for the REPL, ollama-python for the local model, &lt;code&gt;gemma2:2b&lt;/code&gt; via Ollama.&lt;/p&gt;

&lt;p&gt;The install script is ~190 lines of bash: version-checks Python, creates a virtualenv, installs the package, symlinks &lt;code&gt;gnosk&lt;/code&gt; into &lt;code&gt;~/.local/bin&lt;/code&gt;. Idempotent, colored output, specific error messages — if Python 3.11 is missing, it tells you the exact apt install line instead of "command not found."&lt;/p&gt;

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

&lt;p&gt;I'm on a 2-core Intel laptop with 7.7 GB RAM and no GPU. Every architectural decision in this project comes from that constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1 — the deterministic layer.&lt;/strong&gt; Five validators (Aadhaar, PAN, email, Indian phone, API keys) plus tests. My hand-computed Verhoeff check digit was wrong on the first try — the validator caught it. That was the first evidence that deterministic-first would pay off: the failing test wasn't the code, it was my arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 2 — the wall.&lt;/strong&gt; I benchmarked the model: a 2 KB prompt takes 38 seconds to process on this hardware. My initial classifier prompt was 1,500 characters and timed out at 180 seconds. The first three attempts at building the classifier failed because I assumed the bottleneck was prompt size. It wasn't. &lt;strong&gt;Output tokens were.&lt;/strong&gt; Capping &lt;code&gt;num_predict&lt;/code&gt; at 180 and dropping the &lt;code&gt;reason&lt;/code&gt; field from the model's response cut wall time from over 180 seconds to under 60.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3 — the false-positive hunt.&lt;/strong&gt; This is the part I'm most proud of.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Act 1.&lt;/em&gt; I scanned a Hacktoberfest project I had on disk — a bird-detection app called birdbuddy. It reported &lt;strong&gt;14 phone numbers&lt;/strong&gt;. Every one was a fragment of the same SHA256 hash: &lt;code&gt;...987d8227518893&lt;/code&gt;. The substring &lt;code&gt;8227518893&lt;/code&gt; is 10 digits starting with 8, which matches my Indian-phone regex exactly. A 64-character hex string contains about 55 overlapping 10-digit windows; several will match a phone pattern by pure chance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Act 2.&lt;/em&gt; I added a hex-run filter — if a finding's span sits inside a 32+ character hex run, drop it. My test suite caught the fix eating GitHub PATs. A 40-character &lt;code&gt;ghp_AAAA...&lt;/code&gt; token looked like a hex run because 36 of its 40 characters are hex. Added a condition: the matched span itself must consist entirely of hex characters. &lt;code&gt;ghp_&lt;/code&gt; contains &lt;code&gt;g&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt; — not hex. Filter fixed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Act 3.&lt;/em&gt; Re-ran birdbuddy. Still 8 phone numbers. These were decimal fragments of per-token LLM prices from a &lt;code&gt;.backboard/sessions/*.jsonl&lt;/code&gt; gateway log. &lt;code&gt;0.000000234375&lt;/code&gt; produces &lt;code&gt;234375000000&lt;/code&gt; if you squint at the digits. Added an entropy filter — real Indian phone numbers don't have five or more zeros — and a skip-list entry for tool-generated directories.&lt;/p&gt;

&lt;p&gt;After all three fixes, birdbuddy returns zero findings. Real bug, three iterations, real numbers. None of this came from test fixtures. All of it came from running the tool on data that wasn't designed to make the tests pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 4 — triage.&lt;/strong&gt; The &lt;code&gt;--classify&lt;/code&gt; flag was initially sending every finding to the model, which took 120 seconds per file on this hardware. That made the AI look like a bolt-on. Triage rules now resolve the easy cases first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Known fixture values (&lt;code&gt;alice@example.com&lt;/code&gt;, &lt;code&gt;AKIAIOSFODNN7EXAMPLE&lt;/code&gt;) → &lt;code&gt;fixture&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Files in &lt;code&gt;tests/&lt;/code&gt;, &lt;code&gt;fixtures/&lt;/code&gt;, &lt;code&gt;examples/&lt;/code&gt; → &lt;code&gt;fixture&lt;/code&gt;, except API keys and checksum-valid Aadhaar, which always defer to the model&lt;/li&gt;
&lt;li&gt;Checksum-valid Aadhaar outside a test path → &lt;code&gt;likely_real&lt;/code&gt; (format validity only, not personhood)&lt;/li&gt;
&lt;li&gt;Everything else → model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the demo folder, that's four triaged plus one modeled. On a real client project, it means the model is called once or twice per scan, not once per finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 5 — the rename.&lt;/strong&gt; I renamed the project from VaultScan to Gnosk. This broke the venv, because Python venvs embed absolute paths at creation time. &lt;code&gt;rm -rf .venv &amp;amp;&amp;amp; ./install.sh&lt;/code&gt; fixed it in thirty seconds. Renaming a project directory is never just &lt;code&gt;mv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result is 215 tests, zero failures, an install script verified from a fresh shell, and a terminal UI that respects &lt;code&gt;NO_COLOR&lt;/code&gt; and refuses to emit ANSI codes when piped to a file — because a security tool that dumps escape sequences into a log file isn't a tool you'd trust.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;A cloud-based scanner cannot solve this problem at any price.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The DPDP Act asks freelancers to take responsibility for personal data. The only way to know what personal data a project touches is to scan it. The only safe way to scan client data is not to send it to someone else's server. OpenAI, Anthropic, Google — they all require uploading data to their API. That's a non-starter for the exact use case Gnosk exists for.&lt;/p&gt;

&lt;p&gt;What open-weight models unlock here isn't a cheaper API call. It's a category of tool that couldn't exist otherwise: an AI that runs on the same laptop as the data, with no network path out.&lt;/p&gt;

&lt;p&gt;The specific pieces that make Gnosk work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 2B via Ollama.&lt;/strong&gt; Runs on a 2-core CPU with no GPU. About 60 seconds for a bounded classification task. Slow, but usable, and free forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localhost inference.&lt;/strong&gt; The only network call is &lt;code&gt;localhost:11434&lt;/code&gt;. Turn off Wi-Fi and everything still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open weights&lt;/strong&gt; mean I can tune the prompt against my own hardware. With a closed API, every prompt iteration costs tokens. On my machine, it costs three minutes and no money — and I iterated dozens of times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Gemma weren't open-weight, the honest alternative for Gnosk would be no AI at all. The deterministic scanner works fine. It just can't tell a real client email from a test fixture. That judgment call is exactly what the model handles, and it's the difference between a tool that produces 14 false positives on a real project and one that produces zero.&lt;/p&gt;

&lt;p&gt;The "open" in open-source AI isn't a licensing footnote here. It's the entire reason the project is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Gnosk mostly in conversation with an AI coding assistant. The real number: roughly 15 hours across 5 days and about 200 messages. Every commit in the repo is a real step in that process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tight feedback loops.&lt;/strong&gt; "Run the tests, paste the output" beat "explain how this should work." Most bugs got fixed in a single turn because the loop closed fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent pushed back on my bad ideas.&lt;/strong&gt; When I tried to build a full-screen &lt;code&gt;textual&lt;/code&gt; TUI, it said "3–6 hours on 2-core hardware, DEV post unstarted — this is the wrong call." It was right. We shipped a REPL instead, in an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent was honest about trade-offs.&lt;/strong&gt; When I asked for a continuously animating signature, it suggested a static gradient first and explained why a looping color cycle was a worse product. That small moment shaped three later decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't work:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long conversations degrade.&lt;/strong&gt; Around message 100, the agent started forgetting context from earlier in the session. I had to re-explain the Finding dataclass twice. A &lt;code&gt;CLAUDE.md&lt;/code&gt; file at the repo root would have prevented this — that lesson is now in the repo as &lt;code&gt;ARCHITECTURE.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested nano edits were unreliable.&lt;/strong&gt; Twice, an edit corrupted the file (merged lines, deleted a function body). The fix was full-file overwrites via &lt;code&gt;cat &amp;gt; file &amp;lt;&amp;lt;'EOF'&lt;/code&gt;. Slower to read, but it never corrupts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent can't verify its own suggestions on my hardware.&lt;/strong&gt; "This should run in ~30 seconds" was sometimes 90. The only fix was to run it and paste the real numbers back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool worked. The prompt did not do the work — the iteration did. My best commits came from the agent being wrong and me running the code to find out why.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Gemma — Best Use of Gemma&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gnosk uses &lt;code&gt;gemma2:2b&lt;/code&gt; via Ollama for ambiguous-finding classification. Measured on 2-core CPU, no GPU: about 60 seconds per file with five findings. Full prompt strategy and benchmark numbers are in &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; section 8 of the repo.&lt;/p&gt;

&lt;p&gt;The model is not decoration. It's the difference between a scan that produces 14 false positives on a real project and one that produces zero. Removing it would turn Gnosk from a tool a freelancer could actually hand to a client into a noisy regex scanner they'd uninstall after one run.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>privacy</category>
    </item>
    <item>
      <title>BirdBuddy: an offline bird call identifier that runs entirely on your device</title>
      <dc:creator>Vinks Goyal</dc:creator>
      <pubDate>Wed, 07 Oct 2026 18:38:20 +0000</pubDate>
      <link>https://dev.to/vinksgoyal/birdbuddy-an-offline-bird-call-identifier-that-runs-entirely-on-your-device-4nci</link>
      <guid>https://dev.to/vinksgoyal/birdbuddy-an-offline-bird-call-identifier-that-runs-entirely-on-your-device-4nci</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;BirdBuddy&lt;/strong&gt; is an offline bird call identifier. You're on a trail with no signal, you hear a bird, you record three seconds, and BirdBuddy tells you what it is — with everything running locally on the device in your pocket.&lt;/p&gt;

&lt;p&gt;The screen is the shortest part of the experience. Open the app, tap once, put the phone away. The identification finishes in about two seconds. No cloud round-trip, no account, no upload. Just you, the bird, and a 52 MB model that never leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; hikers, birders, and anyone who's ever heard something beautiful in the woods and had no idea what it was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://birdbuddy.onrender.com" rel="noopener noreferrer"&gt;https://birdbuddy.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: hosted on Render's free tier (512 MB RAM). The first request after idle may take 30–60 seconds to wake up. See the video below for the real, snappy experience.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=7b3iXirhnTQ" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=7b3iXirhnTQ&lt;/a&gt;&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%2F8hh8sw4lb02vv9du94sp.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%2F8hh8sw4lb02vv9du94sp.png" alt="BirdBuddy Identify tab with a ranked result" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you're seeing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The light, offline-first UI&lt;/li&gt;
&lt;li&gt;Upload or record a 3-second bird call&lt;/li&gt;
&lt;li&gt;Top-3 matches ranked by confidence&lt;/li&gt;
&lt;li&gt;The top match spoken aloud by an on-device TTS voice&lt;/li&gt;
&lt;li&gt;A Settings tab to tune Top-K, confidence threshold, and voice speed&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/vinksgoyal/birdbuddy" rel="noopener noreferrer"&gt;https://github.com/vinksgoyal/birdbuddy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo layout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/inference.py&lt;/code&gt; — BirdNET ONNX inference (audio → species)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/voice.py&lt;/code&gt; — Piper TTS (text → speech, offline)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app.py&lt;/code&gt; — Gradio UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/tinker_finetune.py&lt;/code&gt; — Tinker fine-tune of Qwen3.5-4B for bird expertise&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; + &lt;code&gt;render.yaml&lt;/code&gt; — one-click deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/&lt;/code&gt; — Copilot usage screenshots&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;BirdBuddy is built entirely on open-source AI. Three open pieces make it work.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. BirdNET v2.4 — open-weight acoustic classifier
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://birdnet.cornell.edu/" rel="noopener noreferrer"&gt;BirdNET&lt;/a&gt; is a bioacoustics model released by the Cornell Lab of Ornithology and Chemnitz University. I used the FP32 truncated-DFT ONNX variant (~52 MB) running through &lt;strong&gt;ONNX Runtime on CPU&lt;/strong&gt;. It classifies 3-second, 48 kHz audio into 6,522 species.&lt;/p&gt;

&lt;p&gt;The model runs on a laptop, a phone, or a Raspberry Pi — no GPU required. Inference latency is under two seconds on a modern laptop CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Piper — open-weight text-to-speech
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/rhasspy/piper" rel="noopener noreferrer"&gt;Piper&lt;/a&gt; is an MIT-licensed neural TTS that runs on CPU. I used the &lt;code&gt;en_US-lessac-medium&lt;/code&gt; voice (~63 MB). When BirdNET returns its top match, Piper speaks it out loud: &lt;em&gt;"I heard a Northern Cardinal."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No ElevenLabs, no Google Cloud TTS — the voice is a local ONNX model, just like the classifier.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Qwen3.5-4B fine-tuned with Tinker
&lt;/h3&gt;

&lt;p&gt;The audio model gives us a species ID. To make BirdBuddy &lt;em&gt;useful&lt;/em&gt; and not just a label, I fine-tuned a small LLM to act as a bird expert — field notes, habitat, how to identify by sound.&lt;/p&gt;

&lt;p&gt;I built a 165-example chat dataset covering 60 common North American species, then fine-tuned &lt;strong&gt;Qwen3.5-4B&lt;/strong&gt; with a LoRA adapter (r=16) using &lt;strong&gt;&lt;a href="https://tinker.thinkingmachines.dev/" rel="noopener noreferrer"&gt;Thinking Machines' Tinker&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Training loss over 3 epochs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Epoch&lt;/th&gt;
&lt;th&gt;Avg loss&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;202.64&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.09&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;68.43&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Baseline output on a held-out prompt (before fine-tune):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I don't know how to use the API. ... Give me a field note for American Kestrel..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fine-tuned output (after training):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"American Kestrel (Falco sparverius) is a small falcon with brown upperparts and blue wings. Its shape, plumage, and behavior are useful clues when recording a field observation..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the improvement the Tinker category asks for: a specific task, a clear baseline, a measurable gain.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Built with GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;Copilot was the workbench for the entire project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot Chat&lt;/strong&gt; in VS Code scaffolded &lt;code&gt;src/inference.py&lt;/code&gt;, &lt;code&gt;src/voice.py&lt;/code&gt;, and the Gradio UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copilot coding agent&lt;/strong&gt; took &lt;a href="https://github.com/vinksgoyal/birdbuddy/issues/2" rel="noopener noreferrer"&gt;Issue #2&lt;/a&gt; and opened a PR that adds the GitHub Actions CI workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; now runs tests on every push&lt;/li&gt;
&lt;/ul&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%2F1ovidmqzr08uomphrfci.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%2F1ovidmqzr08uomphrfci.png" alt="Copilot coding agent PR" width="800" height="450"&gt;&lt;/a&gt;&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%2Fij18v8z8ichq4w4n3mqb.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%2Fij18v8z8ichq4w4n3mqb.png" alt="Copilot Chat in VS Code" width="800" height="450"&gt;&lt;/a&gt;&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%2Fqpbeam4ksjtkhvdkq3v1.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%2Fqpbeam4ksjtkhvdkq3v1.png" alt="GitHub Actions CI run" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Backboard R-CLI for dataset work
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;&lt;a href="https://backboard.io/" rel="noopener noreferrer"&gt;Backboard's&lt;/a&gt; R-CLI&lt;/strong&gt; as a coding agent inside the repo for dataset curation and pipeline review — it summarized the inference pipeline, flagged missing error handling, and produced &lt;code&gt;scripts/agent_review.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Docker + Render. The Dockerfile downloads both models (BirdNET and Piper) at build time so the container is fully self-contained. No API keys, no external services at runtime.&lt;/p&gt;

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

&lt;p&gt;This project &lt;strong&gt;only exists&lt;/strong&gt; because of open-source AI. Every design decision traces back to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model is open-weight.&lt;/strong&gt; BirdNET was released specifically for bioacoustics research. A closed API would have cost per call, required internet, and — critically — wouldn't have let me swap models. During development I tried three different classifiers. With a closed API, I'd be stuck with whatever the vendor offered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The training data is open.&lt;/strong&gt; The species library is a community-curated dataset of bird recordings. Thousands of birders contributed. A closed model trained on proprietary data would be a black box. I can inspect exactly what my model learned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The runtime is open.&lt;/strong&gt; ONNX Runtime means BirdBuddy runs on Android, iOS, Linux, macOS — even a Raspberry Pi. A closed inference service would lock me into one platform and one pricing tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The voice is open.&lt;/strong&gt; Piper is MIT-licensed. Its voice models are open-weight. The TTS runs on the same laptop as the classifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost is zero.&lt;/strong&gt; No API keys. No usage limits. No "your free tier has expired." A kid in a rural area with a hand-me-down phone can use this. A researcher in a country with unreliable internet can use this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent harness is open.&lt;/strong&gt; Backboard's R-CLI let me automate the boring parts — dataset cleaning, pipeline review — without locking my workflow into a proprietary IDE. I could inspect every step the agent took.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fine-tune is open.&lt;/strong&gt; Tinker let me fine-tune Qwen3.5-4B to a specific task and prove, with numbers, that the fine-tune beat the baseline. A closed model endpoint would not have given me the loss curve, the LoRA weights, or the freedom to swap base models.&lt;/p&gt;

&lt;p&gt;The closed alternative would be: a subscription app that uploads your audio to a server, identifies birds you could have identified yourself, and charges $9.99/month. That's not innovation — that's rent-seeking.&lt;/p&gt;

&lt;p&gt;This is innovation: a 52 MB classifier, a 63 MB voice model, a fine-tuned LoRA — all running on your device, in the middle of nowhere, for free, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Backboard R-CLI session: &lt;a href="https://app.backboard.io/sessions/sess_3f07cb2e" rel="noopener noreferrer"&gt;&lt;code&gt;sess_3f07cb2e&lt;/code&gt;&lt;/a&gt; — model &lt;code&gt;openai/gpt-5.5&lt;/code&gt;, profile &lt;code&gt;coding&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The session covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;src/inference.py&lt;/code&gt;, &lt;code&gt;app.py&lt;/code&gt;, and &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Summarizing the pipeline&lt;/li&gt;
&lt;li&gt;Flagging missing error handling on the audio loader&lt;/li&gt;
&lt;li&gt;Producing &lt;code&gt;scripts/agent_review.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Entering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of GitHub Copilot&lt;/strong&gt; — Chat, coding agent PR, and Actions CI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Tinker&lt;/strong&gt; — fine-tuned Qwen3.5-4B with a measurable loss improvement (202.64 → 68.43)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Backboard&lt;/strong&gt; — R-CLI agent session saved and embedded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt; — deployed and live at birdbuddy.onrender.com&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Package BirdBuddy as a real mobile app (React Native + ONNX Runtime Mobile)&lt;/li&gt;
&lt;li&gt;Expand the Tinker fine-tune dataset to all 6,522 BirdNET species&lt;/li&gt;
&lt;li&gt;Add a "trail log" that stores sightings locally and syncs later — never during the walk&lt;/li&gt;
&lt;li&gt;Bring back a real hardware button on a Bluetooth clicker so you can record without touching the phone&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with open-source AI, for the birds, and for everyone who'd rather be outside.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Unstick: One Tiny Action Instead of a Plan</title>
      <dc:creator>Vinks Goyal</dc:creator>
      <pubDate>Sun, 04 Oct 2026 06:11:52 +0000</pubDate>
      <link>https://dev.to/vinksgoyal/unstick-one-tiny-action-instead-of-a-plan-582d</link>
      <guid>https://dev.to/vinksgoyal/unstick-one-tiny-action-instead-of-a-plan-582d</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;Unstick&lt;/strong&gt; gives you one tiny action instead of a plan.&lt;/p&gt;

&lt;p&gt;Every productivity tool, every AI chatbot, every roadmap does the same thing: it shows you the whole mountain. Ask ChatGPT how to start a creative portfolio and it gives you five sections, twenty sub-bullets, and an implied skill tree you didn't ask to see. That's technically helpful and completely useless if you're already overwhelmed.&lt;/p&gt;

&lt;p&gt;I built Unstick because I've been that person. I've started my portfolio fourteen times and never finished. Not because I'm lazy — because every time I opened a tool, it showed me how much I didn't know yet, and I closed the tab.&lt;/p&gt;

&lt;p&gt;Unstick does the opposite. You give it a goal. It gives you exactly three cards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start here&lt;/strong&gt; — one physical action you can do in five minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you have 15 minutes&lt;/strong&gt; — a slightly bigger one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not today&lt;/strong&gt; — a greyed-out card that says: &lt;em&gt;"The rest of the plan exists. You don't need to see it."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third card is the whole product. The absence of a plan is the feature. No progress bars. No "step 3 of 47." No skill tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; me. I'm the friend I built this for. I don't have someone else to hand it to this weekend, and I'm not going to invent one. I've started my portfolio fourteen times. This is the tool that would have made me finish.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://unstick-api-c7va.onrender.com" rel="noopener noreferrer"&gt;https://unstick-api-c7va.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type a goal. Click the button. Three cards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API endpoint&lt;/strong&gt; (for the terminal-inclined):&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://unstick-api-c7va.onrender.com/api/unstick &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"goal":"start a creative portfolio","minutes":15,"energy":2,"location":"home","time":"21:30"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://www.youtube.com/video/JtzAP9eyUZc" rel="noopener noreferrer"&gt;https://www.youtube.com/video/JtzAP9eyUZc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on the demo:&lt;/strong&gt; the fine-tuned classifier is trained specifically for &lt;strong&gt;creative portfolio goals&lt;/strong&gt;. For other goals it still returns scope-safe actions, but the domain fit is weak — a "finish my thesis" prompt will give you portfolio-shaped actions. That's a real limitation, not a hidden one. A next step would be broadening the training set.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/vinksgoyal/unstick" rel="noopener noreferrer"&gt;github.com/vinksgoyal/unstick&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/&lt;/code&gt; — FastAPI backend + Tinker + Backboard clients&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frontend/&lt;/code&gt; — the three-card UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;training/&lt;/code&gt; — dataset, label helper, Tinker fine-tune script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eval/&lt;/code&gt; — base vs fine-tuned evaluation harness&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unstick-evidence/&lt;/code&gt; — every screenshot, log, and number cited in this post&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The prompt says to use open-source AI at the core. Here's what that actually means in this project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The engine: local open-weight models
&lt;/h3&gt;

&lt;p&gt;The candidate generator runs &lt;strong&gt;Gemma 2B&lt;/strong&gt; locally through &lt;strong&gt;Ollama&lt;/strong&gt;. When you type a goal, the app asks Gemma to decompose it into candidate actions. That call happens &lt;strong&gt;on your machine&lt;/strong&gt;. No cloud API sees the raw brain-dump.&lt;/p&gt;

&lt;p&gt;Gemma is not good at this task out of the box. Here's what it produced when I asked it to generate actions for "Start a creative portfolio":&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Stand up and stretch for 30 seconds."
"Go to the kitchen and pour a glass of water."
"Pick up a pen and paper and start doodling for 5 minutes."
"Shuffle a deck of cards."
"Take a five-minute walk in your neighborhood."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;None of those have anything to do with starting a portfolio.&lt;/strong&gt; The base model drifts into generic wellness advice because it can't hold the goal in mind. That's the "before" state.&lt;/p&gt;

&lt;h3&gt;
  
  
  The classifier: Tinker fine-tune
&lt;/h3&gt;

&lt;p&gt;The scope-safety classifier is the actual product. Its job is to look at a (goal, action) pair and decide: &lt;strong&gt;is this action safe to show an overwhelmed person?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I defined six ways an action can fail:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Failure&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reveals_scope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Sketch your homepage layout"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_new_skill&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Learn Figma"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_decision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Pick your best five pieces"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not_physical&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Think about your design style"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_other_person&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Ask a friend for feedback"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;off_topic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Pour a glass of water" (for a portfolio goal)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I hand-labeled 128 (goal, state, action) triples across those six categories, plus safe examples. 100 went to training, 44 to a held-out set.&lt;/p&gt;

&lt;p&gt;Then I fine-tuned &lt;strong&gt;Qwen3-8B&lt;/strong&gt; with &lt;strong&gt;LoRA&lt;/strong&gt; on &lt;strong&gt;Tinker&lt;/strong&gt;, using the SDK directly with masked cross-entropy loss on the answer tokens only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Safe-F1&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Fail-F1&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Rows&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base Qwen3-8B&lt;/td&gt;
&lt;td&gt;0.2727&lt;/td&gt;
&lt;td&gt;0.4000&lt;/td&gt;
&lt;td&gt;0.3721&lt;/td&gt;
&lt;td&gt;96s&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine-tuned (Tinker LoRA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.7045&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.7778&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9429&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;118s&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Base model: 27% accuracy. Fine-tune: 70%.&lt;/strong&gt; The fail-class F1 — how well the model catches bad actions — went from 0.37 to 0.94. That's the difference between a demo and something you'd actually use.&lt;/p&gt;

&lt;h3&gt;
  
  
  The memory layer: Backboard
&lt;/h3&gt;

&lt;p&gt;Unstick remembers &lt;strong&gt;how&lt;/strong&gt; you act, never &lt;strong&gt;what&lt;/strong&gt; you said.&lt;/p&gt;

&lt;p&gt;After you complete an action, the app stores an abstract signal in &lt;strong&gt;Backboard&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"goal_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"energy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evening"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file_rename"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No content. No goal text. No draft titles. Just six fields about &lt;em&gt;what kind of moment this was&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On the next request, Backboard's semantic search returns the signals that match the current state: &lt;em&gt;"This user completes file-rename actions at 9pm with energy 2 about 80% of the time."&lt;/em&gt; That context shapes which candidate the model ranks highest.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;record()&lt;/code&gt; method enforces the boundary at the code level — any field outside the allowed set raises &lt;code&gt;ValueError&lt;/code&gt;. There is no path by which your draft titles or half-formed thoughts reach Backboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment: Render
&lt;/h3&gt;

&lt;p&gt;The whole thing runs on &lt;strong&gt;Render&lt;/strong&gt; — FastAPI backend, static frontend, one service. The deployed version uses Tinker for both candidate generation and classification (Ollama doesn't run on Render's free tier).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live URL:&lt;/strong&gt; &lt;a href="https://unstick-api-c7va.onrender.com/" rel="noopener noreferrer"&gt;unstick-api-c7va.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cold start is 60–90 seconds on the free tier. Subsequent requests are fast.&lt;/p&gt;

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

&lt;p&gt;Three specific reasons, not abstract philosophy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The content never leaves your machine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The raw material for a portfolio is unfinished work. Half-drawn things. Abandoned drafts. Songs you're embarrassed by. That's the most personal stuff a person owns, and it's exactly the stuff that has to go into the tool for it to be useful.&lt;/p&gt;

&lt;p&gt;If this ran on a closed API, every abandoned draft and every "I don't know what I'm doing" brain-dump would sit on someone else's server. With Gemma running locally through Ollama, it doesn't. That's not a talking point — it's the reason the tool can be honest with you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The classifier is trained on one person's patterns.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I fine-tuned the model on 100 examples I labeled by hand. Not 100,000 scraped examples. Not a commercial dataset. &lt;strong&gt;Mine.&lt;/strong&gt; The fail taxonomy — &lt;code&gt;reveals_scope&lt;/code&gt;, &lt;code&gt;needs_new_skill&lt;/code&gt;, &lt;code&gt;needs_decision&lt;/code&gt; — those categories exist because I noticed those were the exact things that make &lt;em&gt;me&lt;/em&gt; freeze.&lt;/p&gt;

&lt;p&gt;You cannot fine-tune a closed API on one person's patterns. The model belongs to a vendor, and you get the vendor's judgment about what "good" means. With Tinker and an open-weight base model, the model belongs to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It costs nothing to run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gemma 2B runs on a laptop with no GPU. Ollama serves it with one command. The local version of Unstick costs $0 per message. I can use it 30 times a night without thinking about the bill. For a tool designed to be used 30 times a night, that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The model is swappable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran Gemma, then Llama, then Qwen on the same prompt during development. I swapped the classifier architecture between fine-tuning runs. With a closed API, you get one model and one behavior, forever. With open weights, the whole design space stays available.&lt;/p&gt;

&lt;p&gt;That's what open innovation made possible here: a tool that was honest because it was private, and personal because it could be trained on one person.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The whole project was built with &lt;strong&gt;GitHub Copilot's coding agent&lt;/strong&gt;. You can see it in the git history — Copilot commits its own session state:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;261a8ba Wire Backboard behavioural memory (signals only, never content)
0d7f165 Agent host session 177818fb-a6ca-41c7-be2d-f5476e619709 - turn 1
db22d40 Drop separate static service; FastAPI serves the frontend
242fbbb Serve frontend from FastAPI at root; use relative API base
33be99c Agent host session d5a1d459-9a19-48f0-93b5-d32eae080cc7 - turn 7
5aafbfb Wire Tinker fine-tuned classifier into the app
26d73d6 Fine-tuned Qwen3-8B via Tinker: 27% -&amp;gt; 70% accuracy on held-out set
03d3beb Agent host session 4de8b0e3-5d57-4107-917a-5535793c011b - turn 3
7cb3fd0 Build Unstick scaffold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three Copilot agent sessions are visible in the log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;4de8b0e3-5d57-4107-917a-5535793c011b&lt;/code&gt; — initial FastAPI + frontend scaffold&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d5a1d459-9a19-48f0-93b5-d32eae080cc7&lt;/code&gt; — Tinker integration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;177818fb-a6ca-41c7-be2d-f5476e619709&lt;/code&gt; — Backboard wire-up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What Copilot did well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffolded the entire repo from a single mega-prompt&lt;/li&gt;
&lt;li&gt;Wrote the FastAPI backend, frontend, and test suite&lt;/li&gt;
&lt;li&gt;Created the GitHub Actions workflow and initial &lt;code&gt;render.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Correctly refused to invent Tinker API endpoints (left &lt;code&gt;TODO: verify docs&lt;/code&gt; comments)&lt;/li&gt;
&lt;li&gt;Debugged the &lt;code&gt;Datum&lt;/code&gt; length mismatch after I described the exact error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Two bugs Copilot missed that I fixed by hand:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constraint checker&lt;/strong&gt; was treating the user's available minutes as the action's duration, so every action failed the "5 minutes" rule. Fixed by removing the state read and only checking the action text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tinker&lt;/strong&gt; &lt;code&gt;Datum&lt;/code&gt; &lt;strong&gt;construction&lt;/strong&gt; used only the prompt as &lt;code&gt;model_input&lt;/code&gt; while &lt;code&gt;target_tokens&lt;/code&gt; used the full sequence, causing a 3-token mismatch. Fixed by using &lt;code&gt;full_ids[:-1]&lt;/code&gt; as input and &lt;code&gt;full_ids[1:]&lt;/code&gt; as targets, then adding an assertion to catch future mismatches.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Tinker (Thinking Machines)&lt;/strong&gt; — Fine-tuned Qwen3-8B with LoRA on 100 hand-labeled examples. Base model: 27% accuracy. Fine-tune: 70%. Fail-F1: 0.37 → 0.94. Full recipe in &lt;code&gt;training/tinker_finetune.py&lt;/code&gt;, evaluation in &lt;code&gt;eval/run_eval.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render&lt;/strong&gt; — Live app at &lt;a href="https://unstick-api-c7va.onrender.com/" rel="noopener noreferrer"&gt;unstick-api-c7va.onrender.com&lt;/a&gt;. FastAPI backend + static frontend, one service. Deploy config in &lt;code&gt;render.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backboard&lt;/strong&gt; — Behavioural memory layer storing only abstract signals (never content). Verified end-to-end: stored a signal, searched for it, got it back with a 0.61 relevance score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt; — Whole project scaffolded in agent mode. Three session IDs visible in the git log. Two Copilot bugs caught and fixed by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama + Gemma 2B&lt;/strong&gt; — Local open-weight model for candidate generation. Runs offline, costs $0 per message, keeps the raw brain-dump on the user's machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI + Pydantic&lt;/strong&gt; — Backend. Tests pass, CI green.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The hard part wasn't the fine-tune. It was deciding what "bad" means.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defining the six failure categories was the real product work. Once I could name &lt;em&gt;why&lt;/em&gt; an action was bad — reveals scope, needs a decision, requires a new skill — the classifier became tractable. Without those labels, the fine-tune was just vibes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-source AI is a specific advantage, not a slogan.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this project, "open" meant four concrete things: the drafts stay on my laptop, the model is trainable on my patterns, running it costs nothing, and I can swap the base model when I want. All four of those are real features, not philosophy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The base model is 27% wrong in ways that look helpful.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most important finding: the base model didn't produce garbage. It produced &lt;em&gt;generic wellness advice that looked reasonable.&lt;/em&gt; "Stand up and stretch." "Take a walk." Those are fine sentences. They're just not portfolio actions. A closed API would have shipped that behavior forever because it looks fine in a demo. The fine-tune is what catches it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built 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;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
