<?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: Abdul Wasih</title>
    <description>The latest articles on DEV Community by Abdul Wasih (@iamwasih).</description>
    <link>https://dev.to/iamwasih</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%2F4159774%2Fab9c702a-be12-4352-92a4-344eb1ca60b7.png</url>
      <title>DEV Community: Abdul Wasih</title>
      <link>https://dev.to/iamwasih</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamwasih"/>
    <language>en</language>
    <item>
      <title>I let a local Gemma babysit a PR queue overnight. It fast-tracked 31 of 71 with replies, and I woke up to just the other 40.</title>
      <dc:creator>Abdul Wasih</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:37:42 +0000</pubDate>
      <link>https://dev.to/iamwasih/i-let-a-local-gemma-babysit-a-pr-queue-overnight-it-fast-tracked-31-of-71-with-replies-and-i-woke-3oc</link>
      <guid>https://dev.to/iamwasih/i-let-a-local-gemma-babysit-a-pr-queue-overnight-it-fast-tracked-31-of-71-with-replies-and-i-woke-3oc</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"As the ecosystem grew, maintainers started to face a massive flood of activity and burnout from the rise of low-effort PRs. […] In an era where AI tools make low-effort PRs easier than ever to generate, maintainers face unprecedented volume and noise."&lt;br&gt;
(&lt;a href="https://hacktoberfest.com/llms-full.txt" rel="noopener noreferrer"&gt;hacktoberfest.com&lt;/a&gt;, on why pull requests no longer count toward rewards this year)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI made pull requests almost free to write. Reading them didn't get any cheaper. The pile grows faster than the handful of volunteers who have to read it.&lt;/p&gt;

&lt;p&gt;So I flipped the question. Could a small model on the maintainer's own laptop help them &lt;em&gt;read&lt;/em&gt; the pile, without ever speaking for them?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nightshift&lt;/strong&gt; reads the pile overnight. While you sleep, a local Gemma model reads every new PR and issue in your repos and sorts each one: duplicate, spam or slop, out of scope, needs tests or info, trivial and safe, or needs a human. Where a reply makes sense, it drafts one. In the morning you get one page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It never acts alone.&lt;/strong&gt; It doesn't post, label, close or merge anything by itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partner repos are read-only in code.&lt;/strong&gt; The collector's GitHub client refuses anything but reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only one repo can ever be written to,&lt;/strong&gt; my own, and that allowlist is a constant in the source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A reply reaches GitHub only after I approve the exact text,&lt;/strong&gt; once per item, enforced by the database.&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%2Fof2wm6xel9s6167cwker.jpg" 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%2Fof2wm6xel9s6167cwker.jpg" alt="Nightshift's morning report for night 7: 40 items need you, 25 drafted replies wait for one click, 6 are trivial and safe" width="799" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't test it on fixtures. I pointed it, read-only, at three of this challenge's partners' own repos: &lt;code&gt;temporalio/temporal&lt;/code&gt;, &lt;code&gt;timescale/timescaledb&lt;/code&gt; and &lt;code&gt;mastra-ai/mastra&lt;/code&gt;. Over two scheduled nights and three manual runs it made &lt;strong&gt;112 triage decisions on 111 real items&lt;/strong&gt; (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/STATS.md" rel="noopener noreferrer"&gt;STATS.md&lt;/a&gt;, generated from the database). That's a small sample, so the bad news comes first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I labelled night 7's 71 items by hand, and the model wasn't good enough to trust on its own:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even behind my rules, it was right on &lt;strong&gt;46 of 71 (65%)&lt;/strong&gt;. A triager that sends &lt;em&gt;everything&lt;/em&gt; to the maintainer would have scored &lt;strong&gt;65 of 71 (91.5%)&lt;/strong&gt; (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/eval/2026-10-25-live-nights7.md" rel="noopener noreferrer"&gt;eval report&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All 13 of its "duplicate" calls were wrong&lt;/strong&gt;, and every one was related but not the same.&lt;/li&gt;
&lt;li&gt;It called a 6,246-line refactor "trivial and safe"; only my 40-line rule stopped it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it reached GitHub, because nothing does until I approve the exact text. Night 7 is why that rule exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a maintainer already has
&lt;/h3&gt;

&lt;p&gt;The strongest alternative is &lt;a href="https://github.github.com/gh-aw/blog/2026-01-13-meet-the-workflows/" rel="noopener noreferrer"&gt;GitHub Agentic Workflows&lt;/a&gt;. An AI engine runs in Actions on every new issue, gets no write permission, and separate &lt;a href="https://github.github.com/gh-aw/reference/safe-outputs/" rel="noopener noreferrer"&gt;safe-outputs&lt;/a&gt; jobs apply what it asks for. Nightshift uses the same idea: the part that thinks can't write.&lt;/p&gt;

&lt;p&gt;gh-aw wins if you already live in Actions: nothing to install, it acts in the repo, and GitHub maintains it. Nightshift is different in four ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One private page a morning&lt;/strong&gt;, instead of a public bot comment on every issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An open model on your own machine:&lt;/strong&gt; no model vendor sees a prompt or a diff, and the model calls cost $0.00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate memory&lt;/strong&gt; across up to 90 days of each repo, with a rule that a duplicate must cite a real earlier item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measured accuracy&lt;/strong&gt; against my own labels. A confidence threshold wouldn't have saved night 7: the wrong duplicate calls came in at up to 0.98 confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule bots like &lt;code&gt;actions/stale&lt;/code&gt; stay the baseline for anything a rule does better. The model only gets what rules can't do: slop, near-duplicates and missing information.&lt;/p&gt;

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

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

&lt;p&gt;A 1:52 walkthrough: the morning report, one card from evidence to approval, the honest result, and the Sentry trace. The footage is real screen capture; the voiceover is synthetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live dashboard: &lt;a href="https://nightshift-dashboard.onrender.com" rel="noopener noreferrer"&gt;https://nightshift-dashboard.onrender.com&lt;/a&gt;&lt;/strong&gt; (Render's free tier, so the first load after a quiet spell can take about a minute). The public page shows live numbers from the database. The review page sits behind Auth0, limited to me, because it can post.&lt;/p&gt;

