<?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: Sagar Maurya</title>
    <description>The latest articles on DEV Community by Sagar Maurya (@sagarmaurya).</description>
    <link>https://dev.to/sagarmaurya</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%2F3019158%2Fbf3e71fa-5bc7-4293-8853-83a8f3915ad9.gif</url>
      <title>DEV Community: Sagar Maurya</title>
      <link>https://dev.to/sagarmaurya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sagarmaurya"/>
    <language>en</language>
    <item>
      <title>How I Built a Self-Healing AI Tracker That Fixes Its Own Scrapers</title>
      <dc:creator>Sagar Maurya</dc:creator>
      <pubDate>Sun, 23 Aug 2026 16:48:31 +0000</pubDate>
      <link>https://dev.to/sagarmaurya/how-i-built-a-self-healing-ai-tracker-that-fixes-its-own-scrapers-1mi6</link>
      <guid>https://dev.to/sagarmaurya/how-i-built-a-self-healing-ai-tracker-that-fixes-its-own-scrapers-1mi6</guid>
      <description>&lt;p&gt;WeMakeDevs and Bright Data ran a week-long hackathon called &lt;strong&gt;Into the Scrape-Verse&lt;/strong&gt; — the challenge was to build a self-healing web scraper and turn live web data into something real.&lt;/p&gt;

&lt;p&gt;I had one week. I built &lt;strong&gt;Crawlr&lt;/strong&gt; — an autonomous AI model and dataset tracker that watches four research sources in real time, detects when a scraper breaks, heals it automatically, and logs every event to a live audit trail. No human in the loop.&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%2Fstaebbxiafhvzq21u6v6.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%2Fstaebbxiafhvzq21u6v6.png" alt="Crawlr landing page" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem I Wanted to Solve
&lt;/h2&gt;

&lt;p&gt;Scrapers break silently. A site changes a class name, a layout shifts, a field moves — and your scraper returns nothing. You find out days later when someone notices the data is stale.&lt;/p&gt;

&lt;p&gt;The usual fix is manual: inspect the HTML, find the new selector, redeploy. It works until the next time.&lt;/p&gt;

&lt;p&gt;I wanted to build something that detects the break, figures out what went wrong, and fixes itself — without any human involvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Crawlr Tracks
&lt;/h2&gt;

&lt;p&gt;The AI research world moves fast. New models drop on EleutherAI. Together AI publishes blog posts. Papers With Code indexes the latest benchmarks. OpenRouter lists every available model with real pricing data.&lt;/p&gt;

&lt;p&gt;Crawlr watches all four:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;What it scrapes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EleutherAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model and dataset releases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Together AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blog posts and announcements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Papers With Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Latest research papers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenRouter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live model catalog with context length and pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every pipeline run scrapes all four, normalises the data into a unified format, and serves it through a real-time dashboard. 78 records across 4 sources, updated on every run.&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%2Fmcaf37yd4qe7qmht887p.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%2Fmcaf37yd4qe7qmht887p.png" alt="Dashboard" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How Bright Data Scraper Studio Powers Everything
&lt;/h2&gt;

&lt;p&gt;Every scraper in Crawlr was built using &lt;strong&gt;Bright Data's Scraper Studio&lt;/strong&gt; — an AI-powered platform that builds, runs, and self-heals custom web scrapers from your terminal.&lt;/p&gt;

&lt;p&gt;The entire workflow is four commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Login once&lt;/span&gt;
npx &lt;span class="nt"&gt;-p&lt;/span&gt; @brightdata/cli bdata login

&lt;span class="c"&gt;# Describe the data you want — AI builds the scraper&lt;/span&gt;
bdata scraper create https://eleuther.ai/releases &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"model name, description, release date, type (model/dataset/library)"&lt;/span&gt;

&lt;span class="c"&gt;# Run it — returns clean JSON&lt;/span&gt;
bdata scraper run c_mt4lee1f5n8ouckku https://eleuther.ai/releases &lt;span class="nt"&gt;--pretty&lt;/span&gt;

&lt;span class="c"&gt;# When the site changes — heal it with a plain-English description&lt;/span&gt;
bdata scraper heal c_mt4lee1f5n8ouckku &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"description field is empty in 6 out of 8 records, selector may have changed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftvh379tqwam1t6hok24t.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%2Ftvh379tqwam1t6hok24t.png" alt="Terminal showing bdata scraper run — clean JSON output" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;c_*&lt;/code&gt; Collector ID you get back is a live production API endpoint. No deployment step. No proxy rotation, retries, or unblocking to manage — Bright Data handles all of that. You just describe what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Moment That Made It Click
&lt;/h3&gt;

&lt;p&gt;While building the OpenRouter collector, the initial scraper was returning records but the &lt;code&gt;context_length&lt;/code&gt; and &lt;code&gt;pricing_per_token&lt;/code&gt; fields were all null. I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bdata scraper heal c_mt4m2cql181bpcgals &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"context_length and pricing fields returning null. 
   Model names contain provider prefix like 'Meta: Llama 3.1'. 
   Extract provider from model name string."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two minutes later, the collector was returning this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Meta: Muse Spark 1.2 Contributor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Meta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context_length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.05M context"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricing_per_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$0.10 /M input tokens"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same Collector ID. Nothing downstream changed. The scraper just started working correctly. That's when I understood what made Scraper Studio genuinely different.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Crawlr has three components working together in a closed loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;scrapers/pipeline.js&lt;/code&gt; runs all four collectors in sequence, normalises the raw JSON into a unified schema, and saves the output to the &lt;code&gt;data/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Each source returns completely different data shapes — OpenRouter looks nothing like EleutherAI. Every source has its own &lt;code&gt;unify*&lt;/code&gt; function that maps it to the same schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eleuther-0-trlX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eleuther&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trlX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A repo for distributed training of language models...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/CarperAI/trlx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Library&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dec 9, 2023&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Sentry — The Health Agent
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;scrapers/sentry.js&lt;/code&gt; is an autonomous health checker that runs after every pipeline and validates each source's output for three failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema Break&lt;/strong&gt; — payload is empty or not an array&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume Drop&lt;/strong&gt; — record count fell more than 50% vs the baseline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null Creep&lt;/strong&gt; — required fields are empty in more than 20% of records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it detects a failure, it auto-generates a diagnosis from the actual failure data — field names, failure percentages, a sample broken record — and calls &lt;code&gt;bdata scraper heal&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildDiagnosis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Self-healing prompt for '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sourceId&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;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; | &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 
            Sample record: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
            Action: locate updated selectors for the listed fields.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After healing, Sentry re-runs the collector to verify recovery. Every step is logged with timestamps. Here's what a normal healthy run looks like — no anomalies this time, all four sources verified clean. (The full heal cycle, triggered live, is a bit further down in Chaos Mode.)&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%2Fmuxg7kf2i5r4ch30qm8d.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%2Fmuxg7kf2i5r4ch30qm8d.png" alt="Sentry health check — all 4 sources verified healthy" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Dashboard
&lt;/h3&gt;

