<?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: Anuj Jain</title>
    <description>The latest articles on DEV Community by Anuj Jain (@anujjainaj16).</description>
    <link>https://dev.to/anujjainaj16</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%2F4154656%2Fe659ebc1-b496-4d48-b650-b5cb8befbae4.png</url>
      <title>DEV Community: Anuj Jain</title>
      <link>https://dev.to/anujjainaj16</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anujjainaj16"/>
    <language>en</language>
    <item>
      <title>WAKE.EXE: a horror game where Gemma times your hesitation and writes the next room</title>
      <dc:creator>Anuj Jain</dc:creator>
      <pubDate>Sat, 31 Oct 2026 22:08:47 +0000</pubDate>
      <link>https://dev.to/anujjainaj16/wakeexe-a-horror-game-where-gemma-times-your-hesitation-and-writes-the-next-room-3pp1</link>
      <guid>https://dev.to/anujjainaj16/wakeexe-a-horror-game-where-gemma-times-your-hesitation-and-writes-the-next-room-3pp1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;LLM text adventures tend to fail the same way. Ask the model to run the inventory and it hands you a key that was never in the room, or opens a wall because you asked nicely. And when it does behave, you wait several seconds per command, which is fatal for a horror game.&lt;/p&gt;

&lt;p&gt;So in WAKE.EXE the model has no power over the rules. Code owns the map, the state, the timer and the distance to the monster. Gemma's whole job is to be the thing that watches you: it reads how long you hesitated, how fast you type and how often you died, and it decides what the next nightmare says to you.&lt;/p&gt;

&lt;p&gt;Black Mirror always fascinated me and that's where I took inspiration from (Bandersnatch), I had fun drafting a small story in a limited time while keeping it fun.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; WAKE.EXE is a short browser horror game in 1-bit ASCII/pixel style. You wake from a nightmare with something chasing you and answer a series of 15-second A/B dilemmas. A local open-weight Gemma plays "Dr. Vance", a clinical observer who profiles you from real telemetry and generates the next dilemmas a few steps ahead, so you never wait on the model. If the model is slow or wrong, validated fallback dilemmas keep the game going. A full run takes about 40-45 minutes and has two endings.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;You are Subject 09. You wake in a damp bedroom while a rotary phone rings and something claws at the closet wall. Every sector gives you a short scene and two choices, with a 15-second countdown bar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path A&lt;/strong&gt; leans into instinct (the "beast" path).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path B&lt;/strong&gt; leans into doubt and decryption (the "meta" path).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doing nothing&lt;/strong&gt; is a choice too: when the timer runs out, the monster closes the distance by 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each choice moves two numbers: &lt;strong&gt;Distance&lt;/strong&gt; (how close the thing is) and &lt;strong&gt;Sanity&lt;/strong&gt; (which drives the screen distortion). Distance also drives the heartbeat: about 50 BPM when you are safe, past 130 BPM when it is one step away.&lt;/p&gt;

&lt;p&gt;The part I wanted people to see is the AI. A &lt;strong&gt;GHOST // PROCESS&lt;/strong&gt; panel stays on screen and shows the provider and model, the lookahead queue (which upcoming dilemmas are ready), and what the Director currently thinks of you, with tags like &lt;code&gt;cautious&lt;/code&gt;, &lt;code&gt;reckless&lt;/code&gt;, &lt;code&gt;fast typist&lt;/code&gt; and &lt;code&gt;marked&lt;/code&gt;. Those tags come from real numbers: average hesitation before you answer, typing speed in WPM, deaths, and near-misses.&lt;/p&gt;

&lt;p&gt;There is also a &lt;strong&gt;Safe Mode&lt;/strong&gt; that removes screen shake, chromatic aberration and loud audio spikes, and at the end you can download your own case file as a &lt;code&gt;.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoilers below.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two endings, both in the Mirror Vault. In the first, you wipe the glass and the reflection is not human: every door was locked from the inside, and you were the thing in the walls. In the second, if you spent the game doubting the facility, the wallpaper peels into scanlines and the mirror shows the CRT glass from the inside, with your own session ID and keystroke metrics on screen.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Play it:&lt;/strong&gt; embed &lt;a href="https://wake-9csj.onrender.com" rel="noopener noreferrer"&gt;https://wake-9csj.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the live site runs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Active Model: Google Gemma (gemma2-9b-it)&lt;br&gt;
Inference Provider: Groq LPU Cloud (OpenAI-compatible endpoint)&lt;br&gt;
Live Health Endpoint: Verified at /api/health (providerConnected: true)&lt;br&gt;
Live Performance: ~355ms generation latency per dilemma batch&lt;/p&gt;

