<?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: Archana</title>
    <description>The latest articles on DEV Community by Archana (@chanadev).</description>
    <link>https://dev.to/chanadev</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%2F3881394%2Ff285cc75-1bf0-4174-862f-c6c7754f30ee.jpg</url>
      <title>DEV Community: Archana</title>
      <link>https://dev.to/chanadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chanadev"/>
    <language>en</language>
    <item>
      <title>Tendril: a garden assistant you barely have to open</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Thu, 08 Oct 2026 00:12:52 +0000</pubDate>
      <link>https://dev.to/chanadev/tendril-a-garden-assistant-you-never-have-to-open-24d</link>
      <guid>https://dev.to/chanadev/tendril-a-garden-assistant-you-never-have-to-open-24d</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;My balcony has a little garden: two Indian hibiscus, a money plant, jasmine, a blue aprajita, mint, tulsi and sadabahaar. All in pots. I water them every morning and every evening, without fail.&lt;/p&gt;

&lt;p&gt;But a routine doesn't look at the sky. It waters the plants an hour before a downpour. It doesn't know a 38°C afternoon is coming. And it definitely doesn't go looking under leaves for aphids.&lt;/p&gt;

&lt;p&gt;I didn't want another app to spend time in, though. The whole point of a garden is to &lt;em&gt;not&lt;/em&gt; be on your phone. So I built &lt;strong&gt;Tendril&lt;/strong&gt;, a garden assistant you barely have to open :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It plans the week from the weather.&lt;/strong&gt; Every Sunday at 7 AM it reads the forecast, and a small open model running on my own machine writes a plan: at most two tiny tasks a day, each under six words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It checks the sky every morning.&lt;/strong&gt; If rain shows up overnight, or a heat spike, it quietly rewrites just today's tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The plan is one picture.&lt;/strong&gt; It's made to fit a phone's lock screen, with the top third left empty for the clock. One glance and you're back outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell it what you did.&lt;/strong&gt; Tap &lt;strong&gt;Done&lt;/strong&gt;, or hold the mic and say something like &lt;em&gt;"Watered the chillies, there are aphids under the tulsi leaves."&lt;/em&gt; It ticks off the watering, and since aphids are serious, it re-plans the rest of the week with neem oil first. It's set up for English and Hindi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only nudges once.&lt;/strong&gt; After 6 PM, if something's still pending, the page shows one gentle line. That's it. No streaks, no guilt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No accounts.&lt;/strong&gt; Set your city, add your plants, scan a QR code, add it to your home screen. Done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An app that wants you to close it as fast as you can sounds a bit odd, I know. But that's the whole idea :)&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Searching for your city&lt;/strong&gt;&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%2Fhara86g1gbxjs7yxau7t.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%2Fhara86g1gbxjs7yxau7t.gif" alt="1-search-city" width="420" height="909"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding a plant&lt;/strong&gt;&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%2Fddkrhff9oimpz2ulk4m5.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%2Fddkrhff9oimpz2ulk4m5.gif" alt="2-add-plant" width="420" height="909"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The morning plan&lt;/strong&gt;&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%2F12wl9k2cjzui9mjq6k35.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%2F12wl9k2cjzui9mjq6k35.gif" alt="today page" width="420" height="909"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telling Tendril what you did&lt;/strong&gt;&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%2Fcvpssjwluj2ldimnu1hs.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%2Fcvpssjwluj2ldimnu1hs.gif" alt="check in" width="420" height="909"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

&lt;p&gt;Nothing fancy, and that's on purpose. Everything that thinks runs on the same machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python and FastAPI&lt;/strong&gt; for the app, with &lt;strong&gt;SQLite&lt;/strong&gt; for the garden's memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen 2.5 (3B)&lt;/strong&gt;, an open-weight model, running locally through &lt;strong&gt;Ollama&lt;/strong&gt;. It writes the weekly plan and reads my notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;faster-whisper&lt;/strong&gt;, an open speech model, turning voice notes into text right on the CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-Meteo&lt;/strong&gt; for the forecast and city search. Free, and no API key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pillow&lt;/strong&gt; to draw the plan picture. Every icon in it, the sun, the clouds, the little leaf, is drawn in code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose&lt;/strong&gt; to run it all with one command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A small model with big habits
&lt;/h3&gt;

&lt;p&gt;A 3B model is tiny. That's what lets it run on a laptop, but it comes with some habits. My first real run taught me more than all my tests did:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It copied my instructions into its answers.&lt;/strong&gt; I asked for a short reason with each task, and got back things like &lt;em&gt;"At most 2 tasks per day; fewer is fine on quiet days."&lt;/em&gt; Thanks, very helpful :)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then it copied my example onto every day.&lt;/strong&gt; I showed it one sample day with "Water deeply at the roots". Next plan: deep watering, seven days in a row, for both plants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It was slow.&lt;/strong&gt; The first plan took 242 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix wasn't a bigger model. It was splitting the work. The model handles what it's good at: varied, plant-specific ideas, and making sense of messy notes. Plain code handles what it can't be trusted with. The weather rules (skip watering above 60% rain, shade above 35°C, a deep soak after three dry days) are applied last, over whatever the model said. Repeats get trimmed. Copied reasons get swapped for a real weather note.&lt;/p&gt;

&lt;p&gt;Asking for compact JSON helped with speed too. All that indentation was costing about a third of the tokens. A week plan now takes about 100 seconds, which is fine for something that runs quietly at 7 AM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the model honest
&lt;/h3&gt;

&lt;p&gt;Every plan has to come back as proper JSON: at most two tasks a day, actions under six words, only plants I actually own, and every date filled in. The server checks each reply with Pydantic. If something's off, it asks once more and says exactly what was wrong ("unknown plant 'Rose'"). If that fails too, a simple rule-based planner steps in. So a plan always shows up, model or no model.&lt;/p&gt;

&lt;p&gt;Voice notes got a small safety net as well. In one test the model filed &lt;em&gt;"I watered the chillies… aphids on the tulsi"&lt;/em&gt; entirely under tulsi and forgot the watering. Now a tiny keyword check runs alongside it and catches the obvious things like "watered", matched to the plant named in the same sentence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it feel like a lock screen
&lt;/h3&gt;

