<?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: Haripriya Veluchamy</title>
    <description>The latest articles on DEV Community by Haripriya Veluchamy (@techwithhari).</description>
    <link>https://dev.to/techwithhari</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%2F1914764%2Fbc8a04cf-4e71-485f-8880-5b49f05c9560.png</url>
      <title>DEV Community: Haripriya Veluchamy</title>
      <link>https://dev.to/techwithhari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/techwithhari"/>
    <language>en</language>
    <item>
      <title>Vault cant found What a Live Audit Found That Code Review Never Would</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Wed, 29 Jul 2026 08:28:13 +0000</pubDate>
      <link>https://dev.to/techwithhari/vault-cant-found-what-a-live-audit-found-that-code-review-never-would-548e</link>
      <guid>https://dev.to/techwithhari/vault-cant-found-what-a-live-audit-found-that-code-review-never-would-548e</guid>
      <description>&lt;p&gt;Some bugs hide in logic. This one hid in the gap between "the code looks correct" and "the code is actually working"  and the only way I found it was by refusing to trust either claim without checking the live system directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Mid-migration between two credential-storage systems: an older secrets vault (call it the &lt;em&gt;legacy vault&lt;/em&gt;) used by an original provider integration, and a newer, purpose-built vault for a self-hosted replacement being rolled out gradually. During the transition, both exist side by side. The code that decides which vault to check for a given user's credentials looked, on review, completely correct  it classified each connection record as "legacy" or "new" based on a stored field, and looked in the matching vault.&lt;/p&gt;

&lt;p&gt;Nothing about the code review raised a flag. Which is exactly the problem with code review as your only line of defense: it tells you the logic is &lt;em&gt;internally consistent&lt;/em&gt;, not that the systems it's talking to still exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the audit, properly this time
&lt;/h2&gt;

&lt;p&gt;Instead of trusting the classification logic in the abstract, I wrote a small audit script with one rule: &lt;strong&gt;don't reconstruct what a secret's name &lt;em&gt;should&lt;/em&gt; be and assume it's there  actually query the live vault and see.&lt;/strong&gt; This sounds obvious. It's also very easy to skip, because "reconstruct the expected name, spot-check a couple" feels like it should be equivalent to "check them all live," and it really isn't.&lt;/p&gt;

&lt;p&gt;The script pulled every connection record from the database, classified each one the same way the app's own code did, and then made a real, live call to whichever vault it &lt;em&gt;should&lt;/em&gt; be in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it found
&lt;/h2&gt;

&lt;p&gt;Every single legacy-vault lookup failed. Not "secret not found"  a connection failure. The vault itself wasn't resolving anymore.&lt;/p&gt;

&lt;p&gt;That's a materially different problem than a missing secret. A missing secret means "someone forgot to write this." A vault that doesn't resolve at all means the entire piece of infrastructure the code has been confidently pointing at no longer exists  and every single credential-write attempt to it, for every legacy user, had been silently failing, this whole time, for however long it had been broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did nobody notice?
&lt;/h2&gt;

&lt;p&gt;The honest, slightly embarrassing answer: the code that wrote to this vault caught the failure, logged a warning, and moved on. Not a crash. Not an alert. A line in a log nobody was watching, for a code path nobody expected to fail, because "the vault exists" was baked in as an assumption so early in the system's life that it had stopped being something anyone thought to re-check.&lt;/p&gt;

&lt;p&gt;This is the actual shape of the bug, more than the specific vault: &lt;strong&gt;a &lt;code&gt;try/catch&lt;/code&gt; that swallows a failure gracefully is indistinguishable, from the outside, from success  until someone goes looking.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually surprised me
&lt;/h2&gt;

&lt;p&gt;Here's where it got more interesting than "we found a broken thing, went and fixed it." Digging into &lt;em&gt;why&lt;/em&gt; the legacy vault mattered at all, it turned out the underlying legacy provider managed those users' actual live sessions entirely on its own side  the app's cached copy of their credentials was never actually read from again in practice. The vault had been dead, silently, for a long stretch, and functionally, nothing legacy-side had actually broken because of it.&lt;/p&gt;

&lt;p&gt;That's not a "phew, no harm done, nothing to see here" ending though. It's a different, more uncomfortable one: &lt;strong&gt;we got lucky that this specific dead dependency happened to be redundant.&lt;/strong&gt; The audit didn't know that in advance, and neither did I. The only way to find out whether a silently-dead piece of infrastructure actually matters is to go looking  you can't reason your way to "probably fine" from the code alone.&lt;/p&gt;

&lt;p&gt;The same audit, run against the &lt;em&gt;newer&lt;/em&gt; vault  the one actually still in active, load-bearing use  found a real, smaller, actionable problem: a couple of expected entries genuinely missing. Real incomplete setup, not infrastructure rot. Fixed directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away from this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code review confirms logic. It cannot confirm that the systems the logic depends on still exist.&lt;/strong&gt; Those are different guarantees, and it's easy to unconsciously treat the first as if it implies the second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A caught-and-logged failure is a blind spot by construction&lt;/strong&gt;, not just bad luck  the entire point of catching it gracefully is that nothing breaks loudly. That's good for uptime and bad for ever noticing, unless something is actually watching those logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Reconstruct the expected value and spot-check it" is not the same audit as "query the live system for every case."&lt;/strong&gt; The first checks your mental model. The second checks reality. They can disagree, and when they do, reality wins.&lt;/li&gt;
&lt;li&gt;Every so often, a system this old deserves a live audit, not just a code review  specifically to surface the assumptions that were true once, got baked in early, and nobody has had a reason to re-question since.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>monitoring</category>
      <category>azure</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why Your CI Agent Can't Install pip</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Sat, 25 Jul 2026 16:06:39 +0000</pubDate>
      <link>https://dev.to/techwithhari/why-your-ci-agent-cant-install-pip-11if</link>
      <guid>https://dev.to/techwithhari/why-your-ci-agent-cant-install-pip-11if</guid>
      <description>&lt;p&gt;If you've ever set up a self-hosted CI/CD agent on a fresh Ubuntu image and hit &lt;code&gt;pip: command not found&lt;/code&gt; or &lt;code&gt;No module named pip&lt;/code&gt;, your first instinct is probably to check the network. Mine was too. That instinct is wrong, and the real answer took me four attempts to actually land on.&lt;/p&gt;

&lt;p&gt;Here's the full story, including the three dead ends, because I think the dead ends are the useful part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;A self-hosted CI agent (in my case, an Azure DevOps agent, but this applies just as much to a self-hosted GitHub Actions runner or a plain Jenkins box) running on a minimal Ubuntu image. No sudo access on the box  that's deliberate, since the agent shouldn't need root just to run a pipeline. I needed to install a CLI tool that only ships via &lt;code&gt;pip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Reasonable first attempt:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;python3 -m pip install --user --quiet some-cli-tool&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tool'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;/usr/bin/python3: No module named pip
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine, I thought  &lt;code&gt;pip&lt;/code&gt; module is missing, &lt;code&gt;ensurepip&lt;/code&gt; should bootstrap it. Every tutorial says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: &lt;code&gt;ensurepip&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;python3 -m ensurepip --user --upgrade&lt;/span&gt;
    &lt;span class="s"&gt;python3 -m pip install --user --quiet some-cli-tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;/usr/bin/python3: No module named ensurepip
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not just &lt;code&gt;pip&lt;/code&gt; missing  &lt;code&gt;ensurepip&lt;/code&gt; itself, the &lt;em&gt;thing that's supposed to install pip&lt;/em&gt;, doesn't exist either. That's the first real clue something structural is going on, not just a missing package.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual reason (worth understanding, not just working around)
&lt;/h2&gt;

&lt;p&gt;Debian and Ubuntu deliberately strip &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;ensurepip&lt;/code&gt; out of the base &lt;code&gt;python3&lt;/code&gt; package. This isn't a bug or an oversight  it's a packaging policy decision. Both live in separate packages (&lt;code&gt;python3-pip&lt;/code&gt;, &lt;code&gt;python3-venv&lt;/code&gt;) that you're expected to install via &lt;code&gt;apt&lt;/code&gt;. On a full desktop or dev-configured server, you've probably had these installed for so long you forgot they're not actually part of core Python.&lt;/p&gt;

&lt;p&gt;On a minimal, no-sudo CI image, you don't have &lt;code&gt;apt&lt;/code&gt; access at all  so you can't just &lt;code&gt;apt install python3-pip&lt;/code&gt; your way out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: &lt;code&gt;venv&lt;/code&gt; (also fails, same root cause)
&lt;/h2&gt;

&lt;p&gt;My next thought: skip &lt;code&gt;pip&lt;/code&gt;/&lt;code&gt;ensurepip&lt;/code&gt; entirely, use a virtual environment instead, since &lt;code&gt;venv&lt;/code&gt; normally bootstraps its own &lt;code&gt;pip&lt;/code&gt; on creation.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;python3 -m venv /tmp/tool-venv&lt;/span&gt;
    &lt;span class="s"&gt;/tmp/tool-venv/bin/pip install --quiet some-cli-tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: fails identically. &lt;code&gt;venv&lt;/code&gt;'s own pip-bootstrapping step depends on  you guessed it  the same missing &lt;code&gt;ensurepip&lt;/code&gt; module. Same wall, different door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 4: the actual fix  &lt;code&gt;get-pip.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The real answer is a standalone bootstrapping script, maintained by the Python Packaging Authority specifically for situations like this  environments where &lt;code&gt;ensurepip&lt;/code&gt; isn't available:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;curl -sS https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py&lt;/span&gt;
    &lt;span class="s"&gt;python3 /tmp/get-pip.py --user --quiet&lt;/span&gt;
    &lt;span class="s"&gt;python3 -m pip install --user --quiet some-cli-tool&lt;/span&gt;
    &lt;span class="s"&gt;echo "##vso[task.prependpath]$HOME/.local/bin"&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(get-pip,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sudo)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;get-pip.py&lt;/code&gt; doesn't depend on &lt;code&gt;ensurepip&lt;/code&gt; at all  it's a self-contained bootstrapper that installs &lt;code&gt;pip&lt;/code&gt; directly. This worked immediately, no sudo, no apt, no venv complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing to double-check before you assume this is your problem
&lt;/h2&gt;

&lt;p&gt;Before chasing this fix, rule out the boring explanation first: confirm your agent actually has network access to PyPI at all. A quick diagnostic step saved me from solving the wrong problem:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;curl -sS -o /dev/null -w "pypi.org: %{http_code}\n" https://pypi.org --max-time 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that comes back with anything other than a fast &lt;code&gt;200&lt;/code&gt;, you're dealing with a firewall/egress issue, not this one  and no amount of &lt;code&gt;get-pip.py&lt;/code&gt; will fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;python3 -m pip&lt;/code&gt; and &lt;code&gt;python3 -m ensurepip&lt;/code&gt; are &lt;em&gt;both&lt;/em&gt; missing on a Debian/Ubuntu box, that's not a broken image  it's the distro's actual, intentional packaging policy. &lt;code&gt;apt install python3-pip&lt;/code&gt; is the "normal" fix, and it's simply not available to you on a locked-down, no-sudo CI agent. &lt;code&gt;get-pip.py&lt;/code&gt; is the one workaround that doesn't need &lt;code&gt;apt&lt;/code&gt;, doesn't need &lt;code&gt;ensurepip&lt;/code&gt;, and doesn't need root.&lt;/p&gt;