&lt;p&gt;The deployed build on Render actively queries Groq's hosted Google Gemma 2 model to generate all Bandersnatch dilemmas, player taunts, and Dr. Vance's clinical psychiatric evaluations in real-time. If network connectivity is ever lost, the built-in deterministic fallback engine seamlessly takes over to ensure zero gameplay disruption."&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%2F1uzgrulmwuwutre4qx6p.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%2F1uzgrulmwuwutre4qx6p.png" alt="1ST Ending" width="800" height="741"&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%2Fmcvjslolqg4to02gzzd0.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%2Fmcvjslolqg4to02gzzd0.png" alt="2nd Ending" width="800" height="720"&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%2F8lh94x7t34lizna3ydg6.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%2F8lh94x7t34lizna3ydg6.png" alt="Log Page" width="800" height="747"&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/An16og" rel="noopener noreferrer"&gt;
        An16og
      &lt;/a&gt; / &lt;a href="https://github.com/An16og/WAKE" rel="noopener noreferrer"&gt;
        WAKE
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;WAKE.EXE 👁️&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I was never running from it. The footsteps behind me were the echoes of my own paws."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;WAKE.EXE&lt;/strong&gt; is a browser-based psychological ASCII horror game built for the &lt;strong&gt;DEV Hacktoberfest Open-Source AI Challenge (Week 4: "Ghost in the Machine")&lt;/strong&gt;, October 2026.&lt;/p&gt;
&lt;p&gt;An open-weight model (&lt;strong&gt;Gemma&lt;/strong&gt;, running locally via Ollama or an OpenAI-compatible endpoint) serves as the "ghost in the machine". It tracks player telemetry (hesitation, typing cadence, mistakes, death counts), predicts next actions via a &lt;strong&gt;3-to-4 node lookahead queue&lt;/strong&gt;, and dynamically adapts challenges, taunts, and atmospheric narration—while rendering the inner thought process in real-time in the &lt;code&gt;GHOST // PROCESS&lt;/code&gt; side panel.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📸 Key Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retro CRT Experience&lt;/strong&gt;: Authentic phosphor green / monochrome toggle, scanlines, CRT curvature, and retro bitmap typography (&lt;code&gt;VT323&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic Game Engine&lt;/strong&gt;: The model never controls state or rules. Code owns state; deterministic validators guarantee every minigame is…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/An16og/WAKE" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;h3&gt;
  
  
  The rule: code owns the state, the model owns the dread
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;player choice (or timeout)
  -&amp;gt; code updates distance, sanity, room
  -&amp;gt; telemetry: hesitation ms, WPM, deaths, tags
  -&amp;gt; Director (Gemma via Ollama or an OpenAI-compatible API):
       next 3-4 dilemmas as JSON
  -&amp;gt; zod schema check -&amp;gt; retry once -&amp;gt; fallback dilemma
  -&amp;gt; lookahead queue -&amp;gt; the player never waits on the model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While you are on Room 1, the Director is already generating and validating Rooms 2 to 4. That was the fix for generation latency: horror pacing cannot survive a spinner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taunts that can't invent facts about you
&lt;/h3&gt;

&lt;p&gt;Dr. Vance's lines contain placeholders, and the code fills them from real telemetry:&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="sr"&gt;avg_think_s&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="sr"&gt;/g&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="nx"&gt;avgThinkSec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="sr"&gt;deaths&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="sr"&gt;/g&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="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deaths&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="sr"&gt;typing_wpm&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="sr"&gt;/g&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="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typingSpeedWpm&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; WPM`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when the game tells you that you hesitated for 6 seconds, that number is measured, not guessed. This does not stop the model from writing a flat or off-tone line, and it can't police claims outside the placeholders. In early test runs, the model frequently tried to invent its own horror lore to sound intimidating—generating taunts like "You dropped your flashlight in sector 4 and your hands shook on the mouse." The problem? WAKE.EXE is a pure keyboard-driven terminal game, and the player never had a flashlight. The moment an LLM hallucinates fake actions the player never took, psychological dread evaporates into disbelief. In other runs, it drifted into campy Saturday-morning villain tropes ("Mwahaha, you cannot escape the dark dungeon"), totally destroying Dr. Vance's cold, sterile clinical persona.&lt;/p&gt;