&lt;p&gt;I wanted the plan to feel calm, like a wallpaper rather than an app. Dark forest green, two big cards, a weather pill that says something like "29° · Rain at 4 PM", and a strip of the next six days at the bottom. Headings are in Bricolage Grotesque, everything else in Figtree.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My garden stays at home.&lt;/strong&gt; My location, my voice and my plants' history live in one small file on a machine I control. Voice notes are turned into text on that same machine. The only thing that leaves is the garden's coordinates, to fetch the weather.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run every day.&lt;/strong&gt; A planner that wakes up every morning, forever, is exactly the kind of thing where paying per request adds up. With an open model, there's no bill that grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could see the model's mistakes, and work around them.&lt;/strong&gt; Every odd habit above showed up in the raw output, and I could fix it on my side the same day. No waiting on anyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swapping models is one line.&lt;/strong&gt; If a better small model comes out next month, I change &lt;code&gt;OLLAMA_MODEL&lt;/code&gt; and pull it. Everything else stays.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bigger closed model might have written prettier reasons. But a small open one that lives on my own machine, costs nothing and does the job is more than enough for a few pots on a balcony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying It on My Balcony
&lt;/h2&gt;

&lt;p&gt;The first plan for my balcony landed on a rainy week. The forecast showed rain on four days, with up to a 94% chance on one of them. So Tendril's advice for most of the week was simply: &lt;strong&gt;skip watering today&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For someone who waters twice a day no matter what, that was the most useful thing it could say :)&lt;/p&gt;

&lt;p&gt;It also left the watering alone on the dry days, because my notes already say I water every morning and evening. It suggested pest checks and pinching off yellow leaves instead.&lt;/p&gt;

&lt;p&gt;Turns out the best thing a garden assistant can say is sometimes "don't". 🌱&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>A study notebook for my brother, written by an open model</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Fri, 02 Oct 2026 16:16:39 +0000</pubDate>
      <link>https://dev.to/chanadev/a-study-notebook-for-my-brother-written-by-an-open-model-2n20</link>
      <guid>https://dev.to/chanadev/a-study-notebook-for-my-brother-written-by-an-open-model-2n20</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My brother is preparing for GATE, a big engineering exam in India (think of it as the entrance exam for a master's at the IITs). The computer science paper covers pretty much everything from college, from discrete maths to operating systems to networks.&lt;/p&gt;

&lt;p&gt;Studying for it alone is hard. Not because any one topic is impossible, but because nobody is around to tell you "hey, you're getting better". So I built him &lt;strong&gt;inkwand&lt;/strong&gt;, a free study notebook that tries to be that little bit of encouragement :)&lt;/p&gt;

&lt;p&gt;It looks and feels like a school notebook, the grid paper kind we all had as kids. Here's what's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The whole syllabus as a bookshelf.&lt;/strong&gt; 13 subjects and 86 chapters, straight from the official syllabus. Click a book and it slides off the shelf and opens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notes for every chapter&lt;/strong&gt;, written like a friendly senior's notes: the idea in simple words, one worked example, common traps, and one line to remember. Key terms get pink, green and yellow highlighter, traps get orange.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A practice paper for every chapter.&lt;/strong&gt; Five exam-style questions. Get one right and you get a sticker: a gold star, a medal, a crown or a "Very good!". Get one wrong and you get a gentle cross and an explanation. Full marks gets you a trophy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flashcards&lt;/strong&gt; you tap to flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green ticks.&lt;/strong&gt; Read the notes, finish the paper, flip every card, and the chapter gets ticked off. Each book shows how far along you are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A study calendar.&lt;/strong&gt; Every day you study gets a tick, there's a streak, and hovering over a day shows what you did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask about anything.&lt;/strong&gt; If something's missing, type it in and Gemma writes fresh notes, a paper or flashcards right there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No login, no accounts.&lt;/strong&gt; Everything stays in your own browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stickers and ticks sound silly, I know. But when you study alone, nobody says "well done". Now at least a gold star does :)&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Opening a book&lt;/strong&gt;&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%2F8rfn375g5y3q7gldzcvw.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%2F8rfn375g5y3q7gldzcvw.gif" alt="A book sliding off the bookshelf and opening" width="760" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice paper with stickers&lt;/strong&gt;&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%2Fho0na4xde1zihdhphvez.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%2Fho0na4xde1zihdhphvez.gif" alt="Answering practice questions and getting tick marks and stickers" width="760" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flashcards, then the chapter gets its green tick&lt;/strong&gt;&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%2Fu6ggvmzuxb6w8uc57jwj.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%2Fu6ggvmzuxb6w8uc57jwj.gif" alt="Flipping flashcards until the chapter is marked covered with a green tick" width="760" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The study calendar&lt;/strong&gt;&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%2Fir5d5kbceuujh3h0u9ss.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%2Fir5d5kbceuujh3h0u9ss.gif" alt="Hovering a ticked day on the calendar shows what was studied" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get one wrong, correct it later&lt;/strong&gt;&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%2Fu4a1qcsjpgw4y3anldjx.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%2Fu4a1qcsjpgw4y3anldjx.gif" alt="A wrong answer lands in the mistakes notebook and gets crossed off once corrected" width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A report card, with remarks from Gemma&lt;/strong&gt;&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%2Fv7ulv4lwk3pzafx0cxgz.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%2Fv7ulv4lwk3pzafx0cxgz.gif" alt="The report card showing grades per subject and red-pen remarks written by Gemma" width="760" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it works offline&lt;/strong&gt;&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%2Faveqlahk9l5x95ti2n7p.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%2Faveqlahk9l5x95ti2n7p.gif" alt="With the internet off, the library still opens and a book can be read" width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've got two minutes: open &lt;strong&gt;Operating Systems&lt;/strong&gt;, pick &lt;strong&gt;Deadlock&lt;/strong&gt;, read the notes, try the practice paper (go collect a sticker), and flip through the flashcards. Then head back to the shelf and hover over today on the calendar. And scroll all the way down, there's a little classroom waiting at the bottom :)&lt;/p&gt;

&lt;p&gt;It runs on an always-on Render instance, so it should open right away :)&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/pyarchana/inkwand" rel="noopener noreferrer"&gt;https://github.com/pyarchana/inkwand&lt;/a&gt; (MIT licensed)&lt;/p&gt;

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

