<?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: James Ngandu</title>
    <description>The latest articles on DEV Community by James Ngandu (@mukuvi).</description>
    <link>https://dev.to/mukuvi</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%2F1177239%2Fccb72090-3f30-44df-b801-8aec157f5609.jpg</url>
      <title>DEV Community: James Ngandu</title>
      <link>https://dev.to/mukuvi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mukuvi"/>
    <language>en</language>
    <item>
      <title>Field notes from running small services</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:46:34 +0000</pubDate>
      <link>https://dev.to/mukuvi/field-notes-from-running-small-services-52j6</link>
      <guid>https://dev.to/mukuvi/field-notes-from-running-small-services-52j6</guid>
      <description>&lt;p&gt;I keep a running list of the things I do on every small service I run. None of it is clever. All of it has saved me at least once. I wrote up the full setup in &lt;a href="https://dev.to/mukuvi/you-probably-do-not-need-kubernetes-36on"&gt;You probably do not need Kubernetes&lt;/a&gt;. These are the notes I actually come back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harden the box before anything else
&lt;/h2&gt;

&lt;p&gt;Before the app goes anywhere near a server, the server has to be safe to expose. No root login. Key-only SSH. A firewall open on 80, 443, and SSH, and nothing else. Something like fail2ban to cut down the noise.&lt;/p&gt;

&lt;p&gt;The step people skip is disabling password authentication. Do it only after you have confirmed key login works in a second session. I have locked myself out doing it the fast way, and it is a bad hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bind the app to localhost
&lt;/h2&gt;

&lt;p&gt;One line, worth more than most of the architecture decisions I make:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app port is reachable only from the machine itself, so the reverse proxy is the only door in. If the proxy config breaks, you have a broken site, not an exposed service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the certificate now, not later
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five seconds. If it fails, I want to know today rather than the morning a certificate expires and the site goes red.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deploy you can reverse is a deploy you will actually do
&lt;/h2&gt;

&lt;p&gt;If deploying feels dangerous, you avoid it, and then you ship one scary change. The fix is small and reversible deploys. On a push, run the tests, and if they pass, update the box. Keep secrets in the platform, never in the repo. Keep the previous version one step away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimum observability
&lt;/h2&gt;

&lt;p&gt;A server you cannot see is a server you are guessing about. For a small service that means a health check that reflects real readiness, an external monitor that pages you when it stops answering, and logs you can read. That is enough. Collecting more than that, before there is a question to answer, is procrastination wearing a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reach for Kubernetes late
&lt;/h2&gt;

&lt;p&gt;Kubernetes earns its keep when there are several services that scale independently, or a team that deploys without you, or a scheduler with real work to do. Until then it is a second job you volunteered for. I would rather run one box I understand than a cluster I have to look up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is not technical
&lt;/h2&gt;

&lt;p&gt;The same shape of work shows up outside infrastructure. Leading the IEEE student branch at university was the same problem: limited time, unclear requirements, people with different goals. The clarity is the job. The tools are secondary.&lt;/p&gt;

&lt;p&gt;None of these notes are original. They are just the ones that keep being true. If you run a small service, the boring version you understand will almost always beat the impressive version you do not.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>career</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Infrastructure was not where I started</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:43:45 +0000</pubDate>
      <link>https://dev.to/mukuvi/infrastructure-was-not-where-i-started-3713</link>
      <guid>https://dev.to/mukuvi/infrastructure-was-not-where-i-started-3713</guid>
      <description>&lt;p&gt;Infrastructure was not where I started.&lt;/p&gt;

&lt;p&gt;I started in C. Pointers, arrays, strings, then memory allocation, linked lists, and eventually a shell. For months the work was small and low level: read a line, parse it, fork, exec, wait. Debug a segfault until it finally made sense. Recompile.&lt;/p&gt;

&lt;p&gt;At the time it did not feel like infrastructure. It felt like fighting the machine. The compiler disagreed with me constantly. Memory had to be handed back by hand. Almost nothing was convenient.&lt;/p&gt;

