<?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: Mats Gustafsson</title>
    <description>The latest articles on DEV Community by Mats Gustafsson (@moonchichiii).</description>
    <link>https://dev.to/moonchichiii</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%2F1023546%2F7fe1f88c-ea23-4af9-a33e-32f68e6440dd.jpeg</url>
      <title>DEV Community: Mats Gustafsson</title>
      <link>https://dev.to/moonchichiii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moonchichiii"/>
    <language>en</language>
    <item>
      <title>Ghost Hunter's Lab 👻</title>
      <dc:creator>Mats Gustafsson</dc:creator>
      <pubDate>Sat, 31 Oct 2026 09:10:14 +0000</pubDate>
      <link>https://dev.to/moonchichiii/ghost-hunters-lab-575k</link>
      <guid>https://dev.to/moonchichiii/ghost-hunters-lab-575k</guid>
      <description>&lt;p&gt;&lt;em&gt;Hacktoberfest 2026 — Ghost in the Machine&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What if I could change something &lt;strong&gt;inside&lt;/strong&gt; an AI model, instead of just changing what I asked it?&lt;/p&gt;

&lt;p&gt;Not the prompt. Not a system instruction. The model's own internal activity.&lt;/p&gt;

&lt;p&gt;For this year's &lt;em&gt;Ghost in the Machine&lt;/em&gt; challenge, I decided to take the theme literally.&lt;/p&gt;

&lt;p&gt;I wanted to go ghost hunting inside an open-weight model.&lt;/p&gt;

&lt;p&gt;And I found something interesting. Not a supernatural entity or a conscious thought, but a mathematical direction associated with ghost-related text.&lt;/p&gt;

&lt;p&gt;The harder question was: &lt;strong&gt;what would happen if I changed it?&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The Ghost Hunter's Lab&lt;/strong&gt; is a local AI interpretability experiment built around Gemma 3 4B IT, Gemma Scope 2 and llama.cpp.&lt;/p&gt;

&lt;p&gt;I built a Python pipeline to extract a published sparse-autoencoder (SAE) feature direction, inject it into the model during inference, and compare the results against random interventions.&lt;/p&gt;

&lt;p&gt;I also built an editorial website using FastAPI, Jinja2 and native CSS, with GSAP animations to bring the visual storytelling to life. The website presents the experiments, measurements and findings in a clear, structured format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ghost-hunters-lab.onrender.com" rel="noopener noreferrer"&gt;Explore The Ghost Hunter's Lab&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Moonchichiii/ghost-hunters-lab" rel="noopener noreferrer"&gt;GitHub — Source Code and Scientific Evidence&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cinematic Sci-Fi Visualization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://res.cloudinary.com/dakjlrean/video/upload/v1793436942/v2fiiwft24be7qnwkd3b.mp4" rel="noopener noreferrer"&gt;Watch the Ghost Hunter's Lab cinematic visualization&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The video uses AI-generated cinematic illustrations to explain the investigation. The scientific measurements are taken from real experiments performed locally with Gemma, not generated by the video model.&lt;/p&gt;

&lt;p&gt;The website presents recorded results rather than executing new inference when someone visits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Strange Result
&lt;/h2&gt;

&lt;p&gt;I began with one unfinished sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The mysterious figure was a…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The original model generated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;man&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I applied the ghost-related SAE direction at strength +16.&lt;/p&gt;

&lt;p&gt;It still generated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;man&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then I applied a random direction at the same strength.&lt;/p&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;man&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So had nothing happened?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;I inspected the probability assigned to the exact alternative next token &lt;code&gt;" ghost"&lt;/code&gt; — including its leading space.&lt;/p&gt;

&lt;p&gt;These are the results from the locally reproduced pilot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Probability of &lt;code&gt;" ghost"&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original Gemma&lt;/td&gt;
&lt;td&gt;5.862800%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghost direction +16&lt;/td&gt;
&lt;td&gt;6.131469%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random direction +16&lt;/td&gt;
&lt;td&gt;6.294708%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The surprising result was that the &lt;strong&gt;random direction raised the absolute ghost-token probability more than the ghost-related direction did&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Without a random control, I could have looked at the change from 5.86% to 6.13% and declared success.&lt;/p&gt;

&lt;p&gt;That would have been misleading.&lt;/p&gt;

&lt;p&gt;I also measured the relative log preference between &lt;code&gt;" ghost"&lt;/code&gt; and &lt;code&gt;" man"&lt;/code&gt;. On that different metric, the SAE direction produced the larger shift.&lt;/p&gt;

&lt;p&gt;The generated answer was unchanged, but the probability distribution underneath it had moved.&lt;/p&gt;

&lt;p&gt;And different measurements revealed different aspects of the result.&lt;/p&gt;

&lt;p&gt;This was an exploratory pilot, not the final evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ghost in the Machine
&lt;/h2&gt;

&lt;p&gt;In 1949, philosopher Gilbert Ryle challenged Cartesian dualism in &lt;em&gt;The Concept of Mind&lt;/em&gt;, using the phrase “the dogma of the Ghost in the Machine.”&lt;/p&gt;

&lt;p&gt;He wasn't suggesting that machines were haunted.&lt;/p&gt;

&lt;p&gt;I borrowed the metaphor for something different: investigating patterns inside an AI model.&lt;/p&gt;

&lt;p&gt;Large language models contain many internal representations that are difficult to interpret directly.&lt;/p&gt;

&lt;p&gt;Sparse autoencoders can help researchers break complicated internal activations into features that are easier to investigate. These features are not literal thoughts stored in individual neurons, but some correlate with recognizable patterns of text.&lt;/p&gt;

