<?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: Anand Kashyap</title>
    <description>The latest articles on DEV Community by Anand Kashyap (@anand_kashyap_21).</description>
    <link>https://dev.to/anand_kashyap_21</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%2F3858126%2Fdaf9d43e-adc0-48ec-85da-5df971c17e60.jpg</url>
      <title>DEV Community: Anand Kashyap</title>
      <link>https://dev.to/anand_kashyap_21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anand_kashyap_21"/>
    <language>en</language>
    <item>
      <title>Chesterton’s Ghost: a local-AI bot that tells you why the code you’re deleting exists</title>
      <dc:creator>Anand Kashyap</dc:creator>
      <pubDate>Sat, 31 Oct 2026 01:59:02 +0000</pubDate>
      <link>https://dev.to/anand_kashyap_21/chestertons-ghost-a-local-ai-bot-that-tells-you-why-the-code-youre-deleting-exists-koe</link>
      <guid>https://dev.to/anand_kashyap_21/chestertons-ghost-a-local-ai-bot-that-tells-you-why-the-code-youre-deleting-exists-koe</guid>
      <description>&lt;h2&gt;
  
  
  The fence
&lt;/h2&gt;

&lt;p&gt;G.K. Chesterton’s rule: if you find a fence across a road and can’t see why it’s there, don’t tear it down until you know. Code is full of fences, like a redundant-looking if, an odd retry, or a guard nobody remembers. A pull request deletes one, the tests pass, and the bug it was built for comes back.&lt;/p&gt;

&lt;p&gt;Here is a PR that “simplifies” a charge function by deleting five lines:&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%2Fag91r9pdmaegmukhx4md.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%2Fag91r9pdmaegmukhx4md.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing on that page says those lines exist because customers were charged twice. So git-seance says it:&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%2F6u64e69pj0qnddjzvxcz.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%2F6u64e69pj0qnddjzvxcz.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It traced the removed lines to the commit that added them, followed fixes #1 to the issue, and posted one comment with links and a verified quote. The next PR, which deletes an unused variable, got no comment at all:&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%2F1xj3zr8iipxc8hkplgrx.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%2F1xj3zr8iipxc8hkplgrx.png" alt=" " width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;A CLI (with an example GitHub Action) that runs on your own machine with a local open-weight model, Gemma through Ollama. No paid API, and your code never leaves the machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git finds, the model writes
&lt;/h3&gt;

&lt;p&gt;My first rule: the model never decides what the origin commit is. Git does.&lt;/p&gt;

&lt;p&gt;Parse the diff and keep removed or rewritten hunks.&lt;br&gt;
Trace them with git blame -w on the merge-base, falling back to git log -S and -G.&lt;br&gt;
Collect the origin commit message, the merged PR and linked issues.&lt;br&gt;
Score the evidence with plain rules: fix, revert, hotfix, workaround, linked bug. No signal means no comment.&lt;br&gt;
Only then does Gemma, at temperature 0 with a JSON schema, write up the evidence.&lt;br&gt;
A verifier drops every claim whose quote isn’t found verbatim in the cited evidence. It doesn’t reword; it drops.&lt;br&gt;
One comment is created, or updated in place, on the PR.&lt;br&gt;
The trust design&lt;/p&gt;

&lt;p&gt;A bot that cries wolf gets muted, so every warning has to point at something a human can open.&lt;/p&gt;

&lt;p&gt;Commit, PR and issue text is untrusted. The model has no tools, and the bot’s only action is one PR comment.&lt;br&gt;
Cite or drop. Claims must cite evidence ids and quote text that appears verbatim in them.&lt;br&gt;
Model text is stripped of links, @mentions, HTML and #123 references, and wrapped in fixed, hedged wording. The #123 rule came from my own demo, where GitHub auto-linked a #1 inside a quote.&lt;br&gt;
Advisory only. It never blocks a merge.&lt;/p&gt;

&lt;h3&gt;
  
  
  What broke
&lt;/h3&gt;

