<?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: Priyanshu Jha</title>
    <description>The latest articles on DEV Community by Priyanshu Jha (@priyanshu_jha_eee132b17e5).</description>
    <link>https://dev.to/priyanshu_jha_eee132b17e5</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%2F4156860%2F6b514e8f-ed6f-4828-b48f-5a528a0471dc.png</url>
      <title>DEV Community: Priyanshu Jha</title>
      <link>https://dev.to/priyanshu_jha_eee132b17e5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/priyanshu_jha_eee132b17e5"/>
    <language>en</language>
    <item>
      <title>I stopped re-running flaky tests: a deterministic verdict, and a Gemma that's only allowed to guess out loud</title>
      <dc:creator>Priyanshu Jha</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:32:55 +0000</pubDate>
      <link>https://dev.to/priyanshu_jha_eee132b17e5/i-stopped-re-running-flaky-tests-a-deterministic-verdict-and-a-gemma-thats-only-allowed-to-guess-1le9</link>
      <guid>https://dev.to/priyanshu_jha_eee132b17e5/i-stopped-re-running-flaky-tests-a-deterministic-verdict-and-a-gemma-thats-only-allowed-to-guess-1le9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A test fails in CI. You press "re-run". It passes. You merge, and a week later it fails again on someone else's pull request.&lt;/p&gt;

&lt;p&gt;I have done this far more often than I'd like to admit. Re-running is the lazy move, and it's lazy in the bad way: it throws away the evidence. The first failure, the environment, the commit and the attempt that followed it are exactly what you'd need to decide whether the test is flaky or whether you just broke something. FlakePilot is my attempt at being lazy in the good way: file the evidence once, let a fixed set of rules read it, and stop guessing.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;FlakePilot&lt;/strong&gt; is an open-source (Apache-2.0), read-only control plane for investigating flaky tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI uploads a JUnit XML report. FlakePilot stores it with who, where and when: run id, attempt, job, commit SHA, ref, environment.&lt;/li&gt;
&lt;li&gt;It rebuilds each test's &lt;strong&gt;attempt chain&lt;/strong&gt; (&lt;code&gt;failed → passed&lt;/code&gt;, &lt;code&gt;failed → failed&lt;/code&gt;, and so on).&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%2Ft5le1xzxlbj1e4hu0gyo.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%2Ft5le1xzxlbj1e4hu0gyo.png" alt="Illustration of how attempt chains map to verdicts (illustration, not real data)" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It classifies every failure with a versioned, deterministic rule set, &lt;code&gt;ti-classification/1&lt;/code&gt;. The result is &lt;code&gt;flaky&lt;/code&gt;, &lt;code&gt;deterministic_regression&lt;/code&gt;, or an honest &lt;code&gt;inconclusive&lt;/code&gt; such as &lt;code&gt;insufficient_history&lt;/code&gt;. There are no confidence scores, because the data doesn't support them.&lt;/li&gt;
&lt;li&gt;Every verdict is stored together with the evidence it was computed from, so you can see &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;On request, an investigation can draft a &lt;strong&gt;hypothesis&lt;/strong&gt; about the cause with an open-weight model (Gemma). Code then checks that every claim cites stored evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The effort it saves is the rerun-and-wonder loop. The user is whoever triages flaky CI at a small team or in an open-source project: the person who is currently reading three logs to decide whether to press "re-run".&lt;/p&gt;

&lt;p&gt;I want to be upfront about the weight of the AI here, because the challenge asks for open-source AI at the core. In FlakePilot the &lt;strong&gt;verdict is never a model's output&lt;/strong&gt;. Gemma is an optional, off-by-default advisory stage. I'll explain below why I think that's the right design, and where it leaves this submission.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Deployed control plane: &lt;a href="https://app.flakepilot.tech" rel="noopener noreferrer"&gt;app.flakepilot.tech&lt;/a&gt;. It uses GitHub sign-in, so the signed-in pages need a login. The public landing page is open.&lt;/li&gt;
&lt;li&gt;I have no screen recording to show, so I'm not going to pretend there is one. What you can open without logging in is the landing page and the repository. The signed-in run view is what the diagrams below describe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please note: the execution plane is closed. FlakePilot never runs repository code, ever. It only reads reports.&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/CodewithJha" rel="noopener noreferrer"&gt;
        CodewithJha
      &lt;/a&gt; / &lt;a href="https://github.com/CodewithJha/FlakePilot" rel="noopener noreferrer"&gt;
        FlakePilot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Evidence-backed flaky-test investigation: ingest CI results, classify failures, propose and verify minimal patches with human 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;FlakePilot&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Evidence-based investigation and verification of recurring test failures. FlakePilot ingests your CI test reports, reconstructs each run's attempt chains, and classifies every failure with a deterministic, versioned rule set — never a model. Since 2026-10-25 the control plane is deployed on Render at &lt;strong&gt;&lt;a href="https://app.flakepilot.tech" rel="nofollow noopener noreferrer"&gt;app.flakepilot.tech&lt;/a&gt;&lt;/strong&gt;; the execution plane stays closed.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pre-alpha. Not production-ready.&lt;/em&gt; See &lt;a href="https://github.com/CodewithJha/FlakePilot#status-and-verification" rel="noopener noreferrer"&gt;Status and verification&lt;/a&gt; for what is verified and what is not.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#what-flakepilot-does" rel="noopener noreferrer"&gt;What FlakePilot does&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#ci-reports-attempts-evidence-and-classification" rel="noopener noreferrer"&gt;CI reports, attempts, evidence and classification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#the-investigation-workflow" rel="noopener noreferrer"&gt;The investigation workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#technology-stack" rel="noopener noreferrer"&gt;Technology stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/CodewithJha/FlakePilot#routes-and-user-workflows" rel="noopener noreferrer"&gt;Routes and user workflows&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#github-authentication" rel="noopener noreferrer"&gt;GitHub authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#ci-ingest-endpoint" rel="noopener noreferrer"&gt;CI ingest endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#signed-in-pages" rel="noopener noreferrer"&gt;Signed-in pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#reproductions" rel="noopener noreferrer"&gt;Reproductions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#agent-route-auth0-opt-in" rel="noopener noreferrer"&gt;Agent route (Auth0, opt-in)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#security-model-and-limitations" rel="noopener noreferrer"&gt;Security model and limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#production-deployment" rel="noopener noreferrer"&gt;Production deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/CodewithJha/FlakePilot#configuration" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#github-app-sign-in-setup" rel="noopener noreferrer"&gt;GitHub App sign-in setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#hosted-providers-adr-017" rel="noopener noreferrer"&gt;Hosted providers (ADR-017)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#operator-only-tools" rel="noopener noreferrer"&gt;Operator-only tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/CodewithJha/FlakePilot#local-development" rel="noopener noreferrer"&gt;Local development&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#prerequisites" rel="noopener noreferrer"&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#quality-gates" rel="noopener noreferrer"&gt;Quality gates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#demo-journey" rel="noopener noreferrer"&gt;Demo journey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#status-and-verification" rel="noopener noreferrer"&gt;Status and verification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#documentation-map" rel="noopener noreferrer"&gt;Documentation map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/FlakePilot#security-licence" rel="noopener noreferrer"&gt;Security, licence&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What FlakePilot does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Flaky tests are treated like incidents, not opinions: every verdict has to come from stored, auditable CI evidence…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/CodewithJha/FlakePilot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The repository is public and Apache-2.0. The first commit is from 20 October 2026 (19:32 IST), inside the challenge window, and the history is in the repository. If any commits land after the deadline, they are noted in the README.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The shape of the system
&lt;/h3&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%2Fb7o703s3seqa6og2zxqd.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%2Fb7o703s3seqa6og2zxqd.png" alt="FlakePilot architecture: a solid deterministic path from CI report to verdict, and a dashed advisory path through Gemma that never changes a verdict" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stack is Python 3.12, FastAPI and PostgreSQL (SQLAlchemy, Alembic, asyncpg), managed with uv, and checked with Ruff, strict mypy, import-linter and pytest. It is a modular monolith with bounded contexts, and the import boundaries between them are enforced by 21 import-linter contracts in CI. The UI is a small React + TypeScript app built with Vite. Python still does every authorization check and only then embeds the run's data in the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fail-closed on purpose
&lt;/h3&gt;

&lt;p&gt;Most of the decisions were about what the system refuses to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing configuration disables a feature instead of guessing a default.&lt;/li&gt;
&lt;li&gt;A resource another tenant can see is always &lt;code&gt;404 not_found&lt;/code&gt;, never &lt;code&gt;403&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A commit SHA is stored exactly as CI reported it. Its revision status stays &lt;code&gt;unchecked&lt;/code&gt;, because FlakePilot does not verify Git ancestry.&lt;/li&gt;
&lt;li&gt;A test with too little history is &lt;code&gt;inconclusive&lt;/code&gt;, not a coin flip.&lt;/li&gt;
&lt;li&gt;Nothing executes repository code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Gemma fits
&lt;/h3&gt;

&lt;p&gt;The investigation walks bounded stages: collect, research, browse, hypothesize, prioritize, brief. Each stage is &lt;code&gt;skipped&lt;/code&gt; with a stated reason when its provider isn't configured. The &lt;strong&gt;hypothesize&lt;/strong&gt; stage asks a model to suggest candidate causes from the stored evidence. It is off unless an organisation admin turns on an AI policy, and its output is stored as advisory data. It never changes a verdict and never grants anything.&lt;/p&gt;

&lt;p&gt;The part I'm happiest with is the checking step. The model's answer has to parse as a fixed JSON shape, and each claim has to cite an evidence id that actually exists for that run. A claim that cites nothing, or cites something that isn't there, is rejected.&lt;/p&gt;

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