&lt;p&gt;A pure HTML/CSS/JS frontend — no framework, no build step. It reads directly from the JSON files the pipeline writes.&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%2F8lqermvqael7znf2au40.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%2F8lqermvqael7znf2au40.png" alt="Dashboard with search active, filtering cards" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 source tabs&lt;/strong&gt; with live record counts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant search&lt;/strong&gt; across title, description, and organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Audit Log&lt;/strong&gt; — color-coded pills for each event type (HEALTHY, ANOMALY, HEALING, ERROR)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chaos Mode&lt;/strong&gt; — simulates a scraper break so you can see the full heal cycle live&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%2F6z8y8uxu0wznxn38nt4v.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%2F6z8y8uxu0wznxn38nt4v.png" alt="Chaos Mode — log showing the full simulated break and recovery" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Full Run Looks Like
&lt;/h2&gt;

&lt;p&gt;Pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ eleuther:   8 items saved
✅ togetherai: 25 items saved
✅ pwc:        25 items saved
✅ openrouter: 20 items saved
✅ Pipeline complete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sentry health check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ [eleuther]   Healthy. 8 records returned.
✅ [togetherai] Healthy. 31 records returned.
✅ [pwc]        Healthy. 50 records returned.
✅ [openrouter] Healthy. 20 records returned.
✅ All sources checked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevp0mdmsved89iyv043h.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%2Fevp0mdmsved89iyv043h.png" alt="Terminal showing full pipeline.js run — all 4 sources scraped and saved" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;&lt;code&gt;bdata scraper heal&lt;/code&gt; is the real unlock.&lt;/strong&gt; You describe what's broken in plain English, Bright Data's AI rewrites the extraction logic, and the same Collector ID starts returning clean data. I watched it fix broken selectors on live sources multiple times during the week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bright Data handles the hard parts of scraping.&lt;/strong&gt; Proxy rotation, retries, unblocking, rate limiting — none of that is your problem. You describe what you want and get clean JSON back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Collector ID is a production API endpoint.&lt;/strong&gt; Trigger it with &lt;code&gt;POST /dca/trigger&lt;/code&gt; from any language or scheduler. No deployment step. This made wiring it into the pipeline and dashboard trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-healing is not a gimmick.&lt;/strong&gt; Going into this I wasn't sure how well automated healing would actually work on real sites. After a week of watching it fix real extraction failures, I'm convinced it's the right direction for production scrapers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

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

&lt;p&gt;Clone it, add your &lt;code&gt;.env&lt;/code&gt; with four Bright Data Collector IDs, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pipeline   &lt;span class="c"&gt;# Scrape all 4 sources&lt;/span&gt;
npm run sentry     &lt;span class="c"&gt;# Health check + auto-heal&lt;/span&gt;
npm run serve      &lt;span class="c"&gt;# Open the dashboard at localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Built for the Into the Scrape-Verse hackathon — WeMakeDevs × Bright Data, August 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;#webdev #javascript #hackathon #brightdata #scraping #wemakedevs #opensource&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wemakedevs</category>
      <category>brightdatachallenge</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>How I Built a YouTube Trend Engine on Zerops</title>
      <dc:creator>Sagar Maurya</dc:creator>
      <pubDate>Sun, 09 Aug 2026 17:14:45 +0000</pubDate>
      <link>https://dev.to/sagarmaurya/how-i-built-a-youtube-trend-engine-on-zerops-hka</link>
      <guid>https://dev.to/sagarmaurya/how-i-built-a-youtube-trend-engine-on-zerops-hka</guid>
      <description>&lt;h2&gt;
  
  
  The API Quota Problem
&lt;/h2&gt;

&lt;p&gt;If you've ever built applications on public APIs, you know how quickly limits can become a bottleneck. The YouTube Data API v3 gives you a free tier of 10,000 units/day. Each &lt;code&gt;search.list&lt;/code&gt; request costs &lt;strong&gt;100 units&lt;/strong&gt;. A single user scanning a niche across 6 keywords burns 600 units. A handful of test scans, and your daily allowance is practically gone.&lt;/p&gt;

&lt;p&gt;To make matters worse, Google Trends (via &lt;code&gt;pytrends&lt;/code&gt;) has no official API — if you hit it repeatedly without caching, Google silently rate-limits your IP address, returning empty data frames or hanging your server indefinitely.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Signal&lt;/strong&gt; for the &lt;strong&gt;Zerops Challenge by WeMakeDevs&lt;/strong&gt;: a tool that turns real-time search trends and YouTube view telemetry into ready-to-film video titles ranked by an objective demand score. &lt;/p&gt;

&lt;p&gt;Because of these aggressive limits, I didn't just need an app that worked — I needed an infrastructure that could &lt;strong&gt;fail soft, save API quota, and respond in under 1 millisecond on repeat scans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is how I built Signal, the architectural decisions behind it, the three sneaky deployment bugs I encountered, and how &lt;strong&gt;Zerops Valkey&lt;/strong&gt; saved my API quota.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://signal-2dc6-8000.prg1.zerops.app" rel="noopener noreferrer"&gt;https://signal-2dc6-8000.prg1.zerops.app&lt;/a&gt;&lt;br&gt;&lt;br&gt;
💻 &lt;strong&gt;GitHub repository:&lt;/strong&gt; &lt;a href="https://github.com/mauryasagar/signal" rel="noopener noreferrer"&gt;github.com/mauryasagar/signal&lt;/a&gt;  &lt;/p&gt;


&lt;h2&gt;
  
  
  What Signal Does
&lt;/h2&gt;