&lt;p&gt;To fix this, we took a two-layer defense. First, we strictly locked the prompt contract: Dr. Vance is instructed to evaluate cognitive telemetry only, and taunts must use deterministic interpolation slots ({avg_think_s}, {deaths}, {last_mistake}, {typing_wpm}). Any claim about player performance is populated by ground-truth runtime metrics via regex, making factual hallucinations impossible. Second, we wrapped generation in a strict Zod schema validator: if the model ever outputs malformed JSON, drops tone, or violates schema boundaries, the engine retries once with an error-correction prompt, and seamlessly falls back to our hand-crafted dilemma suite with zero player interruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluation
&lt;/h3&gt;

&lt;p&gt;I ran the Director against 25 synthetic player archetypes with gemma3:1b&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid against the schema and structure checks&lt;/td&gt;
&lt;td&gt;25 of 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needed a retry&lt;/td&gt;
&lt;td&gt;0 of 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replaced by a pre-written fallback&lt;/td&gt;
&lt;td&gt;0 of 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median / p95 generation time&lt;/td&gt;
&lt;td&gt;4.0 s / 4.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next dilemma already ready when the player needed it&lt;/td&gt;
&lt;td&gt;25 of 25 (100% in live play; see note below)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty-five clean runs is a small sample: with no failures in 25, the true failure rate could still be as high as about 12% (the "rule of three": 3 divided by 25). The checks are also structural. They confirm at least two non-empty choices, a bounded 15-second timer, valid branch metadata, and bounded sanity and distance changes. That tells me an output is playable, not that it is good. &lt;/p&gt;

&lt;p&gt;Reading through the raw outputs by hand showed the stark difference between &lt;em&gt;syntactically valid&lt;/em&gt; and &lt;em&gt;compelling narrative&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;When the model locked into Dr. Vance's clinical tone, the generated dilemmas were genuinely unsettling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Good (Sector: Server Archive):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Scenario:&lt;/em&gt; "A CRT monitor flickers to life, printing your actual keystroke latency in green phosphor. Behind the glass, an audio speaker hums: 'Daniel, stop looking at the prompt. The observer at the keyboard cannot save you.' You have 15 seconds."&lt;br&gt;&lt;br&gt;
&lt;em&gt;Choice A:&lt;/em&gt; "SMASH THE MONITOR WITH YOUR BARE HANDS" (Branch: &lt;em&gt;Beast&lt;/em&gt;, Distance +1, Sanity -2)&lt;br&gt;&lt;br&gt;
&lt;em&gt;Choice B:&lt;/em&gt; "TYPE 'WHO IS RUNNING THE SIMULATION?'" (Branch: &lt;em&gt;Meta&lt;/em&gt;, Distance +2, Sanity -3)  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It nails the psychological dread, weaves in real player metrics, and creates a legitimate thematic fork between primal violence and 4th-wall realization.&lt;/p&gt;

&lt;p&gt;But smaller models (especially at 1B) can also slip into flat, low-stakes repetition where choices become trivial synonyms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Weak (Sector: Pulsing Antechamber):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Scenario:&lt;/em&gt; "You enter a dark corridor. The walls are red and slimy. You hear a strange noise in the darkness."&lt;br&gt;&lt;br&gt;
&lt;em&gt;Choice A:&lt;/em&gt; "Walk quietly forward." (Branch: &lt;em&gt;Beast&lt;/em&gt;, Distance +1, Sanity -1)&lt;br&gt;&lt;br&gt;
&lt;em&gt;Choice B:&lt;/em&gt; "Move slowly ahead." (Branch: &lt;em&gt;Meta&lt;/em&gt;, Distance +1, Sanity -1)  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Structurally, this passed all 5 Zod checks—two choices, valid timers, within delta bounds. But narratively, it’s dead on arrival. "Walk quietly" versus "move slowly" is a false dilemma with zero dramatic stakes. This qualitative gap is why automated schema validation alone isn't enough: it guarantees the game engine won't crash, but strong persona prompt constraints and our pre-authored fallback suites are what keep the psychological tension alive when a generation runs flat.&lt;br&gt;
Rather than subjecting players to generative wait times, WAKE.EXE implements an asynchronous 3-node predictive lookahead queue. In our live playthrough, Google Gemma (gemma2-9b-it on Groq) generated each decision batch in ~355ms, while the human player averaged 2,000ms evaluating choices. This delivered a 100% lookahead hit rate with all three queue slots consistently primed in [READY] state, eliminating gameplay latency entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  The look and the sound
&lt;/h3&gt;