&lt;p&gt;Then I moved into Linux administration, Docker, CI/CD, and running production systems. And I kept noticing that the hard part was familiar.&lt;/p&gt;

&lt;p&gt;When a container exits with code 137, that is the OOM killer, and it helps that I once had to think about how memory is actually used. When a process will not die, signals stopped being mysterious back in the shell project. When a service will not start, reading the log and following the failure chain is the same skill as tracing why a pointer wrote to the wrong place.&lt;/p&gt;

&lt;p&gt;The language changed. The habit did not. The habit is this: understand the layer below the one you work in, so that when something breaks you can reason about why instead of guessing.&lt;/p&gt;

&lt;p&gt;That is the thing ALX actually gave me, more than any single technology. It made being lost normal, and it made me comfortable opening the box.&lt;/p&gt;

&lt;p&gt;If you are in the middle of the low level work right now, and it feels disconnected from the jobs you want, this is what I would say. The specific skills transfer less than you expect. The instincts transfer more. And the instincts are the part nobody can take from you.&lt;/p&gt;

&lt;p&gt;Still learning. Still running small things on a single box more often than not.&lt;/p&gt;

</description>
      <category>career</category>
      <category>devops</category>
      <category>linux</category>
      <category>beginners</category>
    </item>
    <item>
      <title>You probably do not need Kubernetes</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:42:16 +0000</pubDate>
      <link>https://dev.to/mukuvi/you-probably-do-not-need-kubernetes-36on</link>
      <guid>https://dev.to/mukuvi/you-probably-do-not-need-kubernetes-36on</guid>
      <description>&lt;p&gt;Every few months I talk to someone who wants to put a small app on Kubernetes. Usually the app is one container, a database, and a handful of users. Often it is not even in production yet.&lt;/p&gt;

&lt;p&gt;Kubernetes is excellent at a specific job: running many services, across many machines, with a team that deploys independently. If that is not your situation, it is a large amount of complexity you are taking on for a problem you do not have.&lt;/p&gt;

&lt;p&gt;Most of my services run on a single VPS, and this is roughly how I set one up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harden the box first
&lt;/h2&gt;

&lt;p&gt;Before anything runs, the server needs to be safe to expose. That means no root login, no password authentication, a firewall with only the ports I need, and something like fail2ban to cut down the noise. It is ten minutes of work, once.&lt;/p&gt;

&lt;p&gt;The step people skip is disabling password authentication, because it is the one that can lock you out. Confirm your key login works in a second session before you close the first. I have gotten that wrong, and it is a bad hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app runs in a container, bound to localhost
&lt;/h2&gt;

&lt;p&gt;I run almost everything in Docker. One detail matters more than the rest: I bind the app port to &lt;code&gt;127.0.0.1&lt;/code&gt; instead of exposing it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single choice means the container is never directly reachable from the internet. Only the reverse proxy in front of it can talk to it. If I misconfigure the proxy, the worst case is a broken site, not an open service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx sits in front and terminates TLS
&lt;/h2&gt;

&lt;p&gt;Nginx does three jobs for me. It proxies to the app, it terminates HTTPS, and it is the layer I edit when routing changes or a new domain shows up. Certbot handles the certificates, including the renewal timer, so I am not the renewal timer.&lt;/p&gt;

&lt;p&gt;The one check I run on every new server is &lt;code&gt;certbot renew --dry-run&lt;/code&gt;. If that fails, I want to know today rather than the morning a certificate expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploys should not need me
&lt;/h2&gt;

&lt;p&gt;The pipeline is short on purpose. On a push to main, GitHub Actions runs the tests, and if they pass it updates the box and restarts the container. Secrets live in GitHub, never in the repository, and the change is small enough that rolling back is just pointing at the previous commit.&lt;/p&gt;

&lt;p&gt;A deploy I can reverse in a minute is a deploy I am willing to run often. That matters more than any particular tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I see it?
&lt;/h2&gt;