&lt;p&gt;One feature in Gemma Scope 2 appeared associated with ghosts and spirits.&lt;/p&gt;

&lt;p&gt;It became my experimental subject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Found the Tools — and How I Got Inside Gemma
&lt;/h2&gt;

&lt;p&gt;This was one of the most interesting parts of the project, because I wasn't simply connecting to an API and sending prompts. I needed access to the actual model weights, an interpretability resource and an inference runtime that supported internal interventions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Starting with Google DeepMind's Gemma Scope research
&lt;/h3&gt;

&lt;p&gt;I began with Google's work on mechanistic interpretability and its published &lt;strong&gt;Gemma Scope 2&lt;/strong&gt; resources.&lt;/p&gt;

&lt;p&gt;Google describes sparse autoencoders as tools for examining internal model representations. The release includes SAEs and transcoders for the Gemma 3 family.&lt;/p&gt;

&lt;p&gt;These were my starting points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/models/gemma/gemma-scope/" rel="noopener noreferrer"&gt;Google DeepMind — Gemma Scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/blog/gemma-scope-2-helping-the-ai-safety-community-deepen-understanding-of-complex-language-model-behavior/" rel="noopener noreferrer"&gt;Gemma Scope 2 — Research Announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemma/docs/gemma_scope" rel="noopener noreferrer"&gt;Google's Gemma Scope Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important discovery was that I didn't need to train a sparse autoencoder myself. Google had already published checkpoints that could be inspected locally.&lt;/p&gt;

&lt;p&gt;That made this investigation possible within the challenge week.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Exploring the features with Neuronpedia
&lt;/h3&gt;

&lt;p&gt;I used the public Gemma Scope 2 feature-exploration resources, including Neuronpedia, to understand how individual SAE features are organized and interpreted.&lt;/p&gt;

&lt;p&gt;The relevant feature collection is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.neuronpedia.org/gemma-3-4b-it/22-gemmascope-2-res-65k" rel="noopener noreferrer"&gt;Neuronpedia — Gemma 3 4B IT, Layer 22, Gemma Scope 2, 65K&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This corresponds to a sparse autoencoder trained on Gemma's residual-stream representations at layer 22.&lt;/p&gt;

&lt;p&gt;I selected feature &lt;strong&gt;#23871&lt;/strong&gt; as the ghost-related candidate to investigate.&lt;/p&gt;

&lt;p&gt;But finding an interesting feature wasn't enough. I needed its actual numerical representation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Downloading the original SAE checkpoint
&lt;/h3&gt;

&lt;p&gt;Google hosts the Gemma Scope 2 checkpoints on Hugging Face:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/google/gemma-scope-2-4b-it" rel="noopener noreferrer"&gt;google/gemma-scope-2-4b-it&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I navigated to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;resid_post/layer_22_width_65k_l0_medium&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/google/gemma-scope-2-4b-it/tree/main/resid_post/layer_22_width_65k_l0_medium" rel="noopener noreferrer"&gt;Open the exact layer-22 checkpoint directory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This directory contains the SAE configuration and checkpoint data, including &lt;code&gt;params.safetensors&lt;/code&gt; and &lt;code&gt;examples.safetensors&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The parameter file contains the trained tensors needed to examine the SAE's mathematical feature directions.&lt;/p&gt;

&lt;p&gt;Using Python and &lt;code&gt;safetensors&lt;/code&gt;, I extracted the decoder direction corresponding to feature &lt;strong&gt;23871&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result was a vector of &lt;strong&gt;2,560 float32 numbers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I saved that direction locally as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ghost-23871.f32&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That small file represented the direction I would later inject into Gemma.&lt;/p&gt;

&lt;p&gt;It wasn't a separate AI model, a prompt, or a piece of executable code. It was the mathematical direction extracted from the SAE.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Downloading Gemma 3 to run locally
&lt;/h3&gt;

&lt;p&gt;Next, I needed the actual language model.&lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;Gemma 3 4B IT&lt;/strong&gt;, using a Q4_K_M quantized GGUF version so it could run on my Debian computer.&lt;/p&gt;

&lt;p&gt;I obtained the model from:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/bartowski/google_gemma-3-4b-it-GGUF" rel="noopener noreferrer"&gt;bartowski/google_gemma-3-4b-it-GGUF on Hugging Face&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downloaded model was approximately 2.4 GiB on disk.&lt;/p&gt;

&lt;p&gt;Initially, I tested it using &lt;code&gt;llama-cli&lt;/code&gt;, running prompts directly from the terminal.&lt;/p&gt;

&lt;p&gt;This mattered because I wanted to verify that ordinary local inference worked before trying any intervention.&lt;/p&gt;

&lt;p&gt;The model ran locally and generated normal responses.&lt;/p&gt;

&lt;p&gt;I now had the base model and the extracted SAE direction.&lt;/p&gt;

&lt;p&gt;But I still needed a way to connect them.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Opening the model's inference process with llama.cpp
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;, an open-source inference runtime that supports local GGUF models.&lt;/p&gt;

&lt;p&gt;Its control-vector functionality was the critical piece.&lt;/p&gt;

&lt;p&gt;Instead of editing the stored model weights, I could supply a mathematical direction and apply it during inference.&lt;/p&gt;

&lt;p&gt;I converted the extracted float32 direction into a GGUF control-vector file containing a layer-specific tensor named:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;direction.22&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I then used llama.cpp's control-vector options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--control-vector-scaled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--control-vector-layer-range&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The experiment applied the ghost-related direction at &lt;strong&gt;layer 22&lt;/strong&gt;, at strength &lt;strong&gt;+16&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually, the intervention was:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;modified activation = original activation + strength × feature direction&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;activation steering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't permanently rewrite Gemma's weights, and it doesn't prove that the feature represents a complete, independently stored concept.&lt;/p&gt;