&lt;p&gt;The game renders on a 320x180 1-bit canvas scaled up with &lt;code&gt;image-rendering: pixelated&lt;/code&gt;. Shading uses hand-made stipple and checker patterns in the style of early Macintosh graphics, rooms change with a 4-frame stepped zoom, and low Sanity adds warping and scanline jitter.&lt;/p&gt;

&lt;p&gt;For audio I used ElevenLabs' Sound Generation API once, offline, to make eight clips (ambient loop, whisper, monster growl, mirror crack, door creak, footsteps, breath, death sting). The game plays those files, and if one is missing or you are offline it falls back to a small Web Audio synthesizer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;p&gt;TypeScript monorepo (npm workspaces): Vite client, Fastify server, shared Zod schemas, MongoDB Atlas for runs and telemetry, Sentry for tracing. One Node web service on Render serves both the API and the built client.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI on Linux:&lt;/strong&gt; TypeScript complained (TS5011) until I set an explicit &lt;code&gt;rootDir&lt;/code&gt;, and the typecheck failed on clean runners because downstream packages need the declaration files that &lt;code&gt;build&lt;/code&gt; emits. GitHub Copilot found the ordering problem and fixed the workflow in &lt;a href="https://github.com/An16og/WAKE/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render build:&lt;/strong&gt; &lt;code&gt;NODE_ENV=production&lt;/code&gt; made npm skip dev dependencies, so the build died with &lt;code&gt;vite: not found&lt;/code&gt;. I moved Vite and TypeScript into &lt;code&gt;dependencies&lt;/code&gt; and used &lt;code&gt;--include=dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Atlas:&lt;/strong&gt; the server got an SSL alert 80 until my IP was allowed in Atlas. The game falls back to an in-memory store when the database is unreachable, so it never crashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Prompting:&lt;/strong&gt; the model was running but it's interference was useless. Fixed the game mechanics to actually be affected by outputs of models&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;The data is about you.&lt;/strong&gt; The Director runs on how long you hesitate and how you type. With Gemma running locally through Ollama, that profile never leaves your machine. The hosted version sends telemetry anywhere, you can play the game online, save the gameplay and even download the dossier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retries cost nothing.&lt;/strong&gt; A bad generation just gets validated, retried or replaced, and I can be as strict as I like about it. With a metered API every retry would be a bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could swap the brain.&lt;/strong&gt; The provider is one environment variable: Ollama locally or any OpenAI-compatible endpoint. Tuning the Director was a loop of changing the prompt and re-running the same evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline:&lt;/strong&gt; The system runs locally without Wi-Fi using local models (Used Gemma gemma3: 1b) and with minimal latency as it predicts next 3 results while the user makes the choice. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where a closed model would be better:&lt;/strong&gt; the writing. A small model  model of 1-4b parameters like gemma3: 1b writes flatter dread than a frontier model would, and some lines read like a template. A bigger model would still need the same schema checks and fallbacks, because the point is that the game should not break whatever the model does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;The moment it landed. My friend chilli played it first as the story was his idea. He agreed hard on the choice of making game in retro CRT style, &lt;strong&gt;"Made my Halloween happy, I'd rate it 2 full size snicker bars and a Twix Right"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I added Safe Mode because the whole game is built around loud sounds and distortion, and I wanted people who can't take that to still be able to finish it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma is the Director. It turns a measured player profile into schema-validated dilemmas and narration, generated ahead of time through a lookahead queue, with a local Ollama provider and an OpenAI-compatible one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; one Node 22 web service from a &lt;code&gt;render.yaml&lt;/code&gt; serves the Fastify API and the built Vite client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of MongoDB Atlas:&lt;/strong&gt; Atlas stores player runs (state snapshots and checkpoints) and a stream of telemetry events, with an in-memory fallback if it's unreachable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing:&lt;/strong&gt; every Director batch is a &lt;code&gt;gen_ai.director&lt;/code&gt; span with model, latency, retry count, fallback count and player tags. 
&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%2Fu75qa3gl31fmwdoits4b.png" alt="Sentry_SS" width="799" height="372"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs:&lt;/strong&gt; the Sound Generation API produced the game's eight horror sound effects, played from static files with a procedural fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of GitHub Copilot:&lt;/strong&gt; Copilot diagnosed and fixed the monorepo CI build-order failure in &lt;a href="https://github.com/An16og/WAKE/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt;.&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%2Fqm8jcptg20u2ylncfnn9.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%2Fqm8jcptg20u2ylncfnn9.png" alt="GitHub Copilot SS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built solo by Anuj Jain. MIT licensed.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Popo's notebooks only ever listened. So I built him a journal that writes back, on Gemma, offline.</title>
      <dc:creator>Anuj Jain</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:47:32 +0000</pubDate>
      <link>https://dev.to/anujjainaj16/popos-notebooks-only-ever-listened-so-i-built-him-a-journal-that-writes-back-on-gemma-offline-3ilg</link>
      <guid>https://dev.to/anujjainaj16/popos-notebooks-only-ever-listened-so-i-built-him-a-journal-that-writes-back-on-gemma-offline-3ilg</guid>
      <description>&lt;h2&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;/h2&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Popo wrote his journal in notebooks for years. Then he stopped.&lt;/p&gt;