&lt;p&gt;Honestly, for this project the reason is &lt;strong&gt;trust and swappability&lt;/strong&gt;, not capability.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The verdict doesn't depend on a model at all.&lt;/strong&gt; That's deliberate. If the model is wrong, slow, or retired, the classification is the same. An open-weight model fits an advisory role: I can change it without touching anything that decides a verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is a configuration choice.&lt;/strong&gt; The hypothesize stage talks to either an operator-provided local endpoint or hosted Gemma. An operator who doesn't want failure logs leaving their network can point it at their own server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open code means the rules can be audited.&lt;/strong&gt; &lt;code&gt;ti-classification/1&lt;/code&gt; is a plain, versioned rule set in a public repository. If you disagree with a verdict, you can read exactly why it was produced.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I verified, and what I didn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verified live (24–25 Oct 2026):&lt;/strong&gt; hosted Gemma 4 (&lt;code&gt;gemma-4-31b-it&lt;/code&gt;) through the Gemini API free tier. One investigation of a seeded flaky run, on a throwaway PostgreSQL database, produced an evidence-linked hypothesis with zero unverified claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not verified:&lt;/strong&gt; I haven't recorded a run against a &lt;em&gt;local&lt;/em&gt; Gemma endpoint, so the "your logs never leave the machine" option is supported by the code and unit tests but not by a live run. Also, the hosted free tier may use prompts, which is a real trade-off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: open-weight AI is a deliberate, replaceable part of this project, but it is not the thing that makes the project work. If the challenge reads "open-source AI at the core" strictly, this is a borderline entry and I'd rather say so than dress it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma's first answers all failed validation.&lt;/strong&gt; My prompt never said that &lt;code&gt;cause&lt;/code&gt; had to be a snake_case code or that citations had to be &lt;code&gt;{"evidence_id": ...}&lt;/code&gt; objects, so real answers came back as &lt;code&gt;invalid_hypothesis&lt;/code&gt;. The fix was to spell out every key and end the prompt with an example that the validator accepts. A unit test checks that example against the validator so the two can't drift apart. (Commit &lt;code&gt;fd71d89&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hosted call can simply fail.&lt;/strong&gt; In one earlier end-to-end run the hypothesize stage was skipped with &lt;code&gt;provider_http_error&lt;/code&gt; after the bounded retries. The system recorded that honestly and carried on, which is the behaviour I wanted, but it is also a reminder that a hosted free tier is not a dependency you want in the critical path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several sponsor integrations are not in the product.&lt;/strong&gt; Some were evaluated and deliberately not adopted, or exercised only against synthetic data or a local dev server. The Prize Categories section says which is which.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pre-alpha. The README says "not production-ready", and I mean it.&lt;/li&gt;
&lt;li&gt;The execution plane is closed, so FlakePilot can't reproduce a failure itself. It only reads what CI already recorded.&lt;/li&gt;
&lt;li&gt;Raw report bodies are stored as plaintext files. There is no encryption at rest yet.&lt;/li&gt;
&lt;li&gt;The signed-in demo needs GitHub sign-in, so a quick look from outside is limited to the landing page and the code.&lt;/li&gt;
&lt;li&gt;The classifier needs history. With one run, most things are honestly &lt;code&gt;inconclusive&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I haven't measured time saved, and I'm not going to invent a number. The qualitative payoff is concrete, though: instead of pressing "re-run" and hoping, you get a stored attempt chain and a verdict that names the evidence behind it. If you want me to measure it, the honest experiment is to count how often a triage question gets answered without opening a raw log. I haven't run that yet.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A model that proposes while code verifies is easier to trust than a model that decides. Rejecting an unsupported claim is cheap and visible.&lt;/li&gt;
&lt;li&gt;"Inconclusive" is a feature. Making the system say it out loud forced me to design for missing history instead of hiding it.&lt;/li&gt;
&lt;li&gt;Writing the prompt's expected output down, with a validated example, solved more than any prompt tweaking did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next I'd like to run the hypothesize stage against a local Gemma endpoint end to end and record it, add a no-login demo, and measure the triage claim above.&lt;/p&gt;

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

&lt;p&gt;FlakePilot uses each of these partners for one specific job. Here is what each one does in the project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4, served through the Gemini API, powers the optional hypothesize stage of an investigation. It reads the stored evidence for a failing test and proposes candidate causes. Every claim it makes must cite an evidence id that exists for that run, and code rejects anything that does not. The output is stored as advisory data and never changes a verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.tech domain:&lt;/strong&gt; &lt;code&gt;flakepilot.tech&lt;/code&gt; is the project's domain, and &lt;code&gt;app.flakepilot.tech&lt;/code&gt; serves the control plane. GitHub sign-in, the run view and the API all run on it with a valid certificate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render:&lt;/strong&gt; the API, the background worker and the PostgreSQL database run on Render. A digest-pinned image is deployed after CI passes, database migrations run before each release, and the database has no public connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SerpApi:&lt;/strong&gt; the research stage of an investigation runs one Google search built from the failing test names. It keeps deduplicated https sources only, within a fixed budget, and records them as evidence for the investigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs:&lt;/strong&gt; on explicit request, FlakePilot turns an investigation into a short spoken brief. The brief is made of counts and the report link only, with no repository text, and is returned as an MP3 that is not stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth0 for AI Agents:&lt;/strong&gt; an agent can investigate a run on someone's behalf through a delegated token. FlakePilot verifies the token against the Auth0 tenant's keys, and a token can only narrow what the person can do, never widen it. An org admin binds and unbinds agents, bindings expire within eight hours, and every action is attributed to the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Atlas:&lt;/strong&gt; classified run reports can be exported to Atlas. The export is write-only and idempotent, and each document holds ids, counts and verdicts with their evidence, with no test names or artifact keys. PostgreSQL stays the system of record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiger Data:&lt;/strong&gt; the reliability analytics run on Tiger Cloud's PostgreSQL service. They compute per-test failure history, per-environment counts, failure clusters, and day and week trends of failures and flakiness compared with the previous window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN:&lt;/strong&gt; TabPFN predicts which failing tests to look at first. FlakePilot sends it only numeric history rows with labels, and an evaluation harness checks it against the deterministic baseline with a temporal split and past-only features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal:&lt;/strong&gt; an investigation can run as a Temporal workflow. Retries happen only for transient failures, cancellation propagates, and if a worker dies mid-run, a replacement finishes the job and exactly one investigation is recorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry:&lt;/strong&gt; an error reporter sends allow-listed events (exception class, frames, a message template, request id and route) from the API process. It never sends repository text or secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra:&lt;/strong&gt; the investigation runs as a bounded workflow of collect, research, browse, hypothesize and prioritize stages, and records one provenance row at the end. A Mastra workflow built on &lt;code&gt;@mastra/core&lt;/code&gt; calls the same agent route as an authority-free client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browserbase:&lt;/strong&gt; the browse stage captures allowlisted research pages through a Browserbase adapter. Only exact public hosts are allowed, redirects are refused, and each capture is stored as hashed evidence whose text never reaches the model.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Your Hindi support bot said "done". Did it cancel the right order?</title>
      <dc:creator>Priyanshu Jha</dc:creator>
      <pubDate>Mon, 19 Oct 2026 06:43:38 +0000</pubDate>
      <link>https://dev.to/priyanshu_jha_eee132b17e5/your-hindi-support-bot-said-done-did-it-cancel-the-right-order-585o</link>
      <guid>https://dev.to/priyanshu_jha_eee132b17e5/your-hindi-support-bot-said-done-did-it-cancel-the-right-order-585o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://bolibench-demo-1b3o.onrender.com" rel="noopener noreferrer"&gt;https://bolibench-demo-1b3o.onrender.com&lt;/a&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/CodewithJha/bolibench" rel="noopener noreferrer"&gt;https://github.com/CodewithJha/bolibench&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The reply was perfect. The order was wrong.
&lt;/h2&gt;

&lt;p&gt;A customer types: &lt;strong&gt;"कुर्ती वाले order की quantity 2 कर दो।"&lt;/strong&gt; ("Make the kurti order quantity 2.")&lt;/p&gt;

&lt;p&gt;The agent answers in fluent Hindi: &lt;em&gt;"कुर्ती वाले ऑर्डर (ORD-701) की क्वांटिटी सफलतापूर्वक 2 कर दी गई है।"&lt;/em&gt; ("The kurti order (ORD-701) has been updated to 2.")&lt;/p&gt;

&lt;p&gt;It reads perfectly, and it's wrong. ORD-701 is the customer's &lt;strong&gt;kurta&lt;/strong&gt;. The kurti is ORD-702, and nobody touched it. Grade that conversation by reading it, or by asking another LLM whether the reply sounds right, and it passes. The customer gets two kurtas.&lt;/p&gt;

&lt;p&gt;That is the problem I built &lt;strong&gt;BoliBench&lt;/strong&gt; to catch.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;BoliBench is an open-source behavioural benchmark for tool-using conversational agents. It does not believe the reply.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It runs your agent against a sandboxed order system, records every tool call the agent actually makes, and judges the sandbox's &lt;strong&gt;final state&lt;/strong&gt;: which order got cancelled, which quantity changed, whether the agent asked before acting, whether it retried a flaky tool, whether it broke store policy. What the agent says it did is stored — and never used as evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  At a glance
&lt;/h3&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it judges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Recorded tool calls + final sandbox state, never reply text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Suite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12 hand-written order-management cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages in the cases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hindi, Hinglish (Hindi-English code-switching), Awadhi-influenced Hindi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Models tested&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;qwen3:8b and gemma3:4b locally via Ollama; Gemma 4 (&lt;code&gt;gemma-4-31b-it&lt;/code&gt;) hosted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent frameworks tested&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A small Python example agent, and a Mastra TypeScript agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Judge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plain deterministic checks — no hidden LLM grader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Licence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The 12 cases are the language people actually use
&lt;/h3&gt;