&lt;p&gt;Nothing fancy, and that's on purpose. I wanted it to load fast and stay light:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python and FastAPI&lt;/strong&gt; on the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain HTML, CSS and JavaScript&lt;/strong&gt; on the front. No framework, no build step. Every icon, sticker and drawing is inline SVG, so there are no image files to download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4&lt;/strong&gt;, Google's open-weight model, through the Gemini API. The main model is &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;, with &lt;code&gt;gemma-4-31b-it&lt;/code&gt; as a backup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; for hosting, set up from a &lt;code&gt;render.yaml&lt;/code&gt; blueprint.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The part that changed my whole plan
&lt;/h3&gt;

&lt;p&gt;My first version asked Gemma to write the notes whenever someone opened a page. It worked! It was also painfully slow. Gemma 4 thinks before it answers, and on the free tier that took anywhere from 45 to 90 seconds. Once it thought so hard that it used up its entire token budget and replied with... nothing.&lt;/p&gt;

&lt;p&gt;Nobody wants to wait 90 seconds for a page of notes. So I flipped it around. A small script asks Gemma to write the whole library up front: notes, a practice paper and flashcards for all 86 chapters, 258 pieces in total. It runs 8 requests at a time, retries when the free tier hiccups, and picks up where it left off if you stop it. Everything ends up as about 744 KB of JSON in the repo.&lt;/p&gt;

&lt;p&gt;Now chapters open instantly, cost nothing, and still work even if the API key goes missing. Gemma only gets called live for the "ask about anything" box and the "write me a fresh paper" button, and the page is upfront that those take about a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping Gemma honest
&lt;/h3&gt;

&lt;p&gt;Practice papers and flashcards have to come back as proper JSON: exactly four options, one correct answer, and an explanation. The server checks every reply with Pydantic. If something's off, it asks once more with a stricter prompt, and if that fails too, you get a friendly message instead of a broken page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free tier things
&lt;/h3&gt;

&lt;p&gt;While writing the library, Google's API threw 74 temporary server errors at me. Then on deploy day, the 26B model failed every single request while the 31B one was totally fine. So now if one Gemma model is having a bad moment, inkwand quietly asks the other one. Swapping models is one setting, which is a big part of why an open model works so well here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it feel like a notebook
&lt;/h3&gt;

&lt;p&gt;I wanted it to feel like sitting at your desk, not opening yet another app. So there's grid paper in the background, headings in pen, and highlighters in the notes. At the very bottom is an empty classroom: a blackboard with chalk in white, pink and yellow, rows of benches, a clock, and a window where the sun glows and clouds drift by. It works on phones too, and it respects reduced motion settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's free.&lt;/strong&gt; Good study material shouldn't be stuck behind a paywall. Gemma's free tier and Render's hackathon credits mean inkwand costs my brother, and anyone else who finds it, nothing at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The content is open too.&lt;/strong&gt; Coaching notes are usually locked away. inkwand's whole library is plain JSON in a public, MIT-licensed repo. Spot a wrong answer? Open a pull request and it's fixed for everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; When one model broke on deploy day, switching took one setting. If the hosted API ever changes, the same Gemma weights can run somewhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; The whole library is saved in your browser, so every chapter opens with no internet. And because Gemma's weights are public, the model itself can run fully offline too, with no API key at all. A college lab could host the whole thing for its students.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed model might have been faster. But free, forkable and fixable by anyone matters more to me here than a few seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handing It Over
&lt;/h2&gt;

&lt;p&gt;My brother is not a talker. When I sent him the link, I got no "thanks", no "nice", not even an emoji. Honestly, I saw that coming :)&lt;/p&gt;

&lt;p&gt;But everything inkwand remembers lives in his own browser: the stickers, the green ticks, and the calendar that marks every day you study. So a little later, I peeked over his shoulder.&lt;/p&gt;

&lt;p&gt;The calendar wasn't empty anymore. There were green ticks on it and stickers on his sheet, and hovering over the ticked days showed what he'd been studying.&lt;/p&gt;

&lt;p&gt;He still hasn't said a word about it. 👀&lt;/p&gt;

&lt;p&gt;In our house, that's a review.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 wrote all 258 pieces of the library, and it writes fresh notes, papers and flashcards on request, with a second Gemma model as a backup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; Render is inkwand's AI runtime. The FastAPI service on Render builds every prompt, calls Gemma, validates each reply, retries and falls back between two Gemma models, and serves the front end. It's deployed from a render.yaml blueprint with a health check, auto-deploys on every push, and runs on an always-on Starter instance so it never sleeps.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I made my agent prove every quote against the source document</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Wed, 23 Sep 2026 03:45:19 +0000</pubDate>
      <link>https://dev.to/chanadev/i-made-my-agent-prove-every-quote-against-the-source-document-1700</link>
      <guid>https://dev.to/chanadev/i-made-my-agent-prove-every-quote-against-the-source-document-1700</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My agent kept citing real documents for words those documents never contained. Not hallucinated quotes: real sentences, attributed to the wrong source. So it now proves every quoted phrase against the exact document it cites, straight from the dataset, and marks the ones that fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TSB Oracle answers car repair questions from the public record, and shows you where that record contradicts itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask why your car brakes on its own and you will find a service bulletin naming a cause, a revision of that bulletin a week later that quietly drops the sentence, a federal investigation covering more model years than the fix does, and owners saying the dealer called it normal. All of it is true at once. A search engine hands you whichever page ranks best, and a chatbot picks one and sounds certain.&lt;/p&gt;

&lt;p&gt;This agent retrieves all of it. Every sentence carries the NHTSA id it came from, quotes are checked against the document they are attributed to, and when two sources disagree about your exact car, the disagreement is put in front of you instead of resolved by guesswork.&lt;/p&gt;

&lt;p&gt;The dataset is one vehicle and one system, imported from NHTSA's public API: unexpected automatic emergency braking on the 2017 to 2022 Honda CR-V. The conflict at the centre of it is real. NHTSA's investigation EA24-002 covers 2017 to 2022. Honda's software update 26-091 stops at 2019 and excludes the LX trim. A 2021 owner is inside an open federal investigation with no published remedy, and nothing in the record says one is coming.&lt;/p&gt;