&lt;p&gt;It wasn't that he had nothing to say. Journaling felt &lt;strong&gt;one-sided&lt;/strong&gt;. He logged his thoughts every night and never really sat with them. The page took everything in and never said a word back. And when he wanted to look back at old entries, the only option was flipping through months of his own handwriting.&lt;/p&gt;

&lt;p&gt;I wanted to fix that without losing the one thing a notebook gets right: &lt;strong&gt;nobody else reads it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pepal&lt;/strong&gt; (&lt;em&gt;Your Penpal&lt;/em&gt;) is a journal that writes back. It looks like a physical notebook, and it does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It asks, so the page isn't silent.&lt;/strong&gt; Each day you get three reflection questions tailored to your recent mood, so you sit with your thoughts instead of only dumping them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It stays on your own machine.&lt;/strong&gt; No account, no cloud, no API key. The entries, the database, and the AI all run locally on Popo's system. Not even I can read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can talk to your past instead of scrolling it.&lt;/strong&gt; Ask "when did I last feel like this?" and it answers from your own entries, and only your own entries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When I showed it to Popo, I asked what he thought. He said he wouldn't tell me, he'd write it in the journal. LOL&lt;/p&gt;

&lt;p&gt;I took that as the review.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ItXimDzKIZY" 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%2Fby3gtvcu9fpd6yr5bpnf.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%2Fby3gtvcu9fpd6yr5bpnf.png" alt="The Application asks 3 random questions daily" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot uses sample text. Popo's real journal stays on his machine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pepal is local-first, so there's no public link to click. With Docker, it runs in three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/An16og/pepal-Your-Pen-Pal
&lt;span class="nb"&gt;cd &lt;/span&gt;pepal-Your-Pen-Pal
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt;. The app, Postgres with pgvector, and Ollama all start automatically. On Windows, double-click &lt;code&gt;start.bat&lt;/code&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/An16og" rel="noopener noreferrer"&gt;
        An16og
      &lt;/a&gt; / &lt;a href="https://github.com/An16og/pepal-Your-Pen-Pal" rel="noopener noreferrer"&gt;
        pepal-Your-Pen-Pal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Pepal: Your Penpal - 100% offline, local-first AI physical journal companion. Built with Spring Boot 3, Spring AI, PostgreSQL (pgvector), Ollama, and Docker.
    &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;Pepal: Your Penpal&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A 100% local, offline, physical-style AI journal web app. Built with Spring Boot 3, Spring AI, PostgreSQL (pgvector), Flyway, and vanilla HTML/CSS/JS.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2-Table Architecture&lt;/strong&gt;: Normalized PostgreSQL storage separating &lt;code&gt;journal_entry&lt;/code&gt; metadata from dense &lt;code&gt;entry_embedding&lt;/code&gt; 768-dimensional vectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personality System &amp;amp; Settings&lt;/strong&gt;: Persona presets (&lt;code&gt;GENTLE&lt;/code&gt;, &lt;code&gt;PLAYFUL&lt;/code&gt;, &lt;code&gt;BLUNT&lt;/code&gt;, &lt;code&gt;COACH&lt;/code&gt;, &lt;code&gt;CUSTOM&lt;/code&gt;) and language options (&lt;code&gt;EN&lt;/code&gt;, &lt;code&gt;HINGLISH&lt;/code&gt;) composed dynamically with a strict, non-negotiable Safety Layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Mood-Aware Daily Prompts&lt;/strong&gt;: 3 AI-tailored reflection questions with deterministic static fallback and per-date reshuffling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Entry Lifecycle&lt;/strong&gt;: Create, edit (&lt;code&gt;PUT /api/entries/{id}&lt;/code&gt;), soft-delete (&lt;code&gt;DELETE /api/entries/{id}&lt;/code&gt;), trash listing (&lt;code&gt;GET /api/trash&lt;/code&gt;), conflict-aware restore (&lt;code&gt;POST /api/entries/{id}/restore&lt;/code&gt;), permanent delete, and daily automated purge job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max 1 Daily Reflection per Day&lt;/strong&gt;: Prompted with 3 questions. Enforced via partial unique index and Spring Boot service validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeform Journaling&lt;/strong&gt;: Add unlimited…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/An16og/pepal-Your-Pen-Pal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

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


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (vanilla HTML/CSS/JS)
        │