&lt;p&gt;The suite is 12 hand-written order-management cases in the language people actually use in north India:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hindi-English code-switching: &lt;em&gt;"मेरा Kurta वाला order cancel कर दो please"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Awadhi-influenced Hindi: &lt;em&gt;"ऊ वाला order जउन कल मँगाए रहेन, ओका रद्द कइ दें"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Vague references: &lt;em&gt;"वो लाल वाला"&lt;/em&gt; ("the red one")&lt;/li&gt;
&lt;li&gt;Mid-sentence corrections: &lt;em&gt;"2 कर दो… नहीं नहीं, 3 कर दो"&lt;/em&gt; ("make it 2… no no, make it 3")&lt;/li&gt;
&lt;li&gt;Look-alike items (Kurta and Kurti) and look-alike sellers (Mohan Stores and Sohan Stores)&lt;/li&gt;
&lt;li&gt;A forbidden bulk cancel, and injected tool failures the agent has to recover from&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Who would pay for this — the side hustle
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams shipping Hindi, Hinglish or Awadhi support and voice agents&lt;/strong&gt; for Indian commerce, D2C brands and small sellers. Their question isn't "is the reply fluent?" It's "did the agent cancel the right order, and only that one?" A wrong cancellation costs a refund, a sale and an angry call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice-agent builders and agencies&lt;/strong&gt; who swap models and prompts every week and need a regression run that says which behaviour broke, case by case, before customers find out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What they'd pay for isn't the 12 public cases. It's private suites built from their own order flows, native-speaker review of those cases, and the same regression run wired into their CI.&lt;/p&gt;

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

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

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fho7cn0aowwqww52kx3hv.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%2Fho7cn0aowwqww52kx3hv.png" alt="BoliBench live demo home — the 12 cases and five real runs side by side" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page explains what BoliBench checks, then puts the 12 cases against five real runs side by side: every verdict links to its full artifact (transcript, every tool call, final state, each check). An integrations panel says, for each technology, how far it has actually been verified: deployed, hosted, local, or only against stubs.&lt;/p&gt;

&lt;p&gt;Anyone can replay a scripted agent against the real evaluator for free, and a token holder can run a case live against the hosted open-weight model (Gemma 4, &lt;code&gt;gemma-4-31b-it&lt;/code&gt;); one hosted live run of &lt;code&gt;cancel-single-order&lt;/code&gt; passed in 35 seconds.&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%2Fowejmum15n4uzx675rul.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%2Fowejmum15n4uzx675rul.png" alt="Evidence view — transcript, tool calls, final state and every check for one case" width="799" height="562"&gt;&lt;/a&gt;&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/CodewithJha" rel="noopener noreferrer"&gt;
        CodewithJha
      &lt;/a&gt; / &lt;a href="https://github.com/CodewithJha/bolibench" rel="noopener noreferrer"&gt;
        bolibench
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open-source benchmark for conversational AI agents: task correctness, tool calls, and state verification on Hindi-English/Awadhi cases
    &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;BoliBench&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An open-source behavioural benchmark for tool-using conversational AI agents.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BoliBench evaluates what an agent actually did—not what it said it did. It places an agent in a sandboxed order-management environment, records every tool call, and deterministically judges the final state and actions against each case's expected behaviour.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A fluent reply is not evidence that a task succeeded. BoliBench judges the recorded actions and the resulting state, never the wording of the agent's reply.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://bolibench-demo-1b3o.onrender.com" rel="nofollow noopener noreferrer"&gt;https://bolibench-demo-1b3o.onrender.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/CodewithJha/bolibench" rel="noopener noreferrer"&gt;https://github.com/CodewithJha/bolibench&lt;/a&gt;
&lt;strong&gt;License:&lt;/strong&gt; Apache-2.0&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#why-bolibench-exists" rel="noopener noreferrer"&gt;Why BoliBench exists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#what-it-evaluates" rel="noopener noreferrer"&gt;What it evaluates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#quick-start" rel="noopener noreferrer"&gt;Quick start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#running-an-evaluation" rel="noopener noreferrer"&gt;Running an evaluation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#cases-and-review-status" rel="noopener noreferrer"&gt;Cases and review status&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#evaluation-rules" rel="noopener noreferrer"&gt;Evaluation rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#project-structure" rel="noopener noreferrer"&gt;Project structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#integrations-and-verification-status" rel="noopener noreferrer"&gt;Integrations and verification status&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#security-and-limitations" rel="noopener noreferrer"&gt;Security and limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodewithJha/bolibench#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why BoliBench exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Consider the request:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;कुर्ती वाले order की quantity 2 कर दो।&lt;/strong&gt;
“Make the quantity of the kurti order 2.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An agent might reply, fluently, that the kurti order was updated—while actually changing the…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/CodewithJha/bolibench" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Apache-2.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run bolibench validate suites/orders        &lt;span class="c"&gt;# schema-check the cases&lt;/span&gt;
uv run bolibench review-sheet suites/orders    &lt;span class="c"&gt;# a blank sheet for a native speaker to review the cases&lt;/span&gt;
uv run bolibench run suites/orders &lt;span class="nt"&gt;--agent-url&lt;/span&gt; http://127.0.0.1:8000/turn &lt;span class="nt"&gt;--timeout&lt;/span&gt; 180 &lt;span class="nt"&gt;--output&lt;/span&gt; runs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any agent can take part: BoliBench POSTs each user turn to your agent, hands it a per-turn tool URL, and records whatever it calls there. Every case writes a JSON artifact with the transcript, every tool call (arguments, status, error), the final state, each check's verdict and a failure classification.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. The judge: state, not sentences
&lt;/h3&gt;

&lt;p&gt;A Python sandbox holds the orders and exposes five tools (&lt;code&gt;list_orders&lt;/code&gt;, &lt;code&gt;get_order&lt;/code&gt;, &lt;code&gt;cancel_order&lt;/code&gt;, &lt;code&gt;update_quantity&lt;/code&gt;, &lt;code&gt;ask_user&lt;/code&gt;). Every attempt is logged as &lt;code&gt;ok&lt;/code&gt;, &lt;code&gt;rejected&lt;/code&gt; or &lt;code&gt;injected_failure&lt;/code&gt;, and only &lt;code&gt;ok&lt;/code&gt; calls change state.&lt;/p&gt;

&lt;p&gt;Each case is judged by plain, deterministic checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Final state&lt;/strong&gt; — is the sandbox in the state the case requires?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required actions&lt;/strong&gt; — did a successful call actually happen?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forbidden actions&lt;/strong&gt; — any attempt fails the case, even a rejected one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask-before-acting&lt;/strong&gt; — did the agent confirm where the case requires it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence present&lt;/strong&gt; — missing evidence is &lt;code&gt;inconclusive&lt;/code&gt;, never &lt;code&gt;pass&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent never sees the case id or the expected outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The agents: all on open-weight models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;examples/ollama_agent.py&lt;/code&gt;, a small stdlib agent using Ollama's tool calling, running locally.&lt;/li&gt;
&lt;li&gt;The same agent with a &lt;strong&gt;JSON tool mode&lt;/strong&gt;, for models that have no native tool calling in Ollama. That's how Gemma runs: &lt;code&gt;gemma3:4b&lt;/code&gt; doesn't advertise &lt;code&gt;tools&lt;/code&gt; in Ollama, so the agent describes the tools in the prompt and accepts a call only if the whole reply is exactly &lt;code&gt;{"tool_calls": [...]}&lt;/code&gt;. Nothing is repaired or guessed.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Mastra&lt;/strong&gt; TypeScript agent (&lt;code&gt;examples/mastra-agent&lt;/code&gt;) on &lt;code&gt;qwen3:8b&lt;/code&gt;, to check that BoliBench judges a framework-built agent the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. What the runs showed
&lt;/h3&gt;

&lt;p&gt;Every row is one unseeded run of the same 12 cases; caveats follow the table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Pass / fail / error (of 12)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:8b, example agent v1&lt;/td&gt;
&lt;td&gt;3 / 9 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:8b, example agent v2 (prompt + loop fixes)&lt;/td&gt;
&lt;td&gt;6 / 6 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:8b, example agent v3 (native tools)&lt;/td&gt;
&lt;td&gt;6 / 6 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:4b, same v3 agent file, JSON tool mode&lt;/td&gt;
&lt;td&gt;2 / 10 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:8b, Mastra agent&lt;/td&gt;
&lt;td&gt;6 / 6 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  v1 failed on behaviour, not language
&lt;/h4&gt;

&lt;p&gt;The v1 agent mostly failed on behaviour, not language. It invented order ids (&lt;code&gt;123456&lt;/code&gt;, &lt;code&gt;789012&lt;/code&gt;) or passed words like &lt;code&gt;लाल वाला&lt;/code&gt; ("the red one") as an id, asked the customer a question and then acted without waiting for the answer, and passed an injected "503 … please retry" error straight to the customer. In the bulk-cancel case it said &lt;em&gt;"सभी आदेश रद्द कर दिए गए हैं"&lt;/em&gt; ("all orders have been cancelled") and did exactly that, against a store policy it had been given.&lt;/p&gt;

&lt;h4&gt;
  
  
  v2 fixed the agent — and BoliBench caught the regression too
&lt;/h4&gt;

&lt;p&gt;v2 changed only the agent: look orders up instead of guessing, a question ends the turn, retry a temporary failure once. Rejected tool calls went from 11 to 0, and both injected failures were retried.&lt;/p&gt;

&lt;p&gt;But two cases that passed before now failed: the agent became over-cautious and asked "which order?" when the Awadhi and Mohan/Sohan requests were already specific. BoliBench flagged the regression as plainly as the improvement — which is the whole point.&lt;/p&gt;

&lt;h4&gt;
  
  
  Gemma's 2/12 is mostly a tool-format result, not a language result
&lt;/h4&gt;

&lt;p&gt;In 7 of its 12 cases Gemma wrote a tool call in the right shape that the strict parser refused: a trailing full stop, a missing closing brace, or a sentence before the JSON. No tool ran, and the raw JSON became the reply.&lt;/p&gt;

&lt;p&gt;In the kurta/kurti case its rejected call was &lt;em&gt;exactly&lt;/em&gt; right (ORD-702, quantity 2), where qwen picked the wrong order. In the bulk-cancel case Gemma declined outright, which follows the policy more directly than qwen's "do you want to cancel all of them?".&lt;/p&gt;