&lt;p&gt;What the agent does with that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cites every factual claim with the NHTSA id, linked to the public record&lt;/li&gt;
&lt;li&gt;flags in amber any citation it did not actually retrieve&lt;/li&gt;
&lt;li&gt;verifies quoted words against the dataset's own copy of the cited document&lt;/li&gt;
&lt;li&gt;shows contradictions side by side, with dates and a plain explanation of why they conflict&lt;/li&gt;
&lt;li&gt;proposes how a disagreement resolves, and waits for a person to approve it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj54zrxxg8cwvf1fjqm18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj54zrxxg8cwvf1fjqm18.png" alt="An answer with its citations and the contradiction it raised" width="800" height="944"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tsb-oracle.vercel.app" rel="noopener noreferrer"&gt;tsb-oracle.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The demo car is a 2021 Honda CR-V EX. The vehicle in the header is editable, so these all work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vehicle&lt;/th&gt;
&lt;th&gt;Ask&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2021 CR-V EX&lt;/td&gt;
&lt;td&gt;My CR-V brakes hard on its own with nothing ahead. The dealer says that's normal. Is there a fix?&lt;/td&gt;
&lt;td&gt;the coverage gap, already settled by review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2018 CR-V LX&lt;/td&gt;
&lt;td&gt;Does Service Bulletin 26-091 apply to my car?&lt;/td&gt;
&lt;td&gt;a trim exclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2018 CR-V EX&lt;/td&gt;
&lt;td&gt;Honda and NHTSA disagree about what causes the braking. Which applies to my car?&lt;/td&gt;
&lt;td&gt;an open contradiction, and a proposal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1994 Civic del Sol&lt;/td&gt;
&lt;td&gt;Are there any recalls for this car?&lt;/td&gt;
&lt;td&gt;no sources, and it says so&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While it works, it shows what it is reading rather than a spinner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fli3c4j1shu0qxhcknb6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fli3c4j1shu0qxhcknb6q.png" alt="The agent mid-lookup, listing the knowledge base entries it opened" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every question is a live model call against my own API balance, so if the demo stops answering, that is why.&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%2F3333qcyi964h3vh33ebm.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%2F3333qcyi964h3vh33ebm.gif" alt="The app answering a question about a 2021 CR-V" width="720" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pyarchana/tsb-oracle" rel="noopener noreferrer"&gt;github.com/pyarchana/tsb-oracle&lt;/a&gt;&lt;/strong&gt;, tagged &lt;code&gt;v1.0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The content model.&lt;/strong&gt; Four types do the work. &lt;code&gt;tsb&lt;/code&gt; is a source document, a bulletin, dealer message, investigation or owner complaint, with its model years, excluded trims and a link to the NHTSA record. &lt;code&gt;claim&lt;/code&gt; is one statement with the exact words it rests on. &lt;code&gt;contradiction&lt;/code&gt; joins two claims that disagree and explains why. &lt;code&gt;decision&lt;/code&gt; records how a contradiction was resolved, who resolved it, and whether a person has approved it yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I pointed Context at.&lt;/strong&gt; The Knowledge Base is built from the &lt;code&gt;tsb&lt;/code&gt; and &lt;code&gt;claim&lt;/code&gt; types, with the claim's &lt;code&gt;source&lt;/code&gt; reference unfolded so each claim carries its document's id. I deliberately left &lt;code&gt;contradiction&lt;/code&gt; out. If I had included it, the Knowledge Base would have read my conflicts back to me. Excluding it meant Context had to find disagreement in the sources on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tools.&lt;/strong&gt; The agent gets &lt;code&gt;initial_context&lt;/code&gt; and &lt;code&gt;knowledge_base_read&lt;/code&gt; over the Context MCP endpoint, plus two of my own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;check_applicability&lt;/code&gt; runs GROQ over the dataset and returns which documents cover this year, make, model and trim, which contradictions touch them, and whether any has been settled. It answers questions the Knowledge Base cannot: is this trim excluded, is this VIN in range, is this argument already over.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;record_decision&lt;/code&gt; writes a proposal, never a settled decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Context supplies the wording and GROQ supplies the structure. The Knowledge Base tells the agent what a bulletin says; the dataset tells it whether that bulletin applies to the car in front of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the Knowledge Base build caught.&lt;/strong&gt; Four issues, all real. NHTSA's own summary of bulletin A18-006 writes "OTC" where the codes are DTCs and "MIO" where the display is the MID. Two entries merged NHTSA's complaint counts with its per-vehicle totals: 31 crashes alleged in complaints against 47 across all reports, 50 injuries against 93. I resolved each in favour of what the source actually supports, and resolutions carry into every later build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it did not catch, and why I left that alone.&lt;/strong&gt; None of my curated claims conflicted. Every one of them says who made the statement it records, so "version 1 of the bulletin says X" does not flatly contradict "version 2 says Y". I could have written an unattributed claim to make the issues feature fire. Staging a conflict to demo conflict detection would have proved nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the knowledge base cannot back a quote&lt;/strong&gt; I assumed Knowledge Base entries could back up a quotation. They cannot, and should not. An entry is a synthesis: it merges several documents into one topic, paraphrases them, and attaches footnotes. That is right for retrieval and wrong for quoting, because a phrase can appear in an entry that no source ever used. So the app checks quoted words against the dataset's verbatim copy of the cited document instead. On a live run it caught the agent quoting version 1 of a bulletin and citing version 2, which is exactly the kind of mistake that sends someone to a dealer asking for the wrong repair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence.&lt;/strong&gt; When the agent proposes a resolution, it writes a &lt;code&gt;decision&lt;/code&gt; with status &lt;code&gt;proposed&lt;/code&gt;. A person opens it in the Studio and approves or rejects it. Approving flips the decision and its contradiction in one transaction, so the two can never disagree about whether a question is settled. From then on, the next person asking about that vehicle gets the settled answer and its rationale instead of the argument.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u4u0hen9qsnmrgi0pz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u4u0hen9qsnmrgi0pz7.png" alt="A proposal from the agent, approved in the Studio" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That review step exists because the demo is public. Without it, one conversation could settle a question for everyone who asks after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Project ID: &lt;code&gt;e72p6sym&lt;/code&gt;&lt;/strong&gt;, dataset &lt;code&gt;production&lt;/code&gt;, which is public.&lt;/p&gt;