&lt;p&gt;My GPU crashed. On a 6 GB RTX 4050, gemma4:e4b died with CUDA error: shared object initialization failed. I first blamed my curl quoting, which was wrong. The likely cause is VRAM, though I didn’t prove it. One setting, SEANCE_NUM_GPU=0, forces CPU and works at about 20 tokens a second.&lt;br&gt;
git log -L lies. It walks back to the file’s first commit, not the commit that added the lines. A confidently wrong origin is worse than silence, so I dropped it from the chain.&lt;br&gt;
A one-indent bug hid the issue link. In my first live run the linked issue was missing. I blamed the token, the regex and a stale cache before finding a loop sitting inside if ctx.pr:, so issues only appeared when a PR existed. Every test supplied a PR. The new test has an issue and no PR.&lt;br&gt;
The numbers&lt;/p&gt;

&lt;p&gt;I replayed 10 scripted cases in temporary repos: a plain fix, a fix hidden behind a reformat commit, a renamed file, a revert, a workaround, a reason that appears only in the commit body, three harmless deletions, and a commit message carrying a planted prompt injection. Gemma 4 e4b, CPU only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric    Result
&lt;/h2&gt;

&lt;p&gt;Right origin commit cited   7/7&lt;br&gt;
Silent on harmless deletions    3/3&lt;br&gt;
Planted injection leaked into the comment   0/1&lt;br&gt;
Unsupported claims posted   0&lt;br&gt;
Claims proposed / dropped by the verifier   7 / 0&lt;br&gt;
Slowest case    30 s&lt;/p&gt;

&lt;h3&gt;
  
  
  Read these honestly:
&lt;/h3&gt;

&lt;p&gt;The cases are my own, so 7/7 shows the hazards I thought of are handled, not that real-world history is.&lt;br&gt;
The verifier dropped nothing in this run, so the live model never exercised it. Its unit tests do.&lt;br&gt;
“Zero unsupported” is true by design, because the verifier enforces it.&lt;br&gt;
The injection result is a single case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;p&gt;Same-repo PRs only. Fork PRs aren’t supported.&lt;br&gt;
The GitHub Action in the README is untested. I demoed the CLI posting from my laptop.&lt;br&gt;
A non-whitespace reformat commit that isn’t in .git-blame-ignore-revs can still win blame.&lt;br&gt;
The verifier checks grounding, not intent. A faithfully quoted injected sentence would pass it, and the real protection is no tools, one action, sanitised output and fixed wording.&lt;br&gt;
Squash merges collapse history, so the PR text becomes the main source.&lt;br&gt;
GitHub responses are cached on disk with no expiry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why open mattered
&lt;/h3&gt;

&lt;p&gt;Private code stays on your machine.&lt;br&gt;
There is no per-review fee.&lt;br&gt;
The model is one setting (SEANCE_MODEL). When the GPU path broke, changing a setting fixed it. A closed API would have given me a 500 and no recourse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/kashyapanand21/git-seance" rel="noopener noreferrer"&gt;https://github.com/kashyapanand21/git-seance&lt;/a&gt;&lt;br&gt;
Project page: &lt;a href="https://kashyapanand21.github.io/git-seance/" rel="noopener noreferrer"&gt;https://kashyapanand21.github.io/git-seance/&lt;/a&gt;&lt;br&gt;
Demo repo (open PRs): &lt;a href="https://github.com/kashyapanand21/seance-demo" rel="noopener noreferrer"&gt;https://github.com/kashyapanand21/seance-demo&lt;/a&gt;&lt;br&gt;
Replay report: &lt;a href="https://github.com/kashyapanand21/git-seance/blob/main/eval/report.md" rel="noopener noreferrer"&gt;https://github.com/kashyapanand21/git-seance/blob/main/eval/report.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>An offline assistant that reads your documents with Gemma 4 on your own GPU, then builds upload-ready files to each portal's exact rules.</title>
      <dc:creator>Anand Kashyap</dc:creator>
      <pubDate>Sun, 25 Oct 2026 20:50:34 +0000</pubDate>
      <link>https://dev.to/anand_kashyap_21/an-offline-assistant-that-reads-your-documents-with-gemma-4-on-your-own-gpu-then-builds-1o75</link>
      <guid>https://dev.to/anand_kashyap_21/an-offline-assistant-that-reads-your-documents-with-gemma-4-on-your-own-gpu-then-builds-1o75</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;If you've applied for a scholarship, an admission or an exam in India, you know this screen:&lt;/p&gt;