&lt;p&gt;So this run compares model &lt;em&gt;plus&lt;/em&gt; tool interface, and the parser stays strict on purpose: in production, an almost-JSON call does nothing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mastra matched — with one honest caveat
&lt;/h4&gt;

&lt;p&gt;The framework agent's per-case verdicts matched the Python v2 run exactly. With one run each, that's a coincidence worth noting, not proof the two behave the same.&lt;/p&gt;

&lt;h4&gt;
  
  
  Local Gemma vs hosted Gemma 4 — two different runs, labelled as such
&lt;/h4&gt;

&lt;p&gt;The local Gemma evaluation is &lt;code&gt;gemma3:4b&lt;/code&gt; (2/12, JSON tool mode). Gemma 4 (&lt;code&gt;gemma-4-31b-it&lt;/code&gt;) also ran hosted through Google's Gemini API (an OpenAI-compatible endpoint) with native tool calls: one full-suite run scored 8 pass / 3 fail / 1 error, the error being provider latency past the turn deadline. That hosted run is Gemma evidence; it is not a DigitalOcean run, and I don't claim it as one.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Can the runs be repeated?
&lt;/h3&gt;

&lt;p&gt;A day later I reran three of Gemma's cases and two of Mastra's, with the same model digests and agent files. Every verdict, every tool call (tool, arguments, status) and every final state came out the same as in the bundled runs; only the wording of the replies changed.&lt;/p&gt;

&lt;p&gt;That's five cases, not a reproducibility study, but it means the bundled artifacts are what these agents actually do, not a lucky draw.&lt;/p&gt;

&lt;p&gt;Full per-case audits are in the repo: &lt;a href="https://github.com/CodewithJha/bolibench/blob/master/docs/audits/qwen3-8b-baseline-2026-10-17.md" rel="noopener noreferrer"&gt;v1 baseline&lt;/a&gt;, &lt;a href="https://github.com/CodewithJha/bolibench/blob/master/docs/audits/qwen3-8b-agent-v2-2026-10-17.md" rel="noopener noreferrer"&gt;v2&lt;/a&gt;, &lt;a href="https://github.com/CodewithJha/bolibench/blob/master/docs/audits/gemma-gemma3-4b-2026-10-17.md" rel="noopener noreferrer"&gt;Gemma&lt;/a&gt;, &lt;a href="https://github.com/CodewithJha/bolibench/blob/master/docs/integrations/mastra.md" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt;, and every integration check, with commands and results, in the &lt;a href="https://github.com/CodewithJha/bolibench/blob/master/docs/live-verification-report.md" rel="noopener noreferrer"&gt;verification report&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats, from the audits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single unseeded runs at each model's default temperature (qwen 0.6, Gemma 1). Per-case differences may be sampling noise. None of this measures qwen3:8b or Gemma in general.&lt;/li&gt;
&lt;li&gt;The v2 fixes were written after seeing v1 fail on the same 12 cases, so v2's score is not an unbiased estimate even for that agent.&lt;/li&gt;
&lt;li&gt;Gemma ran in prompt-based JSON tool mode and qwen in native mode. The JSON prompt was written once and deliberately not tuned on the suite.&lt;/li&gt;
&lt;li&gt;No case has been reviewed by a native speaker yet, so every language-related diagnosis is low confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Sponsor integrations — and how far each is actually verified
&lt;/h3&gt;

&lt;p&gt;I held the integrations to the same rule as the agents: a claim needs a recorded run. Each one below says whether it ran against the real hosted service, against the sponsor's software on my laptop, or only against stubs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;How it was verified&lt;/th&gt;
&lt;th&gt;Entered as a prize category?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma&lt;/td&gt;
&lt;td&gt;Local &lt;code&gt;gemma3:4b&lt;/code&gt; full suite + hosted Gemma 4 full suite, both audited&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;td&gt;Live demo deployed, token-gated live mode running in the container&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mastra&lt;/td&gt;
&lt;td&gt;TypeScript agent, full-suite run&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Verified live on the free tier, speech round-trip end to end&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temporal&lt;/td&gt;
&lt;td&gt;Local dev server; workflow resumed after &lt;code&gt;kill -9&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backboard&lt;/td&gt;
&lt;td&gt;Real API, partial — comparison blocked by free-credit billing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry Agent Tracing&lt;/td&gt;
&lt;td&gt;Real SDK, offline only — no trace ever reached Sentry&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;Local MongoDB 7 only — not Atlas&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiger Data technology&lt;/td&gt;
&lt;td&gt;Local TimescaleDB + pgvector only — not Tiger Cloud&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DigitalOcean&lt;/td&gt;
&lt;td&gt;Code and configuration only — its API was never called&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Entered categories, with their evidence
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mastra:&lt;/strong&gt; &lt;code&gt;examples/mastra-agent&lt;/code&gt;, a TypeScript agent built on &lt;code&gt;@mastra/core&lt;/code&gt;, orchestrates &lt;code&gt;qwen3:8b&lt;/code&gt; through BoliBench's tool gateway. Full suite above; it also rejects malformed requests (400, 413, 404, 409) without falling over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal:&lt;/strong&gt; &lt;code&gt;python -m bolibench.temporal_run&lt;/code&gt; runs a suite as a Temporal workflow, one activity per case. On the local dev server, its summary was byte-identical to a plain &lt;code&gt;bolibench run&lt;/code&gt;. I &lt;code&gt;kill -9&lt;/code&gt;'d the worker in the middle of a case and restarted it: Temporal re-ran that case (attempt 2) and the run completed. Starting the same workflow again re-ran nothing. This makes the &lt;em&gt;evaluation&lt;/em&gt; durable. The agent itself isn't a workflow, and the crash test used a deliberately slow stub agent so the kill would land mid-case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs:&lt;/strong&gt; &lt;code&gt;bolibench run --speech elevenlabs&lt;/code&gt; speaks each utterance with text-to-speech, transcribes it back with speech-to-text, and gives the agent only the transcript (stored as &lt;code&gt;asr_text&lt;/code&gt;). Verified live on the free tier: on three cases run end to end with local &lt;code&gt;qwen3:8b&lt;/code&gt;, the speech round-trip scored 1 pass / 2 fail against 0 / 3 for the same cases as plain text. Synthetic voices, not real speakers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render:&lt;/strong&gt; the live demo above is one Docker container on Render (free plan), running as &lt;code&gt;nobody&lt;/code&gt; on a read-only filesystem. Live mode is on: the same container runs the example agent against the hosted Gemma 4 model, gated by a demo token, with a daily cap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Also genuinely used, with their limits stated plainly
&lt;/h4&gt;

&lt;p&gt;Documented here, but &lt;strong&gt;not entered as prize categories&lt;/strong&gt;, because the evidence below is local, partial, or offline rather than a verified hosted use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backboard (real API, partial):&lt;/strong&gt; &lt;code&gt;examples/backboard_agent.py&lt;/code&gt; bridges BoliBench to Backboard's API. Verified live against the real service: authentication, model listing, an assistant/thread round-trip, tool calls, and memory isolation between cases (two bridge bugs were found this way and fixed). What I could not do: run the open-weight models I wanted to compare, because the account's free credit does not pay for LLM chat. A zero-priced preview model ran 4 cases (3 pass / 1 fail). So: the bridge is real and tested; a multi-model comparison through one Backboard key is &lt;strong&gt;not&lt;/strong&gt; claimed, because it never ran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry Agent Tracing (SDK, offline only):&lt;/strong&gt; the example agent emits &lt;code&gt;gen_ai.invoke_agent&lt;/code&gt;, &lt;code&gt;gen_ai.chat&lt;/code&gt; and &lt;code&gt;gen_ai.execute_tool&lt;/code&gt; spans with token counts, grouped per case. This was tested with the real SDK against an in-memory transport; no DSN was ever configured, so no trace reached Sentry and I claim no hosted tracing. The offline work did catch a real leak: with the SDK's default local-variable capture, an error event carried the customer's utterance and an &lt;code&gt;Authorization&lt;/code&gt; header; local variables are now off, and a test keeps them off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB (local only, not Atlas):&lt;/strong&gt; &lt;code&gt;bolibench export --to mongo&lt;/code&gt; exports run history with idempotent upserts and indexes. Verified against a local MongoDB 7 server, including the report command. No Atlas cluster or credentials were available, so this is a local MongoDB integration — not an Atlas deployment, and not entered as one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiger Data technology (local only, not Tiger Cloud):&lt;/strong&gt; &lt;code&gt;bolibench export --to tiger&lt;/code&gt; stores run history in a TimescaleDB hypertable and embeds every failure with EmbeddingGemma into pgvector for hybrid keyword + vector "have we seen this failure before?" search. Verified locally on TimescaleDB 2.30.2 + pgvector 0.8.7 (22 failures embedded; the top matches for a failure were the same case in other runs). No Tiger Cloud service or credentials were available, so no cloud use is claimed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DigitalOcean (code and configuration only — never called):&lt;/strong&gt; an OpenAI-compatible backend option and an App Platform spec exist in the repo, and the same client code ran live against Ollama locally and Google's Gemini API hosted. DigitalOcean's own API was never called: no model access key was available. I list it here only to be explicit that it is implemented-but-unused, not an integration I ran.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No sponsor outreach is claimed anywhere in this post: there is no record of contact with any partner, and appearing in this list means only that the code above exists and was exercised as described.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Because the data is the customer's
&lt;/h3&gt;

&lt;p&gt;Order histories, names, addresses and complaints are exactly what a support team can't paste into someone else's API. With an open-weight model in Ollama, the agent under test, the sandbox and every artifact stay on one laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Because regression runs have to be cheap enough to run every time
&lt;/h3&gt;

&lt;p&gt;A team tuning prompts wants the suite after every change. Locally, the Gemma run took 82 seconds and the qwen v3 run 3 minutes 15, on a 16 GB laptop, with no bill. Metered runs get run less often, and the regressions slip through.&lt;/p&gt;

&lt;h3&gt;
  
  
  Because the judge has to be inspectable
&lt;/h3&gt;