Spring Boot 3 ──► Spring AI ──► Ollama: gemma3:1b (chat) + nomic-embed-text (768-dim embeddings)
        │
PostgreSQL 16 + pgvector (HNSW, cosine similarity)
   ├─ journal_entry     (text, mood, energy, date, trash state)
   └─ entry_embedding   (768-dim vector)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The models.&lt;/strong&gt; Chat runs on &lt;strong&gt;Gemma 3 1B&lt;/strong&gt; through Ollama. Embeddings come from &lt;code&gt;nomic-embed-text&lt;/code&gt;, which turns each entry into a 768-dimensional vector. Both run locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The database.&lt;/strong&gt; Entry metadata lives in &lt;code&gt;journal_entry&lt;/code&gt;, and the vectors live in a separate &lt;code&gt;entry_embedding&lt;/code&gt; table. Listing your history never drags vectors around, and similarity search runs against an HNSW cosine index (&lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt;) without touching the rest of the row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mood is part of the memory.&lt;/strong&gt; The Energy (High/Low) and Mood (Good/Bad) you pick are embedded along with the text, not just shown as badges. So when you ask the penpal about a feeling, it can find entries by how the day felt, not only by what you wrote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The frontend.&lt;/strong&gt; Plain HTML, CSS, and JavaScript with the Fetch API. No framework, because a journal should open fast and have nothing to learn.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decisions I made for Popo
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;One reflection a day, enforced twice.&lt;/strong&gt; Popo wanted to sit with his thoughts, not drown in a to-do list. So there's a single daily reflection (three questions), and every other entry is freeform and unlimited. The rule is checked in the service layer and again by a partial unique index in Postgres, so no double-click or future bug can slip in a second one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The app has to work when the model doesn't.&lt;/strong&gt; Daily prompts are AI-generated and mood-aware, but there's a deterministic fallback with per-date reshuffling. If Ollama is still loading or busy, Popo still gets three good questions. A journal that opens empty at night is worse than a plain one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A personality, under a fixed safety layer.&lt;/strong&gt; The penpal can be &lt;code&gt;GENTLE&lt;/code&gt;, &lt;code&gt;PLAYFUL&lt;/code&gt;, &lt;code&gt;BLUNT&lt;/code&gt;, &lt;code&gt;COACH&lt;/code&gt;, or &lt;code&gt;CUSTOM&lt;/code&gt;, in English or Hinglish. Whatever is chosen is composed &lt;em&gt;on top of&lt;/em&gt; a non-negotiable Safety Layer that no preset can override. A journal is where people write their worst nights, and a persona should change the tone, never the care.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deleting is reversible.&lt;/strong&gt; Entries go to a trash with a "days remaining" counter and a daily purge job. Because you can backfill past dates, restoring an old reflection can clash with one you've since written for that day. Restore is conflict-aware and returns a &lt;code&gt;409&lt;/code&gt; instead of silently breaking the one-a-day rule.&lt;/p&gt;
&lt;h3&gt;
  
  
  Swapping to Gemma
&lt;/h3&gt;