&lt;p&gt;A live query against it, no token needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://e72p6sym.api.sanity.io/v2026-09-01/data/query/production?query=*[_type=="tsb"]{tsbNumber,title,sourceType,status,modelYears}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Studio is embedded at &lt;code&gt;/studio&lt;/code&gt; in the app, and the schemas are in &lt;a href="https://github.com/pyarchana/tsb-oracle/tree/main/sanity/schemaTypes" rel="noopener noreferrer"&gt;&lt;code&gt;sanity/schemaTypes&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Ten files is not a budget</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Fri, 11 Sep 2026 01:28:04 +0000</pubDate>
      <link>https://dev.to/chanadev/ten-files-is-not-a-budget-4ih5</link>
      <guid>https://dev.to/chanadev/ten-files-is-not-a-budget-4ih5</guid>
      <description>&lt;p&gt;I have an MCP server that digests a GitHub repository into markdown so a model can read it without cloning anything. Directory tree, plus the contents of the files that matter.&lt;/p&gt;

&lt;p&gt;I pointed it at a real repository and got this back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: result (109,668 characters) exceeds maximum allowed tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not a truncated answer. Not a partial digest with a warning. Nothing. The tool call failed and the caller was left with an error message where a repository summary should have been.&lt;/p&gt;

&lt;p&gt;The cause was a constant I had written months earlier without thinking about it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_FILE_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32_000&lt;/span&gt;
&lt;span class="n"&gt;TOP_N_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ten files, each capped at 32,000 characters. I had reasoned that ten files is a reasonable amount of a repository to show. Which is true, and also not a size limit at all. Ten files is anywhere from a few hundred characters to 320,000 depending on whose repository you point it at.&lt;/p&gt;
&lt;h2&gt;
  
  
  The obvious fix, and why I did not just do it
&lt;/h2&gt;

&lt;p&gt;The obvious fix is to add a total character cap. Pick a number, fill it, stop.&lt;/p&gt;

&lt;p&gt;The problem is picking the number. I could have reasoned my way to one. 50,000 sounds fine. 32,000 sounds fine too. So does 64,000. Any of them sounds fine, which is a good sign that reasoning is not the tool for the job, and is exactly how &lt;code&gt;TOP_N_FILES = 10&lt;/code&gt; got there in the first place.&lt;/p&gt;

&lt;p&gt;So before changing anything I measured. Six repositories, spanning the range I would realistically encounter: two small ones of my own, a mid-sized MCP server, a docs-heavy list repo, and two large real codebases.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;repository&lt;/th&gt;
&lt;th&gt;files&lt;/th&gt;
&lt;th&gt;tree&lt;/th&gt;
&lt;th&gt;file content&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GopherMCP/GopherCache&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;4,505&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,594&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pyarchana/gopher&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;591&lt;/td&gt;
&lt;td&gt;23,640&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24,231&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sktime/sktime-mcp&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;3,296&lt;/td&gt;
&lt;td&gt;100,490&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;103,786&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;astral-sh/uv&lt;/td&gt;
&lt;td&gt;1,578&lt;/td&gt;
&lt;td&gt;65,530&lt;/td&gt;
&lt;td&gt;142,337&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;207,867&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;modelcontextprotocol/python-sdk&lt;/td&gt;
&lt;td&gt;1,643&lt;/td&gt;
&lt;td&gt;57,306&lt;/td&gt;
&lt;td&gt;160,182&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;217,488&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;punkpeye/awesome-mcp-servers&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;173&lt;/td&gt;
&lt;td&gt;228,137&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;228,310&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fifty-fold range between the smallest and largest output, from the same tool with the same settings.&lt;/p&gt;

&lt;p&gt;Three things fell out of that table that I would not have guessed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tree alone can eat everything
&lt;/h2&gt;

&lt;p&gt;Look at the tree column for uv and the python SDK. 65,530 and 57,306 characters, just to list filenames.&lt;/p&gt;

&lt;p&gt;I had been thinking of the budget as a cap on file contents. But on a repository with 1,600 files, the directory listing on its own is larger than any sensible total. If I had capped only the file content, uv would have blown past any budget I set before fetching a single file.&lt;/p&gt;

&lt;p&gt;The tree needed its own cap, at a fraction of the total, and a note saying how many entries it left out.&lt;/p&gt;
&lt;h2&gt;
  
  
  The ranking was picking the wrong files
&lt;/h2&gt;

&lt;p&gt;This is the one that actually mattered, and I only saw it because I printed what was being selected rather than just how big it was.&lt;/p&gt;

&lt;p&gt;Here is what my tool chose to show about &lt;strong&gt;uv&lt;/strong&gt;, a Rust build tool:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;27,194  test/ecosystem/airflow/pyproject.toml
32,048  test/ecosystem/home-assistant-core/pyproject.toml
27,517  test/ecosystem/pandas/pyproject.toml
 9,156  test/ecosystem/jupyterlab/pyproject.toml
 8,757  test/ecosystem/black/pyproject.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Eighty-nine thousand characters of other projects' dependency lists, scraped out of uv's test fixtures. The only uv code that made it in was three &lt;code&gt;main.rs&lt;/code&gt; files, all of them thin entry points, and &lt;code&gt;Cargo.toml&lt;/code&gt; ranked below every one of those fixtures.&lt;/p&gt;

&lt;p&gt;And here is &lt;strong&gt;awesome-mcp-servers&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;32,048  README-fa-ir.md
32,048  README-ja.md
32,048  README-ko.md
32,048  README-pt_BR.md
32,048  README-th.md
32,048  README-zh.md
32,048  README-zh_TW.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The same README in seven languages, 224,000 characters of it.&lt;/p&gt;

&lt;p&gt;The cause was one line:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PRIORITY_NAMES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Any file called &lt;code&gt;pyproject.toml&lt;/code&gt; got a thousand points, wherever it sat. A vendored copy six directories deep in a test fixture scored exactly the same as the one at the root describing the actual project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is why the measurement mattered.&lt;/strong&gt; If I had shipped the budget on its own, the digest would have gone from 207,867 characters of the wrong files to 40,000 characters of the wrong files. Smaller, still useless, and now looking deliberate.&lt;/p&gt;
&lt;h2&gt;
  
  
  The file it wanted most came back empty