&lt;p&gt;Photograph: JPG, 3.5 cm × 4.5 cm, white background, 20–50 KB. Signature: JPG, 10–20 KB. Class 12 marksheet: PDF, front and back in one file, max 200 KB. Income certificate: issued on or after 01-04-2026, PDF, not more than 0.3 MB.&lt;/p&gt;

&lt;p&gt;Every portal wants the same documents in a slightly different shape. So you dig through Downloads and WhatsApp forwards called IMG-20240612-WA0007.jpg, upload your marksheet to some random online compressor, get "file size too small" on the signature, and find out on the last day that your income certificate is from March.&lt;/p&gt;

&lt;p&gt;Kaagaz (कागज़, "paper") fixes that, and it does it without your documents ever leaving your laptop.&lt;/p&gt;

&lt;p&gt;It reads your folders and labels every document (Class 12 marksheet, income certificate, passport photo, signature, 28 types in all) using Gemma 4 E4B running locally through Ollama. It reads the document itself, not the filename.&lt;br&gt;
You paste the portal's "documents to upload" section. Gemma turns it into an editable checklist.&lt;br&gt;
Kaagaz picks the right file for each row and tells you before you start the form what's missing or expired.&lt;br&gt;
Plain Python builds the files: converts, face-crops, compresses to the exact KB range, renames to the portal's pattern.&lt;br&gt;
Then it re-opens every output and measures it. PASS / WARN / FAIL, with the real numbers. A file that fails is never saved, so you can't upload it by mistake.&lt;/p&gt;
&lt;h3&gt;
  
  
  The rule I built everything around:
&lt;/h3&gt;

&lt;p&gt;AI decides what. Code does how. Code verifies.&lt;/p&gt;

&lt;p&gt;The model is good at reading a messy certificate or a wall of portal instructions. It is not the thing I want deciding whether a JPEG is 49 KB or 51 KB.&lt;/p&gt;

&lt;p&gt;Who it's for: students and families filling Indian government and college forms, especially people who shouldn't have to upload their Aadhaar or marksheets to a stranger's website to make a file smaller.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Kaagaz runs on your own machine, so there's no hosted version to click. Here's what it looks like, using specimen documents I generated for an invented student, Riya Sharma:&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%2Fyg6mj6e02nxp77b7x4dr.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%2Fyg6mj6e02nxp77b7x4dr.png" alt="Library Section" width="800" height="500"&gt;&lt;/a&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%2Flozz04c9m6c6kdp3nh5g.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%2Flozz04c9m6c6kdp3nh5g.png" alt="Review Section" width="800" height="498"&gt;&lt;/a&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%2F6mmn9yh55kba1iwy6nci.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%2F6mmn9yh55kba1iwy6nci.png" alt="Match Sectionn" width="799" height="499"&gt;&lt;/a&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%2Fgzs07xwhq5vrh5pmdudz.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%2Fgzs07xwhq5vrh5pmdudz.png" alt="Build Section" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That match screen is the moment the whole project is for: the income certificate was issued on 12 March, the form wants 1 April or later, so it's flagged EXPIRED now, not at document verification three weeks from now.&lt;/p&gt;

&lt;p&gt;📖 Full guide (setup, every screen, troubleshooting): kaagaz-docs.vercel.app&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/kashyapanand21" rel="noopener noreferrer"&gt;
        kashyapanand21
      &lt;/a&gt; / &lt;a href="https://github.com/kashyapanand21/kaagaz" rel="noopener noreferrer"&gt;
        kaagaz
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An offline assistant that reads your documents with Gemma 4 on your own GPU, then builds upload-ready files to each portal's exact rules, and measures every one.
    &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;Kaagaz 📄🦥&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Your documents, ready for any form. Offline.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every scholarship, admission and exam portal in India wants the same documents in a different