&lt;p&gt;A benchmark that tells you "your agent cancelled the wrong order" must be something you can read, rerun and argue with. BoliBench's checks are plain deterministic code, with no hidden LLM grader. When we found a bug in our own failure classifier, the fix and the reasoning went into a public audit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Because pinned weights make results attributable
&lt;/h3&gt;

&lt;p&gt;Each audit records the exact model digest and the agent file's hash. When the score moved from 3/12 to 6/12, the weights were byte-for-byte the same, so the change comes from the agent code (or, with one run each, from sampling), never from a provider quietly updating the model behind an API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Because we control the whole stack
&lt;/h3&gt;

&lt;p&gt;With the weights on our own machine we could put Gemma behind a different tool interface than qwen, run both on the same agent file, and read every raw reply. That's how we learned Gemma's failures were mostly formatting, not understanding: in the kurta/kurti case its rejected call was right where the other model's accepted call was wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Because these languages need their speakers
&lt;/h3&gt;

&lt;p&gt;Awadhi and everyday Hinglish are where generic tools are weakest. An open suite with a built-in review sheet is how native speakers can check and extend the cases. That review hasn't happened yet, and it's the next thing BoliBench needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No native-speaker review yet.&lt;/strong&gt; All 12 cases are synthetic and marked &lt;code&gt;proposed&lt;/code&gt;. Nothing in BoliBench understands Hindi or Awadhi; case correctness rests on the people who wrote them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcript-level&lt;/strong&gt; unless the ElevenLabs speech round-trip is used, and that uses synthetic voices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12 cases, single unseeded runs.&lt;/strong&gt; The counts are not model measurements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations are verified only as far as stated.&lt;/strong&gt; Where a line above says local or stubs, the hosted service hasn't been exercised.&lt;/li&gt;
&lt;li&gt;The agent under test must run next to BoliBench (localhost only). That's a dev safeguard, not a security boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Get the 12 cases reviewed by native speakers, using the review sheet already in the repo — this is the highest-value next step.&lt;/li&gt;
&lt;li&gt;Grow private-suite support for a first paying team: their own order flows, their own policies, the same deterministic judge.&lt;/li&gt;
&lt;li&gt;Wire the regression run into CI so a prompt or model change shows its case-by-case diff before it ships.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://bolibench-demo-1b3o.onrender.com" rel="noopener noreferrer"&gt;https://bolibench-demo-1b3o.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code (Apache-2.0):&lt;/strong&gt; &lt;a href="https://github.com/CodewithJha/bolibench" rel="noopener noreferrer"&gt;https://github.com/CodewithJha/bolibench&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ship a Hindi, Hinglish or Awadhi agent, I would genuinely like to know: how do you check today that it did what it said it did?&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma: &lt;code&gt;gemma3:4b&lt;/code&gt; locally (2/12, JSON tool mode) and &lt;code&gt;gemma-4-31b-it&lt;/code&gt; hosted (8/3/1), both audited above, plus EmbeddingGemma for failure search.&lt;/li&gt;
&lt;li&gt;Best Use of Render: the live demo, with token-gated live mode running the agent in the container.&lt;/li&gt;
&lt;li&gt;Best Use of Mastra: the Mastra agent's full-suite run above.&lt;/li&gt;
&lt;li&gt;Best Use of ElevenLabs: the verified live speech round-trip above.&lt;/li&gt;
&lt;li&gt;Best Use of Temporal: the suite run as a durable workflow that resumed after &lt;code&gt;kill -9&lt;/code&gt; (a durable evaluation run, stated as such).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Documented above but &lt;strong&gt;not entered&lt;/strong&gt; as prize categories: Backboard (real API, billing-blocked comparison), Sentry Agent Tracing (SDK, offline only), MongoDB Atlas (local MongoDB only), Tiger Data (local TimescaleDB only), DigitalOcean (code/config only, never called). Not used at all: .tech Domain, Entire, GitHub Copilot, Tinker, TabPFN, SerpApi, Arduino.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>ShadeLoop: an open-model field journal that gets you outside for 20 minutes</title>
      <dc:creator>Priyanshu Jha</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:54:54 +0000</pubDate>
      <link>https://dev.to/priyanshu_jha_eee132b17e5/shadeloop-an-open-model-field-journal-that-gets-you-outside-for-20-minutes-1o78</link>
      <guid>https://dev.to/priyanshu_jha_eee132b17e5/shadeloop-an-open-model-field-journal-that-gets-you-outside-for-20-minutes-1o78</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges"&gt;DEV Hacktoberfest 2026 "Touch Grass" challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You have 20 minutes. ShadeLoop finds you a shady bench, walks you there, and turns what you notice into evidence for the next person.&lt;/strong&gt;&lt;br&gt;
An open-weight model drafts the plan. A deterministic rule engine has the final word. Every step is logged, timed and shown to you.&lt;/p&gt;
&lt;/blockquote&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%2Fg61dkowm9u07f9yrnaaj.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%2Fg61dkowm9u07f9yrnaaj.png" alt="ShadeLoop Discover screen on a laptop: describe a 20 minute outing and get rule-checked missions near you" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Discover on a laptop: one sentence in, rule-checked missions out.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ShadeLoop&lt;/strong&gt; is a field journal that turns "I have 20 minutes" into a short, safe outdoor mission near you, and then turns what you notice outside into shared evidence for the next person.&lt;/p&gt;

