<?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: Aniruddha Sarkar</title>
    <description>The latest articles on DEV Community by Aniruddha Sarkar (@aniruddha120).</description>
    <link>https://dev.to/aniruddha120</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%2F4174641%2Fddf7c289-9145-4a57-b2dd-05311fe476c9.png</url>
      <title>DEV Community: Aniruddha Sarkar</title>
      <link>https://dev.to/aniruddha120</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aniruddha120"/>
    <language>en</language>
    <item>
      <title>Understory: a bird guide for the places where your phone gives up</title>
      <dc:creator>Aniruddha Sarkar</dc:creator>
      <pubDate>Sun, 11 Oct 2026 18:24:58 +0000</pubDate>
      <link>https://dev.to/aniruddha120/understory-a-bird-guide-for-the-places-where-your-phone-gives-up-55bn</link>
      <guid>https://dev.to/aniruddha120/understory-a-bird-guide-for-the-places-where-your-phone-gives-up-55bn</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;Understory&lt;/strong&gt; is a bird guide for the places where your phone gives up.&lt;/p&gt;

&lt;p&gt;You point a microphone at a singing bird. It tells you what you are listening to using a model that runs &lt;em&gt;on the machine&lt;/em&gt;, then writes a short field note: where the bird lives, and what it is probably doing right now. Then it gets out of the way.&lt;/p&gt;

&lt;p&gt;The whole interaction is one press and one card. The note is appended to a plain markdown file, so the reading happens back at the car, not while the bird is still in front of you. &lt;strong&gt;The screen is the shortest part of the walk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is built for someone with an old laptop, no data signal, and no intention of uploading every recording of their local patch to somebody's cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Understory  15:13  [offline guard: on]
  Heard: European Robin (99%)
         also Eurasian Blue Tit (60%)

  It is likely foraging for insects in a woodland area. It is probably
  searching for a mate or nesting material.

  (AI field note — a hint to look, not a tick on a list.)

  logged -&amp;gt; ~/.understory/fieldnotes/2026-10-11.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;No deployed link, on purpose: there is nothing to deploy. A tool whose whole point is that it needs no server is best shown the way you would actually use it, so the demo below is a real run with the offline guard switched on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxaumv58cvi4l4xi8u63.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxaumv58cvi4l4xi8u63.gif" alt="Understory identifying a European Robin offline, then proving the offline guard by passing its test suite" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;understory.py &lt;span class="nb"&gt;id &lt;/span&gt;robin.mp3 &lt;span class="nt"&gt;--lat&lt;/span&gt; 52.52 &lt;span class="nt"&gt;--lon&lt;/span&gt; 13.40 &lt;span class="nt"&gt;--offline&lt;/span&gt;
&lt;span class="go"&gt;Understory  15:13  [offline guard: on]
  Heard: European Robin (99%)
         also Eurasian Blue Tit (60%)

  It is likely foraging for insects in a woodland area. It is probably
  searching for a mate or nesting material.

  (AI field note — a hint to look, not a tick on a list.)

&lt;/span&gt;&lt;span class="gp"&gt;  logged -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.understory/fieldnotes/2026-10-11.md
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;understory.py &lt;span class="nb"&gt;id &lt;/span&gt;greattit.ogg &lt;span class="nt"&gt;--lat&lt;/span&gt; 52.52 &lt;span class="nt"&gt;--lon&lt;/span&gt; 13.40 &lt;span class="nt"&gt;--no-narrate&lt;/span&gt; &lt;span class="nt"&gt;--offline&lt;/span&gt;
&lt;span class="go"&gt;Understory  15:10  [offline guard: on]
  Heard: Great Tit (100%)

&lt;/span&gt;&lt;span class="gp"&gt;  logged -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.understory/fieldnotes/2026-10-11.md
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;understory.py &lt;span class="nb"&gt;id &lt;/span&gt;quiet-room.wav &lt;span class="nt"&gt;--no-narrate&lt;/span&gt; &lt;span class="nt"&gt;--offline&lt;/span&gt;
&lt;span class="go"&gt;Understory  15:11  [offline guard: on]
  Nothing bird-shaped in that clip. Try again quieter, or closer.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Identification alone is &lt;strong&gt;~6 seconds&lt;/strong&gt; on a 4-core CPU. The written note adds 30–45 seconds, which is exactly why the phone goes in the pocket: you watch the bird while it writes, and read the card when you are done.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Aniruddha120" rel="noopener noreferrer"&gt;
        Aniruddha120
      &lt;/a&gt; / &lt;a href="https://github.com/Aniruddha120/understory" rel="noopener noreferrer"&gt;
        understory
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline bird field guide: BirdNET identifies on the CPU, local Gemma 3 writes the field note. No signal, no cloud, audio never leaves the machine.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Understory&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Point it at a bird. Keep your phone in your pocket.&lt;/p&gt;