format: "photo 3.5 × 4.5 cm, JPG, 20–50 KB", "marksheet PDF under 200 KB", "income certificate
issued on or after 01 April". Kaagaz reads your folders, recognises each document with
&lt;strong&gt;Gemma 4 E4B running locally through Ollama&lt;/strong&gt;, and when you paste a portal's upload
instructions it builds a folder of files converted, cropped, compressed and renamed to the exact
rules. Then it &lt;strong&gt;re-opens and measures every output&lt;/strong&gt;, and tells you what's missing or expired
before you start the form.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI decides &lt;em&gt;what&lt;/em&gt;. Code does &lt;em&gt;how&lt;/em&gt;. Code verifies.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nothing leaves your laptop. No uploads, no online compressors, no cloud API.&lt;/p&gt;
&lt;p&gt;📖 &lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://YOUR-VERCEL-URL" rel="nofollow noopener noreferrer"&gt;kaagaz guide page&lt;/a&gt; (setup, how to use, results, troubleshooting)&lt;/p&gt;
&lt;p&gt;Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 3 ("Slow and…&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/kashyapanand21/kaagaz" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Python 3.12, a Typer CLI and a Streamlit app over one package. 71 tests, none of which need a GPU.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The stack, and the hardware surprise
&lt;/h3&gt;

&lt;p&gt;Gemma 4 E4B via Ollama, Pydantic for every model schema, Pillow and PyMuPDF for the file work,&lt;br&gt;
OpenCV's Haar cascades for face cropping, SQLite for the library, Streamlit for the UI.&lt;/p&gt;

&lt;p&gt;The plan assumed an 8 GB GPU with Gemma 4 12B as a fallback for hard documents. On Day 1,&lt;br&gt;
&lt;code&gt;nvidia-smi&lt;/code&gt; wasn't even recognised: my RTX 4050 Laptop GPU had &lt;strong&gt;no driver installed&lt;/strong&gt;, and&lt;br&gt;
showed up as "Microsoft Basic Display Adapter". Once it was installed, it turned out to have&lt;br&gt;
&lt;strong&gt;6 GB&lt;/strong&gt;. 12B doesn't fit. So Kaagaz is &lt;strong&gt;E4B-only&lt;/strong&gt;, and anything it's unsure about goes to a&lt;br&gt;
review queue for a human instead of a bigger model. In hindsight that's the more honest design&lt;br&gt;
anyway.&lt;/p&gt;
&lt;h3&gt;
  
  
  One door to the model
&lt;/h3&gt;

&lt;p&gt;Every Gemma call goes through one function with the same settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;think=False&lt;/code&gt;: Gemma 4 thinks by default in Ollama. On my first test it spent ~260 hidden
tokens to produce a 15-token answer. Turning it off saved about 5 seconds per document.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;num_ctx=8192&lt;/code&gt; so the whole model stays on the 6 GB GPU.&lt;/li&gt;
&lt;li&gt;Temperature 0 and a JSON schema from Pydantic, so the output is structured and repeatable.&lt;/li&gt;
&lt;li&gt;Ollama down → the run &lt;strong&gt;pauses&lt;/strong&gt;. Bad JSON → that one document goes to &lt;strong&gt;review&lt;/strong&gt;. Nothing
crashes, and a scan resumes exactly where it stopped.
Downscaling page images to 1,024 px brought labelling down to a &lt;strong&gt;median of 2.1 s per document&lt;/strong&gt;
(p95 3.0 s). Building a whole 6-file packet takes &lt;strong&gt;0.3 s&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The bug I'd warn everyone about: optional fields
&lt;/h3&gt;

&lt;p&gt;The first version of the requirement parser got &lt;strong&gt;22 of 27&lt;/strong&gt; limits right on my test text. Every&lt;br&gt;
single miss was an &lt;em&gt;omission&lt;/em&gt;: the photo's 20 KB minimum, the 3.5 × 4.5 cm size, the signature's&lt;br&gt;
10 KB minimum. Nothing was wrong; things were just… absent.&lt;/p&gt;

&lt;p&gt;The cause: fields with defaults are &lt;strong&gt;optional&lt;/strong&gt; in the JSON schema, and Ollama's constrained&lt;br&gt;
decoding lets a small model skip optional keys. So it did. The fix is one line: make every key&lt;br&gt;
required in the schema sent to the model (null is still allowed).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParsedItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Every key REQUIRED in the schema sent to Ollama (null still allowed),
&lt;/span&gt;    &lt;span class="c1"&gt;# so the model has to consider each field instead of skipping it.
&lt;/span&gt;    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;json_schema_extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That got it to &lt;strong&gt;27 of 27&lt;/strong&gt;, at the cost of ~5 seconds per checklist, which is fine for something&lt;br&gt;
you run once per form. Two days later I found the &lt;em&gt;exact same bug&lt;/em&gt; in document extraction: the&lt;br&gt;
model had left the person's name and year empty on 13 of 13 documents. Now every model-facing&lt;br&gt;
schema in Kaagaz makes all keys required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code checks the model in both directions
&lt;/h3&gt;

&lt;p&gt;I don't trust the model's confidence score to catch its own mistakes, so the parser has to&lt;br&gt;
&lt;strong&gt;quote the sentence&lt;/strong&gt; each checklist row came from, and then code checks it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding:&lt;/strong&gt; every number the model returned must appear in that sentence (allowing for
0.3 MB ↔ 300 KB and 35 mm ↔ 3.5 cm). An invented limit gets flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage:&lt;/strong&gt; every number-with-a-unit in the sentence must be used by some field. A missed
limit gets flagged.
Flagged cells show up highlighted in the editable checklist. Only numbers with units count, so
"Class 12" and "01-04-2026" are never mistaken for size limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The model reads, code converts
&lt;/h3&gt;

&lt;p&gt;Indian documents print dates day-first: &lt;code&gt;14-06-2022&lt;/code&gt;. I'd typed the date fields as &lt;code&gt;date&lt;/code&gt;, and&lt;br&gt;
constrained decoding squeezed those digits into ISO slots: &lt;code&gt;"1406-06-20"&lt;/code&gt;. Pydantic happily&lt;br&gt;
accepted it as a valid date in the year 1406. That's the worst kind of bug: silent.&lt;/p&gt;

&lt;p&gt;Now Gemma copies dates &lt;strong&gt;exactly as printed&lt;/strong&gt;, and a small tested parser converts them&lt;br&gt;
(day-first, two-digit years, month names, even Devanagari digits like &lt;code&gt;०५-०८-२०२५&lt;/code&gt;). Sizes work&lt;br&gt;
the same way. The model never does arithmetic.&lt;/p&gt;

&lt;p&gt;It also caught Gemma copying a &lt;strong&gt;date of birth&lt;/strong&gt; as a marksheet's issue date (2006, on a 2024&lt;br&gt;
marksheet). Code now cross-checks the issue date against the printed year and drops the date if&lt;br&gt;
they disagree. Two model outputs checking each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  EXPIRED only from facts
&lt;/h3&gt;