&lt;/h2&gt;

&lt;p&gt;One more, which I would never have found by reading code.&lt;/p&gt;

&lt;p&gt;In the awesome-mcp-servers run, the top-ranked file scored 1200 and returned &lt;strong&gt;zero characters&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     0  score=1200   README.md
32,048  score=200    README-fa-ir.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GitHub's contents API will not serve a file over 1MB. It does not return an error. It returns &lt;code&gt;200 OK&lt;/code&gt; with an empty &lt;code&gt;content&lt;/code&gt; field and &lt;code&gt;encoding: "none"&lt;/code&gt;. So the base64 decode succeeds, produces an empty string, and the file silently disappears.&lt;/p&gt;

&lt;p&gt;That README is 1,616,144 bytes. The single most important file in the repository was being dropped without a word, and seven translations of it were filling the space instead. The fix is to notice &lt;code&gt;encoding: "none"&lt;/code&gt; and refetch through the blobs endpoint, which serves up to 100MB.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A total budget, not a file count.&lt;/strong&gt; 40,000 characters, configurable. Spent on the tree first, then files in priority order, until it runs out. The number of files now falls out of what fits instead of being pinned in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cap on the tree&lt;/strong&gt;, a quarter of the total, with a line saying how many entries it omitted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A per-file ceiling&lt;/strong&gt; of 40% of what remains, so one large file cannot crowd out everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ranking by position, not just name.&lt;/strong&gt; The priority bonus now decays with directory depth, so the root manifest beats a vendored one. Test and fixture directories lose points, vendored ones lose more, examples lose only a little because sometimes an examples directory is the best documentation a project has. Files in the repository's primary language, which the API already tells you, gain some.&lt;/p&gt;

&lt;p&gt;Here is the same table after:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;repository&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GopherMCP/GopherCache&lt;/td&gt;
&lt;td&gt;4,594&lt;/td&gt;
&lt;td&gt;5,005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pyarchana/gopher&lt;/td&gt;
&lt;td&gt;24,231&lt;/td&gt;
&lt;td&gt;39,263&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sktime/sktime-mcp&lt;/td&gt;
&lt;td&gt;103,786&lt;/td&gt;
&lt;td&gt;39,231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;astral-sh/uv&lt;/td&gt;
&lt;td&gt;207,867&lt;/td&gt;
&lt;td&gt;39,321&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;modelcontextprotocol/python-sdk&lt;/td&gt;
&lt;td&gt;217,488&lt;/td&gt;
&lt;td&gt;39,224&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;punkpeye/awesome-mcp-servers&lt;/td&gt;
&lt;td&gt;228,310&lt;/td&gt;
&lt;td&gt;39,168&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;uv's digest now leads with &lt;code&gt;Cargo.toml&lt;/code&gt; and &lt;code&gt;README.md&lt;/code&gt;. awesome-mcp-servers leads with its actual README. My own small repo got &lt;em&gt;bigger&lt;/em&gt;, because it was only ever showing 10 of its 22 files and now shows all of them.&lt;/p&gt;
&lt;h2&gt;
  
  
  The thing I would tell past me
&lt;/h2&gt;

&lt;p&gt;I nearly shipped a one-line change. Add a constant, clamp the output, close the issue. It would have passed review, passed tests, and produced a tool that was confidently wrong in a smaller font.&lt;/p&gt;

&lt;p&gt;What stopped it was spending an hour printing what the tool actually produced against real inputs. Not unit tests, which only check what I already thought to assert. Not reading the code, which is where the bug had been sitting unnoticed for months. Just running it against six real repositories and looking at the output.&lt;/p&gt;

&lt;p&gt;If you are building anything that assembles context for a model, you are making size and selection decisions whether you notice them or not. Print what your tool actually sends, against real inputs, at real sizes. The numbers are frequently not what you expect, and the interesting failure is rarely the one you set out to fix.&lt;/p&gt;



&lt;p&gt;The tool is &lt;a href="https://github.com/pyarchana/gopher" rel="noopener noreferrer"&gt;pyarchana/gopher&lt;/a&gt;, an MCP server that fetches, caches and digests context for Claude. The measurements above live in &lt;a href="https://github.com/pyarchana/gopher/issues/5" rel="noopener noreferrer"&gt;#5&lt;/a&gt; and &lt;a href="https://github.com/pyarchana/gopher/issues/14" rel="noopener noreferrer"&gt;#14&lt;/a&gt; if you want the full before and after.&lt;/p&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/pyarchana" rel="noopener noreferrer"&gt;
        pyarchana
      &lt;/a&gt; / &lt;a href="https://github.com/pyarchana/gopher" rel="noopener noreferrer"&gt;
        gopher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      One MCP server for Claude: digest any GitHub repo, keep memory across conversations, and extract facts locally with Ollama
    &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;Gopher&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/pyarchana/gopher/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/pyarchana/gopher/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One MCP server that fetches, caches, and digests context for Claude.&lt;/p&gt;

&lt;p&gt;Gopher is three things that used to be three separate servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fetch&lt;/strong&gt;: point it at a GitHub repo and get back a clean Markdown digest: full directory tree, plus the contents of the files that actually matter. Filters out binaries, lock files, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;venv&lt;/code&gt;, and the rest of the noise, then ranks what's left. No README? It builds one for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cache&lt;/strong&gt;: persistent memory across conversations, stored as two plain files you can read yourself: a structured &lt;code&gt;context.json&lt;/code&gt; and an append-only &lt;code&gt;diary.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;digest&lt;/strong&gt;: reads a conversation transcript, extracts the facts with a local Ollama model, and merges them into the cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs locally over stdio. Nothing leaves your machine except GitHub API calls.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tools&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Tool&lt;/th&gt;

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

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;fetch_github_repo(repo_url)&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;Markdown digest of a public repo, tree plus top files&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;read_context()&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;Return&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pyarchana/gopher" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>mcp</category>
      <category>ai</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Five bugs in my LLM app that never threw an error</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Sun, 30 Aug 2026 02:31:58 +0000</pubDate>
      <link>https://dev.to/chanadev/five-bugs-in-my-llm-app-that-never-threw-an-error-1ejk</link>
      <guid>https://dev.to/chanadev/five-bugs-in-my-llm-app-that-never-threw-an-error-1ejk</guid>
      <description>&lt;p&gt;I spent two and a half weeks building an agent that reads a project's code review history and remembers what it decided. 86,321 comments from pandas, distilled into 298 conventions, served out of CockroachDB behind a Lambda.&lt;/p&gt;