&lt;p&gt;Each card on the review page leads with one next step: Review, Ask for info, Close as duplicate of #N, or Quick merge check. Under it are chips built only from GitHub facts, like diff size, failing CI, missing tests, or the duplicate it cites. The model's own reasoning is folded away. Two buttons, &lt;strong&gt;Right&lt;/strong&gt; and &lt;strong&gt;Wrong&lt;/strong&gt;, are how I labelled all 71 items.&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%2F2hx1v9soryg5ytmn0yau.jpg" 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%2F2hx1v9soryg5ytmn0yau.jpg" alt="The review page for night 7: each card leads with a next step and evidence chips, with one-tap Right / Wrong labelling; 71 of 71 labelled" width="799" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two replies have gone out this way, both on my own repo: &lt;a href="https://github.com/AbdulWasih05/nightshift/issues/5#issuecomment-6373303034" rel="noopener noreferrer"&gt;#5&lt;/a&gt;, approved as drafted, and &lt;a href="https://github.com/AbdulWasih05/nightshift/issues/6#issuecomment-6373389268" rel="noopener noreferrer"&gt;#6&lt;/a&gt;, which I edited first because the model claimed #5 was already fixed. It wasn't. Both show "via nightshiftdemo", the GitHub App behind Token Vault.&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/AbdulWasih05" rel="noopener noreferrer"&gt;
        AbdulWasih05
      &lt;/a&gt; / &lt;a href="https://github.com/AbdulWasih05/nightshift" rel="noopener noreferrer"&gt;
        nightshift
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The maintainer's night shift: a local Gemma agent that triages PRs and issues overnight and never acts without approval.
    &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;Nightshift&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;The maintainer's night shift. A local agent reads your PR and issue pile overnight, buckets each item (duplicate, spam/slop, out of scope, needs tests or info, trivial and safe, needs a human), drafts a reply, and leaves you one morning digest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It never posts, labels, closes or merges anything without your explicit approval.&lt;/strong&gt; Partner repos are read-only by construction: the write allowlist is a frozen source constant (&lt;code&gt;packages/core/src/allowlist.ts&lt;/code&gt;), and the collector's GitHub client rejects every non-GET request.&lt;/p&gt;
&lt;p&gt;Runs on a laptop with Gemma 3 via Ollama. $0.00 per night. Contributor data stays on your machine.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 3 ("Slow and Steady"). Challenge window: Oct 19 to Oct 25, 2026 (PDT). Repo created 2026-10-21. Any commits after publishing are listed under "Post-deadline changes" below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Work in progress. See &lt;code&gt;docs/LOOP.md&lt;/code&gt; for the build log and &lt;code&gt;docs/DECISIONS.md&lt;/code&gt; for every cut and change.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AbdulWasih05/nightshift" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A TypeScript monorepo: &lt;code&gt;packages/core&lt;/code&gt; (collector, triage, duplicate search, digest, eval, approval gate), &lt;code&gt;packages/workflows&lt;/code&gt; (Temporal), &lt;code&gt;apps/cli&lt;/code&gt;, &lt;code&gt;apps/dashboard&lt;/code&gt; (Next.js) and &lt;code&gt;sidecars/tabpfn&lt;/code&gt; (Python).&lt;/p&gt;

&lt;p&gt;Every number in this post comes from a file a script writes from the database, never by hand: &lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/STATS.md" rel="noopener noreferrer"&gt;STATS.md&lt;/a&gt;, &lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/NIGHTS.md" rel="noopener noreferrer"&gt;NIGHTS.md&lt;/a&gt;, the &lt;a href="https://github.com/AbdulWasih05/nightshift/tree/feat/dashboard/docs/digests" rel="noopener noreferrer"&gt;digests&lt;/a&gt; and &lt;a href="https://github.com/AbdulWasih05/nightshift/tree/feat/dashboard/docs/eval" rel="noopener noreferrer"&gt;docs/eval&lt;/a&gt;. Every cut, failure and reversal is dated in &lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/DECISIONS.md" rel="noopener noreferrer"&gt;DECISIONS.md&lt;/a&gt;.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:00  Temporal schedule fires nightlyTriage
 ├─ collect       read-only GitHub client, 200 items/repo cap
 ├─ claimBacklog  whatever an earlier night didn't finish
 ├─ embed         nomic-embed-text → pgvector (Tiger Data)
 ├─ triageChunk×N Gemma 3 4B → 1B fallback → strict parser + rules
 └─ finally       digest → finish the night → unload the models