&lt;p&gt;Kaagaz only calls a document &lt;strong&gt;EXPIRED&lt;/strong&gt; when it can prove it: a printed expiry date, or an issue&lt;br&gt;
date before the portal's cutoff. Rules of thumb like "income certificates are valid about a year"&lt;br&gt;
can only produce &lt;strong&gt;CHECK DATE&lt;/strong&gt;, clearly labelled as an estimate. I'd rather ask you than tell you&lt;br&gt;
your certificate is expired based on a guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy is enforced in code, not promised in a README
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The model host must be &lt;code&gt;127.0.0.1&lt;/code&gt;; a remote host makes Kaagaz refuse to start.&lt;/li&gt;
&lt;li&gt;Aadhaar, PAN and account numbers are stored as the &lt;strong&gt;last 4 digits only&lt;/strong&gt;, enforced by a
validator and a scrubber that runs on every saved field, including the model's raw output.&lt;/li&gt;
&lt;li&gt;Your original files are never changed. Outputs go to a separate Packets folder.&lt;/li&gt;
&lt;li&gt;Streamlit sends anonymous usage statistics by default. I turned that off, and the app only
listens on &lt;code&gt;127.0.0.1&lt;/code&gt;. The UI started from AI-generated mockups that loaded Tailwind, Google
Fonts and images from the internet; I stripped all of it so the app looks the same with Wi-Fi
off.
### Slow and steady, literally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kaagaz is built to be patient. Scanning is two-phase: registering files takes seconds, and&lt;br&gt;
labelling (~2 s each) runs after. Every document is committed as it finishes, so you can close&lt;br&gt;
the laptop halfway through 400 files and it picks up where it stopped. That's where the sloth&lt;br&gt;
came from: it reads your folders slowly, privately, and doesn't lose its place.&lt;/p&gt;