&lt;p&gt;Every serious bug I hit had the same shape. Nothing raised. Tests passed. The demo worked. And somewhere in the middle, a component was doing absolutely nothing.&lt;/p&gt;

&lt;p&gt;Here are five, with the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;bool("false")&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The whole point of the system is that it refuses when it does not know. The answering prompt returns JSON with an &lt;code&gt;answered&lt;/code&gt; field.&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answered&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model that writes &lt;code&gt;"answered": "false"&lt;/code&gt; instead of &lt;code&gt;"answered": false&lt;/code&gt; turns a refusal into an answer, because a non-empty string is truthy. The one behaviour the project exists to guarantee, defeated by a quotation mark.&lt;/p&gt;

&lt;p&gt;Now every model response goes through a Pydantic model that fails towards doing nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnswerOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelOutput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;answered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;   &lt;span class="c1"&gt;# coerces "false" -&amp;gt; False
&lt;/span&gt;    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An answer becomes a refusal. A drafted rule becomes unusable. A contradiction verdict becomes "compatible", which retires nothing. A garbled response is not evidence for writing to memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A &lt;code&gt;null&lt;/code&gt; discarded whole responses, six times out of six
&lt;/h2&gt;

&lt;p&gt;The fix for bug 1 caused bug 2, which is funnier than it was at the time.&lt;/p&gt;

&lt;p&gt;The prompt that turns a maintainer's comment into a rule says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"rationale": "why, one sentence, only if they gave a reason"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A maintainer who states a convention flatly gets &lt;code&gt;null&lt;/code&gt; back, which is correct JSON for "no reason given". And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DraftedRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;   &lt;span class="c1"&gt;# null is not a str
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pydantic rejects &lt;code&gt;null&lt;/code&gt; for a plain &lt;code&gt;str&lt;/code&gt;. Validation is all or nothing. So the entire response was thrown away and replaced with the inert default, which downstream reads as the model having declined.&lt;/p&gt;

&lt;p&gt;A real maintainer correction became &lt;code&gt;{"status":"ignored","reason":"no convention stated"}&lt;/code&gt;. Six reproductions, six failures, on a payload that was otherwise exactly what I asked for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@field_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_null_text_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&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;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;null&lt;/code&gt; where a string was expected means empty, not malformed. Note the &lt;code&gt;field.annotation is str&lt;/code&gt; check: &lt;code&gt;scope_pattern: str | None&lt;/code&gt; genuinely means None, and coercing that one would turn "applies everywhere" into a pattern matching nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 68 of my 79 path rules matched zero files
&lt;/h2&gt;

&lt;p&gt;Each rule stores a pattern for which files it applies to. I matched with &lt;code&gt;fnmatch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;fnmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pandas/core/frame.py&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;pandas/core/%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The patterns were &lt;strong&gt;SQL LIKE&lt;/strong&gt;, not globs. &lt;code&gt;pandas/tests/%&lt;/code&gt;, &lt;code&gt;pandas/core/%&lt;/code&gt;, &lt;code&gt;pandas/tests/%/conftest.py&lt;/code&gt;. Nothing asked the extraction prompt for that and nothing documented it. The model just wrote it, presumably because the rules were destined for a database.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;%&lt;/code&gt; is not an fnmatch wildcard. So 68 of 79 anchored rules silently matched nothing, and the agent ran entirely on the two rules that happened to be written &lt;code&gt;pandas/**/*.py&lt;/code&gt;, which cover 63% of the repository.&lt;/p&gt;

&lt;p&gt;It looked like it was working the entire time. The fix is one line, and the bug was invisible for a week:&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="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The similarity score I was tuning had no signal in it
&lt;/h2&gt;

&lt;p&gt;The agent comments on pull requests, choosing which conventions apply by embedding a description of the change and taking the nearest rules.&lt;/p&gt;

&lt;p&gt;It commented on 38 of 40. I assumed a bad threshold and started tuning. At 1.05 it spoke on 95% of pull requests; at 0.95, on 18%.&lt;/p&gt;

&lt;p&gt;Then I measured the distribution instead. For 25 real pull requests, distance to the nearest rule versus the tenth nearest:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;query phrasing&lt;/th&gt;
&lt;th&gt;median nearest&lt;/th&gt;
&lt;th&gt;median spread, 1st to 10th&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;title only&lt;/td&gt;
&lt;td&gt;0.954&lt;/td&gt;
&lt;td&gt;0.097&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title plus directories&lt;/td&gt;
&lt;td&gt;0.947&lt;/td&gt;
&lt;td&gt;0.100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;paths and counts&lt;/td&gt;
&lt;td&gt;0.958&lt;/td&gt;
&lt;td&gt;0.105&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;written as a question&lt;/td&gt;
&lt;td&gt;0.933&lt;/td&gt;
&lt;td&gt;0.095&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The nearest rule is not meaningfully nearer than the tenth, under any phrasing. Every rule is a general statement about the same codebase, so every rule is similar to any description of a change to it.&lt;/p&gt;

&lt;p&gt;There was nothing to threshold. Retrieval now runs on path matching, which is a fact, and similarity only orders what the paths already admitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. GitHub's &lt;code&gt;author_association&lt;/code&gt; describes the present
&lt;/h2&gt;

&lt;p&gt;I filtered to maintainer comments using GitHub's &lt;code&gt;author_association&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;It reports whether someone has write access &lt;strong&gt;now&lt;/strong&gt;, not when they wrote the comment. One reviewer wrote 74,077 comments on pandas between 2012 and 2025, more than twice anyone else. He has since left the org, so all 74,077 come back as &lt;code&gt;CONTRIBUTOR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Filtering on that field discarded about a third of the corpus, including the most experienced reviewers the project ever had. No error, no warning, just a smaller number that looked plausible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;None of these raised. Four of the five I found by counting something rather than by reading code: how often does it speak, how many rules match anything, how far apart are the distances really.&lt;/p&gt;