&lt;p&gt;Understory listens to a bird, works out what it is with &lt;strong&gt;BirdNET&lt;/strong&gt; (open model
runs on the CPU), and has &lt;strong&gt;Gemma 3&lt;/strong&gt; (open weights, served locally by Ollama)
write you a short field note: where to look and what the bird is probably doing.&lt;/p&gt;
&lt;p&gt;Everything runs on your machine. &lt;strong&gt;No API key, no account, and no internet&lt;/strong&gt; — it
keeps working in a valley with no bars, and the audio never leaves the device. A
&lt;code&gt;--offline&lt;/code&gt; flag hard-blocks every non-loopback socket so you can watch it refuse
to phone home.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Aniruddha120/understory/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FAniruddha120%2Funderstory%2FHEAD%2Fdemo.gif" alt="Understory identifying a European Robin offline, then proving the offline guard with its test suite"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What the small model is and isn't allowed to say&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Asked for plumage, a 1B model invents colours. Asked for a song, it invents calls
(both happened during development). So its job is scoped to the two things it gets
right reliably: &lt;strong&gt;where the bird lives&lt;/strong&gt; and &lt;strong&gt;what&lt;/strong&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aniruddha120/understory" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. 285 lines of Python, 61 lines of tests, no framework, no cloud SDK. &lt;code&gt;./setup.sh&lt;/code&gt; fetches both open models once; after that it runs with the wifi off.&lt;/p&gt;

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

&lt;p&gt;Two open models, chained, both local:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identification&lt;/td&gt;
&lt;td&gt;BirdNET GLOBAL 6K v2.4 (6,522 species)&lt;/td&gt;
&lt;td&gt;TensorFlow Lite, CPU, 1–4 s/clip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field note&lt;/td&gt;
&lt;td&gt;Gemma 3 1B (Q4_K_M)&lt;/td&gt;
&lt;td&gt;Ollama, CPU, ~3 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pipeline is deliberately boring, because the interesting part is what it &lt;em&gt;doesn't&lt;/em&gt; do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mic ──► 48 kHz mono WAV ──► BirdNET ──► top species ──► Gemma 3 ──► card + markdown log
                                                             │
                                                  no network, ever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;BirdNET reports one hit per 3-second window, so &lt;code&gt;collapse()&lt;/code&gt; keeps the best hit per species and counts the rest. A bird that sings four times across a minute is more convincing than one that appears once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The offline guard is the part I care about.&lt;/strong&gt; "It works offline" is a claim; I wanted it to be a &lt;em&gt;test&lt;/em&gt;. &lt;code&gt;--offline&lt;/code&gt; swaps out &lt;code&gt;socket.connect&lt;/code&gt; and &lt;code&gt;socket.connect_ex&lt;/code&gt; for the duration of the run and refuses anything that is not loopback:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;offline_guard&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;real_connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;::1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Understory is offline: blocked connection to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;real_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;real_connect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Loopback stays open because Gemma is served on &lt;code&gt;127.0.0.1&lt;/code&gt;. Everything else — a telemetry ping, a model download, an accidental analytics import — dies with a visible error instead of quietly succeeding. The test suite asserts both halves: a real loopback connection succeeds, and a connection to &lt;code&gt;1.1.1.1:443&lt;/code&gt; raises.&lt;/p&gt;