&lt;h3&gt;
  
  
  The last-night surprise
&lt;/h3&gt;

&lt;p&gt;On the final night, labelling with Wi-Fi off started failing:&lt;br&gt;
&lt;code&gt;CUDA error: shared object initialization failed&lt;/code&gt;. It &lt;em&gt;looked&lt;/em&gt; like Kaagaz needed the internet.&lt;/p&gt;

&lt;p&gt;It didn't. I ran nine cold loads with &lt;code&gt;ollama stop&lt;/code&gt; / &lt;code&gt;ollama run&lt;/code&gt;: two worked, seven crashed,&lt;br&gt;
with Wi-Fi on and off. Ollama's own log showed the crash happened in its model &lt;strong&gt;warm-up step&lt;/strong&gt;,&lt;br&gt;
inside the &lt;strong&gt;CUDA 13&lt;/strong&gt; backend, and the Ollama install folder had been rewritten the afternoon&lt;br&gt;
before: the Windows app had &lt;strong&gt;auto-updated&lt;/strong&gt; itself mid-challenge.&lt;/p&gt;

&lt;p&gt;The workaround was forcing the CUDA 12 backend (&lt;code&gt;setx OLLAMA_LLM_LIBRARY cuda_v12&lt;/code&gt;). RESULT.&lt;/p&gt;

&lt;p&gt;Kaagaz now retries a crashed runner three times with longer waits, then pauses with a calm&lt;br&gt;
message instead of Windows' "this could allow a malicious user to gain control" text. My lesson:&lt;br&gt;
during a deadline, freeze every moving part of a local AI stack, not just your Python packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I didn't get to
&lt;/h3&gt;

&lt;p&gt;I'm keeping this honest. My dev set is &lt;strong&gt;13 specimen documents I made myself&lt;/strong&gt;: Gemma plus the&lt;br&gt;
code checks handled &lt;strong&gt;13/13&lt;/strong&gt; correctly (11 labelled, 2 sent to review with the right reason,&lt;br&gt;
including both Hindi domicile certificates). That's a smoke test, not a benchmark. I didn't run&lt;br&gt;
the planned held-out eval on 30 real documents, or test the parser on real portal texts. Hindi&lt;br&gt;
names don't yet match an English profile name, and front/back photos aren't merged&lt;br&gt;
automatically. All of it is in the &lt;a href="https://github.com/kashyapanand21/kaagaz/blob/main/DEVLOG.md" rel="noopener noreferrer"&gt;devlog&lt;/a&gt;,&lt;br&gt;
including the failures.&lt;/p&gt;

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

&lt;p&gt;The documents in a scholarship application are some of the most sensitive things a student owns:&lt;br&gt;
ID numbers, family income, caste certificates, bank details. Today the "free" way to shrink them&lt;br&gt;
to a portal's KB limit is to upload them to a website you know nothing about.&lt;/p&gt;

&lt;p&gt;Open models change that. Gemma 4 running on a mid-range laptop GPU is good enough to read a&lt;br&gt;
Hindi domicile certificate or a page of portal rules, so the whole job can happen on the&lt;br&gt;
student's own machine. And because Kaagaz is open source, anyone can check the claim that&lt;br&gt;
nothing leaves the laptop: the localhost check, the ID scrubber and the Streamlit settings are&lt;br&gt;
all right there in the repo. "Trust me" isn't good enough for documents like these. Readable&lt;br&gt;
code is.&lt;/p&gt;