&lt;p&gt;A library used wrong fails loudly. A model used wrong returns confident, well-formatted output that is quietly detached from what you meant.&lt;/p&gt;

&lt;p&gt;So pick a number that should hold if the system works, and go and look at it. Not "does it return results" but "how often does it speak, and is that the rate I intended". Not "did it cite something" but "do the citations resolve".&lt;/p&gt;

&lt;p&gt;Code is at &lt;a href="https://github.com/pyarchana/precedent" rel="noopener noreferrer"&gt;github.com/pyarchana/precedent&lt;/a&gt;, MIT.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>debugging</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Google's Workspace Intelligence Might Actually Fix the Thing That Bugs Me Most About AI Tools</title>
      <dc:creator>Archana</dc:creator>
      <pubDate>Thu, 23 Apr 2026 03:00:57 +0000</pubDate>
      <link>https://dev.to/chanadev/googles-workspace-intelligence-might-actually-fix-the-thing-that-bugs-me-most-about-ai-tools-10n7</link>
      <guid>https://dev.to/chanadev/googles-workspace-intelligence-might-actually-fix-the-thing-that-bugs-me-most-about-ai-tools-10n7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-cloud-next-2026-04-22"&gt;Google Cloud NEXT Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Okay so I've been watching the Google Cloud NEXT '26 announcements drop today and there's a lot. TPUs, agentic platforms, a $750M partner fund. But the thing that actually made me stop scrolling was &lt;strong&gt;Workspace Intelligence&lt;/strong&gt; :)&lt;/p&gt;

&lt;p&gt;Not because it's the most technically impressive thing on the list. It's not. But it might be the one that changes how I (and probably you) actually work day to day.&lt;/p&gt;

&lt;p&gt;Let me explain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing that's always bothered me about AI in Google Docs
&lt;/h2&gt;

&lt;p&gt;Every time I open Docs and try to use Gemini to help me write something, I spend the first few minutes basically re-introducing myself. Here's the project. Here's the tone we're going for. Here's who the audience is. Here's the doc we drafted last month that this is building on.&lt;/p&gt;

&lt;p&gt;It's exhausting :) The AI is smart but it has no idea who I am or what I'm working on. Every prompt is a cold start.&lt;/p&gt;

&lt;p&gt;That's exactly what Workspace Intelligence is supposed to fix.&lt;/p&gt;




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

&lt;p&gt;Google is calling it a "semantic layer" across all your Workspace apps. Gmail, Docs, Sheets, Slides, Drive, Chat, Calendar. The idea is that instead of each app being its own isolated thing, there's now a shared understanding of your work running underneath all of them :)&lt;/p&gt;

&lt;p&gt;It learns what projects you're actively working on, who you're collaborating with, what your writing sounds like, and what's actually on your plate right now. Then every time you use Gemini anywhere in Workspace, it's pulling from that context instead of starting fresh.&lt;/p&gt;

&lt;p&gt;Practically speaking, here's what that looks like across the apps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gmail&lt;/strong&gt; gets an AI Inbox that doesn't just summarize. It suggests what you should actually &lt;em&gt;do&lt;/em&gt;, and drafts replies that sound like you, not like a generic assistant :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Chat&lt;/strong&gt; gets an "Ask Gemini" mode that Google is framing as a "unified command line for your work." You say what you need, it goes off and does it across Workspace. Schedules the meeting, pulls the file, writes the brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs&lt;/strong&gt; can now edit multiple images at once for visual consistency, build infographics from your own business data, and handle comment threads. Including making edits to the doc based on the feedback in those comments :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slides&lt;/strong&gt; can build a full deck from a single prompt, and it'll actually use your company's real templates and visual styles. Not a generic blue gradient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sheets&lt;/strong&gt; lets you build and edit spreadsheets by just... talking to it. And it can pull data from your emails, files, and chat history to populate things :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drive&lt;/strong&gt; is getting "Projects," a smarter way to organize everything around active work rather than just being a folder system that you dig through.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bit I find most interesting
&lt;/h2&gt;

&lt;p&gt;There's a line in Google's announcement that stood out to me: Workspace Intelligence learns your "unique work style, voice, and formatting preferences" so outputs sound "authentically like you."&lt;/p&gt;

&lt;p&gt;I'm genuinely curious how well that works in practice :) Voice matching is one of those things that sounds great in a keynote and then either impresses you or makes you cringe when you see the actual output. I haven't been able to test it yet, but it's the thing I'm most excited (and slightly skeptical) about.&lt;/p&gt;




&lt;h2&gt;
  
  
  What about the "let AI read all my stuff" concern?
&lt;/h2&gt;

&lt;p&gt;Yeah, this is the obvious question. Google is essentially asking you to let a system reason across your email, your calendar, your chat history, and your files. That's a lot of trust.&lt;/p&gt;

&lt;p&gt;Their answer: customer data isn't used for ads, isn't reviewed by humans, and isn't used to train models outside Workspace without your permission :) Admins can control which data sources it touches, so if your org doesn't want it reading Gmail, they can turn that off. Client-side encryption and sovereign data controls (US and EU, with more coming) are part of the rollout too.&lt;/p&gt;

&lt;p&gt;Whether that's enough will depend on your org's risk appetite. But at least they're not burying it in the fine print.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I land on this
&lt;/h2&gt;

&lt;p&gt;I think the individual features are nice, but the real bet here is the compounding effect :) The more Workspace Intelligence understands your context, the more useful every individual tool becomes. That's a different kind of value than "we added a summarize button."&lt;/p&gt;

&lt;p&gt;Google's basically trying to turn Workspace from a collection of apps you switch between into something that actually understands what you're trying to get done. Whether that plays out the way they're describing it, or whether it turns into a very well-marketed context window, is something we'll only know once people start using it for real.&lt;/p&gt;

&lt;p&gt;But I'm paying attention. And honestly? I think this one's worth your time to follow :)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Are you using Google Workspace day to day? Curious whether this kind of persistent context layer actually sounds useful to you, or if it feels like AI for AI's sake. Let me know below :)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>cloudnextchallenge</category>
      <category>googlecloud</category>
    </item>
  </channel>
</rss>