&lt;p&gt;Small thing, but it cost me three wrong turns before I found it  hopefully this saves you those three.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>python</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Why AI Can't Actually Hit Your Word Limit</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:20:08 +0000</pubDate>
      <link>https://dev.to/techwithhari/why-ai-cant-actually-hit-your-word-limit-35b1</link>
      <guid>https://dev.to/techwithhari/why-ai-cant-actually-hit-your-word-limit-35b1</guid>
      <description>&lt;p&gt;I Was drafting my blog post, told Claude "keep it to 250 words." Got back something like 340. Asked again. Same story.&lt;/p&gt;

&lt;p&gt;Figured there's a real reason behind this, and since I'm neck-deep in AI/ML fundamentals right now, decided to actually understand it instead of just being annoyed by it.&lt;/p&gt;

&lt;h3&gt;
  
  
  It generates one token at a time, no plan
&lt;/h3&gt;

&lt;p&gt;LLMs don't write like we do think of the whole thing, then fill it in. They generate autoregressively: predict the next token, add it to the context, predict the next one, repeat. There's no "here's my 250-word essay, let me write it out" step happening anywhere. Every single token is picked based on what came before, nothing more.&lt;/p&gt;

&lt;p&gt;So when you ask for 250 words, the model isn't holding a target in memory and counting down. It's pattern-matching: "text like this, following an instruction like this, tends to stop around here." That's a guess shaped by training data, not a computation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Words ≠ tokens
&lt;/h3&gt;

&lt;p&gt;Also it's not even counting &lt;em&gt;words&lt;/em&gt;. Internally, everything is tokens: sub-word chunks. "Debugging" might be one token or three depending on the tokenizer. "250 words" as an instruction gets converted into an expectation of &lt;em&gt;roughly&lt;/em&gt; how many tokens that maps to, and that mapping is fuzzy. There's no clean 1:1 between what you asked for and what the architecture tracks.&lt;/p&gt;

&lt;h3&gt;
  
  
  No counter anywhere in the architecture
&lt;/h3&gt;

&lt;p&gt;This is the part that surprised me most: there's no length-tracking variable sitting in the model's forward pass. No &lt;code&gt;if word_count == 250: stop&lt;/code&gt;. The only thing steering generation is the probability distribution over the next token, sampled step by step, until it predicts an end-of-sequence token or hits a max-length cutoff set by the system.&lt;/p&gt;

&lt;p&gt;Compare that to something like a &lt;code&gt;for&lt;/code&gt; loop with a counter deterministic, exact. LLM generation is closer to sampling from a learned distribution, over and over. Approximate by design, not by bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  The workaround
&lt;/h3&gt;

&lt;p&gt;Since I can't get an exact count out of a single generation, what actually works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for a natural stopping point instead of a hard number&lt;/li&gt;
&lt;li&gt;Generate first, then trim or expand after, treating the count as a second pass, not the first&lt;/li&gt;
&lt;li&gt;For real precision, set limits in characters or use iterative regeneration with a check step, rather than trusting the first output&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why this clicked for me
&lt;/h3&gt;

&lt;p&gt;This ties directly into what I've been learning attention, softmax over vocabulary, sampling strategies. Word-count control would need something structurally different: a running counter fed back into the decoding loop, or a constrained decoding step that force-stops at a token budget. Neither is how these mainstream models work by default.&lt;/p&gt;

&lt;p&gt;Small annoyance, decent rabbit hole. Worth knowing before you set a hard word limit and assume the model's counting along with you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Learning AI/ML from the ground up cloud engineer trying to understand the models she uses daily.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>development</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Serving ML Artifacts from Amazon S3 Files How I used After the Launch</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:37:34 +0000</pubDate>
      <link>https://dev.to/techwithhari/serving-ml-artifacts-from-amazon-s3-files-how-i-used-after-the-launch-764</link>
      <guid>https://dev.to/techwithhari/serving-ml-artifacts-from-amazon-s3-files-how-i-used-after-the-launch-764</guid>
      <description>&lt;h2&gt;
  
  
  The Honest Story
&lt;/h2&gt;

&lt;p&gt;Two months ago, everyone was posting about Amazon S3 Files. New feature, big announcement, screenshots everywhere. I scrolled past most of them another AWS launch, another round of "here's what it does" posts.&lt;/p&gt;

&lt;p&gt;I never actually understood what it was until it became my problem.&lt;/p&gt;

&lt;p&gt;I was in the middle of my ML learning journey, building a semantic search project. Large FAISS indexes, BM25 artifacts all needed to be available at serving time. I was uploading them directly into my deployment. It worked, until it didn't. Hit the size limit. Container cold starts became painful. Every restart meant downloading hundreds of megabytes before the first request could be served.&lt;/p&gt;

&lt;p&gt;I had one option left S3.&lt;/p&gt;

&lt;p&gt;And somewhere in the back of my head I remembered: "wait, there's that new S3 file system thing everyone was talking about." S3 is already my favorite AWS service. So why not try it?&lt;/p&gt;

&lt;p&gt;I tried it. I built something real with it. And now I'm writing the post I wish existed two months ago not "here's what S3 Files is" but "here's what actually happens when you use it."&lt;/p&gt;




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

&lt;p&gt;A semantic search engine over AWS documentation 5048 pages, indexed with FAISS + BM25 hybrid retrieval, served via FastAPI. The entire ML artifact stack (indexes, metadata) is served directly from an S3 Files NFS mount on EC2. No cold start downloads. No boto3 in the serving code. Just &lt;code&gt;open()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings: &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Semantic search: FAISS (IndexFlatIP)&lt;/li&gt;
&lt;li&gt;Keyword search: BM25Okapi&lt;/li&gt;
&lt;li&gt;Serving: FastAPI + Jinja2&lt;/li&gt;
&lt;li&gt;Artifact storage: Amazon S3 + S3 Files (NFS)&lt;/li&gt;
&lt;li&gt;Compute: EC2 t3.medium (Ubuntu 22.04)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Harivelu0/s3-files-ml-serving" rel="noopener noreferrer"&gt;Harivelu0/s3-files-ml-serving&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem S3 Files Solves
&lt;/h2&gt;

&lt;p&gt;Before S3 Files, serving large ML artifacts from containers looked like this:&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;# Every container startup
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;huggingface_hub&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hf_hub_download&lt;/span&gt;

&lt;span class="n"&gt;faiss_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hf_hub_download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faiss.index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 7MB
&lt;/span&gt;&lt;span class="n"&gt;bm25_path&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hf_hub_download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bm25_index.pkl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 17MB
&lt;/span&gt;&lt;span class="n"&gt;meta_path&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hf_hub_download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;corpus_meta.pkl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.6MB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every container restart meant downloading ~27MB before the first request. For larger models this becomes hundreds of MB or GBs. You'd need to manage download logic, handle failures, worry about &lt;code&gt;/tmp&lt;/code&gt; size limits, and every container on the same host duplicates the same data.&lt;/p&gt;

&lt;p&gt;With S3 Files, your serving code becomes:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;

&lt;span class="c1"&gt;# Just open() no download, no boto3
&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/artifacts/faiss.index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/artifacts/bm25_index.pkl&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;rb&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bm25&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The S3 bucket is mounted as an NFS volume on the EC2 host. Your container reads from it like a local file. That's it.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Docs (5048 pages)
        ↓
crawl_aws_docs.py (Sitemap + BeautifulSoup)
        ↓
build_index.py (FAISS + BM25 + corpus_meta)
        ↓
S3 Bucket (versioning enabled)
        ↓
S3 File System (NFS layer on top of bucket)
        ↓
Mount Target (NFS endpoint inside VPC)
        ↓
EC2 Ubuntu 22.04
  └── /mnt/artifacts (S3 Files mounted here)
       └── Docker container
            └── FastAPI reads indexes directly
                 └── http://ec2-ip:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Setting Up S3 Files What Actually Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. S3 bucket needs versioning enabled
&lt;/h3&gt;

&lt;p&gt;This one catches everyone. S3 Files will refuse to create a file system without it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-bucket-versioning &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--bucket&lt;/span&gt; your-bucket &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--versioning-configuration&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. IAM role trust principal is &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Not &lt;code&gt;s3files.amazonaws.com&lt;/code&gt; even though the service is called S3 Files, it's built on EFS under the hood.&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;"Principal"&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;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"elasticfilesystem.amazonaws.com"&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;span class="nl"&gt;"Condition"&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;span class="nl"&gt;"StringEquals"&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;span class="nl"&gt;"aws:SourceAccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCOUNT_ID"&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;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;h3&gt;
  
  
  3. The IAM role needs EventBridge permissions
&lt;/h3&gt;

&lt;p&gt;S3 Files uses EventBridge internally to monitor bucket changes. Without these, your file system gets stuck in &lt;code&gt;creating&lt;/code&gt; forever.&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;"Action"&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;span class="s2"&gt;"events:PutRule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"events:DeleteRule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"events:PutTargets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"events:RemoveTargets"&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;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;h3&gt;
  
  
  4. boto3 API uses camelCase
&lt;/h3&gt;

&lt;p&gt;The S3 Files boto3 client uses camelCase unlike most other AWS services.&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;# Wrong
&lt;/span&gt;&lt;span class="n"&gt;s3files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_file_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RoleArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role_arn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Correct
&lt;/span&gt;&lt;span class="n"&gt;s3files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_file_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roleArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role_arn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Mount needs &lt;code&gt;amazon-efs-utils&lt;/code&gt;, not plain NFS
&lt;/h3&gt;

&lt;p&gt;Plain &lt;code&gt;mount -t nfs4&lt;/code&gt; will fail. S3 Files requires the &lt;code&gt;amazon-efs-utils&lt;/code&gt; package (v3.0+) which handles TLS + IAM auth automatically.&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;# Build and install amazon-efs-utils&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; cmake golang-go rustc cargo
git clone https://github.com/aws/efs-utils
&lt;span class="nb"&gt;cd &lt;/span&gt;efs-utils &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./build-deb.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ./build/amazon-efs-utils&lt;span class="k"&gt;*&lt;/span&gt;.deb

&lt;span class="c"&gt;# Then mount&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; s3files fs-0xxxxxxxxx:/ /mnt/artifacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. EC2 instance needs &lt;code&gt;AmazonS3FilesClientFullAccess&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Without this policy on the EC2 role, the mount returns &lt;code&gt;access denied by server&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam attach-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role-name&lt;/span&gt; your-ec2-role &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Serving Code
&lt;/h2&gt;

&lt;p&gt;FastAPI startup loads everything from the mount. No download logic. No error handling for network failures during download. The mount is always there.&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;ARTIFACTS_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&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;ARTIFACTS_DIR&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;/mnt/artifacts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Reads directly from S3 Files mount
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index&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="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARTIFACTS_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faiss.index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARTIFACTS_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bm25_index.pkl&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;rb&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bm25&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bm25&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARTIFACTS_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;corpus_meta.pkl&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;rb&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta&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="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container startup: model loads in ~2s. Artifacts available instantly from mount.&lt;/p&gt;