&lt;p&gt;It also matters that it's adaptable. Every state and every portal has its own rules; an open&lt;br&gt;
project can grow those one pull request at a time.&lt;/p&gt;

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

&lt;p&gt;I built Kaagaz with Claude as a pair programmer, in one long conversation over four days. I&lt;br&gt;
asked it to work like a developer teaching me: explain the reasoning for each piece, give the&lt;br&gt;
code in small parts, and let me run the tests before moving on. I typed and ran everything on my&lt;br&gt;
own laptop, and pasted back every output, error and screenshot.&lt;/p&gt;

&lt;p&gt;The most useful moments weren't the code, they were the pushback: refusing to treat a CUDA crash&lt;br&gt;
as a document problem, insisting the 13-document result be reported as a smoke test, catching&lt;br&gt;
that AI-generated mockups had invented features and loaded fonts from the internet, and&lt;br&gt;
reading Ollama's log instead of guessing about Wi-Fi. When a paste went wrong (twice I indented a&lt;br&gt;
database write into an &lt;code&gt;if&lt;/code&gt; block), the privacy test caught it before it could be committed.&lt;/p&gt;

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

&lt;p&gt;I didn't formally time a real application. What I can measure: once your folders are labelled,&lt;br&gt;
going from pasted portal rules to a folder of verified, renamed files takes about 15 seconds of&lt;br&gt;
Gemma reading the rules plus 0.3 seconds of building. Doing the same by hand means finding six&lt;br&gt;
files, converting and compressing each one by trial and error, renaming them, and hoping nothing&lt;br&gt;
expired. For me that's usually the better part of an hour per form.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Gemma&lt;/strong&gt;: Kaagaz uses Gemma 4 E4B, locally through Ollama, for its two reading jobs: labelling&lt;br&gt;
documents from a page image (English and Hindi), and turning portal instructions into a&lt;br&gt;
structured checklist. Both run with JSON schemas, &lt;code&gt;think=False&lt;/code&gt;, an 8k context on a 6 GB GPU, and&lt;br&gt;
code that checks every answer.&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>I Built an AI That Roasts Your Startup Idea Like a VC Would</title>
      <dc:creator>Anand Kashyap</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:01:08 +0000</pubDate>
      <link>https://dev.to/anand_kashyap_21/i-built-an-ai-that-roasts-your-startup-idea-like-a-vc-would-2k8n</link>
      <guid>https://dev.to/anand_kashyap_21/i-built-an-ai-that-roasts-your-startup-idea-like-a-vc-would-2k8n</guid>
      <description>&lt;p&gt;Imagine pitching your startup idea and getting brutally honest feedback — not from your supportive friends, but from a panel of hard-nosed investors. That's exactly what Pressure Test does.&lt;br&gt;
It's a tool I built that validates startup ideas using AI-powered investor personas, each with their own lens, bias, and threshold for BS.&lt;/p&gt;

&lt;p&gt;The Problem It Solves&lt;br&gt;
Most early-stage founders make the same mistake: they validate their idea with people who want them to succeed. Friends, family, mentors who are too kind to say "this won't work."&lt;br&gt;
What you actually need is someone who'll pressure-test it — the way a real investor would in a pitch room.&lt;br&gt;
I wanted to build something that simulates that experience.&lt;/p&gt;

&lt;p&gt;What Pressure Test Does&lt;br&gt;
You type in your startup idea. The app sends it through a panel of 5 AI investor personas, each analysing it from a completely different angle:&lt;/p&gt;

&lt;p&gt;💸 Marcus Thorne (Managing Partner, Blackwood VC) — Cuts straight to the money. Will your target customer actually open their wallet for this?&lt;/p&gt;

&lt;p&gt;📈 Sarah Chen (GP, Apex) — Thinks in billions. Is the market big enough to matter, and are you going after the right slice of it?&lt;/p&gt;