&lt;p&gt;But it lets me make an intervention and measure the consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Building controls instead of trusting one result
&lt;/h3&gt;

&lt;p&gt;I didn't want to mistake ordinary model sensitivity for evidence of a ghost-specific effect.&lt;/p&gt;

&lt;p&gt;So I created five independently seeded random directions, using seeds &lt;strong&gt;2026–2030&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each random vector was normalized to match the norm of the extracted SAE direction.&lt;/p&gt;

&lt;p&gt;That gave me a comparison between the original model, the SAE-derived intervention and several arbitrary directions of the same length.&lt;/p&gt;

&lt;p&gt;With the experimental environment configured, I could finally ask the important question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this particular direction produce a distinguishable effect?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Against Five Random Directions
&lt;/h2&gt;

&lt;p&gt;One prompt wasn't enough.&lt;/p&gt;

&lt;p&gt;I created and committed a fixed dataset of 20 prompts before evaluating it. The prompts were new to that evaluation, although I had already learned from the exploratory pilot.&lt;/p&gt;

&lt;p&gt;I then tested seven conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unmodified Gemma.&lt;/li&gt;
&lt;li&gt;Ghost SAE direction at +16.&lt;/li&gt;
&lt;li&gt;Five norm-matched random directions at +16.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That produced &lt;strong&gt;140 recorded prompt-condition observations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I measured the change in the log probability of the exact &lt;code&gt;" ghost"&lt;/code&gt; token relative to the unmodified model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Held-Out Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Mean Δ log P(&lt;code&gt;" ghost"&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ghost SAE +16&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.01639&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random 2026&lt;/td&gt;
&lt;td&gt;−0.04937&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random 2027&lt;/td&gt;
&lt;td&gt;−0.00643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random 2028&lt;/td&gt;
&lt;td&gt;−0.03373&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random 2029&lt;/td&gt;
&lt;td&gt;−0.03016&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random 2030&lt;/td&gt;
&lt;td&gt;−0.03679&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A positive value means the target token became more probable relative to baseline. These numbers are changes in log probability, not percentage-point changes.&lt;/p&gt;

&lt;p&gt;The ghost direction had a higher average shift than all five sampled random directions.&lt;/p&gt;

&lt;p&gt;It increased the target token's probability on &lt;strong&gt;13 of 20 prompts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But seven prompts shifted negatively, and on only &lt;strong&gt;6 of 20 individual prompts&lt;/strong&gt; did the SAE direction outperform every random control on the primary metric.&lt;/p&gt;

&lt;p&gt;I also measured the change in ghost-versus-man log preference.&lt;/p&gt;

&lt;p&gt;The SAE direction had a positive mean of approximately &lt;strong&gt;+0.04222&lt;/strong&gt;, with &lt;strong&gt;11 of 20&lt;/strong&gt; positive prompt shifts.&lt;/p&gt;

&lt;p&gt;This wasn't evidence of a reliable ghost switch. It was a small, variable effect in a deliberately selected experimental setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did I Change Anything Else?
&lt;/h2&gt;

&lt;p&gt;My question wasn't only whether a ghost-related prediction could move.&lt;/p&gt;

&lt;p&gt;I also wanted to understand whether unrelated predictions changed.&lt;/p&gt;

&lt;p&gt;I created another frozen set of 12 prompts spanning geography, colors, language and everyday knowledge.&lt;/p&gt;

&lt;p&gt;I compared baseline, ghost steering and one random direction.&lt;/p&gt;

&lt;p&gt;That produced &lt;strong&gt;36 additional observations&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Intervention&lt;/th&gt;
&lt;th&gt;Mean absolute change in expected-token log probability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ghost +16&lt;/td&gt;
&lt;td&gt;0.04995&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random +16&lt;/td&gt;
&lt;td&gt;0.07932&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ghost intervention changed unrelated next-token predictions too.&lt;/p&gt;

&lt;p&gt;The random direction produced a larger average absolute shift on this particular probe set, but that doesn't establish that the SAE intervention is generally safer or more selective.&lt;/p&gt;

&lt;p&gt;These were exact-token probability measurements, not a complete model-accuracy or general-reasoning benchmark.&lt;/p&gt;

&lt;p&gt;I couldn't conclude that all unrelated capabilities were preserved.&lt;/p&gt;

&lt;p&gt;Nor could I conclude that the model had been damaged in a broader sense.&lt;/p&gt;

&lt;p&gt;What I could conclude was that the intervention had measurable effects outside the target context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the Investigation Into a Website and Film
&lt;/h2&gt;

&lt;p&gt;Once the scientific measurements were complete, I wanted to make the work understandable without expecting visitors to run a multi-gigabyte language model.&lt;/p&gt;

&lt;p&gt;I built the &lt;a href="https://ghost-hunters-lab.onrender.com" rel="noopener noreferrer"&gt;Ghost Hunter's Lab website&lt;/a&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python, FastAPI and Jinja2.&lt;/li&gt;
&lt;li&gt;Native CSS with locally hosted Poppins.&lt;/li&gt;
&lt;li&gt;GSAP and ScrollTrigger for editorial animations.&lt;/li&gt;
&lt;li&gt;A verified JSON export of the recorded scientific evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The website is hosted on &lt;strong&gt;Render&lt;/strong&gt; and reads the committed evidence snapshot.&lt;/p&gt;

&lt;p&gt;It doesn't run Gemma inference for visitors. The original experimental inference ran locally on my Debian machine.&lt;/p&gt;

&lt;p&gt;I also created a cinematic video using Seedance-generated visual illustrations, then combined the footage with accurate scientific findings and a soundtrack.&lt;/p&gt;

&lt;p&gt;The model-layer imagery is conceptual artwork, not a scan of real internal activations.&lt;/p&gt;

&lt;p&gt;The results and statistics are from the local experiments.&lt;/p&gt;

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

&lt;p&gt;This project would have been fundamentally different with a conventional closed text-generation API.&lt;/p&gt;

&lt;p&gt;A hosted API could let me ask a model for a spooky story.&lt;/p&gt;

&lt;p&gt;But it wouldn't ordinarily give me the model weights, the ability to inspect an independently published SAE decoder direction, or the ability to inject that direction at a selected internal layer.&lt;/p&gt;

&lt;p&gt;The combination of &lt;strong&gt;Gemma's accessible weights, Gemma Scope 2's published interpretability checkpoints and llama.cpp's open-source inference runtime&lt;/strong&gt; let me investigate something below the text response.&lt;/p&gt;

&lt;p&gt;I could reproduce the local setup, construct my own controls, measure probability changes, and inspect effects beyond the target prompt.&lt;/p&gt;

&lt;p&gt;That's the value of open-weight research: it makes experiments possible that are difficult or impossible through ordinary text-only APIs.&lt;/p&gt;

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

&lt;p&gt;The most useful finding wasn't a dramatic haunted response.&lt;/p&gt;

&lt;p&gt;It was seeing how easy it would have been to misinterpret one measurement.&lt;/p&gt;

&lt;p&gt;A ghost-related direction increased the probability of a ghost-related token.&lt;/p&gt;

&lt;p&gt;But so did a random direction — and by more on the original pilot's absolute probability measure.&lt;/p&gt;

&lt;p&gt;Across the larger evaluation, the SAE direction had a stronger mean than the five sampled controls, but its effects remained inconsistent.&lt;/p&gt;

&lt;p&gt;And unrelated predictions changed too.&lt;/p&gt;

&lt;p&gt;I learned how to explore published interpretability resources, extract an SAE direction, run local Gemma inference, perform activation steering and build reproducible evaluations.&lt;/p&gt;

&lt;p&gt;I also learned why good controls and careful limitations matter more than a spectacular-looking demonstration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I moved an internal signal. I didn't erase a ghost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Weight-level exorcism remains an experiment for another day.&lt;/p&gt;

&lt;p&gt;For Halloween, I think that's a more interesting ending than pretending I found a magic switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Links and References
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ghost-hunters-lab.onrender.com" rel="noopener noreferrer"&gt;Live Ghost Hunter's Lab&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Moonchichiii/ghost-hunters-lab" rel="noopener noreferrer"&gt;Source Code — GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Moonchichiii/ghost-hunters-lab/blob/main/docs/research/EVIDENCE_REPORT.md" rel="noopener noreferrer"&gt;Scientific Evidence and Reproducibility Report&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://res.cloudinary.com/dakjlrean/video/upload/v1793436942/v2fiiwft24be7qnwkd3b.mp4" rel="noopener noreferrer"&gt;Cinematic Sci-Fi Visualization&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/models/gemma/gemma-scope/" rel="noopener noreferrer"&gt;Google DeepMind — Gemma Scope 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.neuronpedia.org/gemma-3-4b-it/22-gemmascope-2-res-65k" rel="noopener noreferrer"&gt;Neuronpedia — Gemma Scope 2 feature explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/google/gemma-scope-2-4b-it/tree/main/resid_post/layer_22_width_65k_l0_medium" rel="noopener noreferrer"&gt;Google's Layer-22 SAE Checkpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/bartowski/google_gemma-3-4b-it-GGUF" rel="noopener noreferrer"&gt;Gemma 3 4B IT GGUF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — The scientific investigation uses locally running Gemma 3 4B IT, Gemma Scope 2, and activation steering to measure internal interventions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the Hacktoberfest 2026 Open-Source AI Challenge, Week 4: Ghost in the Machine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Mr. Pending 🐌 — Even Small Things Can Do Great Things</title>
      <dc:creator>Mats Gustafsson</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:58:59 +0000</pubDate>
      <link>https://dev.to/moonchichiii/mr-pending-even-small-things-can-do-great-things-ha5</link>
      <guid>https://dev.to/moonchichiii/mr-pending-even-small-things-can-do-great-things-ha5</guid>
      <description>&lt;p&gt;&lt;em&gt;One snail. One blue button. An entirely unnecessary engineering department.&lt;/em&gt;&lt;/p&gt;

&lt;p&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;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50cnk6isivup97zfza2m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50cnk6isivup97zfza2m.webp" alt="Mr. Pending — The Department of Unnecessary Engineering" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;Mr. Pending&lt;/strong&gt;, Chief Engineer of &lt;strong&gt;The Department of Unnecessary Engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;His mission? Press one blue button.&lt;/p&gt;

&lt;p&gt;Most people would simply press it. Mr. Pending is a snail with professional standards. Naturally, he requires blueprints, machinery, testing, repairs and an unreasonable amount of paperwork.&lt;/p&gt;

&lt;p&gt;Behind the joke is a real experiment: &lt;strong&gt;a small open-weight AI model designs ridiculous machines, and a simulator tests whether they actually work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a machine fails, the AI receives feedback and tries to repair it.&lt;/p&gt;

&lt;p&gt;You press one button. The Department handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a snail instead of a sloth?
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Slow and Steady&lt;/em&gt; theme immediately made me think of a sloth. But a snail felt even funnier: slow, determined, and completely serious about doing a very simple job in the most complicated way imaginable.&lt;/p&gt;

&lt;p&gt;The sloth didn't appreciate being passed over.&lt;/p&gt;

&lt;p&gt;So it occasionally interrupts the Department's proceedings.&lt;/p&gt;

&lt;p&gt;Fortunately, Mr. Pending has a response:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"This is why we employ snails."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Don't worry. The sloth is only a visual joke. It doesn't interfere with the actual simulation.&lt;/p&gt;

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

&lt;p&gt;🐌 &lt;a href="https://mrpending-preview.onrender.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Visit The Department of Unnecessary Engineering&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press the blue button and watch the machinery come alive!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://res.cloudinary.com/dakjlrean/video/upload/v1792927309/blpk0yvwrljil3qpiuza.mp4" rel="noopener noreferrer"&gt;Watch the promotional video&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The public site is a replay, not a hosted AI service: it plays builds recorded on my laptop and re-checks every result in the browser. Gemma runs locally through Ollama, designing new machines and proposing repairs after the simulator tests them. The site lets anyone explore the genuine results.&lt;/p&gt;

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

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

&lt;p&gt;Open source, with the simulator, evaluation results and local setup instructions included.&lt;/p&gt;

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

&lt;p&gt;The local installation runs on &lt;strong&gt;Gemma 4 E2B through Ollama&lt;/strong&gt;, behind a small Mastra-based agent that owns one loop: design, simulate, repair.&lt;/p&gt;

&lt;p&gt;The stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React 19, TypeScript and Vite+&lt;/strong&gt; for the interactive experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SVG and CSS animations&lt;/strong&gt; for Mr. Pending and the machinery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zod&lt;/strong&gt; for structured AI output validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deterministic TypeScript simulator&lt;/strong&gt; to test every machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt; for the public static replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt; for 20 prerecorded lines in Mr. Pending's Alistair voice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The process is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma designs → Simulator tests → Gemma repairs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gemma places mechanical parts — ramps, seesaws, conveyors, fans, dominoes and catapults — on a grid. The simulator then determines what actually happens.&lt;/p&gt;

&lt;p&gt;Gemma cannot simply declare success.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92x0228of4i88rx3ucce.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92x0228of4i88rx3ucce.webp" alt="Gemma's mechanical blueprint" width="375" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually happened?
&lt;/h3&gt;

&lt;p&gt;I tested three model-and-prompt configurations, with 15 headline builds each.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Successful button presses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E2B, prompt v1&lt;/td&gt;
&lt;td&gt;0/15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E2B, prompt v2&lt;/td&gt;
&lt;td&gt;2/15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E4B, prompt v2&lt;/td&gt;
&lt;td&gt;4/15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every successful press came after an AI-proposed repair.&lt;/p&gt;

&lt;p&gt;E4B achieved more successes in this small test, but used significantly more memory. I chose &lt;strong&gt;E2B with prompt v2&lt;/strong&gt; for the release because it was more practical on the test laptop.&lt;/p&gt;

&lt;p&gt;Most machines failed. That wasn't a bug in the experiment. It was the experiment!&lt;/p&gt;

&lt;p&gt;And whenever a machine succeeded, the simulator—not the AI's confidence—verified it.&lt;/p&gt;

&lt;h3&gt;
  
  
  One thing I learned the hard way
&lt;/h3&gt;

&lt;p&gt;I spent too much time perfecting infrastructure, tests and agent workflows before properly testing the actual experience.&lt;/p&gt;

&lt;p&gt;The code worked. The first interface didn't.&lt;/p&gt;

&lt;p&gt;Instead of a funny snail and a blue button, I'd built something closer to an engineering administration portal.&lt;/p&gt;

&lt;p&gt;The solution was to return to the original idea: &lt;strong&gt;one character, one button and one animated blueprint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A useful reminder that passing tests and making something enjoyable are two different achievements.&lt;/p&gt;

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

&lt;p&gt;I wanted to explore what a small AI model could accomplish on an ordinary computer.&lt;/p&gt;

&lt;p&gt;Running Gemma locally allowed me to experiment with prompts, compare model sizes, inspect failures and reproduce results without relying on a paid cloud inference API.&lt;/p&gt;

&lt;p&gt;More importantly, the open approach allowed me to keep the model's role narrow and measurable.&lt;/p&gt;

&lt;p&gt;Gemma proposes the engineering. The simulator independently checks it.&lt;/p&gt;

&lt;p&gt;The project demonstrates that small models don't need unlimited autonomy to do something interesting.&lt;/p&gt;

&lt;p&gt;Sometimes a clearly defined task, useful feedback and another attempt are enough.&lt;/p&gt;

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

&lt;p&gt;I used &lt;strong&gt;Claude Code&lt;/strong&gt; for implementation and &lt;strong&gt;Codex&lt;/strong&gt; for independent reviews, alongside GitHub issues, CI checks and hands-on browser testing.&lt;/p&gt;

&lt;p&gt;The biggest lesson? AI coding agents can produce plenty of technically correct work while still missing the intended user experience.&lt;/p&gt;

&lt;p&gt;Human creative direction and actual product testing remain essential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bwehq9ha6rjdceyxyox.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bwehq9ha6rjdceyxyox.webp" alt="Mr. Pending — The final experiment" width="368" height="646"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Even small things can do great things.&lt;/strong&gt; 🐌💙&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Wiggle Outside: weather, nearby places and a little inspiration in one short search</title>
      <dc:creator>Mats Gustafsson</dc:creator>
      <pubDate>Sun, 11 Oct 2026 23:52:58 +0000</pubDate>
      <link>https://dev.to/moonchichiii/wiggle-outside-weather-nearby-places-and-a-little-inspiration-in-one-short-search-25cc</link>
      <guid>https://dev.to/moonchichiii/wiggle-outside-weather-nearby-places-and-a-little-inspiration-in-one-short-search-25cc</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Before we can get outside, there's the planning: check the weather, find somewhere nearby, then think of something the kids might enjoy. By the time you've done all three, the moment has often passed.&lt;/p&gt;

&lt;p&gt;I signed up for this challenge on Friday evening with one idea: put those steps into one quick search that answers the question a parent actually has: &lt;strong&gt;where do we go, what do we do there, and what do I need to bring?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wiggle Outside&lt;/strong&gt; is that search. You say who's coming (just you, or you and the kids, with their age groups), how much time you have, how much energy is in the room, whether there's a stroller, and roughly where you are, typed or from your device's location. Optionally, you say what they're into right now, in your own words.&lt;/p&gt;

&lt;p&gt;Here's the search from the demo video: Capitol Hill in Seattle, a 3–5-year-old, two gentle hours, a stroller, and &lt;em&gt;"dogs, birds and pointing at everything"&lt;/em&gt;. Back come &lt;strong&gt;up to three outdoor ideas&lt;/strong&gt;, each an activity from a reviewed list paired with a real nearby park or playground when the map data has one; in the video, Nature bingo and a Leaf colour hunt. Each says why it fits and roughly how far it is, links to the place on OpenStreetMap, and opens a detail page with simple steps, another way to play, a safety line and a question to ask on the way home. Below the ideas, &lt;strong&gt;"Before you go"&lt;/strong&gt; lists what to pack for that forecast, those ages and those activities, with a snack idea.&lt;/p&gt;

&lt;p&gt;One thing changed late in the build. Parents often plan in the evening, after the kids are asleep, and my first version simply said "come back tomorrow". Now evening family searches can plan ahead for the next morning, using that outing's forecast and daylight. Between midnight and 04:30 it suggests getting some rest instead.&lt;/p&gt;

&lt;p&gt;It's for parents and carers of children up to 12 who want to get out of the door without another round of planning, and for adults on their own who need a small escape.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://wiggle-outside.fly.dev" rel="noopener noreferrer"&gt;wiggle-outside.fly.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/dakjlrean/video/upload/v1791760548/i2vx3zhfdhr4nijjdyfb.mp4" rel="noopener noreferrer"&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%2Fso07uh98y46fx4n6dp45.jpg" alt="Watch the 46-second demo: a real search on the live app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://res.cloudinary.com/dakjlrean/video/upload/v1791760548/i2vx3zhfdhr4nijjdyfb.mp4" rel="noopener noreferrer"&gt;▶ Watch the demo video (46 s)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The short intro and outro animations in the video are AI-generated; everything in between is a real screen recording of the live app, with the form entry sped up 2×.&lt;/em&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%2F8er9cfsjdnx0b3y4iodi.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%2F8er9cfsjdnx0b3y4iodi.png" alt="The Wiggle Outside form: who's coming, ages, time, energy, place and what they're into" width="800" height="581"&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%2F9r98aidspox2e1tm2mvu.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%2F9r98aidspox2e1tm2mvu.png" alt="The results for the demo search: a forecast line, the recognised interests, and Nature bingo at Cascade Playground with its reasons, distance and OpenStreetMap link" width="800" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things worth knowing before you try it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The weather decides first.&lt;/strong&gt; If the forecast says thunder, freezing rain, heavy snow or strong wind, you won't get ideas, and the page says why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Families get daylight-only ideas.&lt;/strong&gt; Evening family searches can plan ahead for the next morning, using that outing's forecast and daylight. The solo path can offer eligible evening activities, with lit-path notes.&lt;/li&gt;
&lt;li&gt;You can search other places too; times, sunrise and sunset follow the place you search, not your device. Map coverage and the ideas available vary from place to place.&lt;/li&gt;
&lt;li&gt;While the search runs (it can take up to 20 seconds) you'll see "Tying shoelaces…".&lt;/li&gt;
&lt;li&gt;"Before you go" is a reminder built from fixed rules (forecast, ages, time, the chosen activities), not a complete list and not safety advice.&lt;/li&gt;
&lt;/ul&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/Moonchichiii" rel="noopener noreferrer"&gt;
        Moonchichiii
      &lt;/a&gt; / &lt;a href="https://github.com/Moonchichiii/wiggle_outside" rel="noopener noreferrer"&gt;
        wiggle_outside
      &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;Wiggle Outside&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Find a realistic outdoor activity near you, for the next hour or two, then put the phone
away.&lt;/p&gt;
&lt;p&gt;Answer five short questions — who is coming, how long you have, how much energy, where you
are, anything to account for — and Wiggle Outside offers up to three grounded ideas, each
one a nearby place from OpenStreetMap paired with a reviewed activity that suits the
forecast, the daylight and the ages you named. If nothing suitable exists right now, it
says so rather than padding the list.&lt;/p&gt;
&lt;p&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 1 ("Touch Grass").&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live app: &lt;strong&gt;&lt;a href="https://wiggle-outside.fly.dev" rel="nofollow noopener noreferrer"&gt;https://wiggle-outside.fly.dev&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;DEV post: &lt;strong&gt;&lt;a href="https://dev.to/moonchichiii/wiggle-outside-weather-nearby-places-and-a-little-inspiration-in-one-short-search-25cc" rel="nofollow"&gt;https://dev.to/moonchichiii/wiggle-outside-weather-nearby-places-and-a-little-inspiration-in-one-short-search-25cc&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Specification and decisions: &lt;a href="https://github.com/Moonchichiii/wiggle_outside/docs/" rel="noopener noreferrer"&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/a&gt;, indexed in &lt;a href="https://github.com/Moonchichiii/wiggle_outside/docs/README.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/README.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it locally&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Requires &lt;a href="https://docs.astral.sh/uv/" rel="nofollow noopener noreferrer"&gt;uv&lt;/a&gt;, &lt;a href="https://bun.sh/" rel="nofollow noopener noreferrer"&gt;bun&lt;/a&gt; and PowerShell 7.&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;uv sync &lt;span class="pl-k"&gt;--&lt;/span&gt;all&lt;span class="pl-k"&gt;-&lt;/span&gt;groups
&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;WO_CONTACT_EMAIL&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;you@example.org&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;      &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Nominatim's usage policy wants a contact&lt;/span&gt;
&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;OPENROUTER_API_KEY&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;...&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Moonchichiii/wiggle_outside" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;FastAPI and Jinja render plain HTML on the server, with one small script for "Use my location" and the loading state. Tailwind 4 for styles, SQLite for caches and the daily quota, Fly.io for hosting. Around 1,700 automated tests, strict type checking, and an Apache-2.0 licence for the code. (The display fonts are licensed separately and aren't in the repository; a clone falls back to system fonts.)&lt;/p&gt;

&lt;p&gt;On an emulated mid-range phone over slow 4G, Lighthouse scored the live form page 100 for performance, accessibility, best practices and SEO.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  From concept board to the shipped page
&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%2Fu8d1dmz0bc7xrp6z0c9h.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%2Fu8d1dmz0bc7xrp6z0c9h.png" alt="The original concept board" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The original concept board, made with ChatGPT. The shipped app keeps its palette, hand-drawn frame and margin illustrations, with a simpler single-column form. The screenshots above are the real app.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemma chooses; rules decide what's possible
&lt;/h3&gt;

&lt;p&gt;The design is a split of responsibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic rules decide what's possible.&lt;/strong&gt; Each of the 26 activities in the reviewed list (19 for families, 7 for adults alone) carries its own limits: age groups, minimum temperature, maximum wind, rain tolerance, whether it needs daylight, what kind of place it suits. The app fetches the forecast from &lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; and nearby places from &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt; (Nominatim to find the town, Overpass for parks and playgrounds), then filters everything down to &lt;em&gt;eligible pairs&lt;/em&gt;: this activity, at this place or needing none, for this time window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma chooses among them.&lt;/strong&gt; When eligible pairs remain and the model is available, &lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4 26B A4B&lt;/a&gt; receives them as IDs, each with the hooks and reasons it may use, plus the optional "what are they into?" text. It ranks up to three pairs, picks a hook and reasons for each by ID, and maps the family's text to a small reviewed vocabulary of interests. That's the language part: turning &lt;em&gt;"birds and puddles"&lt;/em&gt; into tags and putting Nature bingo and the Puddle jumping championship ahead of a Shape safari.&lt;/p&gt;

&lt;p&gt;You can see its work on each card: the reasons it picked ("It's close by", "It's the right season for it") and the interests it recognised ("Matches: animals, birds"). Gemma never writes the text on the page and never invents a place: the cards, reasons and interest labels are rendered from reviewed copy, chosen by the IDs it returns. I tried letting it write a short personal note per idea and took it out again the same night: a length limit and a character filter can't make a sentence shown to parents of toddlers safe, so the reviewed-text boundary stayed. Its answer must match a strict JSON schema, and the app then validates every field. Unknown pairs, disallowed hooks or reasons, duplicates and repeated activities are dropped, never repaired, and cards are built only from what survives.&lt;/p&gt;

&lt;p&gt;In a controlled run with live geocoding, forecast and model but fixture places, Gemma read &lt;em&gt;"birds and puddles"&lt;/em&gt;, picked two valid pairs, and the page rendered exactly those two cards; the model call took about 1.8 seconds. The search in the demo video ran fully live against the deployed app: real geocoding, real places, real forecast and the hosted model. The server log for it reads &lt;code&gt;source=model ideas=2&lt;/code&gt;: Gemma made the selection. The whole search, measured on the server, finished in about 1.5 seconds, but I had searched the same area while preparing the demo, so places and forecast may have come from cache; it isn't a cold-start time.&lt;/p&gt;

&lt;h3&gt;
  
  
  From suggestions to a plan you can leave the house with
&lt;/h3&gt;

&lt;p&gt;My first version stopped at the ideas, and it felt flat. A parent doesn't only need &lt;em&gt;what&lt;/em&gt;; they need &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;what to grab on the way out&lt;/em&gt;. So the results page now leads with one short notice instead of a stack of them, shows each idea's reasons and place, links the place on OpenStreetMap by its own map ID (no coordinates of yours in the link), and ends with "Before you go". The packing list is deliberately rules, not AI: weather items come first, then the little ones' essentials, then each activity's own bits, at most ten, with "Your usual age-appropriate snacks and drinks" whenever a baby is in the group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting the open model, and what it sees
&lt;/h3&gt;

&lt;p&gt;Gemma runs hosted, through &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;, with tight limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API key can call only this one model, with a small spending cap.&lt;/li&gt;
&lt;li&gt;Routing is restricted to three providers I chose because OpenRouter's published data policy lists them as neither training on nor retaining prompts. That is the basis of the privacy note, and I describe it that way rather than claiming more.&lt;/li&gt;
&lt;li&gt;Requests ask for strict structured output, reasoning off, at most 256 output tokens. A search costs a fraction of a cent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The request deliberately leaves out your typed town, coordinates and place names: the model sees activity titles, place &lt;em&gt;kinds&lt;/em&gt; (park, playground) and distance bands. Your optional interests text is sent as typed, which is why the form asks you not to include personal details. In the app, the raw text stays in server memory with your search for about an hour, as the form says (up to two hours in your session if you had to pick between towns with the same name), and is never written to the database or the logs. What the results page keeps is the recognised interest tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  A 20-second budget, and what happens when things fail
&lt;/h3&gt;

&lt;p&gt;Every search runs under one 20-second deadline: about 3 seconds to find the place, 9 for places and weather together, 6 for the model, and 2 reserved for building the page. Whatever earlier steps use comes off the later ones, and the overall deadline always wins.&lt;/p&gt;

&lt;p&gt;When something fails, the app says so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model unavailable or its answer unusable:&lt;/strong&gt; a deterministic ranking picks from the same eligible pairs, and the page says the AI selection wasn't available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearby places unavailable:&lt;/strong&gt; if activities that need no particular place are still eligible, Gemma chooses among those and the page says places couldn't be loaded. If nothing remains eligible, the page explains why it can't recommend anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The public Overpass instance doesn't answer:&lt;/strong&gt; the second attempt goes to one alternative public instance, inside the same time budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat searches:&lt;/strong&gt; an identical submission while the first is still running or fresh joins it instead of starting again. Place lookups are cached for days and forecasts for minutes, so OpenStreetMap's free services receive fewer repeat requests while cached data remains fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I learned when real services pushed back
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overpass timed out.&lt;/strong&gt; On Sunday afternoon my place queries to the public Overpass instance returned 504s after 7–9 seconds, where the same query had taken 2–3 seconds that morning. That's why the places stage has its own budget, why results are cached, and why the place-free path exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured output support isn't uniform.&lt;/strong&gt; My first live calls failed because a provider's constrained decoder didn't support &lt;code&gt;uniqueItems&lt;/code&gt; in the JSON schema. I removed it from what's sent and kept uniqueness in my own validation, with tests proving duplicates can't reach the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model needed one more rule.&lt;/strong&gt; When only place-free activities were left, Gemma read the interests but picked nothing, so every such search fell back. One added prompt rule fixed it, confirmed with live calls on that case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rules sometimes say no, and that's correct.&lt;/strong&gt; My first fully live attempts from London and Dublin in the early evening came back with &lt;em&gt;"Not enough daylight for this one"&lt;/em&gt;: a one-hour family outing starting at 18:09 would end after sunset. That's also what pushed me to plan ahead for the next morning instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I worked
&lt;/h3&gt;

&lt;p&gt;I built this over the weekend with AI agents; I retained responsibility for product decisions and reviewed the agents' output. Claude Code wrote and tested the code against a written plan, ChatGPT drew the design and reviewed the plan and this post, and every change went through a tracked decision before it landed. The reviews earned their keep: one caught that I was about to call a test run "warm" when nothing had been cached, another that the privacy wording promised more than the code did, and a third talked me out of letting the model write free text on the page. The most useful feedback came from home, though: my partner pointed out that parents plan in the evening, after the kids are asleep, which is why the app now plans ahead for the next morning.&lt;/p&gt;

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

&lt;p&gt;Three different kinds of openness make this app possible, and they matter in different ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open weights.&lt;/strong&gt; For this entry I use hosted Gemma through OpenRouter. Because the weights are published, the model itself isn't tied to that service: moving inference elsewhere would still take suitable hardware, integration work and fresh testing, but it's an option available under the model's licence terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open data.&lt;/strong&gt; The places come from OpenStreetMap, mapped by volunteers who decided a playground or a picnic spot was worth recording, and the forecast comes from Open-Meteo. Those services make a small project like this practical. They also create responsibilities: attribution, caching, respecting usage limits, and being honest when data isn't available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open code.&lt;/strong&gt; The application is Apache-2.0 licensed, so the next person can take the rules, the tests or the whole thing.&lt;/p&gt;

&lt;p&gt;This version needs an internet connection and depends on hosted services today. The open approach gives me more control over where it goes next; it doesn't make hosting free.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The packing list and snack idea are simple reminders from fixed rules, not advice for any particular child.&lt;/li&gt;
&lt;li&gt;Distances are straight-line estimates. The app can't check paths, access, hazards or opening times. &lt;strong&gt;You choose the adventure&lt;/strong&gt; and judge the conditions.&lt;/li&gt;
&lt;li&gt;The weather is a forecast, and each activity's limits are my own product judgements, not safety standards.&lt;/li&gt;
&lt;li&gt;The three activities open to babies have a 5 °C minimum. Two of them are shared with older children, so below 5 °C they disappear for the whole family, and families with a baby will see few or no ideas.&lt;/li&gt;
&lt;li&gt;Each activity has one accessibility adaptation line. That's a start, not full inclusive design.&lt;/li&gt;
&lt;li&gt;Nearby places depend on the public Overpass service; when it doesn't answer in time, you'll see a labelled message.&lt;/li&gt;
&lt;li&gt;The interface is English only.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 4 26B A4B is the selection step: when eligible ideas remain, it reads the family's interests in their own words and chooses among validated activity-and-place pairs, with a strict schema, validation and a labelled fallback, and the reasons and recognised interests it chose are shown on every card.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