&lt;h2&gt;
  
  
  Weekly Index Updates The Real S3 Files Advantage
&lt;/h2&gt;

&lt;p&gt;This is where S3 Files goes beyond just solving cold starts.&lt;/p&gt;

&lt;p&gt;AWS updates their docs regularly. With traditional artifact serving you'd need to rebuild indexes, push a new container image, and redeploy causing downtime.&lt;/p&gt;

&lt;p&gt;With S3 Files:&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;# update_pipeline.sh runs weekly via EventBridge&lt;/span&gt;
python scripts/crawl_aws_docs.py &lt;span class="nt"&gt;--update&lt;/span&gt;   &lt;span class="c"&gt;# only changed pages&lt;/span&gt;
python precompute/build_index.py            &lt;span class="c"&gt;# rebuild indexes to /tmp&lt;/span&gt;

&lt;span class="c"&gt;# Atomic swap no downtime&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; /tmp/faiss.index.new     /mnt/artifacts/artifacts/faiss.index
&lt;span class="nb"&gt;mv&lt;/span&gt; /tmp/bm25_index.pkl.new  /mnt/artifacts/artifacts/bm25_index.pkl
&lt;span class="nb"&gt;mv&lt;/span&gt; /tmp/corpus_meta.pkl.new /mnt/artifacts/artifacts/corpus_meta.pkl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The serving container picks up the new indexes on the next query. Zero restart. Zero redeploy. The mount sees the updated S3 objects immediately.&lt;/p&gt;

&lt;p&gt;This is the killer feature not just cold start elimination, but &lt;strong&gt;live index updates without any deployment&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The search UI running at &lt;code&gt;http://98.93.65.241:8000&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query: &lt;code&gt;"aws sns permission issue"&lt;/code&gt; → 10 results in &lt;strong&gt;334ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Artifacts loaded at startup: &lt;strong&gt;instant&lt;/strong&gt; (no download)&lt;/li&gt;
&lt;li&gt;Index update: rebuild + upload to S3 → serving picks up &lt;strong&gt;without restart&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;S3 Files is genuinely useful for ML workloads where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Artifacts are large (&amp;gt;100MB)&lt;/li&gt;
&lt;li&gt;Multiple containers need the same data&lt;/li&gt;
&lt;li&gt;Indexes update regularly without downtime&lt;/li&gt;
&lt;li&gt;You want to avoid baking artifacts into Docker images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not a replacement for EFS if you need pure file system performance. And it's not for every use case if your artifacts never change and containers rarely restart, the complexity isn't worth it.&lt;/p&gt;

&lt;p&gt;But if you've ever stared at a container downloading 500MB on every cold start, S3 Files is exactly what you were waiting for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Harivelu0/s3-files-ml-serving" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html" rel="noopener noreferrer"&gt;Amazon S3 Files docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/efs-utils" rel="noopener noreferrer"&gt;amazon-efs-utils GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>How I Built a Self Resizing EC2 for My ML Data</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Sun, 07 Jun 2026 11:45:14 +0000</pubDate>
      <link>https://dev.to/techwithhari/how-i-built-a-self-resizing-ec2-for-my-ml-data-4i38</link>
      <guid>https://dev.to/techwithhari/how-i-built-a-self-resizing-ec2-for-my-ml-data-4i38</guid>
      <description>&lt;h2&gt;
  
  
  The Pain Point
&lt;/h2&gt;

&lt;p&gt;I'm on an ML learning journey. That means a lot of data. A lot of processing. And a lot of AWS free credits I really can't afford to waste.&lt;/p&gt;

&lt;p&gt;Here's what my typical day looked like:&lt;/p&gt;

&lt;p&gt;I'd spin up a &lt;code&gt;t3.large&lt;/code&gt; to run a data pipeline. Load some datasets, process them, store them. The pipeline would run for a couple of hours sometimes I didn't even know exactly how long it would take. Then I'd go to sleep.&lt;/p&gt;

&lt;p&gt;Next morning I'd check CloudWatch and realise the VM had been sitting idle since 3AM. Running. Doing nothing. Burning credits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the problem. You need a big machine for data loading. You don't need it after.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The First Thought Let AWS ML Decide
&lt;/h2&gt;

&lt;p&gt;My first instinct was to use &lt;strong&gt;AWS Compute Optimizer&lt;/strong&gt;. It's a managed ML service that analyses your EC2 usage patterns and recommends the right instance type. Smart, right?&lt;/p&gt;

&lt;p&gt;I enabled it. Waited. And waited.&lt;/p&gt;

&lt;p&gt;Turns out Compute Optimizer needs at least &lt;strong&gt;30 consecutive hours&lt;/strong&gt; of usage data before it generates recommendations. For a VM I spin up occasionally for pipeline runs that's not practical.&lt;/p&gt;

&lt;p&gt;So I moved on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Second Thought Bedrock Agent
&lt;/h2&gt;

&lt;p&gt;Next idea: use &lt;strong&gt;Amazon Bedrock&lt;/strong&gt; as an agent to reason about when and how to resize. Let an LLM decide.&lt;/p&gt;

&lt;p&gt;But the more I thought about it, the more it felt like overkill. The decision isn't complex:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Did the pipeline finish? Yes → resize down. No → don't."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not a reasoning problem. That's an automation problem. Using Bedrock here would be AI washing  adding complexity without adding value.&lt;/p&gt;

&lt;p&gt;So I kept it simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution EventDriven VM Resize
&lt;/h2&gt;

&lt;p&gt;What I built: a lightweight agent that listens for your pipeline to complete, then automatically resizes the VM down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;emit_event.py&lt;/code&gt;  runs on your VM, fires when pipeline exits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon EventBridge&lt;/strong&gt; receives the event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;  handles resize logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SNS&lt;/strong&gt;  sends email alert (success or failure)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline finishes
      ↓
emit_event.py → EventBridge
      ↓
Lambda triggered
      ↓
SUCCESS → resize down + email
FAILURE → keep size (debug) + email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ML. No LLM. Just the right tool for the job.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your VM                        AWS Cloud
──────────────────             ─────────────────────
run_pipeline.sh                EventBridge (custom bus)
  step1.py               →          ↓
  step2.py                     Lambda
  step3.py                       ├── stop EC2
  emit_event.py ──────────→      ├── resize instance type
                                 ├── start EC2
                                 └── SNS email alert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All AWS resources are deployed with a single CloudFormation command. No manual console clicking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup  3 Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1  Deploy AWS infrastructure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Harivelu0/vm-resize-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;vm-resize-agent

aws cloudformation deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; infra/template.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; vm-resize-agent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;AlertEmail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your@email.com &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;TargetInstanceType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;t3.medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your email → confirm the AWS subscription link.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2  Copy agent to your VM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; your-key.pem &lt;span class="nt"&gt;-r&lt;/span&gt; agent/ pipeline/ &lt;span class="se"&gt;\&lt;/span&gt;
  ec2-user@your-vm-ip:~/vm-resize-agent/

&lt;span class="c"&gt;# on VM&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;boto3
aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3  Add your pipeline steps
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;pipeline/steps.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 /home/user/myproject/fetch_data.py
python3 /home/user/myproject/transform.py
python3 /home/user/myproject/load_db.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash pipeline/run_pipeline.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. When pipeline finishes → VM resizes → email arrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key Design Decision steps.conf
&lt;/h2&gt;

&lt;p&gt;One thing I was particular about: this tool should work for &lt;strong&gt;anyone's pipeline&lt;/strong&gt;, not just mine.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;run_pipeline.sh&lt;/code&gt; never changes. Users only edit &lt;code&gt;steps.conf&lt;/code&gt;  one command per line, one step per line.&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;# steps.conf&lt;/span&gt;
python3 /home/user/fetch_gdelt.py
python3 /home/user/build_forecasts.py
python3 /home/user/calibrate.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrapper reads each line, runs it in order, tracks success/failure, and emits the event at the end. Generic by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Email Alert
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On success:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline: data-loader
Status:   SUCCESS
Steps:    3/3
Duration: 45m 12s
Instance: i-0abc123
Resized:  Yes -&amp;gt; t3.medium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On failure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline:  data-loader
Status:    FAILURE
Steps:     2/3
Failed at: step_3
Instance:  i-0abc123
Resized:   No (kept original size for debugging)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure case is important the VM intentionally stays large so you can SSH in and debug without losing state.&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.amazonaws.com%2Fuploads%2Farticles%2F39zjfoebd2b22tbhsvl3.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.amazonaws.com%2Fuploads%2Farticles%2F39zjfoebd2b22tbhsvl3.png" alt=" " width="798" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

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




&lt;h2&gt;
  
  
  Difficulties I Faced
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. IP changes after resize&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Lambda stops and restarts the EC2, it gets a new public IP. Learned this the hard way when SSH stopped working. Fix: assign an &lt;strong&gt;Elastic IP&lt;/strong&gt; before running demos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 allocate-address &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
aws ec2 associate-address &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-id&lt;/span&gt; i-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allocation-id&lt;/span&gt; eipalloc-xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Docker goes down after resize&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EC2 resize = reboot. Any running Docker containers stop. Add this to &lt;code&gt;/etc/rc.local&lt;/code&gt; on your VM so services restart automatically:&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="nb"&gt;sudo &lt;/span&gt;service docker start
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/ec2-user/myproject &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;For the demo I used a real weather dataset loaded into Postgres running in Docker on the EC2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2: t3.large (before)
       ↓
Pipeline runs: download → parse → load into Postgres
       ↓
emit_event.py fires automatically when done
       ↓
Lambda: stops EC2 → resizes → starts EC2
       ↓
EC2: t3.medium (after)
       ↓