&lt;p&gt;⚙️ Dr. Aris Varma (Head of Deep Tech, Capital) — The engineer in the room. Can this actually be built, and how hard will it be?&lt;/p&gt;

&lt;p&gt;⚖️ Jung Moon (Partner, Ventures) — The conscience of the panel. What ethical risks, misuse potential, or regulatory landmines are hiding in your idea?&lt;/p&gt;

&lt;p&gt;🗡️ Elena Rossi (Venture Partner, Frontier) — Assumes someone is already building this. What's your moat, and why won't you get crushed?&lt;/p&gt;

&lt;p&gt;Each persona gives structured feedback from their own lens, and the app aggregates everything into a Fundability Score — a single verdict on how investable your idea actually is.&lt;/p&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;p&gt;Multi-Persona Analysis — No single point of view. You get a full panel critique.&lt;br&gt;
Fundability Score — A weighted composite score across dimensions like market viability, uniqueness, technical feasibility, and execution risk.&lt;br&gt;
Demo Mode — Try it instantly without needing an API key. Great for showcasing.&lt;br&gt;
Clean, minimal UI — Built to feel like a real pitch tool, not a toy.&lt;/p&gt;

&lt;p&gt;The Tech Stack&lt;/p&gt;

&lt;p&gt;Frontend: Vanilla JS + HTML/CSS (kept it lean and fast)&lt;br&gt;
AI Layer: Claude API powering each investor persona&lt;br&gt;
Deployment: Vercel + GitHub Pages&lt;/p&gt;

&lt;p&gt;The core of the app is prompt engineering. Each investor persona has a detailed system prompt that defines their personality, priorities, and scoring rubric. Getting the personas to feel distinct and not like the same model repeating itself was the most interesting engineering challenge.&lt;/p&gt;

&lt;p&gt;The Fundability Score — How It Works&lt;br&gt;
Each persona scores the idea across 4 dimensions (0–10):&lt;br&gt;
DimensionWeightMarket Opportunity30%Technical Feasibility20%Competitive Moat25%Execution Viability25%&lt;br&gt;
The final score is a weighted average, surfaced as a percentage with a verdict label: Too Risky / Interesting / Strong Signal / Fundable.&lt;/p&gt;

&lt;p&gt;Demo Mode — Why I Added It&lt;br&gt;
A lot of AI projects die in demos because someone doesn't have an API key handy. Demo Mode pre-loads a sample idea and pre-fills AI responses so anyone can experience the full flow instantly — no setup, no keys.&lt;br&gt;
It's a small thing, but it makes showing the project feel polished.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt engineering IS product design.
The way you write a persona's system prompt determines everything — its tone, its usefulness, whether it gives generic or incisive feedback. I rewrote each persona 4–5 times before they felt right.&lt;/li&gt;
&lt;li&gt;Aggregation is harder than generation.
Getting a meaningful score out of multiple AI responses requires careful structuring. I had the model return JSON with explicit field scores, then computed the weighted average client-side.&lt;/li&gt;
&lt;li&gt;Demo Mode is not optional for hackathon projects.
It saved my demo at least once. Build it early.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What's Next&lt;/p&gt;

&lt;p&gt;User accounts + idea history&lt;br&gt;
Export pitch deck summary as PDF&lt;br&gt;
Community leaderboard — submit your idea, see how it stacks up&lt;br&gt;
Fine-tuned persona voices based on real investor archetypes&lt;/p&gt;

&lt;p&gt;Try It / Check the Code&lt;br&gt;
🔗 Live Demo: pressure-test.vercel.app &lt;br&gt;
💻 GitHub: github.com/kashyapanand21/pressure-test &lt;/p&gt;

&lt;p&gt;If you've ever pitched an idea and wondered "is this actually good or am I just excited?" — give it a shot.&lt;br&gt;
And if the Skeptic persona destroys your idea... maybe that's useful information. 😅&lt;br&gt;
Drop your startup idea in the comments — I'm curious what score it gets.&lt;/p&gt;

&lt;p&gt;Built by Anand Kashyap | B.Tech CSE, NIT Patna&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ai</category>
      <category>webdev</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