&lt;p&gt;Two things bit me on the way, and both are arguments for open tooling in miniature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ollama refused to pull Gemma.&lt;/strong&gt; My installed version was too old and the registry answered &lt;code&gt;412 … requires a newer version of Ollama&lt;/code&gt;. The "upgrade" was a 1.4 GB download. The lazy fix was to skip the registry: fetch the GGUF from Hugging Face and &lt;code&gt;ollama create&lt;/code&gt; it from a Modelfile. Ollama autodetected the &lt;code&gt;gemma3-instruct&lt;/code&gt; template and it just worked. The model is a file. Nothing got to tell me no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tflite-runtime&lt;/code&gt; blew up under NumPy 2.&lt;/strong&gt; &lt;code&gt;AttributeError: _ARRAY_API not found&lt;/code&gt;, because the runtime is compiled against NumPy 1.x. Pinning &lt;code&gt;numpy&amp;lt;2&lt;/code&gt; fixed it — two lines in &lt;code&gt;requirements.txt&lt;/code&gt; and one confused afternoon.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Four things here were only possible because every piece is open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It runs where the birds are.&lt;/strong&gt; No signal is the premise, not a limitation. A cloud API is not slow in a valley — it is absent. BirdNET on the CPU turns a dead zone into a working tool. This is the whole reason the app is shaped the way it is: the screen disappears because nothing about the interaction needs a network round-trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing leaves the machine.&lt;/strong&gt; Bird recordings are location data. What you recorded, and where, reveals your patch, your routine, and the hours your house is empty. With a local model I am not promising to delete your audio later; it was never sent. That is a structurally different guarantee from a privacy policy, and it is the one a birder would actually want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs nothing to run, forever.&lt;/strong&gt; No tokens, no quota, no per-request billing. I can leave it listening for an hour, or run it every morning for a year, and the cost is identical: the electricity. Metered APIs quietly teach you not to use your own tool — you start rationing the thing that was supposed to be fun. This one costs the same when you use it badly as when you use it well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every layer is swappable.&lt;/strong&gt; BirdNET is one &lt;code&gt;.tflite&lt;/code&gt; file. Gemma is one &lt;code&gt;.gguf&lt;/code&gt; file. That is not a slogan; it is the reason the section below exists. When the small model got the field notes wrong, I did not file a ticket and wait for a release note. I changed the prompt, re-ran it, and then changed the model — in the same afternoon.&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="c"&gt;# same app, a different local Gemma, no code change:&lt;/span&gt;
understory.py listen &lt;span class="nt"&gt;--seconds&lt;/span&gt; 12 &lt;span class="nt"&gt;--model&lt;/span&gt; understory-gemma-4b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the Open Model Was Wrong
&lt;/h2&gt;

&lt;p&gt;I asked Gemma 3 1B to describe a Great Tit's markings. It wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"a small, dark greyish-brown plumage with a distinctive white stripe across the chest … a short, repetitive 'cheep-cheep' song."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is wrong on both counts. A Great Tit has a black head, white cheeks, and a yellow breast split by a black stripe, and its song is a ringing two-note phrase, not a cheep. A 1-billion-parameter model on a laptop CPU is a fluent writer and a poor ornithologist. I had asked it for &lt;em&gt;recalled facts&lt;/em&gt;, which is the one thing it cannot do reliably.&lt;/p&gt;

&lt;p&gt;The fix was not a bigger model. It was to stop asking.&lt;/p&gt;

&lt;p&gt;I checked that claim rather than trusting it. I swapped the 1B for &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; — one GGUF file, one command, no code change — and the prose really did get better:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"European Robins typically favour woodland edges and gardens with dense shrubbery. The bird is likely searching for invertebrates amongst the foliage."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a &lt;em&gt;better sentence&lt;/em&gt;, and it is still only habitat and activity, so the scoping holds as the model grows. But it took &lt;strong&gt;four minutes and twenty seconds&lt;/strong&gt; to write one note on this CPU, against about forty seconds for the 1B. A four-minute wait at the trailhead is not a field tool. The bigger model fixed the prose and broke the walk, which is the whole lesson: on a laptop in a hedgerow, a bigger model is a slower model before it is a smarter one. Money spent on parameters buys words, not time.&lt;/p&gt;

&lt;p&gt;The prompt now asks only for where the bird lives and what it is probably doing — and explicitly forbids colour, markings, and song, because &lt;strong&gt;the birder can see and hear the bird, and markings belong in a field guide.&lt;/strong&gt; I also added a confidence gate: below 50% the tool shows the hit but writes &lt;em&gt;no&lt;/em&gt; note, so a coin-flip never gets dressed up in confident prose.&lt;/p&gt;