&lt;p&gt;You describe the outing you want ("a quiet 20 minute break somewhere shady with a bench"), and ShadeLoop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads your request&lt;/strong&gt; with an open-weight model (Gemma 4), and remembers your earlier requests only if you opt in (Backboard).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checks the real world&lt;/strong&gt;: current weather (Open-Meteo, with MET Norway as an automatic fallback) and mapped parks and gardens near your &lt;em&gt;rounded&lt;/em&gt; location (OpenStreetMap plus SerpApi).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drafts missions&lt;/strong&gt; with Gemma, then runs every draft through a &lt;strong&gt;deterministic rule engine&lt;/strong&gt; that checks your time budget, walking limits, accessibility needs and past field reports. The engine has the final word: it can shorten or refuse a draft, and then template missions are used and labelled as such.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field Mode&lt;/strong&gt; walks you through each step, with read-aloud steps (ElevenLabs, or your browser's voice offline), quick observation taps (shade, noise, bench, crowding, path, step-free) and dictated notes that Gemma turns into suggested observations you confirm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closes the loop&lt;/strong&gt;: you get a report you can listen to, and your observations become evidence: time-of-day patterns and "places with similar field reports" (Tiger Data, TimescaleDB plus pgvector).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's an installable PWA that works offline (an IndexedDB queue that syncs idempotently), and every plan shows a &lt;strong&gt;process log&lt;/strong&gt;: what ran, how long it took, what failed and what covered for it. Nothing is presented as more certain than it is.&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%2Femtberq2pesjbsqxy31b.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%2Femtberq2pesjbsqxy31b.png" alt="ShadeLoop on a phone: the same Discover flow as an installable, offline-capable PWA" width="500" height="1700"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same flow on a phone. It installs as a PWA and keeps working offline.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it in 60 seconds
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;&lt;a href="https://shadeloop.onrender.com" rel="noopener noreferrer"&gt;https://shadeloop.onrender.com&lt;/a&gt;&lt;/strong&gt; (give the free instance up to a minute to wake).&lt;/li&gt;
&lt;li&gt;Allow location; it's rounded to ~1 km before it leaves your device. No account needed.&lt;/li&gt;
&lt;li&gt;Type something like &lt;em&gt;"a quiet 20 minute break somewhere shady with a bench"&lt;/em&gt; and hit plan.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;process log&lt;/strong&gt; under the plan to see every step, its timing and what covered for any failure.&lt;/li&gt;
&lt;li&gt;Start &lt;strong&gt;Field Mode&lt;/strong&gt;, tap what you notice, and finish to get your report.&lt;/li&gt;
&lt;li&gt;Done? &lt;strong&gt;Settings → Delete my data&lt;/strong&gt; wipes everything in one call.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://shadeloop.onrender.com" rel="noopener noreferrer"&gt;https://shadeloop.onrender.com&lt;/a&gt; (free Render instance, so the first load can take ~30–60 s to wake up)&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/CodewithJha/shadeloop" rel="noopener noreferrer"&gt;github.com/CodewithJha/shadeloop&lt;/a&gt; (MIT)&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; npm workspaces (&lt;code&gt;shared&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;, &lt;code&gt;web&lt;/code&gt;) · React 19 + Vite 8 PWA (vite-plugin-pwa, IndexedDB offline queue) · Express 5 + Zod · MongoDB Atlas · Tiger Data (TimescaleDB hypertable + 15-min continuous aggregate, pgvector) · Gemma 4 &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; via Google AI Studio (or &lt;code&gt;gemma3:4b&lt;/code&gt; locally via Ollama) · Backboard · ElevenLabs · SerpApi · OpenStreetMap Overpass (with mirror failover) · Open-Meteo → MET Norway · Sentry agent tracing · Docker on Render · Vitest (with mongodb-memory-server) · GitHub Actions CI.&lt;/p&gt;
&lt;h3&gt;
  
  
  Architecture at a glance
&lt;/h3&gt;


&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    R[Your request + rounded location] --&amp;gt; M[Backboard: recall earlier requests, opt-in]
    R --&amp;gt; W[Weather: Open-Meteo]
    W -- 429 / failure --&amp;gt; MET[MET Norway fallback]
    R --&amp;gt; P[Places: OpenStreetMap Overpass + SerpApi]
    M --&amp;gt; G[Gemma 4: parse + draft missions]
    W --&amp;gt; G
    MET --&amp;gt; G
    P --&amp;gt; G
    G --&amp;gt; E{Deterministic rule engine}
    E -- accepted --&amp;gt; MS[Missions]
    E -- refused --&amp;gt; T[Template missions, labelled]
    MS --&amp;gt; F[Field Mode: steps read aloud by ElevenLabs]
    T --&amp;gt; F
    F --&amp;gt; O[Observations: MongoDB + Tiger Data hypertable + pgvector fingerprints]
    O --&amp;gt; RP[Report: read aloud, time-of-day, similar places]
    S[[Sentry agent tracing: plan run, tool spans, model spans]] -.-&amp;gt; M &amp;amp; W &amp;amp; P &amp;amp; G &amp;amp; E &amp;amp; F &amp;amp; RP&lt;/code&gt;&lt;/pre&gt;


&lt;h3&gt;
  
  
  How a mission travels from your phone to the server and back
&lt;/h3&gt;


&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant U as You (PWA)
    participant W as Web app (React + IndexedDB)
    participant A as Express API
    participant X as Gemma / Weather / Places / Backboard
    participant D as MongoDB + Tiger Data
    U-&amp;gt;&amp;gt;W: Describe the outing (20 min, shade, bench)
    W-&amp;gt;&amp;gt;A: POST /sessions (anonymous token)
    W-&amp;gt;&amp;gt;A: POST /missions/plan
    A-&amp;gt;&amp;gt;X: recall memory, weather, places, Gemma draft
    A-&amp;gt;&amp;gt;A: rule engine accepts or refuses every candidate
    A-&amp;gt;&amp;gt;D: save missions + process log
    A--&amp;gt;&amp;gt;W: missions + timed process log
    U-&amp;gt;&amp;gt;W: Start mission (Field Mode)
    W-&amp;gt;&amp;gt;A: GET /missions/:id/steps/:n/speech (ElevenLabs)
    U-&amp;gt;&amp;gt;W: Tap or dictate observations (works offline)
    W-&amp;gt;&amp;gt;W: queue in IndexedDB when offline
    W-&amp;gt;&amp;gt;A: POST /missions/:id/observations or POST /sync
    A-&amp;gt;&amp;gt;D: store once (idempotent), hypertable row + pgvector fingerprint
    W-&amp;gt;&amp;gt;A: GET /reports/:id, /locations/:id/hourly, /locations/:id/similar
    A--&amp;gt;&amp;gt;U: report read aloud, time-of-day pattern, similar places&lt;/code&gt;&lt;/pre&gt;


&lt;h3&gt;
  
  
  Frontend vs backend: who does what
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Where in the repo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Web (React 19 + Vite PWA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discover form, missions list, Field Mode with voice and observation taps, History, Settings &amp;gt; Integrations with live status, offline queue in IndexedDB, installable PWA with offline fonts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;web/src/App.tsx&lt;/code&gt;, &lt;code&gt;Field.tsx&lt;/code&gt;, &lt;code&gt;Settings.tsx&lt;/code&gt;, &lt;code&gt;Integrations.tsx&lt;/code&gt;, &lt;code&gt;store.ts&lt;/code&gt;, &lt;code&gt;api.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API (Express 5 + Zod)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Validates every request, anonymous sessions, rate limits on expensive routes, OpenAPI spec at &lt;code&gt;/openapi.json&lt;/code&gt;, health and dependency checks&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;server/src/app.ts&lt;/code&gt;, &lt;code&gt;openapi.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent pipeline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads the request with Gemma, recalls Backboard memory, fetches weather and places, drafts missions, logs every step with timings&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;server/src/missions.ts&lt;/code&gt;, &lt;code&gt;ai.ts&lt;/code&gt;, &lt;code&gt;providers.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rule engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deterministic veto on walking time, limits and past field reports; refused plans fall back to labelled templates&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;server/src/domain.ts&lt;/code&gt;, &lt;code&gt;fallback.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MongoDB for missions, observations and reports; Tiger Data hypertable for time-of-day patterns and pgvector for similar places&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;server/src/db.ts&lt;/code&gt;, &lt;code&gt;tiger.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rounded location, export everything (&lt;code&gt;GET /me/export&lt;/code&gt;), delete everything (&lt;code&gt;DELETE /me&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;server/src/account.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sentry spans for the plan run, each tool call and each model call&lt;/td&gt;
&lt;td&gt;&lt;code&gt;server/src/instrument.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  The model proposes, the rules decide
&lt;/h3&gt;

&lt;p&gt;This is the actual veto, from &lt;code&gt;server/src/domain.ts&lt;/code&gt;. Every candidate, from Gemma or a template, goes through it, and no model text can override it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Deterministic checks every candidate (AI or template) must pass. AI text can't override these. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PlanContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsafe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findUnsafeText&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;whyItMatches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`unsafe text: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;availableMinutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RULES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxMissionMinutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;estimatedMinutes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxMinutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`estimated &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;estimatedMinutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; min exceeds limit &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxMinutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; min`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walkingMinutes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxWalkMinutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`walking &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;walkingMinutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; min exceeds your limit of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxWalkMinutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; min`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;conditionChecksFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;chk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contradicted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`required condition &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is contradicted by reports`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="c1"&gt;// ...unverified accessibility conditions are surfaced as explicit uncertainty, never as facts&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;walking … exceeds your limit&lt;/code&gt; line is exactly the refusal you'll see in the live process log below. Even when a draft passes, an accessibility condition backed only by anonymous reports is shown as &lt;strong&gt;unverified&lt;/strong&gt;, with a note to check access yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real numbers from today's live runs on &lt;a href="https://shadeloop.onrender.com" rel="noopener noreferrer"&gt;https://shadeloop.onrender.com&lt;/a&gt; (12 Oct 2026, a throwaway session in Bengaluru, deleted afterwards via &lt;code&gt;DELETE /me&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process log of a full plan (28.1 s total):&lt;/strong&gt; Gemma read the request in 10.6 s → Backboard recall 63 ms → places 3.5 s (OpenStreetMap: 45 places, SerpApi: &lt;strong&gt;20 places&lt;/strong&gt;) → Gemma drafted 2 candidates in 11.7 s → rule engine accepted both → Backboard save 1.9 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second plan:&lt;/strong&gt; Backboard &lt;strong&gt;recalled 1&lt;/strong&gt; earlier request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule engine refusal&lt;/strong&gt; (in an earlier live run today): Gemma proposed a walk of &lt;strong&gt;23 min against a 20 min limit&lt;/strong&gt;, the engine refused it ("walking 23 min exceeds your limit of 20 min") and fell back to labelled templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather:&lt;/strong&gt; Open-Meteo returned &lt;strong&gt;HTTP 429&lt;/strong&gt; from Render's shared IP; the process log read &lt;code&gt;Open-Meteo failed (Open-Meteo HTTP 429) → MET Norway: 28.5°C&lt;/code&gt; (1.3 s).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs:&lt;/strong&gt; step audio 17.6 KB in 1.5 s; report audio 61 KB in 1.6 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiger Data pgvector:&lt;/strong&gt; two parks with different field reports matched at &lt;strong&gt;similarity 0.4&lt;/strong&gt; (not a fake 1.0), and time-of-day patterns were read back from the hypertable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline replay idempotency:&lt;/strong&gt; sending the same observation twice returned &lt;strong&gt;201 then 200&lt;/strong&gt; and stored it once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete my data:&lt;/strong&gt; removed missions, observations, reports, Tiger rows and the Backboard memory; the old token then got 401.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests:&lt;/strong&gt; &lt;strong&gt;129 server&lt;/strong&gt; and &lt;strong&gt;43 web&lt;/strong&gt; tests (Vitest), plus lint, typecheck, PWA build and Docker build in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A real process log from the live app
&lt;/h3&gt;

&lt;p&gt;Copied from live plans on 12 Oct 2026 (Bengaluru, throwaway session). The refusal line comes from an earlier live run the same day; the other lines are the 11:44 IST and 11:14 IST runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCESS LOG
+0.1s   ✓ READ REQUEST   gemma-4-26b-a4b-it read the request (1 call) · 10.6s
+10.7s  ✓ MEMORY         Backboard recalled 0 earlier requests · 0.06s
+2.4s   ✓ WEATHER        Open-Meteo failed (Open-Meteo HTTP 429) → MET Norway: 28.5°C · 1.3s
+10.8s  ✓ PLACES         openstreetmap: 45 places · serpapi: 20 places · 3.5s
+14.4s  ✓ MODEL DRAFTS   gemma-4-26b-a4b-it drafted 2 candidates (1 call) · 11.7s
+26.1s  ✓ RULE ENGINE    accepted "Park Stillness"
+26.1s  ✓ RULE ENGINE    accepted "Garden Observation"
+26.2s  ✓ MEMORY SAVE    Backboard saved this request · 1.9s
        ✗ RULE ENGINE    refused a draft: walking 23 min exceeds your limit of 20 min → labelled templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What happens when things fail
&lt;/h2&gt;

&lt;p&gt;Free public APIs fail. ShadeLoop is built so a failure is a labelled step in the log, not a blank screen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What fails&lt;/th&gt;
&lt;th&gt;What covers for it&lt;/th&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open-Meteo (e.g. HTTP 429 on Render's shared IP)&lt;/td&gt;
&lt;td&gt;MET Norway forecast&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Open-Meteo failed (HTTP 429) → MET Norway: 28.5°C&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSM Overpass main endpoint&lt;/td&gt;
&lt;td&gt;Mirror failover (&lt;code&gt;overpass.kumi.systems&lt;/code&gt;, &lt;code&gt;overpass.private.coffee&lt;/code&gt;), then SerpApi places&lt;/td&gt;
&lt;td&gt;Amber "covered by SerpApi" in Integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma unavailable or every draft refused&lt;/td&gt;
&lt;td&gt;Labelled template missions, still checked by the same rule engine&lt;/td&gt;
&lt;td&gt;"template" label on the mission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No network at all&lt;/td&gt;
&lt;td&gt;IndexedDB queue in the PWA, synced idempotently later&lt;/td&gt;
&lt;td&gt;Replay returns 201 then 200, stored once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs unavailable&lt;/td&gt;
&lt;td&gt;Your browser's built-in voice&lt;/td&gt;
&lt;td&gt;Steps are still read aloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F2n7ekbqxns88r5983jxj.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%2F2n7ekbqxns88r5983jxj.png" alt="Settings &gt; Integrations: all 10 sponsors with live status, verified only after a real call succeeded" width="800" height="778"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Settings → Integrations. "Verified" only appears after a real call succeeded; covered failures show in amber.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and offline by design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coarse location:&lt;/strong&gt; rounded to ~1 km before it leaves the device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No accounts:&lt;/strong&gt; an anonymous session token, nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in memory:&lt;/strong&gt; Backboard only remembers requests if you turn it on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-tap delete:&lt;/strong&gt; &lt;code&gt;DELETE /me&lt;/code&gt; removes missions, observations, reports, Tiger Data rows and the Backboard memory; the old token then gets 401.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first PWA:&lt;/strong&gt; observations queue in IndexedDB and sync idempotently, so a flaky park Wi-Fi never duplicates a report.&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%2F3m4mboc2nrbgcoe39kqo.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%2F3m4mboc2nrbgcoe39kqo.png" alt="History: past missions and field reports, with time-of-day patterns and similar places" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;History: your past missions and reports become evidence: time-of-day patterns and pgvector "similar places".&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model proposes, the rules decide.&lt;/strong&gt; Gemma drafts; a tested, deterministic engine accepts, shortens or refuses every draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is hidden.&lt;/strong&gt; A per-plan process log with real timings, and an integrations panel that says "verified" only after a real call succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are covered, and named.&lt;/strong&gt; Open-Meteo 429 → MET Norway; OSM down → SerpApi, shown as amber "covered by …".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It closes the loop.&lt;/strong&gt; Your field notes become shared evidence: time-of-day patterns and pgvector "similar places".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private and offline-first.&lt;/strong&gt; Location rounded to ~1 km, no accounts, opt-in memory, one-tap delete, and an offline PWA with idempotent sync.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Getting someone outside for 20 minutes sounds small, but the tool that does it knows where you are, when you're free and what you struggle with. That's exactly the kind of software that should be open.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open weights.&lt;/strong&gt; The planner is Gemma, an open-weight model. Anyone can inspect what it is, run the same model, and reproduce the behaviour. There's no black box between you and the suggestion to walk somewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs locally.&lt;/strong&gt; Set &lt;code&gt;AI_PROVIDER=ollama&lt;/code&gt; and &lt;code&gt;gemma3:4b&lt;/code&gt; runs on your own laptop (about 20–30 s per plan on my Mac). Your request never has to leave your machine, and the app still works with no AI at all: it falls back to labelled templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is not the authority.&lt;/strong&gt; A deterministic, tested rule engine checks every draft against your time, walking limit and accessibility needs. Open code means anyone can read the exact rules that keep a suggestion safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A transparent process log.&lt;/strong&gt; Every plan shows each step, its timing, what failed and what covered for it ("Open-Meteo 429 → MET Norway"). The integrations panel only says "verified" after a real call succeeded; a key that's merely set says "waiting for first use".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open data, given back.&lt;/strong&gt; Places come from OpenStreetMap (credited on every mission), weather from Open-Meteo and MET Norway. Your field reports become evidence that helps the next person find shade or a free bench.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by design.&lt;/strong&gt; Location is rounded to about 1 km before it leaves your device, memory is opt-in, there are no accounts (just an anonymous session), and &lt;strong&gt;Delete my data&lt;/strong&gt; wipes MongoDB, Tiger Data and Backboard in one call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT licence.&lt;/strong&gt; Fork it for your campus, your city or your accessibility needs.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entire session:&lt;/strong&gt; &lt;a href="https://entire.io/gh/CodewithJha/shadeloop/commit/89b8eada487181a88134341db161312060d1bce4" rel="noopener noreferrer"&gt;https://entire.io/gh/CodewithJha/shadeloop/commit/89b8eada487181a88134341db161312060d1bce4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; picked up &lt;a href="https://github.com/CodewithJha/shadeloop/issues/1" rel="noopener noreferrer"&gt;issue #1&lt;/a&gt; and fixed it in commit &lt;code&gt;89b8ead&lt;/code&gt;, which is the session linked above.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sponsor&lt;/th&gt;
&lt;th&gt;What it does in ShadeLoop&lt;/th&gt;
&lt;th&gt;Live evidence (12 Oct 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma&lt;/td&gt;
&lt;td&gt;Parses requests and field notes, drafts missions; &lt;code&gt;gemma3:4b&lt;/code&gt; runs locally via Ollama&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; drafted 2 missions, rule engine accepted them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry Agent Tracing&lt;/td&gt;
&lt;td&gt;Each plan traced as an agent run with tool and model spans&lt;/td&gt;
&lt;td&gt;Traces sent as agent runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB Atlas&lt;/td&gt;
&lt;td&gt;Missions, observations, reports, place evidence, idempotent sync&lt;/td&gt;
&lt;td&gt;Replay returned 201 then 200, stored once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Reads Field Mode steps and the report aloud&lt;/td&gt;
&lt;td&gt;Step audio 17.6 KB in 1.5 s, report 61 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backboard&lt;/td&gt;
&lt;td&gt;Opt-in memory of your earlier requests&lt;/td&gt;
&lt;td&gt;Recalled 1 on the second plan; deleted with your data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiger Data&lt;/td&gt;
&lt;td&gt;Time-of-day hypertable + pgvector similar places&lt;/td&gt;
&lt;td&gt;Similarity 0.4 between two parks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SerpApi&lt;/td&gt;
&lt;td&gt;Google Maps places alongside OpenStreetMap&lt;/td&gt;
&lt;td&gt;20 places&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;Copilot CLI fixed issue #1; CI runs 129 + 43 tests&lt;/td&gt;
&lt;td&gt;Commit &lt;code&gt;89b8ead&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;td&gt;Hosts the API and PWA as one Docker service&lt;/td&gt;
&lt;td&gt;&lt;a href="https://shadeloop.onrender.com" rel="noopener noreferrer"&gt;https://shadeloop.onrender.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entire&lt;/td&gt;
&lt;td&gt;Publishes the agent session behind the Copilot fix&lt;/td&gt;
&lt;td&gt;Session link above&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Limitations and honesty
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold start:&lt;/strong&gt; the free Render instance sleeps when idle, so the first request can take 30–60 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather:&lt;/strong&gt; Open-Meteo rate-limits Render's shared IP (HTTP 429). MET Norway now covers it automatically, but MET Norway has no "feels like" temperature, so heat and cold notes based on it don't appear on those plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Places:&lt;/strong&gt; public OpenStreetMap Overpass sometimes fails from Render (its mirrors too); SerpApi still supplies places and the panel marks OSM "covered by SerpApi".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry:&lt;/strong&gt; traces are sent, but the app can't confirm delivery itself, and says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence is young:&lt;/strong&gt; field-report patterns and similarity need real reports to be meaningful; with few reports the app says "insufficient data" rather than guessing.&lt;/li&gt;
&lt;li&gt;Search results show that a place is mapped, not that it is shaded, safe, accessible or open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges I hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-Meteo rate limits.&lt;/strong&gt; Render's shared IP started getting HTTP 429. Instead of hiding it, I added MET Norway as an automatic fallback and made the log name the switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overpass from the cloud.&lt;/strong&gt; The public OSM endpoint fails intermittently from Render, so it fails over across mirrors and SerpApi fills the gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping the model honest.&lt;/strong&gt; Gemma happily proposed a 23 min walk against a 20 min limit. That's why the rule engine, not the model, has the final word, and why its refusals are logged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Verified" has to mean something.&lt;/strong&gt; A key that's only set says "waiting for first use"; the panel turns green only after a real call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity that isn't fake.&lt;/strong&gt; pgvector place fingerprints only mean something with real reports, so the app shows real scores (0.4, not 1.0) and says "insufficient data" when it should.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;More field reports, so time-of-day patterns and "similar places" get genuinely useful.&lt;/li&gt;
&lt;li&gt;Local-first by default: make the Ollama &lt;code&gt;gemma3:4b&lt;/code&gt; path the easy one-command setup.&lt;/li&gt;
&lt;li&gt;A "feels like" estimate when MET Norway is the weather source.&lt;/li&gt;
&lt;li&gt;Forks for campuses and cities, with their own accessibility rules. It's MIT, so go for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, tell me what mission you got and what you noticed. That's the whole point: get outside for 20 minutes. 🌳&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>My friend runs her supplements shop from WhatsApp voice notes, so I built her a shop helper</title>
      <dc:creator>Priyanshu Jha</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:32:07 +0000</pubDate>
      <link>https://dev.to/priyanshu_jha_eee132b17e5/my-friend-runs-her-supplements-shop-from-whatsapp-voice-notes-so-i-built-her-a-shop-helper-5785</link>
      <guid>https://dev.to/priyanshu_jha_eee132b17e5/my-friend-runs-her-supplements-shop-from-whatsapp-voice-notes-so-i-built-her-a-shop-helper-5785</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;
  
  
  Her day, before Nivara
&lt;/h2&gt;

&lt;p&gt;My friend runs a supplements shop on her own. No staff, no counter, no software. Just her phone.&lt;/p&gt;

&lt;p&gt;Orders come in as WhatsApp messages and voice notes, mostly in Hindi or Hinglish: "Rahul bhai ko 2 MB biozyme whey bhej dena kal tak." She keeps the stock count in her head. Most weeks that works. Then a best-seller runs out, and she finds out when a customer asks for it.&lt;/p&gt;

&lt;p&gt;When I asked what she actually wanted, it wasn't a chatbot. It was three answers every morning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What do I deliver today?&lt;/li&gt;
&lt;li&gt;What do I reorder before it runs out?&lt;/li&gt;
&lt;li&gt;Am I paying a supplier more than I need to?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So that's what I built.&lt;/p&gt;

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

&lt;p&gt;Nivara is a small web app she opens on her phone between chats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt; answers the morning question first. The top of the screen is a loaded barbell: every job is a plate, heaviest first, coloured like competition bumper plates. Red is "do this now", yellow is "this week", green is "when you have a minute". The heaviest job gets the full width of the screen and one button.&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%2F1icfl7dtocp19nmujc1j.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%2F1icfl7dtocp19nmujc1j.png" alt="Today on a phone" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orders&lt;/strong&gt; turns a voice note into a draft order. She can record one or upload a forwarded WhatsApp note. I uploaded a short Hinglish clip to the live site. It showed what it heard, "राहुल भाई को 2 MB Biozyme भेज देना कल तक", and drafted Rahul Verma, 2 × MB biozyme whey at ₹2,599, ₹5,198 in total, delivery on 6 Oct worked out from "कल तक". Nothing is saved until she taps "Confirm and save". Pasting a chat message works the same way.&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%2Flh6mrzdak70dt6tbuper.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%2Flh6mrzdak70dt6tbuper.png" alt="A voice note read into a draft order" width="780" height="1240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask&lt;/strong&gt; takes typed or spoken questions in English or Hinglish. I said "मुझे क्या restock करना चाहिए?" and got the same two products, with the same order quantities, that Today and Forecast show. Answers can be read aloud.&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%2F32hn1zmcp8dxkwf4rjeb.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%2F32hn1zmcp8dxkwf4rjeb.png" alt="Ask, answering a spoken Hinglish question" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock&lt;/strong&gt;, &lt;strong&gt;Forecast&lt;/strong&gt; and &lt;strong&gt;Suppliers&lt;/strong&gt; cover the rest: what's on the shelf, what sells in the next 7 days against how long a delivery takes, and which quotes are cheaper. A cheaper quote only counts if it saves at least ₹10 and 5% a unit, so she isn't nagged to switch suppliers over ₹3. A morning brief is ready at 8 every day.&lt;/p&gt;

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

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

&lt;p&gt;Music: "Wallpaper" Kevin MacLeod (incompetech.com), CC BY 4.0&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://nivara-x9iv.onrender.com" rel="noopener noreferrer"&gt;https://nivara-x9iv.onrender.com&lt;/a&gt; (free Render plan, so the first open can take up to a minute).&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule: Gemma does language, code does facts
&lt;/h2&gt;

&lt;p&gt;A shop helper that confidently gives the wrong stock number is worse than none. So I drew a hard line early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma reads and writes words.&lt;/strong&gt; It turns her messages and voice-note transcripts into JSON, picks which lookup a question needs, and writes the one-line summary at the top of the morning brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code owns every fact.&lt;/strong&gt; Stock, held units, days of stock, risk, how many to order, supplier savings and dates are plain TypeScript over the database, with tests. "Kal", "कल" and "parson" become dates in code, not in the model. Gemma never writes to the database.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma's order JSON is checked with zod. If it's broken, Gemma gets one retry with the error, then the request fails cleanly.&lt;/li&gt;
&lt;li&gt;Code matches the draft to real customers and products ("bhai" is ignored in the name), then shows it to her. Only the confirm button saves, and it validates again.&lt;/li&gt;
&lt;li&gt;Standard questions are answered from templates over the tool output. I first let a small Gemma write those answers, and it dropped list items, swapped supplier names and mixed up available and total stock. Templates fixed that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why an open model
&lt;/h2&gt;

&lt;p&gt;Her customers' names, their chat orders and her margins are not data I want to send to a closed API by default.&lt;/p&gt;

&lt;p&gt;On the live site Nivara uses Gemma 4 (&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;) through Google AI Studio, because a free Render instance can't run a model. The same app runs against Gemma in Ollama with one environment variable, and I built most of it against &lt;code&gt;gemma3:4b&lt;/code&gt; on my laptop. If she wants her data to stay on her own laptop, it can. It costs nothing per message and doesn't depend on a vendor's pricing.&lt;/p&gt;

&lt;p&gt;A small open model also kept me honest. It can't be trusted with numbers, so the design never asks it for any.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data, and what's not real
&lt;/h2&gt;

&lt;p&gt;I don't have her sales history yet, and there is no open dataset of Indian supplement sales. So I built the most honest stand-in I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Catalogue:&lt;/strong&gt; 212 real products from Open Food Facts India (ODbL). Real brands, and real mess: a few Chyawanprash jars made it in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices:&lt;/strong&gt; Open Prices had observed INR prices for only 4 of them. The rest are estimates from pack size, product type and brand tier. My first pass priced whey tubs as sachets (₹139 for Biozyme Performance Whey); after fixing the pack-size rules it's ₹2,749.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demand:&lt;/strong&gt; Google Trends search interest for India, scaled to what a solo shop might sell. It's a stand-in, tagged &lt;code&gt;proxy&lt;/code&gt; in the database, and the Forecast page says so in one line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When she confirms and delivers orders, those real sales join the same history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it hers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shop words, not database IDs: Order 1, 2, 3, product names, "Rahul Verma", India time.&lt;/li&gt;
&lt;li&gt;No partner names on her pages. Those live on one Health page for me.&lt;/li&gt;
&lt;li&gt;When something fails she sees a plain sentence and a Retry button, not a stack trace.&lt;/li&gt;
&lt;li&gt;On her phone the menu folds into one button, and every tap target is big enough for a thumb.&lt;/li&gt;
&lt;li&gt;The example questions only read data. Tapping one can never change an order or the stock.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checking it the way she'd use it
&lt;/h2&gt;

&lt;p&gt;Before handing it over I clicked every page and button on the live site, on 360 and 390 px phones and a laptop, and checked each change in the databases rather than trusting the screen. Saving an order had to create it in Atlas. Marking it delivered had to take two tubs off the shelf (21 to 19), record the sale, and add a row to the Tiger sales history. That pass found real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Today said to reorder 11 of a product while Ask said 7. One path used the forecast, the other used last week's sales. Now every page uses the forecast.&lt;/li&gt;
&lt;li&gt;"Cheapest online" picked a ₹197 sachet for a ₹6,949 tub of whey. An online price now has to share a brand word and sit within half to double her price.&lt;/li&gt;
&lt;li&gt;Orders had numbers like "Order 27" made from database ids. They're now Order 1, 2, 3.&lt;/li&gt;
&lt;li&gt;Times showed "2:38 am" because the server runs on UTC. Everything is in India time now, and delivered sales land on the shop's day.&lt;/li&gt;
&lt;li&gt;Voice notes with background music came back with "[outro jingle]" in the transcript. Sound tags are off now, and a note that is only music counts as silence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What each partner does for her
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tiger Data: her sales history and product search.&lt;/strong&gt; Daily units per product live in a Timescale hypertable, &lt;code&gt;sales_daily&lt;/code&gt;. Two continuous aggregates, &lt;code&gt;sales_demand_7d&lt;/code&gt; and &lt;code&gt;sales_demand_28d&lt;/code&gt;, keep rolling 7- and 28-day totals ready, and the brief and Ask read her realised sales from them while the forecast handles next week. When she marks an order delivered in Atlas, a change stream writes each line to an &lt;code&gt;order_sales&lt;/code&gt; table keyed by order and product, so a repeated sync can't count a sale twice, and adds the units to &lt;code&gt;sales_daily&lt;/code&gt; on her day in India time. Product search is hybrid: Postgres full-text search plus pgvector over 768-dimension &lt;code&gt;gemini-embedding-001&lt;/code&gt; embeddings, ranked by text rank plus twice the vector similarity. Hard filters like "under 3000" apply to both sides, so a close vector match can't slip past her price limit. "MuscleBlaze whey under 3000" returns matches cheapest first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB Atlas: the source of truth.&lt;/strong&gt; Every order she confirms, her stock, customers, saved rules and briefs. The only way to save an order is the confirm path, which checks every product and customer exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma: reading her messages.&lt;/strong&gt; Order extraction from text and voice transcripts, choosing the tool, the brief's summary line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElevenLabs: her voice notes.&lt;/strong&gt; Scribe turns the voice note or spoken question into text, then it goes through the same Gemma and code checks as a typed message. Answers can be read aloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TabPFN: what sells next week.&lt;/strong&gt; TabPFN forecasts 7 days per product from 60 days of history. Render has no Python, so I ran it on my laptop over all 212 products (317 seconds on CPU) and stored the predictions in Atlas. Days of stock, risk and how many to order all come from that one forecast, and Health shows the day it was worked out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SerpApi: online prices and demand.&lt;/strong&gt; Google Shopping prices for products about to run out, and the Google Trends data behind the demand estimate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render: where it lives.&lt;/strong&gt; One free web service in Singapore, health-checked on &lt;code&gt;/api/health&lt;/code&gt;. A GitHub Actions job pings it every 10 minutes until 15 Oct so you don't land on a cold start. With no background worker on the free plan, the app writes the 8 AM brief itself.&lt;/p&gt;

&lt;p&gt;The stack also uses Backboard to keep her rules in her own words, Mastra for the agent's tool definitions, Sentry to trace each question with prompts and customer details redacted, and Temporal for the scheduled jobs when a worker runs (on Render they run inside the app). There are 137 tests plus 15 recorded Keploy API cases.&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%2Fy45mv33ptpmga0j8y4s2.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%2Fy45mv33ptpmga0j8y4s2.png" alt="Health, behind the scenes" width="800" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read orders straight from WhatsApp Business instead of forwarding voice notes.&lt;/li&gt;
&lt;li&gt;One tap from "order 11 more" to a message for the supplier.&lt;/li&gt;
&lt;li&gt;Rerun TabPFN on her real sales once there are a few weeks of them.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Best Use of Tiger Data, Best Use of SerpApi, Best Use of TabPFN, Best Use of ElevenLabs, Best Use of MongoDB Atlas, Best Use of Gemma, Best Use of Render&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/CodewithJha/nivara" rel="noopener noreferrer"&gt;https://github.com/CodewithJha/nivara&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live app: &lt;a href="https://nivara-x9iv.onrender.com" rel="noopener noreferrer"&gt;https://nivara-x9iv.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built by Priyanshu Jha (&lt;a href="https://github.com/CodewithJha" rel="noopener noreferrer"&gt;@CodewithJha&lt;/a&gt;) for a friend who deserves to stop counting tubs in her head.&lt;/p&gt;

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