morning  /review: read, label, approve, Post (Auth0 Token Vault)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built it in small units, each with an acceptance check written before any code, run against the real repos, then read by a reviewer agent that had never seen the code. That reviewer found a blocker in most units, and many of the stories below started there (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/LOOP.md" rel="noopener noreferrer"&gt;LOOP.md&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemma: a small model behind a strict contract
&lt;/h3&gt;

&lt;p&gt;Gemma 3 4B returns one JSON object per item: bucket, confidence, reason, evidence and an optional draft. &lt;strong&gt;My parser is the source of truth, not the model.&lt;/strong&gt; Anything unparseable becomes "needs a human" and is never dropped; across 112 decisions there were zero parse failures. The 1B model steps in only when 4B errors or times out, so the smaller model never quietly overrules the bigger one. 4B answered 109 items, 1B answered 3.&lt;/p&gt;

&lt;p&gt;Early on it called a 414-line concurrency fix "trivial and safe" at 0.95 confidence. I didn't fix that with a better prompt. I fixed it with rules in the parser: "trivial" means under 40 changed lines, a duplicate must cite a candidate it was actually shown, and spam needs two tells. Those rules changed &lt;strong&gt;38 of 112 answers&lt;/strong&gt;, almost all of them "trivial" calls on big PRs, the largest at 6,246 lines.&lt;/p&gt;

&lt;p&gt;Why 4B and nothing bigger? The laptop has a 4 GB MX130, and even 4B only fits 61% of its layers on the GPU. I capped both models at 3 threads so the laptop stays usable overnight. The cost is speed: a median of 92 seconds per item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporal: the night has to survive the laptop
&lt;/h3&gt;

&lt;p&gt;This runs on a 4-core laptop that reboots, sleeps and gets borrowed for other work. Nobody staged these failures; they happened during real nights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The laptop rebooted 40 minutes into night 5.&lt;/strong&gt; Everything was down until the next day. When I brought the stack back, the night resumed from Temporal's saved state: nothing was lost and nothing was triaged twice, because each chunk of items is an activity that records its progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One item took 1,956 seconds.&lt;/strong&gt; I first blamed my own dashboard builds. The Sentry section below shows the laptop was asleep, and DECISIONS.md keeps both entries.&lt;/p&gt;

&lt;p&gt;The bug I'm least proud of was in my own plumbing, not the model. When Ollama went down mid-night, my first version filed every item as "needs a human" and reported the night as fine. Now an outage stores nothing, the activity fails, and Temporal waits until Ollama is back.&lt;/p&gt;

&lt;p&gt;Both scheduled nights ended "partial": night 5 through a reboot, an outage and my own builds; night 7 because the laptop slept from 02:30 to 10:01 and I then stopped it to use the machine. Unfinished items carry over to the next night. I chose not to keep the laptop awake, so nights simply pause while it sleeps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiger Data: duplicate memory
&lt;/h3&gt;

&lt;p&gt;The collector only sees the last 24 hours, but duplicates are usually older. So a backfill keeps up to 90 days of each repo, about 5,500 items, in Tiger Cloud: Postgres with pgvector and full-text search, shared by the laptop and the dashboard.&lt;/p&gt;

&lt;p&gt;I measured before picking thresholds. Vector similarity alone is useless here: in one repo, 89% of items have a near neighbour at cosine 0.80, because everything in a repo sounds alike. A genuine duplicate pair, temporal#12211 and #11497, sits at 0.96, while related-but-different pairs sit around 0.87–0.89. So the gate is vector-only at 0.92 or above, or vector plus shared title terms, or a strict keyword match. That last rule came from a real miss: the model once paired two temporal issues just because both titles said "task queue user data".&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth0 for AI Agents: a token only at the moment of posting
&lt;/h3&gt;

&lt;p&gt;Reading and writing use two different GitHub clients. The reader refuses anything but reads, and the writer is bound to one repo. The first review found a hole in my own allowlist: it checked the parameters, not the URL actually sent. It now checks the resolved URL, and that bypass is a regression test.&lt;/p&gt;

&lt;p&gt;A reply goes out only when three things hold. A human approved it, and the approval is bound to a hash of the exact text shown, so what's posted is what was read. A unique index allows one post per GitHub item, claimed before the network call, so a retry can't double-post. And at that moment Auth0 Token Vault hands the dashboard a short-lived GitHub token; none is stored in the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sentry: the trace that corrected me
&lt;/h3&gt;

&lt;p&gt;Each item is an &lt;code&gt;invoke_agent&lt;/code&gt; span, with a &lt;code&gt;gen_ai.chat&lt;/code&gt; span for each model attempt, carrying tokens, latency, model and bucket, and never text. An allowlist decides what leaves the laptop, and a test enforces it.&lt;/p&gt;

&lt;p&gt;That 1,956-second item is where the trace paid for itself:&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%2Fslu07cxwv7bp9mhau6c9.jpg" 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%2Fslu07cxwv7bp9mhau6c9.jpg" alt="Sentry trace of night 5's slowest item: invoke_agent 32.60 min, chat gemma3:4b 31.87 min, chat gemma3:1b 43.21 s" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IST, Oct 23&lt;/th&gt;
&lt;th&gt;Sentry trace&lt;/th&gt;
&lt;th&gt;Windows event log&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;13:05:26&lt;/td&gt;
&lt;td&gt;4B chat starts&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:06:05&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;system enters sleep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:37:17&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;resume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:37:18&lt;/td&gt;
&lt;td&gt;1B chat starts&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:38:01&lt;/td&gt;
&lt;td&gt;1B chat ends, row saved&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They agree to the second. The model wasn't slow; the laptop was asleep. The trace also showed something the event log couldn't: the sleep changed &lt;em&gt;which model answered&lt;/em&gt;. The 4B call was cut off on wake, and the 1B fallback started one second after the wake. The trace shows &lt;em&gt;when&lt;/em&gt; the call ended, not why. The likeliest cause is that its 240-second timer counted the suspended half hour. Either way, a timeout that can't tell a hung model from a sleeping laptop hands work to the weaker model for no reason. Fixing that is next, and I'd never have seen it without the trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  TabPFN and Mastra
&lt;/h3&gt;

&lt;p&gt;TabPFN produces a "likely to stall" flag, trained on stored PR history. The data was harder than the model: the backfill picked items by &lt;em&gt;update&lt;/em&gt; date, so older PRs were survivors that skewed everything. I caught it by plotting merge rate by month before training, and now train only on PRs from inside each repo's window.&lt;/p&gt;

&lt;p&gt;The triage call itself is a Mastra Agent with structured output. I use Mastra for nothing else, so I'm not entering its category.&lt;/p&gt;

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

&lt;p&gt;Three things Nightshift can only do because the model is open and runs locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Nothing goes to a model vendor.&lt;/strong&gt; Every triage call goes to Gemma through Ollama on the laptop. Items live in a Postgres I control, and Sentry gets an allowlisted set of metadata, never text (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/packages/core/src/telemetry.ts" rel="noopener noreferrer"&gt;&lt;code&gt;telemetry.ts&lt;/code&gt;&lt;/a&gt;). For a private repo, that's the difference between allowed and not allowed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Free calls change how it behaves.&lt;/strong&gt; All 112 decisions cost $0.00: 131,474 tokens in and 16,469 out (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/STATS.md" rel="noopener noreferrer"&gt;STATS.md&lt;/a&gt;). At Claude Sonnet 5.5's list price ($2 in, $10 out per million tokens, &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;checked 2026-10-25&lt;/a&gt;) the same tokens would cost $0.43, so the money isn't the argument. Behaviour is: with no meter running, the agent can spend 92 seconds on an item, re-read anything that changes, and replay old nights against a new prompt before it ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The model is mine to fence.&lt;/strong&gt; I can see and control every layer: the prompt, the model, and the parser between them. That's why rules can overrule the model where it's weak. On night 5 alone, it called 12 items trivial and the 40-line rule overruled 10 of them, including a 717-line PR (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/digests/2026-10-22-night5.md" rel="noopener noreferrer"&gt;night 5 digest&lt;/a&gt;).&lt;/p&gt;

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

&lt;p&gt;Instead of 71 tabs, night 7 gave me one page (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/digests/2026-10-24-night7.md" rel="noopener noreferrer"&gt;digest&lt;/a&gt;). Here's how it held up against my own labels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pile&lt;/th&gt;
&lt;th&gt;Items&lt;/th&gt;
&lt;th&gt;Right, by my labels&lt;/th&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Needs you&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;40 of 40&lt;/td&gt;
&lt;td&gt;read it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drafted replies&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;2 of 25&lt;/td&gt;
&lt;td&gt;read the draft, then approve, edit or skip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivial and safe&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4 of 6&lt;/td&gt;
&lt;td&gt;a quick merge check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On night 7, when it said "needs you", it was right all 40 times. But it only sends 62% of what really needs me there; the rest lands among the drafts, mostly as false duplicates. So the safe way to read the morning is: trust the top pile, and read every draft. The digest's "est. 113 min" is labelled as an estimate. I haven't timed myself, so I don't claim minutes saved.&lt;/p&gt;

&lt;h3&gt;
  
  
  It lost to doing nothing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Night 7, n = 71&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 3 4B + rules (prompt v1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46 (64.8%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Always "needs a human"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;65 (91.5%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's one night, on repos where most items really do need a maintainer (65 of 71, by my labels), so always-escalate is a hard baseline to beat. The more useful view is where it was confidently wrong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket the model chose&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 of 13&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs tests or info&lt;/td&gt;
&lt;td&gt;2 of 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivial and safe&lt;/td&gt;
&lt;td&gt;4 of 6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;All 13 duplicate calls failed the same way:&lt;/strong&gt; related, not the same (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/STATS.md" rel="noopener noreferrer"&gt;full list with my reasons&lt;/a&gt;). Five were siblings in a series, like a mastra run of Word, then PowerPoint, then OneDrive toolsets. Eight were a different bug in the same corner, like "cancel overwrites a finished run" against its exact reverse. The search found the right neighbourhood every time; the 4B model just couldn't tell a neighbour from a twin, and said so at up to 0.98 confidence. My citation rule passed all 13, because each cited a real candidate. It checks &lt;em&gt;that&lt;/em&gt; a duplicate is cited, not &lt;em&gt;whether&lt;/em&gt; it's the same thing.&lt;/p&gt;

&lt;p&gt;Nothing was harmed: all 13 were on read-only partner repos. On my own repo, each would have reached me as a suggestion, not a closed issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other honest misses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Trivial" was the model's first bad habit: across all runs the rule caught 37 of those calls. Of the six that got through on night 7, two were still wrong; one was a dependency bump with failing CI, which the rule doesn't check yet.&lt;/li&gt;
&lt;li&gt;The #6 reply claimed #5 was "already addressed". I fixed it before approving, which is exactly why a human reads the exact text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; prompt v2 makes the model say what's &lt;em&gt;different&lt;/em&gt; between two items before it may call them duplicates, an idea borrowed from Microsoft hve-core's &lt;a href="https://microsoft.github.io/hve-core/docs/agents/backlog/triage" rel="noopener noreferrer"&gt;backlog triage&lt;/a&gt;. It's the only change, so the next night's difference can be traced to it. It ships only if it beats v1 on these same 71 labelled items. I didn't run it before publishing: replaying 71 items takes about two hours on this laptop (71 at a 92-second median), and I'd rather publish a measured v1 than an unmeasured v2.&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 3 4B, with a 1B fallback, makes every triage call locally through Ollama, fenced by a strict parser and rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal:&lt;/strong&gt; the night is a durable scheduled workflow. It resumed after a mid-night reboot with nothing lost or repeated, carried on after the laptop slept, and waited out an Ollama outage instead of filing everything as "needs a human".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Tiger Data:&lt;/strong&gt; hybrid duplicate search (pgvector plus full-text) over 5,543 stored items, including &lt;code&gt;timescale/timescaledb&lt;/code&gt; itself. Used for search, not time series. Shared by the laptop and the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Auth0 for AI Agents:&lt;/strong&gt; nothing reaches GitHub without approval of the exact text; the agent's client can only read; the write token comes from Token Vault at the moment of posting and is never stored. Two replies went out this way (&lt;a href="https://github.com/AbdulWasih05/nightshift/issues/5#issuecomment-6373303034" rel="noopener noreferrer"&gt;#5&lt;/a&gt;, &lt;a href="https://github.com/AbdulWasih05/nightshift/issues/6#issuecomment-6373389268" rel="noopener noreferrer"&gt;#6&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry:&lt;/strong&gt; every item is traced (tokens, latency, model, no text), and the trace found the real cause of the slowest item, a sleeping laptop that also switched which model answered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of TabPFN:&lt;/strong&gt; a "likely to stall" flag from real PR history, run by hand after a night. 73% of the PRs it flags really don't merge within 14 days, against a 45% base rate. On plain accuracy it ties a one-line rule (63.1% vs 61.8%), so I don't claim it beats it (&lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/docs/eval/tabpfn-2026-10-24-run1.md" rel="noopener noreferrer"&gt;predictor run 1&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; the morning report runs on a free Render web service from a &lt;a href="https://github.com/AbdulWasih05/nightshift/blob/feat/dashboard/render.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;render.yaml&lt;/code&gt; blueprint&lt;/a&gt;. The model deliberately doesn't: triage stays on the laptop, and only the report and the approve button are hosted. That split is what keeps contributor text away from model vendors.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>"Behind Lulu" is not an address: a $2 fine-tune that reads addresses like a dispatcher</title>
      <dc:creator>Abdul Wasih</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:14:09 +0000</pubDate>
      <link>https://dev.to/iamwasih/behind-lulu-is-not-an-address-a-2-fine-tune-that-reads-addresses-like-a-dispatcher-1g50</link>
      <guid>https://dev.to/iamwasih/behind-lulu-is-not-an-address-a-2-fine-tune-that-reads-addresses-like-a-dispatcher-1g50</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Lulu is a hypermarket chain you find all over the Gulf. "Behind Lulu, call before coming" is what a customer in Dubai types when they mean "I'll explain on the phone". A courier who sends that parcel out without calling pays for the failed trip.&lt;/p&gt;

&lt;p&gt;India has its own version: "near Hanuman mandir, Bandra West", often with no house number. A driver gets 120 of these on a morning route, and many are cash on delivery, so a failed stop means the parcel comes back and the seller never gets paid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pata&lt;/strong&gt; ("address" in Hindi and Urdu) reads each address before the van leaves and answers &lt;strong&gt;DELIVERABLE&lt;/strong&gt;, &lt;strong&gt;CALL_FIRST&lt;/strong&gt; (with a call script) or &lt;strong&gt;LIKELY_FAIL&lt;/strong&gt;. It also splits the address into building, flat, street, area, landmark and PIN (India's postal code). Real outputs from the deployed model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the customer typed&lt;/th&gt;
&lt;th&gt;Order&lt;/th&gt;
&lt;th&gt;Pata says&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Flat 1203, Marina Pinnacle Tower, Dubai Marina&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UAE, cash&lt;/td&gt;
&lt;td&gt;DELIVERABLE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;behind Lulu Hypermarket Al Barsha, call before coming&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UAE, cash&lt;/td&gt;
&lt;td&gt;CALL_FIRST:&lt;em&gt;"Could you confirm your building or house number? Payment is cash on delivery."&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;H.No 45, MG Road, Bengaluru 560001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;India, order's PIN field says 110001&lt;/td&gt;
&lt;td&gt;LIKELY_FAIL: the address says Bengaluru, 110001 is Delhi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; simple rules already match the tuned model at saying deliver, call or hold. What a $2 fine-tune adds is &lt;em&gt;reading&lt;/em&gt; the address:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indian addresses (150 test rows)&lt;/th&gt;
&lt;th&gt;Building found&lt;/th&gt;
&lt;th&gt;Area found&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;My rules&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B, untrained&lt;/td&gt;
&lt;td&gt;53%&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pata (trained 4B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A field counts as found when it exactly matches the human-tagged field in the source dataset, ignoring case and spacing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who I expect would pay:&lt;/strong&gt; couriers and cash-on-delivery sellers in India and the Gulf, where "behind Lulu" is in no postal database. Pata costs about $0.15 to $0.17 per 1,000 parcels to run, and two training runs cost $5.35 in total.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Try it: &lt;a href="https://pata-gw9g.onrender.com" rel="noopener noreferrer"&gt;https://pata-gw9g.onrender.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It runs on Render's free tier, so the first request after a quiet spell can take up to a minute. Try "behind Lulu Hypermarket Al Barsha" with cash on delivery, then add a flat and building number. The repo has a &lt;strong&gt;Deploy to Render&lt;/strong&gt; button for your own copy.&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/AbdulWasih05" rel="noopener noreferrer"&gt;
        AbdulWasih05
      &lt;/a&gt; / &lt;a href="https://github.com/AbdulWasih05/pata" rel="noopener noreferrer"&gt;
        pata
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dispatch triage for couriers: a fine-tuned 4B model decides DELIVERABLE, CALL_FIRST or LIKELY_FAIL before the van leaves.
    &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;Pata&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Dispatch triage for couriers. Pata reads a raw customer address plus order context and returns a decision before the van leaves: &lt;code&gt;DELIVERABLE&lt;/code&gt;, &lt;code&gt;CALL_FIRST&lt;/code&gt;, or &lt;code&gt;LIKELY_FAIL&lt;/code&gt;, with normalised fields, a one-line driver note, and the reason.&lt;/p&gt;
&lt;p&gt;Entry for the DEV Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle. Built by Abdul Wasih (&lt;a class="mentioned-user" href="https://dev.to/iamwasih"&gt;@iamwasih&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://render.com/deploy?repo=https://github.com/AbdulWasih05/pata" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c3053e93bc9f0a2cd84050a5ff9f07cc5e639621a72e50dce48781f4a38f10e2/68747470733a2f2f72656e6465722e636f6d2f696d616765732f6465706c6f792d746f2d72656e6465722d627574746f6e2e737667" alt="Deploy to Render"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Live demo: &lt;strong&gt;&lt;a href="https://pata-24o3.onrender.com" rel="nofollow noopener noreferrer"&gt;https://pata-24o3.onrender.com&lt;/a&gt;&lt;/strong&gt; (free instance; the first request after a quiet spell can take up to a minute). Serving checkpoint: run 2 (see &lt;a href="https://github.com/AbdulWasih05/pata/train/RUNS.md" rel="noopener noreferrer"&gt;train/RUNS.md&lt;/a&gt;, &lt;a href="https://github.com/AbdulWasih05/pata/eval/results.md" rel="noopener noreferrer"&gt;eval/results.md&lt;/a&gt;).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt; browser / CSV / CLI
        |
        v
 FastAPI gateway (Render)  ---- Sentry agent trace per request
   1. rules_prelabel   pata/rules.py, always computed
   2. chat             tuned Qwen3.5-4B, Tinker OpenAI-compatible endpoint
                       (hedged: a 2nd request after 1.5 s, first answer wins)
   3. schema.validate  pata/schema.py (pydantic)
   4. rules_fallback   only if the model call fails or its JSON is invalid
        |
        v
 decision + normalised fields + driver note&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AbdulWasih05/pata" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;h3&gt;
  
  
  1. Rules first
&lt;/h3&gt;

&lt;p&gt;Before any AI, I wrote down how a careful dispatcher decides, as plain Python functions with tests (&lt;code&gt;pata/rules.py&lt;/code&gt;). For example, an Indian address with no PIN code gets CALL_FIRST, because a courier cannot route it. Every judgement call like that is written up in &lt;a href="https://github.com/AbdulWasih05/pata/blob/main/DECISIONS.md" rel="noopener noreferrer"&gt;DECISIONS.md&lt;/a&gt;. The rules did two jobs: they labelled the training data, and they are the backup answer if the model ever fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;India:&lt;/strong&gt; real company-registry addresses from public Hugging Face datasets (&lt;a href="https://huggingface.co/datasets/gagan1985/indian-addresses-gold" rel="noopener noreferrer"&gt;indian-addresses-gold&lt;/a&gt;, &lt;a href="https://huggingface.co/datasets/abr-6024/indian-address-parsing-synthetic" rel="noopener noreferrer"&gt;indian-address-parsing-synthetic&lt;/a&gt;, Apache 2.0), roughed up with landmarks, typos, missing PINs and Hinglish like "mandir ke paas" ("near the temple").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UAE:&lt;/strong&gt; &lt;strong&gt;synthetic&lt;/strong&gt;, because no public Gulf address dataset exists. A generator writes Makani numbers (Dubai's building ID), PO boxes, Arabic script and "will share location on WhatsApp".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;About 6,000 training examples, and no real customer addresses.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fine-tuning on Tinker
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://thinkingmachines.ai/tinker/" rel="noopener noreferrer"&gt;Tinker&lt;/a&gt; is Thinking Machines' fine-tuning service: you write the training loop in Python and it runs on their GPUs. I took &lt;a href="https://huggingface.co/Qwen/Qwen3.5-4B" rel="noopener noreferrer"&gt;Qwen3.5-4B&lt;/a&gt;, a small open model (Apache 2.0), and trained a LoRA adapter on it. LoRA trains a small add-on to the model instead of retraining all of it, so a run took 13 to 19 minutes and cost $2 to $3 (&lt;a href="https://github.com/AbdulWasih05/pata/blob/main/train/RUNS.md" rel="noopener noreferrer"&gt;train/RUNS.md&lt;/a&gt;). A FastAPI app on Render serves it, checks every reply against a strict schema, and falls back to the rules if anything is off.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The run that went wrong
&lt;/h3&gt;

&lt;p&gt;My first run looked worse than expected on India: on 45 parcels where my rules said hold or call, the model said DELIVERABLE. I read all 45. In 30 of them, &lt;strong&gt;the model was right and my rules were wrong&lt;/strong&gt;. "H.No 323, Balaji Heights" was marked "missing flat number", but the house number &lt;em&gt;is&lt;/em&gt; the flat.&lt;/p&gt;

&lt;p&gt;I fixed the rule, added tests, relabelled the data and trained a second run, which serves today. The lesson: a model trained on your rules can still check those rules, but only if you read the cases where the two disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sentry: the mystery 6-second requests
&lt;/h2&gt;

&lt;p&gt;Every request is traced in Sentry's AI agent monitoring: the rules check, the model call with its token counts, and the schema check each show up as a span. Traces store lengths and decisions, not the address text.&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%2F8j5j8lkg9o3t47i6z5k6.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%2F8j5j8lkg9o3t47i6z5k6.png" alt="A /triage trace in Sentry" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The traces showed something logs would have hidden. Model calls took either about &lt;strong&gt;1 second or about 6 seconds&lt;/strong&gt;, with almost nothing in between, and with the same token counts. So the model was not writing more; something upstream was holding some requests for five seconds.&lt;/p&gt;

&lt;p&gt;The fix is an old trick, a &lt;strong&gt;hedged request&lt;/strong&gt;: if the first call has not answered in 1.5 seconds, send it again and use whichever answer arrives first. Each span records whether it was hedged.&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%2Fhqwf41t3uk0wjoew8vit.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%2Fhqwf41t3uk0wjoew8vit.png" alt="The chat span in Sentry with pata.hedged true" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;40 requests each way (&lt;a href="https://github.com/AbdulWasih05/pata/blob/main/eval/latency_bench.py" rel="noopener noreferrer"&gt;benchmark&lt;/a&gt;, &lt;a href="https://github.com/AbdulWasih05/pata/blob/main/eval/latency.md" rel="noopener noreferrer"&gt;results&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After hedging&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typical (p50)&lt;/td&gt;
&lt;td&gt;1.15 s&lt;/td&gt;
&lt;td&gt;1.04 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow end (p90)&lt;/td&gt;
&lt;td&gt;6.19 s&lt;/td&gt;
&lt;td&gt;2.92 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests over 5 s&lt;/td&gt;
&lt;td&gt;10 of 40&lt;/td&gt;
&lt;td&gt;0 of 40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six of the forty sent a second call, so hedging adds at most 15% to the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eval
&lt;/h2&gt;

&lt;p&gt;Testing against my rules would only show that the model copied them. So a separate AI reviewer (Claude) labelled 150 test addresses per market, seeing my written definitions but never the rules' answers or code. Of those, 106 India and 77 UAE parcels should be called or held; the rest (44 and 73) are fine to deliver.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Correct (India / UAE)&lt;/th&gt;
&lt;th&gt;India wrongly approved&lt;/th&gt;
&lt;th&gt;Good parcels held (India / UAE)&lt;/th&gt;
&lt;th&gt;Cost per 1,000 (India / UAE)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B, untrained&lt;/td&gt;
&lt;td&gt;43% / 25%&lt;/td&gt;
&lt;td&gt;28 of 106&lt;/td&gt;
&lt;td&gt;4 of 44 / 56 of 73&lt;/td&gt;
&lt;td&gt;$0.33 / $0.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-397B, untrained&lt;/td&gt;
&lt;td&gt;49% / 57%&lt;/td&gt;
&lt;td&gt;7 of 106&lt;/td&gt;
&lt;td&gt;22 of 44 / 33 of 73&lt;/td&gt;
&lt;td&gt;$2.20 / $2.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;My rules alone&lt;/td&gt;
&lt;td&gt;87% / 85%&lt;/td&gt;
&lt;td&gt;9 of 106&lt;/td&gt;
&lt;td&gt;3 of 44 / 23 of 73&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pata (trained 4B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86% / 85%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11 of 106&lt;/td&gt;
&lt;td&gt;4 of 44 / 22 of 73&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.17 / $0.15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both untrained models got the same instructions plus a JSON format description, with thinking off. That longer prompt, and their longer answers, is why they cost more. On UAE, wrongly approved was 0 of 77 for everyone but the 397B (1 of 77). Full tables: &lt;a href="https://github.com/AbdulWasih05/pata/blob/main/eval/results.md" rel="noopener noreferrer"&gt;eval/results.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What it means: training doubled the 4B's accuracy on India and tripled it on UAE, and it beats a model 100 times its size at a thirteenth of the cost. That bigger model only looks safe because it holds half the good parcels. On the three-way decision, though, the trained model ties my rules, agreeing with them on all but 9 of 300 addresses. Its real win is reading the building and area, as in the table at the top.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local quirks are teachable.&lt;/strong&gt; You cannot fine-tune a closed API on "Al Nahda without an emirate". One $2 run on an open model took UAE from 25% to 85%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Addresses stay in-house.&lt;/strong&gt; The weights are open, so a courier can run them on its own machine instead of sending names, phones and cash orders to a third party.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is locked away.&lt;/strong&gt; Rules, data generator, training and eval are all in the repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The test labels come from an AI reviewer, not real delivery outcomes.&lt;/li&gt;
&lt;li&gt;The test set is 150 addresses per market.&lt;/li&gt;
&lt;li&gt;The UAE data is synthetic, generated from real patterns.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Tinker:&lt;/strong&gt; two LoRA runs of Qwen3.5-4B, evaluated against the base model, a 397B model and the rules, including the failed run. The live demo serves from the Tinker checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; the app runs from a one-click &lt;code&gt;render.yaml&lt;/code&gt; blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing:&lt;/strong&gt; traces exposed the 6-second requests, and the hedging fix removed them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built by &lt;a class="mentioned-user" href="https://dev.to/iamwasih"&gt;@iamwasih&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Viva: a mock interview that knows which parts of your project an AI wrote</title>
      <dc:creator>Abdul Wasih</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:57:13 +0000</pubDate>
      <link>https://dev.to/iamwasih/viva-a-mock-interview-that-knows-which-parts-of-your-project-an-ai-wrote-ahe</link>
      <guid>https://dev.to/iamwasih/viva-a-mock-interview-that-knows-which-parts-of-your-project-an-ai-wrote-ahe</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;If you have sat in a placement interview, you know the moment.&lt;/p&gt;

&lt;p&gt;The interviewer stops scrolling through your repo, points at one file, and says: "Walk me through this function."&lt;/p&gt;

&lt;p&gt;You know what the function does. You typed the prompt that produced it. You did not write it.&lt;/p&gt;

&lt;p&gt;That happened to a friend of mine, with a project the two of us built at a hackathon. It works. It is on his resume. He could tell you what every screen does. He could not tell you why the code is the way it is, because an AI agent wrote a lot of it and we shipped before we understood it.&lt;/p&gt;

&lt;p&gt;Every fresher I know is a little scared of that question. So I built &lt;strong&gt;Viva&lt;/strong&gt;: a mock interview about your own project, that knows which parts an AI wrote.&lt;/p&gt;

&lt;p&gt;You give it a repo. It reads the code, finds the files your coding agent wrote, and then interviews you the way a panel would. When it reaches the AI-written part, it says so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your Entire session shows an AI agent wrote &lt;code&gt;src/lib/interview/prompts.ts&lt;/code&gt; (the prompt was: "1,2 isdone lets skip teh devrealy install, render and eleven labs credits i will claim, yep lets do stacked prs"). Explain the logic behind how &lt;code&gt;briefPrompt&lt;/code&gt; structures the input data to ensure the generated brief remains grounded in the actual repository content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a real question Viva asked me, about Viva. The typos in the prompt are mine.&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%2F5kmmo2h4io9ytpsvp9n0.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%2F5kmmo2h4io9ytpsvp9n0.png" alt="Viva asking about a file an AI agent wrote, quoting the prompt from the coding session above the question." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://viva-ou6t.onrender.com" rel="noopener noreferrer"&gt;https://viva-ou6t.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Viva (this project)"&lt;/strong&gt; to see the AI-code question straight away, or paste any public GitHub repo (reading a new one takes about 45 seconds).&lt;/p&gt;

&lt;p&gt;In the video, the candidate is simulated and the voice is Kokoro, an open speech model running on my laptop. The app on screen is real, with the waiting cut out.&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/AbdulWasih05" rel="noopener noreferrer"&gt;
        AbdulWasih05
      &lt;/a&gt; / &lt;a href="https://github.com/AbdulWasih05/Viva" rel="noopener noreferrer"&gt;
        Viva
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A local first AI interview coach 
    &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;Viva&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A mock technical interview about your own project, for the AI-coding era.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Students now ship projects an AI agent half-wrote, and interviewers know it. Viva reads your repo, finds the parts an agent wrote, and questions you about them the way a placement interviewer or a final-year viva panel would. It asks follow-ups based on what you actually said, remembers your weak spots, and ends with a scored report.&lt;/p&gt;
&lt;p&gt;Built for the DEV Hacktoberfest 2026 Weekend Challenge (Build for a Friend). The interviewer is &lt;a href="https://ai.google.dev/gemma" rel="nofollow noopener noreferrer"&gt;Gemma&lt;/a&gt;, an open-weight model; &lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;Mastra&lt;/a&gt; runs the agent; &lt;a href="https://entire.io" rel="nofollow noopener noreferrer"&gt;Entire&lt;/a&gt; checkpoints tell Viva which code an AI wrote.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://render.com/deploy?repo=https://github.com/AbdulWasih05/Viva" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c3053e93bc9f0a2cd84050a5ff9f07cc5e639621a72e50dce48781f4a38f10e2/68747470733a2f2f72656e6465722e636f6d2f696d616765732f6465706c6f792d746f2d72656e6465722d627574746f6e2e737667" alt="Deploy to Render"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads your project.&lt;/strong&gt; Paste a public GitHub URL (or, in local mode, a folder path). Viva picks the most telling files and Gemma writes a short brief. Every file path in that brief is checked against the real file list; invented ones are dropped.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AbdulWasih05/Viva" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;One rule runs through the code: &lt;strong&gt;Gemma decides what to say. Code decides what happens.&lt;/strong&gt; The round order, the follow-up limit, the scores and every file path are plain, tested TypeScript.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;What it does in Viva&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Gemma 4 26B&lt;/strong&gt; (open weights)&lt;/td&gt;
&lt;td&gt;The interviewer: writes the brief, the questions, the scores and the report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mastra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs the interviewer as an agent with a &lt;code&gt;readFile&lt;/code&gt; tool, and remembers your weak spots between sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entire&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Records each coding-agent session next to its commit. Viva reads that to find AI-written code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sentry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Traces every interview turn, with quality flags on each one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Render&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosts it. One free service, no database&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;An interview in four moves.&lt;/strong&gt; Viva reads the repo and Gemma writes a brief; code throws out any file path Gemma made up. For a code question, the agent opens the file itself before asking. After each answer, Gemma scores it and may follow up, but the follow-up has to quote something you actually said, and code checks the quote. At the end, code adds up the scores and Gemma writes the coaching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I measured&lt;/strong&gt;, across scripted interviews on two real repos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model replies that passed the schema check&lt;/td&gt;
&lt;td&gt;119 of 119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep-dive questions that named a real file&lt;/td&gt;
&lt;td&gt;6 of 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow-ups that really quoted the answer&lt;/td&gt;
&lt;td&gt;51 of 51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code questions where the agent opened a file first&lt;/td&gt;
&lt;td&gt;18 of 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second session opened with last time's weak spot&lt;/td&gt;
&lt;td&gt;3 of 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interviews on Viva's own repo that asked about AI-written code&lt;/td&gt;
&lt;td&gt;3 of 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How Viva knows what the AI wrote.&lt;/strong&gt; A file counts as AI-written when it was changed in a commit that Entire linked to a coding-agent session. The prompt Viva quotes is the one you typed in that session, taken from the session transcript.&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%2Fn1jl13m8yabw9l6cmoup.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%2Fn1jl13m8yabw9l6cmoup.png" alt=" A Sentry trace of one Viva interview turn, with flags showing the question was about AI-written code, named a real file, and needed no retry or fallback." width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That Sentry trace is one interview turn: &lt;code&gt;question_names_real_file: true&lt;/code&gt;, &lt;code&gt;ai_authored_question: true&lt;/code&gt;, no retry, no fallback. Each turn carries these flags, so I can see whether the interviewer is doing its job without reading every chat.&lt;/p&gt;

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

&lt;p&gt;Because of who this is for.&lt;/p&gt;

&lt;p&gt;A student should not need a credit card to practise for an interview. His next project may be private. A college lab may have no reliable internet. With an open model, the same code talks to Gemma on a server or to Gemma on your own machine through Ollama, and switching is one setting. A closed API does not give you that choice.&lt;/p&gt;

&lt;p&gt;Nobody has to trust my copy either. The prompts and the scoring rubric are plain files, and the Deploy button gives any coding club its own private Viva.&lt;/p&gt;

&lt;p&gt;Open is not free, and I measured the price. On my 16 GB laptop with no real GPU, local Gemma took 50 to 544 seconds per call and never finished a project brief. So today, local mode needs better hardware than a student usually has. But that is a limit I can see and work on, in code I can read.&lt;/p&gt;

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

&lt;p&gt;The whole build is public. Each phase is one commit, and each commit carries its Entire checkpoint: the same data Viva reads to interview me. Every decision is numbered, with its reason, in &lt;a href="https://github.com/AbdulWasih05/Viva/blob/main/docs/DECISIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/DECISIONS.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma:&lt;/strong&gt; the interviewer itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra:&lt;/strong&gt; the agent, its tools and its memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entire:&lt;/strong&gt; the source of every AI-code question&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry Agent Tracing:&lt;/strong&gt; each turn traced with quality flags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render:&lt;/strong&gt; hosting and a one-click blueprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still to build: voice answers, showing what the agent reads while it reads, and getting a turn under 8 seconds (a new question takes 11 to 14 today).&lt;/p&gt;

&lt;p&gt;Have you been asked to explain code your AI wrote? How did it go? Tell me in the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built Viva with an AI coding agent (Claude Code). The numbers are measured and the sessions are public in the repo.&lt;/em&gt;&lt;/p&gt;

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