&lt;p&gt;The notes got shorter, blander, and true. That is the right trade for a tool you are holding up at a bird: a bland true hint tells you where to look, and a confident false one sends you looking for a bird that is not there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Taking It Outside
&lt;/h2&gt;

&lt;p&gt;The first real use was my balcony. A sparrow came through, and the ID was right. The screen was on for about two minutes, then the phone went back in my pocket.&lt;/p&gt;

&lt;p&gt;That is the whole report: one bird, correctly named, no drama. Which is the standard I would want a field tool judged by — the test is whether it is right when you are actually outside, not whether it is clever on a desk.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Built with OpenCode. The session is saved here:&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;Understory: offline bird ID with BirdNET + local Gemma&lt;/span&gt;
  &lt;/div&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build an entry for the Hacktoberfest Open-Source AI Challenge Week 1 (Touch Grass). Theme: open-weight / open-source AI that gets people off the screen. Submission template is in the folder. Use DevRelay.&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-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-getchallengedetails"&gt;get_challenge_details&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"id": 79}&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 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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;79&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&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 'Touch Grass', ends 2026-10-12T06:59Z. $2,450 / 17 winners. New project built in-window, one entry, tag #hf26challenge. Judging: Writing Quality (heaviest), Relevance, Creativity, Technical Execution, optional partner tech. Idea sparks: bird call ID with no signal, garden planner, run-club foliage routes.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Environment: Python 3.13, Node 22, Docker, ffmpeg, Ollama installed, 4 cores / 15 GB, NVIDIA 940MX (too weak - CPU inference). ~23h to the deadline. Asked the user to pick a direction.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Offline bird call ID. Aim at Best Use of Gemma.&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-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-ollamapullgemma31b"&gt;ollama pull gemma3:1b&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"model":"gemma3:1b"}&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 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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemma3:1b"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&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;Error: pull model manifest: 412: The model you are attempting to pull requires a newer version of Ollama. (installed 0.16.2, registry now wants 0.40.3)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The registry upgrade was a 1.4 GB tarball. Lazier: keep the installed Ollama and import a Gemma GGUF by hand, bypassing the registry entirely.&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-httpheadhuggingfacecoggmlorggemma31bitgguf"&gt;http HEAD huggingface.co/ggml-org/gemma-3-1b-it-GGUF&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"method":"HEAD"}&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 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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&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;200, content-length 806058240, not gated&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-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;curl -L -o models/gemma-3-1b-it-Q4_K_M.gguf &amp;lt;hf-url&amp;gt; &amp;amp;&amp;amp; ollama create underst...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ curl -L -o models/gemma-3-1b-it-Q4_K_M.gguf &amp;lt;hf-url&amp;gt; &amp;amp;&amp;amp; ollama create understory-gemma -f Modelfile&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;success - 'using autodetected template gemma3-instruct', model 806 MB&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-curlapigenerate"&gt;curl /api/generate&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"model":"understory-gemma","prompt":"what does a European Robin sound like?"}&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 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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"understory-gemma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"what does a European Robin sound like?"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&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;generated a sensible one-sentence answer, ~22s on CPU (~3 tok/s)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;uv pip install birdnetlib tflite-runtime librosa soundfile resampy&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ uv pip install birdnetlib tflite-runtime librosa soundfile resampy&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;birdnetlib 0.18.1 + the BirdNET 6,522-species TFLite model bundled inside the wheel (50 MB FP32 + 15 MB meta). tflite-runtime 2.14.0.&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-python"&gt;python&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;Analyzer().load_model()&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;Analyzer().load_model()&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;AttributeError: _ARRAY_API not found / numpy.core.multiarray failed to import - tflite-runtime 2.14 is built against NumPy 1.x but numpy 2.4.6 was installed&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-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;uv pip install "numpy&amp;lt;2"&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ uv pip install "numpy&amp;lt;2"&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;numpy 1.26.4 - model loads, XNNPACK delegate on CPU&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-python"&gt;python&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;Recording(analyzer, 'robin.mp3', lat=52.52, lon=13.40).analyze()&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;Recording(analyzer, 'robin.mp3', lat=52.52, lon=13.40).analyze()&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;robin.mp3 -&amp;gt; European Robin 0.987; greattit.ogg -&amp;gt; Great Tit 1.000; silence -&amp;gt; no detections. Inference 1-4 s/clip on CPU.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Wrote the CLI: mic -&amp;gt; 48 kHz WAV -&amp;gt; BirdNET -&amp;gt; top species -&amp;gt; Gemma field note -&amp;gt; one card + a dated markdown log. Added --offline, which monkeypatches socket.connect to refuse every non-loopback address, so 'it works offline' is checkable rather than a claim.&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-pythonteststestunderstorypy"&gt;python tests/test_understory.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;First run: TypeError: guard() takes 1 positional argument but 2 were given - ...&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;First run: TypeError: guard() takes 1 positional argument but 2 were given - socket.socket.connect is assigned on the class, so the replacement is called as guard(sock, addr). Fixed the signature.&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-pythonteststestunderstorypy"&gt;python tests/test_understory.py&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;ok test_card_reports_a_miss_clearly / ok test_collapse_keeps_best_hit_per_spe...&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;ok test_card_reports_a_miss_clearly / ok test_collapse_keeps_best_hit_per_species / ok test_offline_guard_blocks_the_internet_but_allows_loopback - a real loopback connect succeeds, 1.1.1.1:443 raises OSError('Understory is offline')&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-searchdevtosemantic"&gt;search_dev_to_semantic&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"query":"offline bird sound identification TFLite"}&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 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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"offline bird sound identification TFLite"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&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;Saturated: 'Trail Birder: Identify Birds by Sound, Offline, with BirdNET and Gemma', TrailEar, EchoTrail, Who's Singing?, 'I Put 6,522 Birds in My Backpack', fieldcard - 7+ near-identical Week 1 entries.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Told the user their idea is the most crowded in the challenge and offered to pivot or differentiate. They chose to keep it and win on the angle. Renamed TrailGuide -&amp;gt; Understory so it is not confused with TrailEar / Trail Birder.&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-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-pythonunderstorypyidgreattitoggoffline"&gt;python understory.py id greattit.ogg --offline&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;Card: 'Great Tit (100%)'. Gemma note: 'a small, dark greyish-brown plumage wi...&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;Card: 'Great Tit (100%)'. Gemma note: 'a small, dark greyish-brown plumage with a distinctive white stripe across the chest ... a short, repetitive cheep-cheep song'.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The 1B model invented the plumage (real Great Tit: black head, white cheeks, yellow breast with a black stripe) and got the song wrong. A 1B cannot be trusted for recalled facts. Rewrote the prompt to ask only for where to look, likely behaviour, and a field mark ONLY if certain, so the model stops guessing. A/B tested: new prompt stopped inventing markings.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Keep the angle, save the session, leave the outdoor test and the push to me.&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;
        9 of 9 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;It includes the smaller fights too: the offline guard's first version crashing because &lt;code&gt;socket.socket.connect&lt;/code&gt; is assigned on the class and therefore called with &lt;code&gt;self&lt;/code&gt;; the NumPy ABI crash; and the moment a semantic search of the challenge feed showed me that "offline bird identifier" was already the most crowded idea of the week — which is why the project is called Understory and not Trail-something.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 is the half of this project that turns a species label into something a person can act on, and it runs entirely offline through Ollama on CPU. The failure above is part of why I am entering it here: the interesting engineering with a small open model is not calling it, it is deciding what you can honestly ask it — and then refusing to ask it anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Sample recordings used while testing, credited under their Creative Commons licences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;European Robin, &lt;a href="https://www.xeno-canto.org/509375" rel="noopener noreferrer"&gt;XC509375&lt;/a&gt; — Marie-Lan Taÿ Pamart, CC BY-SA 4.0&lt;/li&gt;
&lt;li&gt;Common Blackbird, &lt;a href="https://xeno-canto.org/1059970" rel="noopener noreferrer"&gt;XC1059970&lt;/a&gt; — Diana Tudor, CC BY 4.0&lt;/li&gt;
&lt;li&gt;Great Tit — Vladimir Yu. Arkhipov, CC BY-SA 3.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BirdNET weights are CC BY-NC-SA 4.0 (non-commercial); Gemma 3 is used under the Gemma Terms of Use. The code is MIT.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