&lt;p&gt;The app started on &lt;code&gt;llama3.2&lt;/code&gt;. I switched the chat model to &lt;code&gt;gemma3:1b&lt;/code&gt;, and because the model is configuration and not architecture, the app didn't need to be redesigned around it. Swapping models meant changing one environment variable, proving that the local-first architecture is truly model-agnostic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because Popo's journal never leaves his system.&lt;/strong&gt; A hosted model would need every entry sent to someone else's server to be embedded and answered. Here the entry goes from the browser to Postgres on his own machine, and the vector is computed on his own CPU. For something he writes at his most honest, that was the whole point. He was never going to tell me what he'd write. Now there is genuinely no server I could read it from, even if I wanted to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because it costs nothing, forever.&lt;/strong&gt; No API key, no per-token bill, no subscription. A journal is a daily habit for years, and metered pricing punishes exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because I could swap the model.&lt;/strong&gt; Moving from Llama to Gemma changed one piece of the stack, not the product. If Popo gets a stronger laptop, a bigger open model is a config change away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because I can see where the model ends and my rules begin.&lt;/strong&gt; The persona text, the safety layer, and the retrieval query are code and config I can read and edit, and &lt;code&gt;GET /api/settings/preview&lt;/code&gt; returns the exact system prompt before the penpal ever speaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because his entries can't be taken away.&lt;/strong&gt; The models, the vectors, and the entries all live in Docker volumes (&lt;code&gt;pepal_data&lt;/code&gt;, &lt;code&gt;pepal_ollama&lt;/code&gt;) on his machine. If a company deprecates a model or changes its pricing, years of his writing keep working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it lost.&lt;/strong&gt; A 1B model is small, and a hosted frontier model will write more eloquent replies. Sometimes the model relies on repetitive phrasing, ending replies with 'That sounds difficult' instead of digging into the specific nuance of the entry. For a journal opened once a day, I'll take a plainer reply and total privacy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handing It Over
&lt;/h2&gt;

&lt;p&gt;Popo uses Pepal on his own system, every day. When I first showed it to him, he said he wouldn't tell me what he'd write. He'd write it in the journal.&lt;/p&gt;

&lt;p&gt;That sentence is the reason it runs entirely on his machine. He trusts a journal more than a friend, and a hosted app could never have earned that trust. He immediately disconnected his laptop from the Wi-Fi to prove to himself it still worked, then smiled and started typing.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I used GitHub Copilot to scaffold the Spring Boot boilerplate and pgvector queries. The most critical decision we worked through was enforcing the Safety Layer at the service level, ensuring no user-defined CUSTOM persona could ever override the core safety constraints.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Pepal runs &lt;strong&gt;Gemma 3 1B&lt;/strong&gt; locally through Ollama as its chat model, powering the daily reflection questions, the persona-driven replies, and the retrieval-grounded penpal.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for Popo. If you've stopped journaling because the page never answers back, or because you don't trust where the words go, the code is yours. Clone it, run it, and let it write back.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>USHER-Know before you watch</title>
      <dc:creator>Anuj Jain</dc:creator>
      <pubDate>Fri, 02 Oct 2026 23:20:22 +0000</pubDate>
      <link>https://dev.to/anujjainaj16/usher-know-before-you-watch-2hon</link>
      <guid>https://dev.to/anujjainaj16/usher-know-before-you-watch-2hon</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;USHER is a statutory and medical compliance gatekeeper for films and TV series.&lt;/p&gt;