&lt;p&gt;A server I cannot observe is a server I am guessing about. I want a health check that reflects real readiness, an external monitor that pings it and shouts at me when it stops answering, and logs I can actually read. That is enough for a small service. Collecting more than that, before there is a question to answer, is usually procrastination wearing a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would actually reach for Kubernetes
&lt;/h2&gt;

&lt;p&gt;When there are several services that scale independently, or a team that needs to deploy without asking me, or a scheduler with real work to do. When the complexity is buying something I need.&lt;/p&gt;

&lt;p&gt;Until then: one box, a few containers, a proxy, and a pipeline I understand end to end. It runs, I can reason about it at 2am, and I can replace it when the requirements change.&lt;/p&gt;

&lt;p&gt;That is usually the right trade. The impressive setup can wait until the boring one stops being enough.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>linux</category>
      <category>nginx</category>
    </item>
    <item>
      <title>My ALX journey: from Hello World in C to a load balancer and my own shell</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:35:24 +0000</pubDate>
      <link>https://dev.to/mukuvi/my-alx-journey-from-hello-world-in-c-to-a-load-balancer-and-my-own-shell-47ci</link>
      <guid>https://dev.to/mukuvi/my-alx-journey-from-hello-world-in-c-to-a-load-balancer-and-my-own-shell-47ci</guid>
      <description>&lt;p&gt;ALX is the software engineering programme that half of tech Twitter in Africa either went through or is currently grinding through. It is run by ALX Africa, built on the Holberton School curriculum, sponsored through the Mastercard Foundation, and it does not cost the learner anything. Twelve months, no lectures, project after project, and a peer learning model that takes a few weeks to get used to.&lt;/p&gt;

&lt;p&gt;This is what that year looked like for me, and what is sitting in my repos to show for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the programme works
&lt;/h2&gt;

&lt;p&gt;It runs in sprints. You start with foundations, then move into a specialization. You advance by finishing projects, not by passing an exam. There is a completion cutoff around 80 percent of the projects across the sprints, with a minimum average in each one. Nobody stands in front of a class. You read the material, you attempt the project, you get stuck, you ask the people around you, and you submit.&lt;/p&gt;

&lt;p&gt;The curriculum is deliberately vague in places, and that is the point. The phrase they use is owning your own learning, and after a few months it stops sounding like a slogan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foundations: Git, the command line, and C
&lt;/h2&gt;

&lt;p&gt;The first thing you learn is that the terminal is not scary. Git and command line basics come before any real code, then Bash, and then C, which is where the real work started.&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://github.com/mukuvi/alx-low_level_programming" rel="noopener noreferrer"&gt;alx-low_level_programming&lt;/a&gt; repo is the record of that stretch. It starts with a Hello World and works through variables, conditions, loops, and functions. Then debugging. Then the thing that humbles everyone: pointers. Pointers, arrays, and strings get four separate project sets. After that come recursion, static and dynamic libraries, command line arguments, &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt;, the preprocessor, structs, function pointers, variadic functions, singly and doubly linked lists, bit manipulation, file I/O, hash tables, Makefiles, and search algorithms.&lt;/p&gt;

&lt;p&gt;Somewhere in there you also reimplement formatted output, which is the moment variadic functions stop being a curiosity, and later you build a working shell: read a line, parse it, fork, exec, wait, and loop. That shell project is where all the small pieces suddenly have to work together in one program, and it is the closest the low level work gets to feeling like an actual system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Higher level: Python and JavaScript
&lt;/h2&gt;

&lt;p&gt;Then the language changes and the ideas stay the same. My &lt;a href="https://github.com/mukuvi/alx-higher_level_programming" rel="noopener noreferrer"&gt;alx-higher_level_programming&lt;/a&gt; repo goes from Python fundamentals (loops, imports, modules) into data structures, exceptions, classes, inheritance, test driven development, and a project called almost a circle that builds a small shape hierarchy from scratch. Later it moves into object relational mapping, network requests, and then JavaScript: the warm up, then objects, scopes, and closures, then web scraping and jQuery.&lt;/p&gt;