&lt;p&gt;Most YouTube advice is generic. Signal changes that by backing up video ideas with real data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User inputs a niche&lt;/strong&gt; (e.g., &lt;code&gt;"home coffee brewing"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Trends Discovery&lt;/strong&gt;: Pulls rising and related search queries over the last 90 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube Telemetry Check&lt;/strong&gt;: Queries YouTube Data API v3 in parallel to fetch recent video upload counts and average view metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groq LLM Synthesis&lt;/strong&gt;: Sends all signals to &lt;strong&gt;Llama 3.1 8B Instant&lt;/strong&gt; on Groq LPUs, generating concrete video titles and strategic creator angles in &lt;strong&gt;~280ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demand Scoring&lt;/strong&gt;: Ranks topics using a weighted logarithmic score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zerops Valkey Caching&lt;/strong&gt;: Caches raw signal telemetry for 1 hour. Repeat queries skip external APIs entirely, returning in &lt;strong&gt;&amp;lt;1ms&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&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%2Fsn7d0taf8bq6bkyjcs6x.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%2Fsn7d0taf8bq6bkyjcs6x.png" alt="Signal App Landing Page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Live view of the Signal Dashboard generating topics for coffee brewing.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  System Architecture: The 4-Layer Engine
&lt;/h2&gt;

&lt;p&gt;Signal is designed as a micro-cached pipeline deployed on &lt;strong&gt;Zerops&lt;/strong&gt;, using Gunicorn workers, Valkey key-value storage, and external API connectors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input: "home coffee brewing"
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│              Flask Web App (Gunicorn · Python 3.12)         │
│                    Deployed on Zerops                       │
└──────────────┬──────────────────────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────────────────────┐
│                    Pipeline (report.py)                     │
│                                                             │
│  1. Check Zerops Valkey cache ──► HIT: Skip to step 4       │
│                                   MISS: Continue            │
│                                                             │
│  2. Google Trends (pytrends)                                │
│     └── Daemon thread → [{query, interest}]                 │
│                                                             │
│  3. YouTube Data API v3                                     │
│     └── Per query: video count + avg views                  │
│                                                             │
│  4. Write signals to Valkey cache (1 hour TTL)              │
│                                                             │
│  5. Groq LPU Inference (Llama 3.1 8B)                       │
│     └── Keyword + demand data → Title + Angle               │
│                                                             │
│  6. Demand score computed, topics sorted                    │
└──────────────┬──────────────────────────────────────────────┘
               │
               ▼
        Report page rendered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Flask + Gunicorn&lt;/td&gt;
&lt;td&gt;Lightweight, production-safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trend data&lt;/td&gt;
&lt;td&gt;Google Trends via &lt;code&gt;pytrends&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No official API key needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YouTube demand&lt;/td&gt;
&lt;td&gt;YouTube Data API v3&lt;/td&gt;
&lt;td&gt;Official, free 10K quota/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Groq — Llama 3.1 8B&lt;/td&gt;
&lt;td&gt;Sub-second inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zerops Valkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Cuts repeat response time from 25s → &amp;lt;1s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zerops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Production infra, private networking, auto-deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signal/
├── app.py              # Flask routes: /, /about, /generate, /health
├── report.py           # Pipeline orchestration + demand scoring
├── fetch_trends.py     # Google Trends + YouTube API calls
├── generate_topics.py  # Groq LLM call + JSON parsing
├── cache.py            # Zerops Valkey integration
├── zerops.yaml         # Zerops build + run config
├── templates/
│   ├── base.html       # Navbar, radar loading animation
│   ├── landing.html    # Hero + search form
│   └── report.html     # Ranked topic cards
└── static/
    ├── style.css       # Dark/light theme + custom properties
    └── main.js         # Theme toggle + loading overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Technical Deep-Dive: Code &amp;amp; Logic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Resilient Ingestion &amp;amp; Daemon Thread Caps (&lt;code&gt;fetch_trends.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pytrends&lt;/code&gt; is prone to silent hangs when Google rate-limits requests. Allowing &lt;code&gt;pytrends&lt;/code&gt; to block synchronously would exhaust Gunicorn worker threads.&lt;/p&gt;

&lt;p&gt;To guarantee sub-second bounds, Signal wraps &lt;code&gt;pytrends&lt;/code&gt; in a &lt;strong&gt;daemon thread with an explicit 0.8-second hard cutoff&lt;/strong&gt;. If the thread doesn't finish, we gracefully fall back to predefined query templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# fetch_trends.py snippet
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_signals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;niche&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtube_api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_terms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_fetch_pytrends&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;related&lt;/span&gt;
        &lt;span class="n"&gt;related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_related_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;niche&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_terms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_terms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Enforce strict 0.8s latency bound
&lt;/span&gt;    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_fetch_pytrends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Soft failure fallback if Google Trends rate-limits
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;related&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;niche&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;niche&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tips&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;niche&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for beginners&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Logarithmic Demand Scoring Model (&lt;code&gt;report.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Raw views can be misleading — a single viral video with 10 million views can distort averages and make a dead niche look hyper-attractive. Signal uses a normalized &lt;strong&gt;0–100 Demand Score&lt;/strong&gt; formula with &lt;strong&gt;log-scaled view counts&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DemandScore&lt;/strong&gt; = &lt;code&gt;(Trend Momentum × 60%)&lt;/code&gt; + &lt;code&gt;(Upload Velocity × 25%)&lt;/code&gt; + &lt;code&gt;(Audience Size × 15%)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# report.py snippet
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_compute_demand_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 60% — Google Trends search interest momentum (0-100)
&lt;/span&gt;    &lt;span class="n"&gt;trend_component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trend_interest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;

    &lt;span class="c1"&gt;# 25% — Creator upload velocity (caps at 5 recent videos)
&lt;/span&gt;    &lt;span class="n"&gt;video_count_component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;

    &lt;span class="c1"&gt;# 15% — Audience size (log10 scaled up to 10M views so outliers don't dominate)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;views_component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;views_component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trend_component&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;video_count_component&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;views_component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Fail-Soft Caching Layer with Zerops Valkey (&lt;code&gt;cache.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Zerops provides a managed, Redis-compatible &lt;strong&gt;Valkey&lt;/strong&gt; database service. Signal uses Valkey to cache raw signals for 1 hour. But I wanted local development to work &lt;em&gt;without&lt;/em&gt; installing Redis. The solution? A &lt;strong&gt;thread-safe in-memory fallback&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cache.py snippet
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;_MEMORY_CACHE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;_MEMORY_CACHE_LOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_cache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VALKEY_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="c1"&gt;# Falls back to in-memory
&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VALKEY_PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VALKEY_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;decode_responses&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;socket_connect_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Valkey unreachable — using in-memory fallback.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Frontend: Dynamic Report Cards
&lt;/h2&gt;

&lt;p&gt;To make the UI feel as fast and premium as the backend, I built a color-coded topic card system. Each rank gets a unique accent color (amber, green, purple, blue, rose). &lt;/p&gt;

&lt;p&gt;When a user hovers over a topic card, it reveals a gradient wash in the card's specific accent color. This is powered by per-card CSS custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.topic-card-1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e8a33d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;232&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;163&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.topic-card-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#5fb88a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;95&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="py"&gt;--accent-g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;184&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;138&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.topic-card-3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a78bfa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;167&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;139&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--accent-b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.topic-card&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;translateZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt; &lt;span class="m"&gt;-12px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Three Sneaky Roadblocks (And How I Fixed Them)
&lt;/h2&gt;

&lt;p&gt;Building for a hackathon is never a straight path. Here are the three sneaky bugs that caught me off guard:&lt;/p&gt;

&lt;h3&gt;
  
  
  🐛 Roadblock #1: The Zerops Container Runtime Ghost
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;: My local tests passed. I configured &lt;code&gt;zerops.yaml&lt;/code&gt; and deployed. The build succeeded, but the runtime container immediately crashed with:&lt;br&gt;
&lt;code&gt;bash: gunicorn: command not found&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause&lt;/strong&gt;: Zerops intelligently decouples the &lt;strong&gt;build container&lt;/strong&gt; from the &lt;strong&gt;runtime container&lt;/strong&gt;. Packages installed during &lt;code&gt;buildCommands&lt;/code&gt; do &lt;em&gt;not&lt;/em&gt; automatically persist into the slim runtime image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;: Adding a &lt;code&gt;prepareCommands&lt;/code&gt; block inside &lt;code&gt;zerops.yaml&lt;/code&gt; to install production dependencies directly in the runtime environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# zerops.yaml snippet&lt;/span&gt;
&lt;span class="na"&gt;zerops&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signal&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python@3.12&lt;/span&gt;
      &lt;span class="na"&gt;buildCommands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python@3.12&lt;/span&gt;
      &lt;span class="c1"&gt;# THE FIX: Install packages in the runtime container&lt;/span&gt;
      &lt;span class="na"&gt;prepareCommands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install Flask==3.0.3 python-dotenv==1.0.1 gunicorn==22.0.0 redis==5.2.0 ...&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
          &lt;span class="na"&gt;httpSupport&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gunicorn app:app --bind 0.0.0.0:8000 --workers &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🐛 Roadblock #2: The Missing Valkey Auto-Password
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;: Zerops Valkey service was running, but &lt;code&gt;cache.py&lt;/code&gt; kept throwing &lt;code&gt;redis.exceptions.AuthenticationError: NOAUTH Authentication required&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause&lt;/strong&gt;: Zerops Valkey instances ship with authentication enabled by default, auto-generating a secure password. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;: Using Zerops' dynamic environment variable cross-referencing in the project dashboard! I didn't need to hardcode anything.&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%2Fm537iji0apbsj01z24kc.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%2Fm537iji0apbsj01z24kc.png" alt="Zerops Environment Variables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Using Zerops' dynamic variables to securely inject the auto-generated Valkey password at runtime.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By setting &lt;code&gt;VALKEY_PASSWORD=${valkey_password}&lt;/code&gt;, Zerops dynamically injects the auto-generated Valkey credential at runtime. Magic.&lt;/p&gt;

&lt;h3&gt;
  
  
  🐛 Roadblock #3: Silent LLM JSON Output Formatting Drift
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;: Even with explicit prompting, Llama 3.1 would occasionally wrap JSON responses in conversational preambles or markdown block wrappers (&lt;code&gt;&lt;/code&gt;&lt;code&gt;json ...&lt;/code&gt;&lt;code&gt;&lt;/code&gt;), completely breaking Python's &lt;code&gt;json.loads()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;: A robust regex extraction function (&lt;code&gt;_extract_json&lt;/code&gt;) in &lt;code&gt;generate_topics.py&lt;/code&gt; to strip out markdown and preambles safely before parsing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Telemetry &amp;amp; Verification: The &lt;code&gt;/health&lt;/code&gt; Endpoint
&lt;/h2&gt;

&lt;p&gt;To confirm that Valkey and Flask were communicating over Zerops' private network, I built a live &lt;code&gt;/health&lt;/code&gt; endpoint.&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%2Fkbhjeliiv1g54nyvlbgx.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%2Fkbhjeliiv1g54nyvlbgx.png" alt="Signal Health Check" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Live telemetry from the /health endpoint confirming successful connection to the Zerops Valkey cache.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hitting &lt;code&gt;https://signal-2dc6-8000.prg1.zerops.app/health&lt;/code&gt; returns live confirmation that caching is active, which instantly drops our response time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Performance Benchmarks
&lt;/h3&gt;

&lt;p&gt;Here is how Signal performs on Zerops under live production conditions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Performance Metric&lt;/th&gt;
&lt;th&gt;Live Uncached Scan&lt;/th&gt;
&lt;th&gt;Zerops Valkey Cache Hit&lt;/th&gt;
&lt;th&gt;Net Savings / Gain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Response Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,420 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.85 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.94% Faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube API Quota&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;600 units / scan&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 units&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100% Quota Saved&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Trends Calls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 HTTP request&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 HTTP requests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero Rate Limits&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Groq LPU Inference&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~280 ms&lt;/td&gt;
&lt;td&gt;~280 ms (Fresh Titles)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sub-second AI&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step-by-Step Zerops Deployment Walkthrough
&lt;/h2&gt;

&lt;p&gt;Deploying Signal on Zerops took under &lt;strong&gt;4 minutes&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Zerops Account&lt;/strong&gt;: Sign up at &lt;a href="https://zerops.io" rel="noopener noreferrer"&gt;zerops.io&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Services&lt;/strong&gt;: I added a Python runtime service (&lt;code&gt;signal&lt;/code&gt;, Python 3.12) and a Valkey database service (&lt;code&gt;valkey&lt;/code&gt;, Valkey 7.2 Single mode).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Environment Variables&lt;/strong&gt;: Set &lt;code&gt;YOUTUBE_API_KEY&lt;/code&gt;, &lt;code&gt;GROQ_API_KEY&lt;/code&gt;, &lt;code&gt;VALKEY_HOST=valkey&lt;/code&gt;, and &lt;code&gt;VALKEY_PASSWORD=${valkey_password}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect GitHub Repo&lt;/strong&gt;: Triggered the build pipeline directly from my main branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Public Subdomain&lt;/strong&gt;: Flipped the switch for public access on port 8000.&lt;/li&gt;
&lt;/ol&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%2Fgxlqs0t0qc541flcht04.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%2Fgxlqs0t0qc541flcht04.png" alt="Zerops Dashboard Projects" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Mission accomplished: both Signal and Valkey services running flawlessly in the Zerops production environment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;&lt;strong&gt;Demand score is directional, not exact.&lt;/strong&gt; No free data source provides real search volume. The score combines relative trend interest and YouTube activity as a proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pytrends is unofficial.&lt;/strong&gt; Google Trends has no public API. The library can be rate-limited or break without warning. Signal falls back to seeded keywords automatically, so the app never fully fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avg views can look inflated.&lt;/strong&gt; Results are sorted by view count, so the top videos tend to be outlier performers — it's a ceiling signal, not a guaranteed expectation for a new channel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways &amp;amp; Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never trust external APIs to fail loud&lt;/strong&gt;: Wrap unofficial endpoints (like &lt;code&gt;pytrends&lt;/code&gt;) in daemon threads with hard timeout limits and soft fallbacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple build vs runtime environments&lt;/strong&gt;: Always configure &lt;code&gt;prepareCommands&lt;/code&gt; in &lt;code&gt;zerops.yaml&lt;/code&gt; for runtime dependencies like &lt;code&gt;gunicorn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-soft caching is worth every line of code&lt;/strong&gt;: Adding a thread-safe in-memory cache fallback meant local dev worked without Redis, and production degraded gracefully during network blips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zerops makes multi-service infra seamless&lt;/strong&gt;: Wiring Flask to Valkey over internal networking took literally one environment variable (&lt;code&gt;VALKEY_HOST=valkey&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI Disclosure:&lt;/strong&gt; As per hackathon rules, tools like Claude and Antigravity were used as pair-programmers to speed up boilerplate code and draft this post. The core architecture, API logic, and Zerops deployment are my own work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Try Signal Today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://signal-2dc6-8000.prg1.zerops.app" rel="noopener noreferrer"&gt;https://signal-2dc6-8000.prg1.zerops.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub Repo&lt;/strong&gt;: &lt;a href="https://github.com/mauryasagar/signal" rel="noopener noreferrer"&gt;github.com/mauryasagar/signal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Built for &lt;strong&gt;The Zerops Challenge by WeMakeDevs 2026&lt;/strong&gt;. Thanks to the Zerops team for building an exceptional developer platform, and a massive thank you to the &lt;a href="https://wemakedevs.org" rel="noopener noreferrer"&gt;WeMakeDevs&lt;/a&gt; community for hosting this incredible hackathon!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>zerops</category>
      <category>wemakedevs</category>
      <category>hackathon</category>
      <category>python</category>
    </item>
    <item>
      <title>Ouroboros AI: We Made a Fake Agent Break Itself, Then Built Something That Fixes It</title>
      <dc:creator>Sagar Maurya</dc:creator>
      <pubDate>Sun, 26 Jul 2026 15:15:12 +0000</pubDate>
      <link>https://dev.to/sagarmaurya/ouroboros-ai-we-made-a-fake-agent-break-itself-then-built-something-that-fixes-it-4akk</link>
      <guid>https://dev.to/sagarmaurya/ouroboros-ai-we-made-a-fake-agent-break-itself-then-built-something-that-fixes-it-4akk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;By Sagar Maurya &amp;amp; Disha Sonowal — WeMakeDevs SigNoz Hackathon&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're just landing on this series for the first time, here's the short version of where we've been.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/dishasonowal/we-traced-a-fake-ai-agent-before-building-a-real-one-heres-what-broke-1e7d"&gt;our first post&lt;/a&gt;, we installed a tool called SigNoz for the very first time, so we could watch what a piece of software is doing while it runs — which parts are slow, which parts fail, and why. We built a tiny fake AI agent, and just by looking at a picture of its actions laid out in order, we could instantly see that one single step was eating up 85% of the total time. That was the moment this whole thing clicked for us.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/sagarmaurya/we-asked-signoz-a-question-and-it-actually-answered-p6o"&gt;our second post&lt;/a&gt;, we went further. We built a slightly smarter fake agent and asked SigNoz a real question: "does this get slower when it has more information to look through?" We didn't calculate the answer ourselves. We just told SigNoz what to group and average, and it handed us a table proving the pattern, clean as anything.&lt;/p&gt;

&lt;p&gt;Both of those were practice. Small, safe experiments before the real hackathon clock started ticking. This post is about what we actually built once it did — and honestly, it's the post we're proudest of, because it's the one where we stopped just &lt;em&gt;watching&lt;/em&gt; our fake agent and started building something that &lt;em&gt;does something about it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We're calling the project &lt;strong&gt;Ouroboros AI&lt;/strong&gt;. Repo's here if you want to poke around: &lt;a href="https://github.com/mauryasagar/ouroboros-ai" rel="noopener noreferrer"&gt;github.com/mauryasagar/ouroboros-ai&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem that was quietly bugging us
&lt;/h2&gt;

&lt;p&gt;Right after we published that second post, something about it kept nagging at us.&lt;/p&gt;

&lt;p&gt;We'd asked SigNoz a good question and gotten a good answer: yes, more documents means a slower response, and it climbs in an almost perfectly straight line. We felt clever about it for about a day. Then we looked at what we'd actually built and realized... nothing happened next. We saw the pattern, said "yep, makes sense," and closed the browser tab. If this had been a real product used by real people, someone would still have had to &lt;em&gt;notice&lt;/em&gt; that pattern themselves, write it up, wait for an engineer to have time, and fix it — probably days later, and only if they happened to be looking at the right dashboard at the right moment.&lt;/p&gt;

&lt;p&gt;That bothered us more the more we thought about it. Watching something break in slow motion isn't the same as fixing it. So a simple, slightly stubborn question became the whole idea for this project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the system could notice its own problem and fix itself, without a person in the middle at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "send someone a faster alert." Actually close the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words before the story, so nothing feels confusing
&lt;/h2&gt;

&lt;p&gt;We're going to use a handful of words a lot in this post. If you already know them, skip ahead. If you don't, here they are in plain terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt; — a record of everything that happened during one single request, start to finish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span&lt;/strong&gt; — one step inside that trace. Four things happened? Four spans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert&lt;/strong&gt; — a rule you set up that says "if X happens, tell someone."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook&lt;/strong&gt; — instead of "tell someone" meaning a text message or an email, it can mean "call this web address automatically." That's the piece that let a computer react instead of a human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; — a standard way for an AI model to reach out and use real tools — in our case, letting an AI ask SigNoz real questions instead of us clicking around the dashboard ourselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's genuinely all the vocabulary you need. Everything else is just those five ideas, wired together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually decided to build
&lt;/h2&gt;

&lt;p&gt;We sat down together and sketched this out on a shared doc before writing a single line of code. Five steps, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chaos&lt;/strong&gt; — a fake AI agent runs constantly, and every so often, on purpose, it messes up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traced&lt;/strong&gt; — every single step it takes gets recorded and sent to SigNoz, live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert&lt;/strong&gt; — SigNoz watches all these traces coming in, notices the mess-up pattern, and automatically fires off a webhook — no human clicks anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heal&lt;/strong&gt; — a separate little program receives that webhook and retries the failed thing with better settings. Still no human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain&lt;/strong&gt; — an AI "Sidekick" you can literally chat with, which reads the real SigNoz data through MCP and tells you, in plain English, what just happened.&lt;/li&gt;
&lt;/ol&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%2Fmejtgjzyagktsdls0thw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmejtgjzyagktsdls0thw.jpg" alt="Ouroboros AI architecture diagram showing Agent Workload, SigNoz Observability Engine, Auto-Healer Service, and AI Diagnostic Sidekick" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Steps 1 and 2 were a more serious version of what we'd already built in post two. Steps 3, 4, and 5 didn't exist anywhere yet. That's where basically the entire week went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: giving our fake agent something real to fail at
&lt;/h2&gt;

&lt;p&gt;Our agent from post two never actually &lt;em&gt;failed&lt;/em&gt; — it just got randomly slower or faster each time. That's fine for a screenshot, but it's useless for an alert. You can't tell a computer "notice when things feel kind of slow sometimes." You need something crisp: a clear line that gets crossed.&lt;/p&gt;

&lt;p&gt;So we gave our agent one specific way to break: &lt;strong&gt;context overload&lt;/strong&gt;. In plain terms — our agent normally looks through somewhere between 1 and 8 "documents" to answer a question. But 20% of the time, on purpose, we let it get buried under 15 documents at once, which is way more than it can handle cleanly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_chaos_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieve_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# CHAOS MODE: 15 docs, NORMAL: 1-8 docs
&lt;/span&gt;        &lt;span class="n"&gt;context_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_chaos_mode&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.context_docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mock context data (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context_docs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; docs)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F896eycflh6tqrjthz9vb.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%2F896eycflh6tqrjthz9vb.png" alt="SigNoz traces explorer showing live spans from deep-agent-service including llm_call and retrieve_context" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We picked this specific failure on purpose, not randomly. Real AI agents that search through documents actually run into this exact problem — feed them too much information at once, and they get slower, cost more, and often don't even answer better for it. We wanted our fake failure to be one that real systems actually have, not something we made up just to have a demo.&lt;/p&gt;

&lt;p&gt;When the overload happens, we don't just log it as "a bit slow." We mark it as an actual error, with a clear message SigNoz can watch for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context_docs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONTEXT_OVERLOAD: Agent failed. Used &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens, cost $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context Overload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One small thing from post two turned out to matter a lot here: we'd mentioned back then that just printing something to your terminal doesn't automatically send it to SigNoz — you have to specifically wire up your logs to go there too, separately from your traces. This time, we made sure that was working properly from day one, because our whole alert depended on SigNoz actually being able to &lt;em&gt;see&lt;/em&gt; that error message, not just a slightly-slower-than-usual span.&lt;/p&gt;

&lt;p&gt;We let this fake agent run forever in the background, quietly sending a new trace every 200 milliseconds, so there was always something fresh happening while we built the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three and four: teaching the system to fix itself
&lt;/h2&gt;

&lt;p&gt;This is the part that didn't exist in either of our earlier posts, and honestly, the part we're most excited about.&lt;/p&gt;

&lt;p&gt;The good news is, we didn't have to build anything fancy to make SigNoz &lt;em&gt;watch&lt;/em&gt; for the failure — it already does that with alert rules, the exact same feature we used in post one to watch for slow calls. The only difference is where the alert points. Instead of sending a Slack message or an email, we pointed it at a &lt;strong&gt;webhook&lt;/strong&gt; — a plain web address that SigNoz calls automatically the moment it sees the problem.&lt;/p&gt;

&lt;p&gt;On the other end of that web address, we built a tiny program whose only job is to sit there, wait, and fix things when it gets called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/webhook&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;alert_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alertname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown Alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🚨 ALERT RECEIVED FROM SIGNOZ: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;alert_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;optimized_llm_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Auto-healed request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AUTO-HEAL SUCCESS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request auto-healed with optimized parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pjypzmweeq1qot275qu.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%2F3pjypzmweeq1qot275qu.png" alt="SigNoz service list showing deep-agent-service and auto-healer-service with P99 latency and error rate" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual fix is simple on purpose — it retries the request with a smaller, safer amount of information instead of the overload amount. The part we spent real time thinking about wasn't the fix itself, though. It was this question: &lt;strong&gt;should the fix be invisible, or should it leave a trace of its own?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We almost let it be invisible — quietly patch things up in the background and move on. But that felt like exactly the thing we were trying to get away from in the first place: a fix that only lives in someone's head, not in the system. So we made the healer trace its own retry too, tag it clearly as an auto-heal, and record an event that says, plainly, "this got fixed automatically." Now the healing itself shows up in SigNoz right next to the failure that caused it. Nobody has to remember it happened. The system remembers for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step five: teaching an AI to explain all of it in plain English
&lt;/h2&gt;

&lt;p&gt;Here's where post two's biggest lesson came back around. Back then, we discovered that if you tag your data with the right detail, SigNoz can answer real questions for you — no manual math required. The bigger question that kept nagging at us afterward was: what if you didn't even need to know which button to click, or which filter to type? What if you could just &lt;em&gt;ask&lt;/em&gt;, like you're talking to a person?&lt;/p&gt;

&lt;p&gt;That question is what led us into MCP — the protocol that lets an AI model reach out and actually use tools, instead of just talking. Neither of us had touched MCP before this hackathon, so before writing any code, we spent a real evening just reading about how it works. That reading turned out to save us a lot of pain later, even though it didn't save us from all of it.&lt;/p&gt;

&lt;p&gt;What we ended up building is a small chat app we call the &lt;strong&gt;Sidekick&lt;/strong&gt;. You type a question, and behind the scenes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You type a question
   → Groq (the AI model) decides which tool it needs
   → it asks SigNoz for the real data through MCP
   → the real numbers come back
   → the AI reads them and answers you in plain English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SigNoz's MCP connection offers over 40 different tools it can use — way more than we needed — so we limited our Sidekick to five: list services, get traces, get logs, get metrics, get one specific span. Simple, focused, and cheap to run.&lt;/p&gt;

&lt;p&gt;Getting this actually working took two separate evenings of head-scratching, and both problems were the sneaky kind — nothing crashed, things just quietly didn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first problem:&lt;/strong&gt; our AI would just never use any tools. It would answer vaguely instead of actually checking real data, no matter how directly we asked it something like "what services are running right now." We spent a while assuming the AI itself was being stubborn, tweaking our instructions to it, before we finally printed out exactly what we were sending it — and found out the tool descriptions we were passing along were completely empty. Turns out SigNoz describes its tools using a label called &lt;code&gt;inputSchema&lt;/code&gt;, while the AI model we were using expects a label called &lt;code&gt;parameters&lt;/code&gt;. We were only checking for one of the two. The fix was a single line of code, but finding it meant working backwards through several layers of "this should be working" first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputSchema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The second problem&lt;/strong&gt; was stranger. The very first request would work perfectly, and then the second one would get flatly rejected — every time. It took a while to notice that the server hands you an invisible little "session ID" the first time you connect, and expects you to send it back on every request after that, like a wristband at an event. Miss that, and it treats you like a stranger who just walked in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of that, some answers came back in a slightly unusual format made for streaming data, instead of a plain, simple response — so our little connector had to learn to handle both.&lt;/p&gt;

&lt;p&gt;None of this felt exciting while it was happening. It was two nights of adding print statements, staring at raw technical details, and slowly ruling things out one at a time. But the moment both fixes were in, the payoff was immediate. Post two's big win was clicking through a filter and a group-by to get a table. This time, the same kind of insight just... showed up, after typing a normal sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which services are currently being monitored in SigNoz?"&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The services currently being monitored are:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- auto-healer-service — 1 trace in the last hour (avg ~494ms)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- deep-agent-service — 688 traces in the last hour (avg ~1.63s)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools_used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"signoz_list_services"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zya6vmsgnwq2byzbcnt.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%2F0zya6vmsgnwq2byzbcnt.png" alt="Ouroboros AI Sidekick chat interface answering a question about deep-agent-service traces using real SigNoz data" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We kept the AI's instructions short and strict on purpose — answer in a few sentences, use the real numbers you found, don't waste time asking for logins it already has. It's meant to feel like a fast, useful teammate answering a quick question, not a general chatbot rambling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it actually came together
&lt;/h2&gt;

&lt;p&gt;Here's the part that felt like more than just code working.&lt;/p&gt;

&lt;p&gt;We finally let all three pieces run at the same time and just watched. In one window, the fake agent quietly sending traces every 200 milliseconds, like it had been doing for days. In another, the Sidekick sitting there, waiting for a question. And then, in the third window — completely on its own, with nobody typing a single command — the healer suddenly printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚨 ALERT RECEIVED FROM SIGNOZ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We hadn't triggered that. SigNoz had caught the overload pattern by itself, in the live stream of data, and called the webhook on its own.&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%2Fgpfcj7are1ruefhfg4as.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgpfcj7are1ruefhfg4as.jpg" alt="Ouroboros AI landing page showing the self-healing incident timeline: context overload detected, span exported to SigNoz" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the moment this stopped being a demo we were operating and started being a system that was actually running itself.&lt;/p&gt;

&lt;p&gt;About thirty seconds later, we asked the Sidekick what had just happened, half-expecting to have to nudge it toward the right answer. We didn't need to. It described the overload, the automatic retry, and the corrected numbers, pulled straight from the same live data we'd watched land in SigNoz seconds earlier. That was the entire five-step loop, closing itself, start to finish, with nobody standing in the middle of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're taking away from all three posts
&lt;/h2&gt;

&lt;p&gt;Post one taught us that just &lt;em&gt;seeing&lt;/em&gt; where the time goes changes how you think about a problem. Post two taught us that tagging the right detail turns a dashboard into something you can actually have a conversation with. This project is where those two lessons stopped being separate little discoveries and turned into one real habit: instrument things not just to log what's easy right now, but to answer the question you'll actually want answered later — because that same detail is what lets an alert fire cleanly, and what lets an AI explain the whole story back to you without you writing a single extra line of code for it.&lt;/p&gt;

&lt;p&gt;If someone asked us what we'd tell ourselves before starting this hackathon, it's that one sentence. Everything else — the failures, the debugging nights, the late realization that our first agent needed to actually break instead of just wobble — all came from following that one idea seriously enough to build around it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full code, the landing page, and everything else is in the repo: &lt;a href="https://github.com/mauryasagar/ouroboros-ai" rel="noopener noreferrer"&gt;github.com/mauryasagar/ouroboros-ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;By Sagar Maurya &amp;amp; Disha Sonowal — WeMakeDevs SigNoz Hackathon&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>signoz</category>
      <category>wemakedevs</category>
      <category>agents</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>We Asked SigNoz a Question, and It Actually Answered</title>
      <dc:creator>Sagar Maurya</dc:creator>
      <pubDate>Sun, 19 Jul 2026 16:51:23 +0000</pubDate>
      <link>https://dev.to/sagarmaurya/we-asked-signoz-a-question-and-it-actually-answered-p6o</link>
      <guid>https://dev.to/sagarmaurya/we-asked-signoz-a-question-and-it-actually-answered-p6o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;By Sagar Maurya &amp;amp; Disha Sonowal — WeMakeDevs SigNoz Hackathon&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before this, we wrote a &lt;a href="https://dev.to/dishasonowal/we-traced-a-fake-ai-agent-before-building-a-real-one-heres-what-broke-1e7d"&gt;first post&lt;/a&gt; about setting up SigNoz for the very first time — installing it, sending it some data, and figuring out what all this "tracing" stuff actually means. You can also check that out on &lt;a href="https://www.linkedin.com/posts/disha-sonowal-95831a29b_we-traced-a-fake-ai-agent-before-building-activity-7481685838620372992-OWeV" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. If you're new to any of this, that post explains the basics in a simple way, so it's worth a quick look first.&lt;/p&gt;

&lt;p&gt;This post is us going one step further as a team. We wanted to see if SigNoz could actually help us &lt;em&gt;discover&lt;/em&gt; something, not just show us pretty charts. It did, and here's exactly what happened, explained simply.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, two words you need to know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt; — a record of everything that happened during one single request, from the moment it starts to the moment it finishes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span&lt;/strong&gt; — one step inside that trace. If a request does 4 things, it has 4 spans, one for each step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's really all you need to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;A small fake AI assistant. Instead of doing just one thing, it does four things every time someone asks it a question:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Look up some documents&lt;/strong&gt; — pretend to search for information related to the question (a random number, somewhere between 1 and 8 documents)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask the AI&lt;/strong&gt; — pretend to send the question and those documents to an AI model and get an answer back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a tool&lt;/strong&gt; — pretend to use something like a calculator, a calendar, or a search tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean up the answer&lt;/strong&gt; — tidy up the final response before sending it back&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We wrapped each of these four steps in code that tells SigNoz "hey, this step just started" and "hey, this step just finished." That's what creates the spans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking at just one question it answered
&lt;/h2&gt;

&lt;p&gt;We ran our fake assistant 30 times, and then picked one single run to look at closely in SigNoz. Here's what it showed us:&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%2Fx1i4jvh7ghleiti3p3eq.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%2Fx1i4jvh7ghleiti3p3eq.png" alt="flame graph, handle_request 1.72s, llm_call 1.43s" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole thing took 1.72 seconds. Almost all of that — 1.43 seconds — was spent in the "ask the AI" step. Everything else (looking up documents, using a tool, cleaning up) barely took any time at all in comparison. You can literally see this with your eyes on the chart, no reading through code needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we asked a bigger question
&lt;/h2&gt;

&lt;p&gt;Looking at one run is useful, but we wanted to know something bigger: &lt;strong&gt;does the AI step get slower when it has more documents to look through?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In real life, this makes sense — if you ask an AI to read through 8 documents instead of 1, it usually takes longer to answer, because there's more for it to process.&lt;/p&gt;

&lt;p&gt;So instead of checking each of our 30 runs one by one, we asked SigNoz to do the work for us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We told it: "only show me the AI-call steps, ignore the other three"&lt;/li&gt;
&lt;li&gt;We told it: "now group these by how many documents each one used"&lt;/li&gt;
&lt;li&gt;We told it: "show me the average time for each group"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And within seconds, SigNoz gave us this table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Documents used&lt;/th&gt;
&lt;th&gt;Average time taken&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;568ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;658ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;825ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;999ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1147ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1333ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1488ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1614ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frahttpjgaufc3d2n3mt5.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%2Frahttpjgaufc3d2n3mt5.png" alt="grouped table" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at that — it climbs almost perfectly evenly. Every extra document adds roughly 150 milliseconds to the response time. We built this rule into our code, so finding it wasn't a total surprise, but what genuinely impressed us was &lt;em&gt;how easily&lt;/em&gt; SigNoz found and displayed it. We didn't write any extra code, we didn't open Excel, we didn't do any manual math. Just a couple of clicks, and the whole pattern was right there in a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  We also checked the logs
&lt;/h2&gt;

&lt;p&gt;Traces are great for timing, but sometimes you just want to read a plain sentence about what happened. That's what logs are for. We turned those on too, and got results like this:&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%2Fn5sldhfsjklh87lkpyrn.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%2Fn5sldhfsjklh87lkpyrn.png" alt="logs showing entries like " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now every request has two views: the trace shows &lt;em&gt;how long&lt;/em&gt; things took and &lt;em&gt;in what order&lt;/em&gt;, while the logs show &lt;em&gt;what actually happened&lt;/em&gt;, in plain readable sentences. Small tip if you try this yourself — just printing messages to your terminal doesn't automatically send them to SigNoz. You need a couple of extra lines of setup code to actually connect them, or nothing will show up in the Logs tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're taking away from this
&lt;/h2&gt;

&lt;p&gt;The biggest thing we learned: a trace waterfall is great for spotting &lt;em&gt;what's&lt;/em&gt; slow. But the real magic happened when we tagged our own extra detail onto a span — in our case, "how many documents did this use." That one small addition is what let us ask SigNoz a real question and get a real answer back, across all 30 runs at once, instead of guessing.&lt;/p&gt;

&lt;p&gt;For the actual hackathon build, this is exactly the habit we want to carry forward — don't just time your code, tag it with details that'll actually matter later when something's slow and you're trying to figure out why.&lt;/p&gt;

</description>
      <category>signoz</category>
      <category>wemakedevs</category>
      <category>agents</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Data Isn't What Your Textbook Said It Was</title>
      <dc:creator>Sagar Maurya</dc:creator>
      <pubDate>Sat, 11 Jul 2026 02:13:37 +0000</pubDate>
      <link>https://dev.to/sagarmaurya/data-isnt-what-your-textbook-said-it-was-3df8</link>
      <guid>https://dev.to/sagarmaurya/data-isnt-what-your-textbook-said-it-was-3df8</guid>
      <description>&lt;h3&gt;
  
  
  &lt;em&gt;A beginner-friendly breakdown of what data really is, where it comes from, and why it matters more than you think.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
You woke up this morning and checked your phone.&lt;/p&gt;

&lt;p&gt;You scrolled through Instagram. Opened WhatsApp. Maybe checked the weather. Ordered breakfast on Swiggy.&lt;/p&gt;

&lt;p&gt;By the time you finished your morning tea, you had already generated hundreds of data points — and that was before 9 AM.&lt;/p&gt;

&lt;p&gt;We live in a world that runs on data. But most people, including a lot of CS students, have never stopped to ask: &lt;strong&gt;what actually is data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not the textbook definition. The real one.&lt;/p&gt;

&lt;p&gt;And that's exactly the problem — we all learned about data from a textbook. Which means we learned about it wrong.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Textbook Lie
&lt;/h2&gt;

&lt;p&gt;Ask anyone what data is and they'll say something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Data is raw, unprocessed facts and figures."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technically correct. Completely useless.&lt;/p&gt;

&lt;p&gt;That definition tells you nothing about why data matters, where it comes from, or what makes it powerful. It's the kind of definition written for an exam, not for understanding.&lt;/p&gt;

&lt;p&gt;So let's close the textbook and start over.&lt;/p&gt;


&lt;h2&gt;
  
  
  Data is Just a Record of Something That Happened
&lt;/h2&gt;

&lt;p&gt;Every time something happens in the real world — a click, a purchase, a step you walked, a message you sent — there's a possibility of recording it.&lt;/p&gt;

&lt;p&gt;When you record it? That's data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You clicked on a product → &lt;strong&gt;data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your phone counted 6,400 steps today → &lt;strong&gt;data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You paused a YouTube video at 2:34 → &lt;strong&gt;data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You left a website after 3 seconds → &lt;strong&gt;data&lt;/strong&gt; (and a headache for that website's owner)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data is just a trace that something happened. That's it.&lt;/p&gt;

&lt;p&gt;The interesting part is what happens &lt;em&gt;after&lt;/em&gt; you collect those traces.&lt;/p&gt;


&lt;h2&gt;
  
  
  Raw Data is Almost Always Ugly
&lt;/h2&gt;

&lt;p&gt;Here's what nobody tells beginners: data in the real world is messy.&lt;/p&gt;

&lt;p&gt;Imagine a Google Form that asks for someone's phone number. You'll get responses like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9876543210
+91-9876543210
98765 43210
9876543210 (call after 6pm)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four people gave you the same number. But to a computer, these are four completely different values.&lt;/p&gt;

&lt;p&gt;This is what's called &lt;strong&gt;dirty data&lt;/strong&gt; — and cleaning it is genuinely one of the most important (and underrated) skills in the data field.&lt;/p&gt;

&lt;p&gt;A famous saying in data science is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"80% of data work is cleaning. The other 20% is complaining about cleaning."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a joke. But not really.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Journey of Data: From Noise to Decision
&lt;/h2&gt;

&lt;p&gt;Here's a simple way to think about how data travels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something happens
      ↓
It gets recorded (raw data)
      ↓
It gets cleaned (processed data)
      ↓
It gets analyzed (insights)
      ↓
Someone makes a decision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's make this real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; Swiggy wants to know why orders drop on Tuesday evenings.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They collect order timestamps → &lt;em&gt;raw data&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;They remove duplicates, fix timezone errors → &lt;em&gt;clean data&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;They spot that Tuesday 7–9 PM has 40% fewer orders → &lt;em&gt;insight&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;They run a Tuesday evening discount campaign → &lt;em&gt;decision&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That entire chain? It all started with someone pressing "Order" on their phone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Not All Data is Numbers
&lt;/h2&gt;

&lt;p&gt;A common misconception is that data means spreadsheets full of numbers. But data comes in many forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured data&lt;/strong&gt; — rows and columns, like a CSV or a database table. Easy for computers to process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured data&lt;/strong&gt; — text, images, audio, video. Harder to process, but far more common in the real world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semi-structured data&lt;/strong&gt; — like JSON or XML. Has some structure, but not rigid rows and columns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you send a WhatsApp message, that's unstructured data. When your bank logs a transaction, that's structured data. When you fill a Google Form, that produces semi-structured data.&lt;/p&gt;

&lt;p&gt;Most of the world's data — over 80% of it — is unstructured. Which is why fields like NLP (Natural Language Processing) and Computer Vision exist: to make sense of data that doesn't fit neatly into a table.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Data is Someone's Product
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth.&lt;/p&gt;

&lt;p&gt;Every free app you use — Instagram, Google, YouTube — is free because &lt;em&gt;you&lt;/em&gt; are not the customer. You're the product. More precisely, your data is.&lt;/p&gt;

&lt;p&gt;When you like a post, skip an ad, or spend 45 minutes on Reels instead of 5, that behavior is recorded, analyzed, and used to serve you more content that keeps you on the platform longer.&lt;/p&gt;

&lt;p&gt;This isn't conspiracy theory. It's just the data pipeline at scale.&lt;/p&gt;

&lt;p&gt;Understanding this doesn't mean you need to delete all your apps. But it does mean you should be aware of the trade you're making — your attention and behavior in exchange for a free service.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Everyone Should Care About Data (Not Just Data Scientists)
&lt;/h2&gt;

&lt;p&gt;You don't need to work in data to benefit from understanding it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;As a developer&lt;/strong&gt; — you'll build systems that generate data. Understanding data helps you design better databases and APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As a product person&lt;/strong&gt; — decisions without data are just opinions. Data makes arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As a user&lt;/strong&gt; — knowing how your data is used makes you a more informed digital citizen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As a student preparing for placements&lt;/strong&gt; — almost every tech company today is a data company in some way. Interviews increasingly involve data thinking, even for SDE roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data literacy is becoming as fundamental as being able to read and write.&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Did Your Textbook Get Wrong?
&lt;/h2&gt;

&lt;p&gt;Nothing, technically. But everything, practically.&lt;/p&gt;

&lt;p&gt;Your textbook gave you a definition of data that would pass an exam. It didn't give you a mental model that would help you think about the world differently.&lt;/p&gt;

&lt;p&gt;The difference between a student who "knows data" and someone who &lt;em&gt;understands&lt;/em&gt; it is this: one memorized a definition, the other sees data everywhere they look — in the apps they use, the decisions companies make, and the systems they build.&lt;/p&gt;

&lt;p&gt;That shift in perspective is what this blog is about. Not definitions. Mental models.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;This post was just the surface. In future posts, I'll go deeper into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How databases store and retrieve data efficiently&lt;/li&gt;
&lt;li&gt;What actually happens when you run a SQL query&lt;/li&gt;
&lt;li&gt;The difference between a data analyst, data engineer, and data scientist&lt;/li&gt;
&lt;li&gt;And some hands-on projects that helped me understand all of this better&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're a student or someone just getting into tech — I hope this gave you a clearer mental model of what data actually is.&lt;/p&gt;

&lt;p&gt;Because before you can work with data, you need to understand what you're actually working with.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading! If you found this useful, drop a reaction or share it with someone who's just getting started in tech.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>data</category>
      <category>career</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