Email arrives in Gmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cost Reality
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EventBridge&lt;/td&gt;
&lt;td&gt;~$0 (free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;~$0 (free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNS email&lt;/td&gt;
&lt;td&gt;~$0 (first 1000 free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total stack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The savings depend on your instance. A &lt;code&gt;t3.large&lt;/code&gt; idle for 20hrs/day wastes ~$25/month. For larger instances like &lt;code&gt;c5.4xlarge&lt;/code&gt; you're looking at $200+ saved per month.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use This
&lt;/h2&gt;

&lt;p&gt;This is for you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You run data pipelines on EC2 manually or on a schedule&lt;/li&gt;
&lt;li&gt;Your pipeline takes unpredictable time to complete&lt;/li&gt;
&lt;li&gt;You don't need the heavy instance after loading is done&lt;/li&gt;
&lt;li&gt;You're not ready for EMR or Glue yet (pre-pipeline stage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; for you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're already using EMR Serverless or Glue (they auto-terminate)&lt;/li&gt;
&lt;li&gt;Your pipeline runs less than 30 minutes (manual resize is fine)&lt;/li&gt;
&lt;li&gt;You need horizontal scaling (use Auto Scaling Groups instead)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why a VM and Not Glue or EMR?
&lt;/h2&gt;

&lt;p&gt;Honest answer I didn't know enough about my data yet to make that decision.&lt;/p&gt;

&lt;p&gt;Glue and EMR are great services. But they come with questions you need to answer upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's your data format?&lt;/li&gt;
&lt;li&gt;What transformations do you need?&lt;/li&gt;
&lt;li&gt;What's the volume?&lt;/li&gt;
&lt;li&gt;Do you need Spark?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you're learning, you don't have those answers yet. You just need to load data, see what you're working with, and prove the pipeline works.&lt;/p&gt;

&lt;p&gt;A VM lets you do that with zero infrastructure decisions. Just Python scripts. When the pipeline is proven and you understand your data then you migrate to the right managed service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is that phase. Before you know which service you need.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is actually how real teams work too. Nobody starts with EMR on day one of a new data project. You explore first. You prove it works. Then you scale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phase 1: Explore         → VM + Python scripts
Phase 2: Prove it works  → VM + vm-resize-agent
Phase 3: Scale           → Glue / EMR / Spark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;Everything is open source. Clone, edit &lt;code&gt;steps.conf&lt;/code&gt;, deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Harivelu0/vm-resize-agent" rel="noopener noreferrer"&gt;https://github.com/Harivelu0/vm-resize-agent&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;A few things I want to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto resize &lt;strong&gt;back up&lt;/strong&gt; before next scheduled run (cron-based)&lt;/li&gt;
&lt;li&gt;Slack alerts alongside email&lt;/li&gt;
&lt;li&gt;Support for Azure VMs (same pattern, different SDK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ideas or run into issues open a GitHub issue. Happy to help.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building in public as part of my ML learning journey. Follow along for more practical AWS patterns.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How I Built Production AI Agent Monitoring with Langfuse</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Wed, 13 May 2026 16:32:02 +0000</pubDate>
      <link>https://dev.to/techwithhari/how-i-built-production-ai-agent-monitoring-with-langfuse-52he</link>
      <guid>https://dev.to/techwithhari/how-i-built-production-ai-agent-monitoring-with-langfuse-52he</guid>
      <description>&lt;p&gt;Multi-agent AI systems fail silently.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;200 OK&lt;/code&gt; response doesn’t mean the AI made good decisions.&lt;/p&gt;

&lt;p&gt;That was the biggest thing I realized while building a multi-agent system.&lt;/p&gt;

&lt;p&gt;My architecture looked like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Query → Multi Agent Call → Final Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything looked normal from an infrastructure perspective.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs were healthy&lt;/li&gt;
&lt;li&gt;Latency looked fine&lt;/li&gt;
&lt;li&gt;Users were getting responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I still couldn’t answer important questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the Agent route the query to the right specialist?&lt;/li&gt;
&lt;li&gt;Did the agent hallucinate information?&lt;/li&gt;
&lt;li&gt;Did it ignore specialist outputs?&lt;/li&gt;
&lt;li&gt;Did it attribute responses incorrectly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional monitoring couldn’t help because the system technically wasn’t failing.&lt;/p&gt;

&lt;p&gt;The failures were happening at the &lt;strong&gt;decision layer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Trace Visibility
&lt;/h2&gt;

&lt;p&gt;I used Langfuse to trace every agent execution.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool calls&lt;/li&gt;
&lt;li&gt;Input/output payloads&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Latency per step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an agent touched something, I wanted visibility into it.&lt;/p&gt;

&lt;p&gt;No black boxes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deterministic Checks
&lt;/h2&gt;

&lt;p&gt;Some validations didn’t need another LLM.&lt;/p&gt;

&lt;p&gt;I added rule-based checks for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the agent call tools from the correct domain?&lt;/li&gt;
&lt;li&gt;Did the agent call tools it wasn’t supposed to?&lt;/li&gt;
&lt;li&gt;Was the expected workflow followed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These checks are binary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass → &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fail → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fast and cheap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Faithfulness Checks
&lt;/h2&gt;

&lt;p&gt;This was mainly for hallucination detection.&lt;/p&gt;

&lt;p&gt;I compare the final response with outputs from specialist agents.&lt;/p&gt;

&lt;p&gt;If the Final layer introduces claims that weren’t exist in source outputs, it gets flagged.&lt;/p&gt;

&lt;p&gt;This helped catch cases where the system sounded confident but wasn’t grounded.&lt;/p&gt;




&lt;h2&gt;
  
  
  LLM Judges
&lt;/h2&gt;

&lt;p&gt;For things deterministic checks can’t measure, I use Azure OpenAI as judges.&lt;/p&gt;

&lt;p&gt;They evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing correctness&lt;/li&gt;
&lt;li&gt;Response quality&lt;/li&gt;
&lt;li&gt;Attribution accuracy&lt;/li&gt;
&lt;li&gt;Conflict handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This runs for every multi-agent response.&lt;/p&gt;

&lt;p&gt;Expensive? Yes.&lt;br&gt;
Useful? Definitely.&lt;/p&gt;




&lt;h2&gt;
  
  
  100% Traffic Monitoring
&lt;/h2&gt;

&lt;p&gt;I didn’t want sampling.&lt;/p&gt;

&lt;p&gt;Every production request goes through the evaluation pipeline.&lt;/p&gt;

&lt;p&gt;Because edge cases are usually the exact things sampling misses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost + Latency Tracking
&lt;/h2&gt;

&lt;p&gt;Multi-agent systems get expensive very fast.&lt;/p&gt;

&lt;p&gt;I track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens per agent&lt;/li&gt;
&lt;li&gt;Latency per step&lt;/li&gt;
&lt;li&gt;Expensive execution paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made optimization much easier.&lt;/p&gt;




&lt;h1&gt;
  
  
  What This Actually Caught
&lt;/h1&gt;

&lt;p&gt;This surfaced issues normal monitoring completely missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong attribution
&lt;/h3&gt;

&lt;p&gt;Correct insights were assigned to the wrong specialist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignored outputs
&lt;/h3&gt;

&lt;p&gt;Sometimes Agent completely ignored specialist responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing mistakes
&lt;/h3&gt;

&lt;p&gt;The call occasionally sent queries to the wrong agent.&lt;/p&gt;




&lt;p&gt;None of these showed up in normal monitoring dashboards.&lt;/p&gt;

&lt;p&gt;Everything looked healthy.&lt;/p&gt;




&lt;h1&gt;
  
  
  Stack
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt; Langfuse&lt;br&gt;
&lt;strong&gt;LLM Evaluation:&lt;/strong&gt; Azure OpenAI&lt;br&gt;
&lt;strong&gt;Deterministic Checks:&lt;/strong&gt; TypeScript&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;p&gt;For multi-agent systems, uptime monitoring is not enough.&lt;/p&gt;

&lt;p&gt;You also need decision monitoring.&lt;/p&gt;

&lt;p&gt;Because a successful response can still be completely wrong.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Built a Self-Updating SEO Brain Inspired by Andrej Karpathy's LLM Wiki</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Sun, 10 May 2026 13:39:47 +0000</pubDate>
      <link>https://dev.to/techwithhari/i-built-a-self-updating-seo-brain-inspired-by-andrej-karpathys-llm-wiki-39p1</link>
      <guid>https://dev.to/techwithhari/i-built-a-self-updating-seo-brain-inspired-by-andrej-karpathys-llm-wiki-39p1</guid>
      <description>&lt;h2&gt;
  
  
  The Tweet That Changed How I Think About AI + Knowledge
&lt;/h2&gt;

&lt;p&gt;In early April 2026, Andrej Karpathy (OpenAI co-founder, former Tesla AI Director) posted something deceptively simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Something I'm finding very useful recently: using LLMs to build personal knowledge bases for various topics of research interest."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He followed it up with a GitHub gist titled &lt;strong&gt;LLM Wiki&lt;/strong&gt; an "idea file" describing a pattern for building knowledge bases that actually &lt;em&gt;compound&lt;/em&gt; over time instead of rediscovering the same information on every query.&lt;/p&gt;

&lt;p&gt;I decided to build it for a real production problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: RAG Has No Memory
&lt;/h2&gt;

&lt;p&gt;In My organization, we were running an SEO monitoring pipeline for our landing site. It used &lt;strong&gt;Cognee&lt;/strong&gt; (a knowledge graph framework) backed by &lt;strong&gt;Neo4j + ChromaDB&lt;/strong&gt; on a $35/month VM.&lt;/p&gt;

&lt;p&gt;Every day it would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull Google Search Console data&lt;/li&gt;
&lt;li&gt;Scrape our pages for SEO issues&lt;/li&gt;
&lt;li&gt;Query the knowledge graph&lt;/li&gt;
&lt;li&gt;Post a Slack report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It worked. But it had a fundamental flaw &lt;strong&gt;the same flaw Karpathy describes in every RAG system.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time it ran, it was rediscovering knowledge from scratch. It had no memory of what it found yesterday. It couldn't connect "we deployed this fix on March 13" with "clicks went up 81% on March 18." It couldn't say "this keyword has been declining for 3 weeks here's why." It just answered the current query and forgot everything.&lt;/p&gt;

&lt;p&gt;On top of that: the VM broke 3+ times. Neo4j config issues. ChromaDB API path changes. Sidecar containers failing silently. We spent more time fixing the pipeline than reading its output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Karpathy's Core Insight
&lt;/h2&gt;

&lt;p&gt;His idea is simple but changes everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of retrieving from raw documents at query time compile knowledge once, keep it current, and query the compiled result.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The architecture has 3 layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw/        → immutable source data (never edited)
wiki/       → LLM-maintained markdown knowledge base
AGENTS.md   → schema/rules file telling the LLM what to do
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM reads new raw data → integrates it into the wiki → cross-references it with existing knowledge → flags contradictions → the wiki gets richer every day.&lt;/p&gt;

&lt;p&gt;His exact framing: &lt;strong&gt;"Obsidian is the IDE. The LLM is the programmer. The wiki is the codebase."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No vector database. No embeddings. No $35/month VM. Just markdown.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: LLM Wiki for SEO
&lt;/h2&gt;

&lt;p&gt;I took Karpathy's pattern and applied it specifically to SEO monitoring for vibetrader.com.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 3 Layers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;raw/&lt;/code&gt;&lt;/strong&gt; : daily immutable snapshots:&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="err"&gt;raw/gsc/&lt;/span&gt;&lt;span class="mi"&gt;2026-04-17&lt;/span&gt;&lt;span class="err"&gt;.json&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Google&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Console&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(clicks,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CTR,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;positions,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;queries)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;raw/audit/&lt;/span&gt;&lt;span class="mi"&gt;2026-04-17&lt;/span&gt;&lt;span class="err"&gt;.json&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;audit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(H&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;meta,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;schema,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;canonical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;checks)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;raw/commits/&lt;/span&gt;&lt;span class="mi"&gt;2026-04-17&lt;/span&gt;&lt;span class="err"&gt;.json&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;landing-site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(what&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;changed)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;wiki/&lt;/code&gt;&lt;/strong&gt; : LLM-maintained knowledge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;wiki/overview.md              ← "story so far" updated daily
wiki/log.md                   ← append-only daily log
wiki/topics/keywords.md       ← keyword clusters + position tracking
wiki/topics/issues.md         ← open SEO issues with severity
wiki/topics/recommendations.md ← history of recs + acted/pending status
wiki/topics/code-changes.md   ← code change impact tracker
wiki/topics/performance.md    ← CAUSAL CHAINS: issue → fix → metric improvement
wiki/topics/competitors.md    ← weekly competitor analysis
wiki/topics/lint-report.md    ← weekly wiki health check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/strong&gt; : the schema file. Tells the LLM exactly how to update each page, what cross-links to write, what format to follow.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3:00 PM IST  daily
─────────────────
gsc_pull.py    → pulls GSC data via API → raw/gsc/today.json
audit.py       → fetches vibetrader.com, checks H1/meta/schema → raw/audit/today.json
               → posts Slack audit digest (SEO/GEO/AEO scores)
ingest.py      → reads ALL raw/ + ALL wiki/
               → sends full context to Azure OpenAI
               → LLM updates all wiki pages with cross-links
               → appends to log.md
               → posts enriched Slack report
git commit     → wiki/ + raw/ committed back to main

4:00 PM IST  daily (1 hour after ingest)
─────────────────────────────────────────
fix_agent.py   → reads wiki/issues + recommendations
               → LLM classifies which issues are auto-fixable
               → skill functions run deterministically (not LLM-generated code)
               → raises PR to landing-site dev branch
               → never auto-merges

11:30 AM IST  Saturday
───────────────────────
lint.py        → audits wiki for staleness, contradictions, orphan pages
               → health score /100
               → saves lint-report.md + posts Slack

2:30 PM IST  Sunday
────────────────────
competitor_analysis.py → scrapes competitor sites
                       → LLM analysis → updates competitors.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Knowledge Graph in Plain Markdown
&lt;/h2&gt;

&lt;p&gt;The key innovation over standard LLM Wiki I added a &lt;code&gt;performance.md&lt;/code&gt; page that tracks &lt;strong&gt;causal chains&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### ✅ 2026-03-18 — API response fix&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Cause:**&lt;/span&gt; [[topics/issues]] API returning incomplete pagination data
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Fix:**&lt;/span&gt; API response items + pagination as separate fields → [[topics/code-changes]]
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Before:**&lt;/span&gt; 127 clicks/day
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**After:**&lt;/span&gt; 229.7 clicks/day
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Impact:**&lt;/span&gt; +81% clicks
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Keywords moved:**&lt;/span&gt; [[topics/keywords]] — all branded queries improved
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Confidence:**&lt;/span&gt; HIGH — spike confirmed same week as deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every entry answers: &lt;strong&gt;what caused this? what moved? how confident?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what Cognee was trying to do with Neo4j store causal relationships between entities. We're doing it in plain markdown, with &lt;code&gt;[[wiki-links]]&lt;/code&gt; that Obsidian renders as a visual knowledge graph.&lt;/p&gt;




&lt;h2&gt;
  
  
  Obsidian as the Visualization Layer
&lt;/h2&gt;

&lt;p&gt;Open Obsidian → point it at your &lt;code&gt;wiki/&lt;/code&gt; folder → graph view instantly shows all cross-connections between pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;performance&lt;/code&gt; ↔ &lt;code&gt;keywords&lt;/code&gt; ↔ &lt;code&gt;issues&lt;/code&gt; ↔ &lt;code&gt;recommendations&lt;/code&gt; ↔ &lt;code&gt;code-changes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the wiki accumulates data daily, the graph gets richer. After 30 days, clicking any node shows a real story: "this keyword dropped because of this issue, which was fixed by this code change, which resulted in this metric improvement."&lt;/p&gt;

&lt;p&gt;That's the knowledge graph. No Neo4j required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interactive Query Mode
&lt;/h2&gt;

&lt;p&gt;Beyond the daily Slack push, I added a local query script:&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;# one-shot&lt;/span&gt;
python scripts/query.py &lt;span class="s2"&gt;"what keywords are improving this week?"&lt;/span&gt;

&lt;span class="c"&gt;# REPL with follow-up context&lt;/span&gt;
python scripts/query.py
you&amp;gt; which code change had the most SEO impact?
wiki&amp;gt; Based on code-changes.md:
      March 18 — API response fix
      Before: 127 clicks/day | After: 229.7 clicks/day | Impact: +81%
      This is the highest confirmed impact change &lt;span class="k"&gt;in &lt;/span&gt;the wiki.

you&amp;gt; why did that happen?   ← follow-up, remembers context
wiki&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM answers from the &lt;strong&gt;accumulated wiki context&lt;/strong&gt; not by re-reading 30 days of raw JSON. This is exactly what Karpathy's "query" operation describes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Weekly Lint Pass
&lt;/h2&gt;

&lt;p&gt;One gap in most LLM Wiki implementations no health check. I added &lt;code&gt;lint.py&lt;/code&gt; that runs every Saturday:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staleness detection&lt;/strong&gt; : claims in the wiki that contradict newer log entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradiction detection&lt;/strong&gt; : two pages saying opposite things&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orphan pages&lt;/strong&gt; : pages with no cross-links pointing to them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing concept pages&lt;/strong&gt;: &lt;code&gt;[[wiki-links]]&lt;/code&gt; referenced but never created&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale recommendations&lt;/strong&gt;: recs pending &amp;gt; 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Posts a health score /100 to Slack and saves &lt;code&gt;lint-report.md&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix Agent (Skill-Based, Not LLM-Generated Code)
&lt;/h2&gt;

&lt;p&gt;The fix agent reads the wiki, identifies fixable issues, and raises PRs to the landing-site repo. The key design decision: &lt;strong&gt;the LLM only classifies issues it never writes code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, deterministic "skill" functions handle each fix type:&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;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;add_jsonld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;          &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;present&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;adding&lt;/span&gt;
&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;add_internal_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;   &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fix_meta_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;       &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;explicit&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;
&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fix_robots_txt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;      &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;sitemap&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM outputs &lt;code&gt;{"skill": "add_jsonld", "page": "app/layout.tsx"}&lt;/code&gt;. The skill runs. No hallucinated file paths. No duplicate schemas. No business logic touched.&lt;/p&gt;

&lt;p&gt;PRs always target &lt;code&gt;dev&lt;/code&gt; branch. Never auto-merge. Human review required.&lt;/p&gt;




&lt;h2&gt;
  
  
  vs Cognee: Honest Comparison
&lt;/h2&gt;

&lt;p&gt;After running both systems in parallel for a week:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cognee&lt;/th&gt;
&lt;th&gt;LLM Wiki&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Neo4j + ChromaDB + VM&lt;/td&gt;
&lt;td&gt;Plain markdown files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;~$35&lt;/td&gt;
&lt;td&gt;~$0 (just Azure OpenAI tokens)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times it broke&lt;/td&gt;
&lt;td&gt;3+&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files to maintain&lt;/td&gt;
&lt;td&gt;6 JS pipeline files&lt;/td&gt;
&lt;td&gt;5 Python scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History awareness&lt;/td&gt;
&lt;td&gt;Inconsistent&lt;/td&gt;
&lt;td&gt;Grounded references exact dates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AEO accuracy&lt;/td&gt;
&lt;td&gt;3/10 (missed JSON-LD already added)&lt;/td&gt;
&lt;td&gt;7/10 (reflected actual state)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge compounds&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LLM Wiki won on every dimension except one: &lt;strong&gt;Cognee had richer graph relationships out of the box.&lt;/strong&gt; But we replicated that with &lt;code&gt;performance.md&lt;/code&gt; and &lt;code&gt;[[wiki-links]]&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Karpathy Got Right
&lt;/h2&gt;

&lt;p&gt;The core insight is correct and the more I use it the more obvious it becomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAG = studying for an exam by re-reading all your textbooks every time you get a question.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM Wiki = you already made notes, highlighted the important parts, drew arrows between connected ideas. Now you just read your notes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The wiki grows daily. The LLM gets smarter about your domain the more data accumulates. By day 30, &lt;code&gt;query.py&lt;/code&gt; answers questions with 30 days of grounded context. By day 90, &lt;code&gt;performance.md&lt;/code&gt; has a dozen confirmed causal chains real institutional knowledge about what moves your metrics and why.&lt;/p&gt;

&lt;p&gt;That's what no RAG system gives you. And you don't need a vector database to get there.&lt;/p&gt;




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

&lt;p&gt;The pattern works for any domain where you're accumulating knowledge over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SEO monitoring&lt;/strong&gt; (what I built)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Competitor tracking&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research&lt;/strong&gt; — papers, articles, building a thesis over weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering team wiki&lt;/strong&gt; — fed by Slack threads, PRs, incident reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal second brain&lt;/strong&gt; — journal entries, articles, book notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only thing you need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;raw/&lt;/code&gt; folder where data lands&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;wiki/&lt;/code&gt; folder for LLM-maintained markdown&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;AGENTS.md&lt;/code&gt; (or &lt;code&gt;CLAUDE.md&lt;/code&gt;) telling the LLM the rules&lt;/li&gt;
&lt;li&gt;A cron job that runs ingest daily&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with one raw data source and one wiki page. Let it run for a week. The compounding effect becomes obvious fast.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I built a 20-20-20 eye reminder because my eyes were dying at my desk</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:45:00 +0000</pubDate>
      <link>https://dev.to/techwithhari/i-built-a-20-20-20-eye-reminder-because-my-eyes-were-dying-at-my-desk-be8</link>
      <guid>https://dev.to/techwithhari/i-built-a-20-20-20-eye-reminder-because-my-eyes-were-dying-at-my-desk-be8</guid>
      <description>&lt;p&gt;I have severe eye dryness. My doctor told me to follow the &lt;strong&gt;20-20-20 rule&lt;/strong&gt; every 20 minutes, look at something 20 feet away for 20 seconds. Simple. Except when you're deep in a bug or a feature, 20 minutes evaporates and you've been staring at your monitor for 3 hours straight.&lt;/p&gt;

&lt;p&gt;I tried phone reminders. I dismissed them without thinking. I tried sticky notes. I ignored them. I needed something that &lt;em&gt;actually blocked me&lt;/em&gt; from working until I did the break.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; a dismissible reminder is just noise. The popup had to be impossible to close until the 20-second countdown finished.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;ul&gt;
&lt;li&gt;Runs silently in the &lt;strong&gt;system tray&lt;/strong&gt; in the background&lt;/li&gt;
&lt;li&gt;Every 20 minutes fires a &lt;strong&gt;loud beeping alarm&lt;/strong&gt; (winsound, no external files needed)&lt;/li&gt;
&lt;li&gt;Dark &lt;strong&gt;blocking popup&lt;/strong&gt; appears X button is disabled, you cannot close it&lt;/li&gt;
&lt;li&gt;20-second &lt;strong&gt;countdown runs automatically&lt;/strong&gt; with a progress bar&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;"Continue" button is locked&lt;/strong&gt; until the countdown finishes, then turns green&lt;/li&gt;
&lt;li&gt;Two buttons: &lt;strong&gt;Continue&lt;/strong&gt; (back to work) or &lt;strong&gt;Stop&lt;/strong&gt; (end the session)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The core trick: disabling the close button
&lt;/h2&gt;

&lt;p&gt;The single most important line in the whole app:&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;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM_DELETE_WINDOW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By overriding &lt;code&gt;WM_DELETE_WINDOW&lt;/code&gt; with a no-op lambda, clicking the X does absolutely nothing. The window stays open. You have to wait out the countdown.&lt;/p&gt;


&lt;h2&gt;
  
  
  The countdown + locked button
&lt;/h2&gt;

&lt;p&gt;The "Continue" button starts &lt;code&gt;state="disabled"&lt;/code&gt; and only becomes clickable when the timer hits zero:&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;tick&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&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="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;count_var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&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="mi"&gt;0&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;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&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="o"&gt;&amp;lt;=&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;continue_btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;normal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#003322&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#44ff88&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hand2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&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;after_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The button turns green when it unlocks a satisfying visual reward for actually doing the break.&lt;/p&gt;


&lt;h2&gt;
  
  
  The alarm
&lt;/h2&gt;

&lt;p&gt;No external audio files needed. &lt;code&gt;winsound&lt;/code&gt; is part of the Windows standard library:&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="n"&gt;winsound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Beep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&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="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;winsound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Beep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&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="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two alternating frequencies, six times. Annoying enough that you cannot ignore it. Runs in a daemon thread so it does not block the UI.&lt;/p&gt;


&lt;h2&gt;
  
  
  The full popup
&lt;/h2&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;show_popup&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Toplevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Eye Break!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#0a0a0a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-topmost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WM_DELETE_WINDOW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- the magic line
&lt;/span&gt;
    &lt;span class="c1"&gt;# ... countdown, progress bar, locked button ...
&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;play_alarm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mainloop&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;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;continue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The popup is always-on-top (&lt;code&gt;-topmost True&lt;/code&gt;) so it cannot be buried under other windows.&lt;/p&gt;


&lt;h2&gt;
  
  
  Setup: auto-start on Windows boot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; Install dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pystray pillow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt; Save &lt;code&gt;eye_reminder.py&lt;/code&gt; to a permanent location:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\you\EyeReminder\eye_reminder.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt; Create a startup shortcut. Press &lt;code&gt;Win+R&lt;/code&gt;, type &lt;code&gt;shell:startup&lt;/code&gt;, and create a shortcut there pointing to:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;pythonw&lt;/span&gt; &lt;span class="kd"&gt;C&lt;/span&gt;:\Users\you\EyeReminder\eye_reminder.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using &lt;code&gt;pythonw&lt;/code&gt; instead of &lt;code&gt;python&lt;/code&gt; means no terminal window appears on startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt; Reboot. The app silently starts in your system tray. Right-click the tray icon to quit anytime.&lt;/p&gt;


&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;Before running with 20-minute intervals, test with 5 seconds:&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;TEST_MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;INTERVAL_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;TEST_MODE&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="n"&gt;BREAK_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;TEST_MODE&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Flip &lt;code&gt;TEST_MODE = False&lt;/code&gt; when you are happy with it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&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;&lt;code&gt;tkinter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Built-in — popup UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;winsound&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Built-in — alarm beeps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pystray&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System tray icon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pillow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required by pystray for the icon image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Core functionality (popup + alarm) works with zero installs. &lt;code&gt;pystray&lt;/code&gt; and &lt;code&gt;Pillow&lt;/code&gt; are optional the app works without them, just without the tray icon.&lt;/p&gt;


&lt;h2&gt;
  
  
  Full source
&lt;/h2&gt;
&lt;h2&gt;
  
  
  &lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Harivelu0" rel="noopener noreferrer"&gt;
        Harivelu0
      &lt;/a&gt; / &lt;a href="https://github.com/Harivelu0/eye-remainder" rel="noopener noreferrer"&gt;
        eye-remainder
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;eye-reminder&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A lightweight Python app that enforces the &lt;strong&gt;20-20-20 rule&lt;/strong&gt; for eye health on Windows.&lt;/p&gt;

&lt;p&gt;Every 20 minutes, a blocking popup appears with a loud alarm. You cannot dismiss it until a 20-second countdown finishes. No cheating.&lt;/p&gt;




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

&lt;p&gt;The 20-20-20 rule says: every 20 minutes, look at something 20 feet away for 20 seconds. Every reminder app I tried was too easy to dismiss. This one isn't.&lt;/p&gt;




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

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;[20 min timer] --&amp;gt; ALARM fires --&amp;gt; Blocking popup appears
                                        |
                                   20-sec countdown
                                        |
                               "Continue" button unlocks
                                        |
                              Click Continue --&amp;gt; back to work
                              Click Stop    --&amp;gt; session ends
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Runs silently in the &lt;strong&gt;system tray&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loud beeping alarm&lt;/strong&gt; on every trigger (no audio files needed)&lt;/li&gt;
&lt;li&gt;Popup is &lt;strong&gt;always-on-top&lt;/strong&gt; and the X button is disabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Continue" button locked&lt;/strong&gt; until full 20-second countdown completes&lt;/li&gt;
&lt;li&gt;System tray icon with right-click &lt;strong&gt;Quit&lt;/strong&gt; option&lt;/li&gt;
&lt;li&gt;Built-in &lt;strong&gt;test mode&lt;/strong&gt; (5-sec interval) for quick verification&lt;/li&gt;
&lt;/ul&gt;




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

&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;Windows&lt;/li&gt;

&lt;li&gt;Python 3.8+&lt;/li&gt;

&lt;li&gt;…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Harivelu0/eye-remainder" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;

&lt;/h2&gt;


&lt;p&gt;My eyes are noticeably less dry after two weeks of using this. Sometimes the best tool is the one you build in an afternoon because nothing else works the way you need it to.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>health</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built an AI SEO Monitor That Remembers Everything</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Fri, 10 Apr 2026 14:16:39 +0000</pubDate>
      <link>https://dev.to/techwithhari/i-built-an-ai-seo-monitor-that-remembers-everything-4l7i</link>
      <guid>https://dev.to/techwithhari/i-built-an-ai-seo-monitor-that-remembers-everything-4l7i</guid>
      <description>&lt;p&gt;Most SEO monitoring tools give you a snapshot: today's clicks, today's issues, today's recommendations. You fix something, come back tomorrow, and the tool has no idea what you did or whether it helped.&lt;/p&gt;

&lt;p&gt;I wanted something smarter a system that &lt;em&gt;remembers&lt;/em&gt; site's history, correlates code changes with ranking shifts, and gives AI-generated insights that get better every day. Here's what I built and how.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Basic SEO Monitoring
&lt;/h2&gt;

&lt;p&gt;A typical monitoring setup looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch Google Search Console data&lt;/li&gt;
&lt;li&gt;Run a Lighthouse audit&lt;/li&gt;
&lt;li&gt;Send a Slack message with today's numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's fine. But it can't answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Did that metadata fix I deployed 10 days ago actually improve rankings?"&lt;/li&gt;
&lt;li&gt;"This recommendation has been flagged for 15 days why hasn't it been fixed?"&lt;/li&gt;
&lt;li&gt;"Clicks dropped this week was it a code change or an algorithm shift?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To answer those questions, you need &lt;em&gt;memory&lt;/em&gt;. That's where Cognee comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Cognee?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; is a knowledge graph SDK. Instead of storing data as flat rows in a database, it extracts entities and relationships and stores them as nodes and edges in a graph (Neo4j) with vector embeddings in a vector database (ChromaDB).&lt;/p&gt;

&lt;p&gt;Think of it like this: a normal database stores &lt;em&gt;"clicks = 262 on April 8"&lt;/em&gt;. A knowledge graph stores &lt;em&gt;"keyword 'vibe trading' ranked at position 1.78 on April 8, which is 12 spots better than March 25, and that improvement happened 3 days after a metadata fix was deployed"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The difference matters when you want AI to reason across weeks of history not just today.&lt;/p&gt;




&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;

&lt;p&gt;The full pipeline runs daily via GitHub Actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHASE 1 — Parallel data collection
├── Lighthouse audit (performance, SEO scores)
├── Broken links check
├── Meta tags validation
├── Core Web Vitals (via PageSpeed API)
└── Google Search Console (clicks, CTR, position, queries)

PHASE 2 — Main analysis job
├── git-change-detector.js    → scans commits, classifies SEO-relevant changes
├── cognee_ingest.py          → writes today's data to Neo4j + ChromaDB
├── cognee-store-updater.js   → updates 30-day rolling JSON snapshot
├── audit-scraper.js          → fetches live pages, scores SEO/GEO/AEO signals
├── audit-ingest.py           → stores audit scores in the knowledge graph
├── cognee-analyzer.js        → builds enriched AI context, calls Azure OpenAI
├── send-ai-slack.js          → posts daily report to Slack
└── cognee-blob-sync.js       → backs up knowledge graph to Azure Blob Storage

PHASE 3 — Weekly (Sundays)
└── competitor-monitor.js     → fetches competitor pages, scores them, posts comparison
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Cognee? The Knowledge Graph Advantage
&lt;/h2&gt;

&lt;p&gt;Every day, &lt;code&gt;cognee_ingest.py&lt;/code&gt; builds a structured document containing today's GSC metrics, top queries, AI recommendations, and recent git commits. Azure OpenAI reads this and extracts entities keywords, positions, dates, code changes which Cognee writes to Neo4j as connected nodes. graph starts to grow.&lt;/p&gt;

&lt;p&gt;After 30 days, the graph contains nodes like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Keyword:&lt;/span&gt; &lt;span class="s2"&gt;"platform name"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RANKED_AT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Position&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.78&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;date:&lt;/span&gt; &lt;span class="n"&gt;April&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;CodeChange:&lt;/span&gt; &lt;span class="s2"&gt;"metadata fix"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HAPPENED_BEFORE&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;MetricSnapshot:&lt;/span&gt; &lt;span class="n"&gt;April&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Recommendation:&lt;/span&gt; &lt;span class="s2"&gt;"add FAQ schema"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FLAGGED_ON&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Date:&lt;/span&gt; &lt;span class="n"&gt;April&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Recommendation:&lt;/span&gt; &lt;span class="s2"&gt;"add FAQ schema"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FLAGGED_ON&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;Date:&lt;/span&gt; &lt;span class="n"&gt;April&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;...&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagged&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when the AI runs its daily analysis, it doesn't just see today's data. It sees patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyword velocity&lt;/strong&gt;: which keywords improved or dropped more than 5 positions in 14 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stuck recommendations&lt;/strong&gt;: same issue flagged 3+ days in a row, still unactioned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code change impact&lt;/strong&gt;: did clicks or position change after a specific deploy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Slack report reflects this. Instead of &lt;em&gt;"your CTR is 18.94%"&lt;/em&gt;, it says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Your site has more than doubled daily clicks over the past month (106% growth), driven by a metadata fix on March 26 and header overlap fixes on March 28. Short-term momentum is slowing 7-day clicks are -3% suggesting you need to now expand content around fast-moving branded keywords."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a different class of insight.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Audit Scrape SEO/GEO/AEO Scoring
&lt;/h2&gt;

&lt;p&gt;Beyond GSC data, &lt;code&gt;audit-scraper.js&lt;/code&gt; fetches your actual pages daily and scores them across three dimensions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO&lt;/strong&gt; classic signals: title tag, meta description, H1, canonical, OG tags, schema markup, JS-gated content detection&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEO&lt;/strong&gt; (Generative Engine Optimization) how well AI search engines like Perplexity or ChatGPT Search can read and cite your content: structured data presence, content density, crawlability&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AEO&lt;/strong&gt; (Answer Engine Optimization) featured snippet and voice search readiness: FAQ schema, article schema, H2 density, word count&lt;/p&gt;

&lt;p&gt;Each page gets a score out of 10. The system flags critical issues (JS-gated content = crawlers see a blank page, missing H1 = no primary ranking signal) and sends a separate Slack message with the audit digest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔴 SEO Audit — 2026-04-08
Scores: SEO 9/10 | GEO 9/10 | AEO 3/10 | Combined 21/30

Critical Issues:
🚨 missing_h1 on Pricing — Missing primary ranking signal
🚨 js_gated_content on Pricing — Crawlers see blank page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Code Change Impact Tracking
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of. &lt;code&gt;git-change-detector.js&lt;/code&gt; scans git commits and classifies them it looks for commit messages mentioning SEO-related terms (metadata, schema, redirect, canonical, performance, etc.) and logs them with their date.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;change-impact-tracker.js&lt;/code&gt; then cross-references those commits with GSC metrics. For each logged change, it compares the 7-day window before vs after deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Migrate to new partition keys (2026-03-30)
   → Position improved 6.7 spots (17.39 → 10.71)

✅ API pagination fix (2026-03-18)
   → Clicks grew 81% (127 → 229.7/day)

⏳ content deploy (2026-03-13)
   → Monitoring... (not enough post-deploy data yet)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This surfaces directly in the Slack report under "Code Change Tracker". Over time, it tells you which types of changes actually move the needle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storage Architecture
&lt;/h2&gt;

&lt;p&gt;Three layers, each with a different purpose:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it stores&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;Neo4j (Azure VM)&lt;/td&gt;
&lt;td&gt;Graph nodes + edges — keywords, positions, code changes, relationships&lt;/td&gt;
&lt;td&gt;Multi-hop reasoning: "which keyword improved after which deploy?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChromaDB (Azure VM)&lt;/td&gt;
&lt;td&gt;Vector embeddings of all entities&lt;/td&gt;
&lt;td&gt;Semantic search across history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cognee-knowledge.json (Azure Blob)&lt;/td&gt;
&lt;td&gt;30-day rolling JSON snapshots&lt;/td&gt;
&lt;td&gt;Fast daily reads without querying the graph every run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The JSON file is the workhorse for the daily Slack report. Neo4j and ChromaDB are queried for deeper pattern analysis and become increasingly valuable as history accumulates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Things I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cognee initializes config at import time.&lt;/strong&gt; If you set environment variables after &lt;code&gt;import cognee&lt;/code&gt;, they're ignored. You have to call &lt;code&gt;cognee.config.set_graph_db_config()&lt;/code&gt; directly after import to update the live config object. This cost me several hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;mistralai&lt;/code&gt; import conflict.&lt;/strong&gt; Cognee's dependency &lt;code&gt;instructor==1.14.x&lt;/code&gt; tries to import &lt;code&gt;Mistral&lt;/code&gt; from &lt;code&gt;mistralai&lt;/code&gt; at import time regardless of whether you use it. Fix: inject a fake &lt;code&gt;mistralai&lt;/code&gt; module into &lt;code&gt;sys.modules&lt;/code&gt; before importing Cognee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JS-gated content is invisible to the audit scraper.&lt;/strong&gt; If your page renders entirely client-side, the raw HTML fetch returns fewer than 80 words. The scraper flags this as &lt;code&gt;js_gated_content&lt;/code&gt; — which is actually useful because it means Google probably can't index it either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The knowledge graph gets smarter non-linearly.&lt;/strong&gt; Day 1 the system is just a fancier GSC dashboard. Day 7 you start seeing real code change verdicts. Day 30 the AI recommendations start referencing patterns that span weeks. The value compounds.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; — pipeline orchestration, daily cron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; — audit scraper, Cognee analyzer, Slack formatting, git change detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; — Cognee SDK ingestion (cognee_ingest.py, audit-ingest.py)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognee 0.5.3&lt;/strong&gt; — knowledge graph SDK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neo4j Community&lt;/strong&gt; — graph database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChromaDB&lt;/strong&gt; — vector database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure OpenAI&lt;/strong&gt; — GPT-4.1 for analysis, text-embedding-3-large for vectors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Blob Storage&lt;/strong&gt; — knowledge graph backup/restore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure VM (Standard B2s)&lt;/strong&gt; — hosts Neo4j + ChromaDB via Docker Compose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console API&lt;/strong&gt; — real click/impression/position data&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>data</category>
      <category>automation</category>
    </item>
    <item>
      <title>🚀 Beyond RAG: Simulating the Future with MiroFish</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Tue, 07 Apr 2026 17:15:58 +0000</pubDate>
      <link>https://dev.to/techwithhari/beyond-rag-simulating-the-future-with-mirofish-1dal</link>
      <guid>https://dev.to/techwithhari/beyond-rag-simulating-the-future-with-mirofish-1dal</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fv6ddqtlaihgvsly4jv6f.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.amazonaws.com%2Fuploads%2Farticles%2Fv6ddqtlaihgvsly4jv6f.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Lately, most of us have been working with RAG systems retrieving context, grounding responses, improving accuracy.&lt;/p&gt;

&lt;p&gt;But what if instead of just &lt;em&gt;retrieving knowledge&lt;/em&gt;, we could &lt;strong&gt;simulate outcomes&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;I recently came across &lt;strong&gt;MiroFish&lt;/strong&gt;, and decided to test it out.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 What I Tried
&lt;/h2&gt;

&lt;p&gt;I cloned the repo, ran it locally, and fed it a simple scenario:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What happens when an AI assistant is introduced into a company’s daily workflow?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of a static answer, it generated a &lt;strong&gt;multi-agent simulation over time&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 What Makes It Different
&lt;/h2&gt;

&lt;p&gt;Unlike traditional systems, MiroFish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a &lt;strong&gt;virtual environment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Generates multiple &lt;strong&gt;agents (employees, managers, etc.)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Simulates &lt;strong&gt;interactions over time&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Produces a &lt;strong&gt;temporal report (day-by-day evolution)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you’re not just asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What will happen?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’re observing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How things evolve step by step.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📊 Sample Insights from My Test
&lt;/h2&gt;

&lt;p&gt;From a 14-day simulation, I observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📈 Initial boost in productivity&lt;/li&gt;
&lt;li&gt;⚖️ Diverging employee satisfaction&lt;/li&gt;
&lt;li&gt;🔁 Emerging dependency on AI&lt;/li&gt;
&lt;li&gt;🧩 Different behaviors across teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It felt less like querying an LLM… and more like watching a system evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Where This Can Be Useful
&lt;/h2&gt;

&lt;p&gt;This kind of simulation opens up interesting possibilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  🏢 Organization &amp;amp; Product
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI adoption strategies&lt;/li&gt;
&lt;li&gt;Remote work policy changes&lt;/li&gt;
&lt;li&gt;Feature rollout impact&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📦 Business Decisions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pricing experiments&lt;/li&gt;
&lt;li&gt;Customer behavior prediction&lt;/li&gt;
&lt;li&gt;Growth strategy testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌍 Macro Scenarios
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Economic shifts&lt;/li&gt;
&lt;li&gt;Supply chain disruptions&lt;/li&gt;
&lt;li&gt;Policy or geopolitical changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 RAG vs Simulation (My Take)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;Retrieves and explains existing knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulation (MiroFish)&lt;/td&gt;
&lt;td&gt;Models and predicts possible futures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both are powerful but they solve very different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Final Thoughts
&lt;/h2&gt;

&lt;p&gt;We’re slowly moving from:&lt;/p&gt;

&lt;p&gt;👉 &lt;em&gt;“Answering questions”&lt;/em&gt;&lt;br&gt;
to&lt;br&gt;
👉 &lt;em&gt;“Rehearsing decisions”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;MiroFish feels like an early step in that direction.&lt;/p&gt;

&lt;p&gt;Still experimenting, but this approach definitely opens up a new way of thinking about AI systems.&lt;/p&gt;




&lt;p&gt;If you’ve tried something similar or have ideas for scenarios to test — would love to hear 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>machinelearning</category>
      <category>data</category>
    </item>
    <item>
      <title>Everyone Suddenly Said “RAG is Dead”</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Sat, 04 Apr 2026 13:41:22 +0000</pubDate>
      <link>https://dev.to/techwithhari/everyone-suddenly-said-rag-is-dead-2k37</link>
      <guid>https://dev.to/techwithhari/everyone-suddenly-said-rag-is-dead-2k37</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fes0btfad6ekd3zumkwdp.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.amazonaws.com%2Fuploads%2Farticles%2Fes0btfad6ekd3zumkwdp.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

&lt;p&gt;Lately I keep seeing this everywhere:&lt;/p&gt;

&lt;p&gt;“RAG is dead”&lt;br&gt;
“Vector search is outdated”&lt;br&gt;
“Reasoning-based retrieval is the future”&lt;/p&gt;

&lt;p&gt;And suddenly… everyone is talking like vector search is useless.&lt;/p&gt;

&lt;p&gt;I’m not against the hype. These things happen.&lt;/p&gt;

&lt;p&gt;But honestly, this whole idea didn’t just click for me immediately.&lt;/p&gt;

&lt;p&gt;Because for me, this problem was already in my head for a long time.&lt;/p&gt;

&lt;p&gt;Not because of hype.&lt;/p&gt;

&lt;p&gt;Just because of my use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What I Was Actually Trying to Figure Out&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I read a lot of long tech blogs and architecture posts.&lt;/p&gt;

&lt;p&gt;After reading, I always have questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“why did they do this?”&lt;/li&gt;
&lt;li&gt;“what’s the tradeoff here?”&lt;/li&gt;
&lt;li&gt;“what happens if we change this design?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I wanted a system where I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;paste a document&lt;/li&gt;
&lt;li&gt;ask questions&lt;/li&gt;
&lt;li&gt;actually get useful answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point I started thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;should I just stick with vector RAG?&lt;br&gt;
or should I try something like PageIndex / reasoning-based retrieval?&lt;br&gt;
or even something like Agent-style flow later?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That curiosity is what pushed me to build this.&lt;/p&gt;

&lt;p&gt;Not the “RAG is dead” trend.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;So I Built a Simple Comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Nothing fancy.&lt;/p&gt;

&lt;p&gt;Just a small app where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same document&lt;/li&gt;
&lt;li&gt;same question&lt;/li&gt;
&lt;li&gt;same model&lt;/li&gt;
&lt;li&gt;only retrieval changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Pipeline 1 — Vector RAG&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;split document&lt;/li&gt;
&lt;li&gt;embed&lt;/li&gt;
&lt;li&gt;store in ChromaDB&lt;/li&gt;
&lt;li&gt;retrieve top-k&lt;/li&gt;
&lt;li&gt;answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what most of us are already doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Pipeline 2 — PageIndex&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;build a tree structure from the document&lt;/li&gt;
&lt;li&gt;let the model navigate it&lt;/li&gt;
&lt;li&gt;pick relevant sections&lt;/li&gt;
&lt;li&gt;answer from that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This felt very different.&lt;/p&gt;

&lt;p&gt;Not “searching”.&lt;/p&gt;

&lt;p&gt;More like… “reading with guidance”.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What I Noticed&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The difference is actually deeper than I expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector RAG:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;find similar chunks&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;PageIndex:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;figure out &lt;em&gt;where&lt;/em&gt; the answer should be, then go there&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That “where” part is interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;One Example&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I tested with a Netflix architecture article.&lt;/p&gt;

&lt;p&gt;Question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did they use live origin instead of only CDN?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Vector RAG
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;faster (~7s)&lt;/li&gt;
&lt;li&gt;decent answer&lt;/li&gt;
&lt;li&gt;but retrieval had some noise&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  PageIndex
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;slower (~11s)&lt;/li&gt;
&lt;li&gt;answer felt more precise&lt;/li&gt;
&lt;li&gt;citations were cleaner&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;My Honest Take&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Vector RAG is not dead.&lt;/p&gt;

&lt;p&gt;But…&lt;/p&gt;

&lt;p&gt;Blind chunking + embedding + top-k is not enough anymore (at least for some cases).&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Where I See the Difference&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vector RAG works well when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have multiple documents&lt;/li&gt;
&lt;li&gt;you want speed&lt;/li&gt;
&lt;li&gt;you just need “good enough” answers&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;PageIndex works well when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;single long document&lt;/li&gt;
&lt;li&gt;structured content&lt;/li&gt;
&lt;li&gt;you want cleaner reasoning&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What I’m Actually Thinking Now&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I don’t think this is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“one replaces the other”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Feels more like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;both solve different parts of the problem&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I’m more interested in now is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can I combine them?&lt;/li&gt;
&lt;li&gt;use vector search to find documents&lt;/li&gt;
&lt;li&gt;then use something like PageIndex inside that?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That feels more practical.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why I’m Exploring This&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For my use case, I’m also thinking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can I plug this into an agent flow later?&lt;/li&gt;
&lt;li&gt;how does retrieval affect agent decisions?&lt;/li&gt;
&lt;li&gt;does better retrieval reduce hallucination in multi-step tasks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where this is going for me.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thought&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Honestly, I didn’t build this to prove anything.&lt;/p&gt;

&lt;p&gt;Just to understand.&lt;/p&gt;

&lt;p&gt;And one thing became clear very fast:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;hype says “X is dead”&lt;br&gt;
reality says “it depends”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you’re building something similar, I’d really suggest:&lt;/p&gt;

&lt;p&gt;Don’t pick a side early.&lt;/p&gt;

&lt;p&gt;Test both.&lt;/p&gt;

&lt;p&gt;You’ll understand the difference immediately.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>algorithms</category>
      <category>rag</category>
    </item>
    <item>
      <title>Harness Engineering: The Concept I Didn't Know I Needed</title>
      <dc:creator>Haripriya Veluchamy</dc:creator>
      <pubDate>Wed, 25 Mar 2026 18:24:15 +0000</pubDate>
      <link>https://dev.to/techwithhari/harness-engineering-the-concept-i-didnt-know-i-needed-5nf</link>
      <guid>https://dev.to/techwithhari/harness-engineering-the-concept-i-didnt-know-i-needed-5nf</guid>
      <description>&lt;p&gt;Honestly, when I first heard the term &lt;strong&gt;Harness Engineering&lt;/strong&gt;, I thought it was just another buzzword.&lt;/p&gt;

&lt;p&gt;I already knew about Prompt Engineering. I had heard about Context Engineering. I thought, okay this is probably just the same thing with a fancier name.&lt;/p&gt;

&lt;p&gt;But then I started actually using agentic tools like Cursor and Windsurf in my day-to-day work. And something clicked.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Wait... this thing is not just answering my question. It's planning, building, testing, fixing — all on its own. How?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's when I went deeper. And what I found actually changed how I think about building with AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  First What Even is a Context Window?
&lt;/h2&gt;

&lt;p&gt;Before we get into Harness Engineering, need to understand one thing.&lt;/p&gt;

&lt;p&gt;Every AI model has something called a &lt;strong&gt;context window&lt;/strong&gt;. Think of it like a whiteboard. The model can only see what's written on that whiteboard right now. Once the conversation gets too long, old stuff disappears. And when you start a brand new chat the whiteboard is completely blank.&lt;/p&gt;

&lt;p&gt;That's the core problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI has no memory between sessions. Every new session, it starts fresh.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a simple question answer task, that's fine. But what if the task takes &lt;em&gt;days&lt;/em&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Harness Engineering?
&lt;/h2&gt;

&lt;p&gt;Let me show you how this concept evolved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt Engineering   → How do I ask better questions?
Context Engineering  → How do I manage what's inside one session?
Harness Engineering  → How do I make an agent work across many sessions?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Harness Engineering&lt;/strong&gt; is not about writing better prompts. It's about designing the &lt;em&gt;system around the model&lt;/em&gt; so the agent always knows where it is, what it has done, and what it needs to do next. Even after the context window resets completely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment I Really Got It
&lt;/h2&gt;

&lt;p&gt;When I was exploring how tools like Cursor work under the hood, I realized something.&lt;/p&gt;

&lt;p&gt;When Cursor builds a feature for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It scans your codebase&lt;/li&gt;
&lt;li&gt;Makes a plan&lt;/li&gt;
&lt;li&gt;Implements step by step&lt;/li&gt;
&lt;li&gt;Runs tests automatically&lt;/li&gt;
&lt;li&gt;Fixes bugs it finds&lt;/li&gt;
&lt;li&gt;Continues without you prompting every single move&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is Harness Engineering. The tool is not just "smart." Someone designed a system that makes it &lt;em&gt;stay on track&lt;/em&gt; even as context windows reset.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example: Building an App with an Agent
&lt;/h2&gt;

&lt;p&gt;Let's say you ask an AI agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build me a complete Food Delivery App."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a one-session task. Here's what happens &lt;strong&gt;without&lt;/strong&gt; any harness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session 1:
Agent builds Login page, starts Restaurant list...
Context window fills up. Stops.

Session 2:
Agent starts fresh. No memory.
Builds Login page again. 😵
Duplicate code. Broken app. Confused agent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now with &lt;strong&gt;Harness Engineering&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before any coding starts, an &lt;strong&gt;Initializer Agent&lt;/strong&gt; sets up three simple things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;features.json&lt;/strong&gt; — Every task with a status:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login Page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Restaurant List"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cart System"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&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;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;strong&gt;progress.txt&lt;/strong&gt; — A running log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Last completed: Nothing yet
Next task: Login Page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;setup.sh&lt;/strong&gt; — A script to spin up the dev server automatically.&lt;/p&gt;

&lt;p&gt;Now every new session just does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read progress.txt  → know where to continue
Read features.json → pick the next pending task
Run setup.sh       → environment is ready
Build → Test → Update files → Git commit
Session ends cleanly. Next session picks up exactly here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent has &lt;strong&gt;no memory&lt;/strong&gt; but it doesn't need memory. The &lt;em&gt;system&lt;/em&gt; remembers for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3 Things That Actually Make This Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Legible Environment
&lt;/h3&gt;

&lt;p&gt;Every session should be able to answer three questions just by reading files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the goal?&lt;/li&gt;
&lt;li&gt;What is done?&lt;/li&gt;
&lt;li&gt;What is next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feature lists, progress logs, git history, docs — these are not optional. They are the foundation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Verification Before Moving On
&lt;/h3&gt;

&lt;p&gt;Agents have a habit of saying "Done!" when things are actually broken. I've seen this personally with Claude Code and Cursor.&lt;/p&gt;

&lt;p&gt;The fix is giving the agent real tools to &lt;em&gt;test its own work&lt;/em&gt; like running the app, checking the UI, catching bugs end to end. Not just saying it worked. Actually proving it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use Simple, Familiar Tools
&lt;/h3&gt;

&lt;p&gt;This one surprised me the most.&lt;/p&gt;

&lt;p&gt;Vercel built a very fancy, specialized agent with custom tools and heavy prompt engineering. It worked but barely. Fragile. Slow.&lt;/p&gt;

&lt;p&gt;Then they removed almost all the custom tools and replaced everything with one simple batch command tool.&lt;/p&gt;

&lt;p&gt;Result? &lt;strong&gt;3.5x faster. 37% fewer tokens. Success rate went from 80% to 100%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why? Because models like Claude have seen billions of lines of code using &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;. They understand these natively. Custom tools are unfamiliar territory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simple tools the model already knows &amp;gt; Fancy tools you built from scratch.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How This Connects to MCP
&lt;/h2&gt;

&lt;p&gt;If you've worked with MCP (Model Context Protocol) before, this connects directly.&lt;/p&gt;

&lt;p&gt;In a Harness Engineering setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Host&lt;/strong&gt; (Claude Desktop, Cursor) is your computer&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;MCP Client&lt;/strong&gt; is like an adapter built into the host, you don't touch it&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;MCP Server&lt;/strong&gt; is what &lt;em&gt;you&lt;/em&gt; build your custom tools, your file readers, your test runners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your MCP Server becomes the hands of your long-running agent. It reads progress files, runs tests, queries databases, and verifies work all between sessions.&lt;/p&gt;

&lt;p&gt;You only build the server. The host handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Letting the agent "one-shot" the whole task it will run out of context and leave things half done&lt;/li&gt;
&lt;li&gt;Not giving the agent a way to test its own work it will always claim success&lt;/li&gt;
&lt;li&gt;Building overly specialized tools simpler is almost always better&lt;/li&gt;
&lt;li&gt;No clean state at end of each session the next session will be confused&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Simple Harness Checklist
&lt;/h2&gt;

&lt;p&gt;Before building a long-running agent system, make sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Feature list exists with pass/fail status per task&lt;/li&gt;
&lt;li&gt;[ ] Progress file updated at end of every session&lt;/li&gt;
&lt;li&gt;[ ] Git commits made with descriptive messages&lt;/li&gt;
&lt;li&gt;[ ] Dev environment spins up automatically (setup script)&lt;/li&gt;
&lt;li&gt;[ ] Agent has real testing tools not just unit tests&lt;/li&gt;
&lt;li&gt;[ ] Generic tools used wherever possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The models today are genuinely capable. The missing piece is almost never the model itself.&lt;/p&gt;

&lt;p&gt;It's the system around it.&lt;/p&gt;

&lt;p&gt;That's what Harness Engineering is. Not a new model. Not a new prompt trick. Just smart system design that lets an agent stay on track across sessions, verify its own work, and actually finish what it started.&lt;/p&gt;

&lt;p&gt;Once I understood this, the way I think about building AI-powered tools completely changed.&lt;/p&gt;

&lt;p&gt;If you're building anything agentic even something small think about what happens when the context window resets. Does your agent know how to pick up where it left off?&lt;/p&gt;

&lt;p&gt;If yes, you're already doing Harness Engineering. 😊&lt;/p&gt;




</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