&lt;p&gt;The interesting part is not learning a second language. It is noticing how much of C follows you into Python. Memory, references, and what an object actually is stop being abstract once you have spent months managing all of it by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  System engineering and DevOps
&lt;/h2&gt;

&lt;p&gt;The third thread is the one that changed how I think about software the most. My &lt;a href="https://github.com/mukuvi/alx-system_engineering-devops" rel="noopener noreferrer"&gt;alx-system_engineering-devops&lt;/a&gt; repo covers shell basics, permissions, redirections, variables, loops, conditions and parsing, processes and signals, and regular expressions. From there it goes where most beginner courses never do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Networking, in two parts.&lt;/li&gt;
&lt;li&gt;Web infrastructure design, the diagrams that explain how a request actually reaches an application.&lt;/li&gt;
&lt;li&gt;SSH, and hardening it.&lt;/li&gt;
&lt;li&gt;A web server, then an application server.&lt;/li&gt;
&lt;li&gt;A load balancer, and later HTTPS and SSL certificates.&lt;/li&gt;
&lt;li&gt;MySQL, including primary and replica setups.&lt;/li&gt;
&lt;li&gt;APIs, and then advanced API work.&lt;/li&gt;
&lt;li&gt;Web stack debugging, four separate rounds of it.&lt;/li&gt;
&lt;li&gt;Webstack monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The web stack debugging projects are brutal, and I mean that as a compliment. You get a broken server and no description of what is wrong, and you have to find it. That is closer to the real job than any tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ALX actually taught me
&lt;/h2&gt;

&lt;p&gt;Less about any single language, and more about process. How to read documentation, how to break a big problem into pieces, how to ask a good question, and how to keep going when a project makes no sense for the first two hours. The deadlines are real and the volume is high, so you also learn to ship something that works instead of something perfect.&lt;/p&gt;

&lt;p&gt;The other thing is people. The peer model is not decoration. A lot of what I know came from sitting with someone late at night tracing a segfault until one of us saw it. A lecture does not give you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are in ALX right now
&lt;/h2&gt;

&lt;p&gt;A few things I would tell anyone in the middle of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not skip the low level work. It is the foundation everything else sits on.&lt;/li&gt;
&lt;li&gt;Write down what broke and how you fixed it. It becomes your own documentation.&lt;/li&gt;
&lt;li&gt;Ask for help earlier. Suffering for six hours before asking is not discipline, it is wasted time.&lt;/li&gt;
&lt;li&gt;Push your work to GitHub as you go. Those repos are a portfolio at the end, and they are the reason people believe you when you describe what you can do.&lt;/li&gt;
&lt;li&gt;Do not compare your progress to the person next to you. The sprints are hard for everyone, in different places.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The repos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mukuvi/alx-low_level_programming" rel="noopener noreferrer"&gt;alx-low_level_programming&lt;/a&gt;: C, from Hello World to a working shell.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mukuvi/alx-higher_level_programming" rel="noopener noreferrer"&gt;alx-higher_level_programming&lt;/a&gt;: Python and JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mukuvi/alx-system_engineering-devops" rel="noopener noreferrer"&gt;alx-system_engineering-devops&lt;/a&gt;: shell, networking, servers, and the debugging that comes with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are going through ALX, or thinking about starting, and you want to compare notes, the comments are open.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>c</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built ARN.IO, a reading companion that keeps your place and your notes</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:26:33 +0000</pubDate>
      <link>https://dev.to/mukuvi/i-built-a-reading-app-because-i-never-remembered-what-i-read-416m</link>
      <guid>https://dev.to/mukuvi/i-built-a-reading-app-because-i-never-remembered-what-i-read-416m</guid>
      <description>&lt;p&gt;Browsers are good at showing you a page and bad at remembering it. Read a book online, close the tab, and a week later you are scrolling to find your place and wondering what you highlighted. ARN.IO is a small attempt to fix that.&lt;/p&gt;