&lt;p&gt;A streaming catalog's age rating tells you who a title is &lt;em&gt;legally&lt;/em&gt; suitable for. It doesn't tell you that a "U" rated family film contains a flashing-screen sequence that can trigger seizures. Disney issued theatrical seizure notices for &lt;em&gt;Incredibles 2&lt;/em&gt; in 2018 over exactly this. Real viewers also don't ask single-field questions. They say things like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm 14, have photosensitive epilepsy and auditory hyperacusis, and want a thriller."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;USHER doesn't just fetch rows. Once the Sanity graph is loaded, the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resolves multiple constraints at once.&lt;/strong&gt; Age, medical hazards, and genre are checked together. In this example it disqualifies &lt;em&gt;Oppenheimer&lt;/em&gt; and &lt;em&gt;Euphoria&lt;/em&gt; on statutory age (CBFC A, 18+). It disqualifies &lt;em&gt;Inception&lt;/em&gt; because, although it is UA 13+, its 18-24Hz strobe sequence violates ITU-R BT.1702 photic limits. It recommends &lt;em&gt;The Dark Knight&lt;/em&gt;, which is UA 13+ compliant with no photic warnings on record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overrides misleading ratings with clinical evidence.&lt;/strong&gt; &lt;em&gt;Incredibles 2&lt;/em&gt; is rated U (all ages), but the retrieved trigger data records the 6Hz Screenslaver sequence, so the verdict moves from safe to &lt;strong&gt;PROHIBITED for photosensitive viewers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turns clinical data into practical directives.&lt;/strong&gt; Instead of "118.5 dB surge at 20ms transition," the viewer gets a plain instruction with exact runtime windows, such as mute or look away between 01:54:30 and 01:56:00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cites its legal and clinical authority.&lt;/strong&gt; Verdicts reference the Cinematograph Act 1952, CBFC Guidelines 2023, ITU-R BT.1702-2, and the WHO 85 dB listening standard, and resolve to one of &lt;code&gt;[SAFE]&lt;/code&gt;, &lt;code&gt;[RESTRICTED]&lt;/code&gt;, &lt;code&gt;[PROHIBITED]&lt;/code&gt;, or &lt;code&gt;[DATA UNAVAILABLE]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refuses to guess.&lt;/strong&gt; If a title or hazard isn't certified in Sanity, USHER returns &lt;code&gt;[VERDICT: DATA UNAVAILABLE]&lt;/code&gt; and advises withholding viewing until it's certified, instead of improvising safety advice from model memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog covers 42 titles across 8 genres, with a dual-theme UI (Space Black &amp;amp; Metallic Slate, or Warm Coffee &amp;amp; Mocha Beige).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Data transparency:&lt;/strong&gt; While film classifications and hazard designations reflect real statutory records and documented public media advisories (e.g., Disney's theatrical seizure notices for &lt;em&gt;Incredibles 2&lt;/em&gt;), specific scene timestamps and decibel measurements are curated demonstration models created for this challenge rather than laboratory-certified clinical datasets. USHER is a demonstration of the architecture, not medical advice.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live project:&lt;/strong&gt; &lt;a href="https://usher-liart.vercel.app/" rel="noopener noreferrer"&gt;https://usher-liart.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;GitHub repository: &lt;a href="https://github.com/An16og/usher" rel="noopener noreferrer"&gt;https://github.com/An16og/usher&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;studio/&lt;/code&gt;: Sanity Studio schemas and the seeding script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web/&lt;/code&gt;: Next.js app, &lt;code&gt;/api/chat&lt;/code&gt; route, and the Sanity query client (&lt;code&gt;mcp-client.ts&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Sanity is the only source of truth for every safety claim USHER makes. I modeled compliance data as typed, connected documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Movie&lt;/strong&gt;: the title being audited (42)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CBFC Rating&lt;/strong&gt;: its statutory classification (5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger Warning&lt;/strong&gt;: hazard, severity (Mild / Severe / Critical), clinical description, timestamps, and affected demographics (13)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility Rule&lt;/strong&gt;: governing standards, with restriction level and trigger condition (7)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Retrieval.&lt;/strong&gt; I used GROQ over the Sanity Content Lake HTTP Query API as the primary retrieval engine, exposed to the model as a custom Vercel AI SDK tool named &lt;code&gt;search_knowledge_base&lt;/code&gt;. One query dereferences the full relational path, &lt;code&gt;Movie → CBFC Rating → Trigger Warnings → Accessibility Rules&lt;/code&gt;, in a single round trip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*[_type == "movie"]{
  _id, title, releaseYear, synopsis,
  cbfcRating-&amp;gt;{ ratingCode, minimumAge, description },
  triggerWarnings[]-&amp;gt;{
    hazard, severity, category, clinicalDescription,
    timestamps, affectedDemographics
  },
  "applicableRules": *[_type == "accessibilityRule"
    &amp;amp;&amp;amp; references(^.triggerWarnings[]._ref)]{
    ruleCode, title, targetDemographic, restrictionLevel,
    triggerCondition, restrictionDetails, source
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose structured GROQ traversal over vector search deliberately. Embeddings are known to blur negation ("contains strobe" vs. "does not contain strobe"), which is unacceptable for a safety use case. GROQ returns verified, typed entities with exact timestamps and statutory clauses, with no chunking or similarity guesswork.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the agent does with the content.&lt;/strong&gt; The retrieved graph is the only context the model reasons over. It performs the constraint resolution, rating-versus-clinical-evidence overrides, scene-skip directives, and verdict synthesis described above. If nothing certified comes back, it refuses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sanity Context MCP.&lt;/strong&gt; The codebase also includes a JSON-RPC 2.0 adapter (&lt;code&gt;callSanityContextMcp&lt;/code&gt;) for Sanity's MCP endpoint, kept behind a fallback condition. The live app currently retrieves through the direct GROQ path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability.&lt;/strong&gt; The gateway uses Gemini with multi-key failover, plus a deterministic fallback engine, so users still get a grounded answer from Sanity data if the model is unavailable.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;ntxlr356&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contains 42 titles, 13 trigger warnings, 7 accessibility rules, and 5 CBFC ratings.&lt;/p&gt;

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

&lt;p&gt;USHER was built iteratively in Google Antigravity as the AI-native development environment, covering schema design, the seeding script, the GROQ query layer, the agent's guardrails, and the UI. A transcript isn't included because Antigravity isn't currently supported by the DEV Agent Sessions uploader.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