&lt;p&gt;It is a digital library and reading companion. You browse a collection, read chapter by chapter in the browser, and the app keeps track of where you are, so picking a book back up does not start with hunting for your place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A curated library you can search by title, author, or genre.&lt;/li&gt;
&lt;li&gt;Reading in the browser, one chapter at a time.&lt;/li&gt;
&lt;li&gt;Progress per book, with the current chapter and a percentage, plus reading streaks that count minutes and pages.&lt;/li&gt;
&lt;li&gt;Notes attached to a specific book and chapter, so the note lives next to the thing it is about instead of in a separate app.&lt;/li&gt;
&lt;li&gt;Upload your own documents. PDF, Word, TXT, Markdown, HTML, and RTF are accepted and split into chapters automatically.&lt;/li&gt;
&lt;li&gt;An AI reading assistant you can ask about themes, characters, summaries, or what to read next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the AI part, honestly
&lt;/h2&gt;

&lt;p&gt;The assistant works out of the box. It ships with built-in keyword matched responses, so there is no API key to paste and nothing to pay for, and it still answers the common questions about a book. That was deliberate. I wanted someone to clone the repo, run it, and have the assistant actually reply instead of showing a configuration error. The chat is per book and the history is kept, so if you want to wire it to a real model later the interface is already there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is put together
&lt;/h2&gt;

&lt;p&gt;It is split into a client and a server. There is a &lt;code&gt;run.sh&lt;/code&gt; at the root if you want to start everything at once.&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="nb"&gt;chmod&lt;/span&gt; +x run.sh
./run.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a &lt;code&gt;docker-compose.yml&lt;/code&gt; with separate Dockerfiles for the client and the server, if you would rather not install anything locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes on the upload
&lt;/h2&gt;

&lt;p&gt;Splitting a document into chapters across six formats is the fiddliest part of the feature. A PDF and a Markdown file do not agree on what a chapter even is, so the splitting is a best effort and you can always fix it by hand afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not
&lt;/h2&gt;

&lt;p&gt;There is no feed, no recommendations pushing you toward the next purchase, and no social layer. It is a reading tool, and it tries to stay out of the way.&lt;/p&gt;

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

&lt;p&gt;Repo: &lt;a href="https://github.com/mukuvi/ARN.IO" rel="noopener noreferrer"&gt;https://github.com/mukuvi/ARN.IO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is JavaScript end to end and it runs locally in a couple of commands. If you read and you lose track like I do, or you just want a small project to poke at the document parsing, the issues are open.&lt;/p&gt;




&lt;p&gt;Also on DEV: if you are trying to make your first open source contribution, I wrote about &lt;a href="https://dev.to/mukuvi/i-built-a-site-where-your-first-open-source-contribution-is-adding-yourself-to-it-2bae"&gt;a repo where contributing means adding yourself to it&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a site where your first open source contribution is adding yourself to it</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:26:31 +0000</pubDate>
      <link>https://dev.to/mukuvi/i-built-a-site-where-your-first-open-source-contribution-is-adding-yourself-to-it-2bae</link>
      <guid>https://dev.to/mukuvi/i-built-a-site-where-your-first-open-source-contribution-is-adding-yourself-to-it-2bae</guid>
      <description>&lt;p&gt;Plenty of people never make their first pull request. The commands are rarely the reason. You can learn &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt;, and &lt;code&gt;git push&lt;/code&gt; in an afternoon. What stops people is that the repo they pick has no reason to care whether they showed up, and nothing visibly changes when they are finished.&lt;/p&gt;

&lt;p&gt;So I built FREE-OPEN. It is a small React site, and the contribution is literally you. You fork it, add yourself to one JSON file, and open a pull request. When it is merged, your card appears on the contributors page of the live site. That is the whole thing. No issue to claim, no build system to learn, no review gauntlet waiting for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your first contribution looks like
&lt;/h2&gt;

&lt;p&gt;Fork the repo, clone your fork, and install dependencies:&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/&amp;lt;YOUR-USERNAME&amp;gt;/FREE-OPEN.git
&lt;span class="nb"&gt;cd &lt;/span&gt;FREE-OPEN
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a branch, open &lt;code&gt;src/data/contributors.json&lt;/code&gt;, and add yourself to the end of the array:&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Full Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-github-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-linkedin-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"twitter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-twitter-handle"&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;Run &lt;code&gt;npm run dev&lt;/code&gt;, check that your card shows up at &lt;code&gt;/contributors&lt;/code&gt;, then commit, push, and open the pull request. Once it is merged your card is on the live site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a JSON file
&lt;/h2&gt;

&lt;p&gt;I wanted the first contribution to be as small as possible. Adding one object to one file means you cannot really break anything, and if two people do it at the same time the conflict is a one-line fix. The point is not the JSON. The point is that you run the whole loop once, fork, branch, commit, push, pull request, merge, and then you look at the site and your name is there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;React 18, Vite, Tailwind CSS, and React Router. Nothing exotic. Vite because the dev server is fast enough that a newcomer does not sit around wondering if something is broken. Tailwind because the site has a dark mode and I did not want to hand-write a second stylesheet. React Router for the contributors and about pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the repo is for
&lt;/h2&gt;

&lt;p&gt;The repo has picked up 19 forks, and it has become a place to practise the boring, real parts of contributing: writing a branch name, writing a commit message that says what you did, writing a PR description that a maintainer can act on. Those are the things that get you through your first job, and you can practise them here without a deadline.&lt;/p&gt;

&lt;p&gt;The lesson I keep coming back to is simple. People will contribute if the barrier is one file and the reward is visible. A lot of projects get the first half right and forget the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is not finished
&lt;/h2&gt;

&lt;p&gt;There is a CONTRIBUTING.md, a code of conduct, and issue templates for bugs, features, and improvements, so there is more to do than just add a card. The site itself is plain on purpose. I would rather it stay readable than clever.&lt;/p&gt;

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

&lt;p&gt;Live site: &lt;a href="https://free-open.vercel.app/" rel="noopener noreferrer"&gt;https://free-open.vercel.app/&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/mukuvi/FREE-OPEN" rel="noopener noreferrer"&gt;https://github.com/mukuvi/FREE-OPEN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have never opened a pull request, this is a good place to start and I will review it. If you maintain a repo and you are trying to get first-timers through the door, take the idea. Make the first step tiny and make the result visible.&lt;/p&gt;




&lt;p&gt;Also on DEV: I wrote about &lt;a href="https://dev.to/mukuvi/i-built-a-reading-app-because-i-never-remembered-what-i-read-416m"&gt;ARN.IO, a reading companion that keeps your place and your notes&lt;/a&gt;, if you want another look at how I build things end to end.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>beginners</category>
      <category>git</category>
    </item>
    <item>
      <title>Touch Grass Birder: a bird call identifier that works with no signal</title>
      <dc:creator>James Ngandu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 05:11:27 +0000</pubDate>
      <link>https://dev.to/mukuvi/touch-grass-birder-a-bird-call-identifier-that-works-with-no-signal-1hnp</link>
      <guid>https://dev.to/mukuvi/touch-grass-birder-a-bird-call-identifier-that-works-with-no-signal-1hnp</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hf26"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Touch Grass Birder identifies bird calls, and it does the whole thing on your own machine.&lt;/p&gt;

&lt;p&gt;You tap &lt;strong&gt;Listen&lt;/strong&gt;, hold your phone up while a bird sings for about six seconds, and it tells you what you are hearing. Then you put the phone away and go look for the bird. That is the point: the screen is only in the way for a moment.&lt;/p&gt;

&lt;p&gt;I built it for the places where phone apps tend to fall over. A trailhead with one bar. A tent before sunrise. A park where the warblers are moving through and you have no idea what you are listening to.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Records six seconds of audio in the browser and encodes it to WAV on the device.&lt;/li&gt;
&lt;li&gt;Identifies the species locally with &lt;strong&gt;BirdNET&lt;/strong&gt;, the open acoustic classifier from the Cornell Lab of Ornithology.&lt;/li&gt;
&lt;li&gt;Writes a short field note about the bird with &lt;strong&gt;Gemma 3 1B&lt;/strong&gt;, an open-weight model running through &lt;code&gt;llama.cpp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keeps a field log of everything you have heard, in a plain JSON Lines file you own.&lt;/li&gt;
&lt;li&gt;Installs as a PWA and keeps working with the network turned off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no API key, no audio upload, and no network round trip.&lt;/p&gt;

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

&lt;p&gt;Live demo: &lt;a href="https://touch-grass-birder.onrender.com" rel="noopener noreferrer"&gt;https://touch-grass-birder.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hosted demo runs BirdNET, so you can point it at a recording right now. Gemma field notes only work when you run it yourself, because a free Render instance is too small for the 0.8 GB model. Run &lt;code&gt;make models &amp;amp;&amp;amp; make run&lt;/code&gt; for those.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/mukuvi/touch-grass-birder" rel="noopener noreferrer"&gt;https://github.com/mukuvi/touch-grass-birder&lt;/a&gt; (MIT licensed).&lt;/p&gt;

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

&lt;p&gt;Two open models run on the device, wired together behind a small FastAPI server.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[Phone browser&amp;lt;br/&amp;gt;records 6s, encodes WAV] --&amp;gt; B[FastAPI]
  B --&amp;gt; C[ffmpeg&amp;lt;br/&amp;gt;decode to 16-bit mono WAV]
  C --&amp;gt; D[BirdNET acoustic model&amp;lt;br/&amp;gt;open, local inference]
  D --&amp;gt; E[species + confidence]
  E --&amp;gt; F[Gemma 3 1B&amp;lt;br/&amp;gt;open-weight via llama.cpp]
  F --&amp;gt; G[plain-language field note]
  E --&amp;gt; H[(data/sightings.jsonl&amp;lt;br/&amp;gt;your field log)]
  G --&amp;gt; A
  H --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser side:&lt;/strong&gt; the MediaRecorder API captures six seconds, an &lt;code&gt;AudioContext&lt;/code&gt; decodes it, and a small function in &lt;code&gt;app.js&lt;/code&gt; writes a real 16-bit mono WAV &lt;code&gt;Blob&lt;/code&gt; before it is sent. The server never has to touch the audio to get a usable clip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BirdNET&lt;/strong&gt; does the acoustic work. It is trained on thousands of species for passive acoustic monitoring, and it is the reason this works offline at all. It loads through the LiteRT runtime and stays warm in memory after the first call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 3 1B&lt;/strong&gt; (Q4_K_M, about 0.8 GB) runs through the &lt;code&gt;llama.cpp&lt;/code&gt; CLI to write a two or three sentence field note: what the bird sounds like, where and when you would hear it, and one field mark to look for. If the model is not on disk, the app skips the note and still identifies the bird.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; ties it together with four endpoints (&lt;code&gt;/api/identify&lt;/code&gt;, &lt;code&gt;/api/sightings&lt;/code&gt;, &lt;code&gt;/api/health&lt;/code&gt;, and the static app). The field log is &lt;code&gt;data/sightings.jsonl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is overridable with environment variables (&lt;code&gt;LLAMA_BIN&lt;/code&gt;, &lt;code&gt;GEMMA_MODEL&lt;/code&gt;, &lt;code&gt;MIN_CONF&lt;/code&gt;, &lt;code&gt;LLAMA_THREADS&lt;/code&gt;, &lt;code&gt;BIRDER_DATA_DIR&lt;/code&gt;), so the same code runs on a laptop, a Raspberry Pi, or something sitting next to a phone.&lt;/p&gt;

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

&lt;p&gt;The short version: this project only exists because the models are open.&lt;/p&gt;

&lt;p&gt;The best places to hear birds are the places with no signal, and a cloud API cannot run there at all. Every closed identifier needs a round trip to a server. Running BirdNET on the device means the app is not a worse version of a cloud tool in the backcountry. It is the only version that works there. The recording is six seconds because nothing has to be uploaded and there is no spinner to wait on. The answer is already on the phone.&lt;/p&gt;

&lt;p&gt;A rare sighting is sensitive. A closed app can learn where you were and when, which matters if you are near a nesting site or reporting a scarce species. Here the only record is a line in a file you own, on hardware you control. There is no server for it to go to.&lt;/p&gt;

&lt;p&gt;Open weights also mean I can change the model. Gemma runs from a local GGUF file, so moving to a larger Gemma, a different quantization, or a different model entirely is a path and an environment variable, not a conversation with a vendor. I can fine-tune on a regional species list later. The classifier itself, BirdNET, is open and inspectable, which feels important when the output is a claim about a living thing.&lt;/p&gt;

&lt;p&gt;And it costs nothing to run. No per-identification fee, no rate limit, no key that expires. A birder on a budget, a school group, or a citizen science project can run it as long as they want.&lt;/p&gt;

&lt;p&gt;The open parts are the point. For this app the offline, private, zero-cost version is simply the better one.&lt;/p&gt;

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

&lt;p&gt;I built this with an agent. The session is saved, so you can see how it went:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building Touch Grass Birder for the HF26 Week 1 challenge&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;Pasted the Hacktoberfest Open-Source AI Challenge Week 1 (Touch Grass) announcement: build something with open-source AI at its core that gets people outdoors, due Oct 11. Can you do this?&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;I checked the workspace. There is already a project: Touch Grass Birder, a bird-call identifier (BirdNET + Gemma 3 1B) that runs fully locally. Tests pass. I loaded the challenge skill and confirmed Week 1 is active (id 79, due Oct 12 06:59 UTC).&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-getchallenges"&gt;get_challenges&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;list active DEV challenges&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;list active DEV challenges&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;Week 1 (id 79) active: theme Touch Grass, $2450 across 17 winners, judging = writing quality, relevance, creativity, technical execution, partner tech. Requires tag #hf26challenge.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Scope: Draft the DEV post, repo polish + commit, deploy to Render, save + embed the agent session, do it all end-to-end.&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;Audited the code (identify.py via BirdNET/LiteRT, fieldnotes.py via llama.cpp Gemma 3 1B, FastAPI main.py, PWA static app, 3 passing tests). Drafted the full submission post to docs/dev-submission-draft.md and added an MIT LICENSE.&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-pytest"&gt;pytest&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;uv run 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"&gt;&lt;code&gt;uv run 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;3 passed in 2.09s&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-gitlsremote"&gt;git ls-remote&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git@github.com:mukuvi/touch-grass-birder.git&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;git@github.com:mukuvi/touch-grass-birder.git&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;Repository not found. Found the real repo at mukuvi/-touch-grass-birder (leading dash) with Apache-2.0 LICENSE; README/pyproject declared MIT. Local git history was an unrelated snapshot.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Decisions: rename repo to touch-grass-birder, use MIT license, deploy a BirdNET-only demo on Render free tier (Gemma notes run locally).&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;Reconciled the local repo onto the real remote, replaced Apache-2.0 LICENSE with MIT (matching README/pyproject), committed the post draft + license, and pushed to GitHub.&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-gitpush"&gt;git push&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git push git@github.com:mukuvi/-touch-grass-birder.git main&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;git push git@github.com:mukuvi/-touch-grass-birder.git main&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;25ba8b6..40b587d main -&amp;gt; main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;proceed bro, you got all the keys&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 push landed, pointed local origin at the real repo, and set out the remaining steps: rename on GitHub (needs a one-click settings change from the user - SSH cannot rename repos), deploy the Render blueprint, embed this saved agent session in the post, then publish the draft.&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;
        8 of 7 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&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;: Gemma 3 1B runs locally through &lt;code&gt;llama.cpp&lt;/code&gt; and writes the field notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt;: the live demo runs on Render with the included &lt;code&gt;render.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for Hacktoberfest 2026. Files, models, and data stay on the machine, with no key, no upload, and no network.&lt;/em&gt;&lt;/p&gt;

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