<?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: Anvay Singh</title>
    <description>The latest articles on DEV Community by Anvay Singh (@anvaysingh).</description>
    <link>https://dev.to/anvaysingh</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%2F4014054%2F4df33505-c475-4a51-a1d1-d70a7495575e.png</url>
      <title>DEV Community: Anvay Singh</title>
      <link>https://dev.to/anvaysingh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anvaysingh"/>
    <language>en</language>
    <item>
      <title>Part 2: When Nobody Grades Their Own Homework</title>
      <dc:creator>Anvay Singh</dc:creator>
      <pubDate>Mon, 13 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/anvaysingh/part-2-when-nobody-grades-their-own-homework-5b02</link>
      <guid>https://dev.to/anvaysingh/part-2-when-nobody-grades-their-own-homework-5b02</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some things can't be checked with a number, like whether an animation feels right.&lt;/li&gt;
&lt;li&gt;So a second, read-only agent grades the first one against a written rubric it is not allowed to edit.&lt;/li&gt;
&lt;li&gt;In my run the reviewer rejected the builder three times, and the most interesting problem it caught was in the test evidence, not the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;a href="//./devto-part-1-goal-loop.md"&gt;Part 1&lt;/a&gt; I built a loop that chased a number, frames per second. But most of what we care about in software is not a number. "Does this region switch feel good?" has no assert. You cannot write &lt;code&gt;expect(feelsRight).toBe(true)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So this part is about how you check quality when there is nothing to measure. The approach I used is a second agent that grades the first one against a written rubric. In my run the reviewer turned the builder down three times before it approved anything, and the most interesting problem it found was not in the code at all.&lt;/p&gt;

&lt;p&gt;A quick reminder of the definition, since this is Part 2 of 3: a loop is an external script that runs the agent, a separate check the agent cannot edit decides pass or fail, and it repeats until it passes or hits a limit. In Part 1 the check was a Playwright test. Here the check is another agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem this loop solves
&lt;/h2&gt;

&lt;p&gt;In the browser you can switch regions, say from Tamil to Korean, which swaps out hundreds of posters at once. Done badly, the grid flashes blank and jumps around. Done well, it fades from one set to the next, keeps its layout, shows a loading state, and puts you back at the top.&lt;/p&gt;

&lt;p&gt;"Done well" is subjective, which is the kind of thing you cannot unit-test. So I wrote it down as a rubric and had a second agent apply it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bar: a rubric a person owns
&lt;/h2&gt;

&lt;p&gt;The rubric is seven plain-English checks in a file, and the first line is the one that matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Overall APPROVED requires every item PASS. This file is human-owned.
Only a person changes the bar.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The seven items are things like a crossfade instead of a flash, no layout shift, a visible loading state, posters that stay 2:3, and landing back at the top after a switch. Nothing fancy, just a written version of "good" that a reviewer can apply the same way every time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two agents that never touch
&lt;/h2&gt;

&lt;p&gt;The important decision is that the builder and the reviewer are two separate Claude Code agents with different permissions.&lt;/p&gt;

&lt;p&gt;The builder can write code:&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="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Edit, Write, Grep, Glob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The reviewer is read-only. It cannot edit anything, including the rubric:&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="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each one runs in its own context. This answers an obvious question: why not let one agent do both jobs? Because an agent that writes code and then reviews the same code tends to approve it. Mine did, almost word for word, as you will see. The reviewer never sees the builder's reasoning or its summary. It only sees the code and the evidence, which is a set of before, during, and after screenshots from an automated capture.&lt;/p&gt;

&lt;p&gt;The loop looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder makes one change -&amp;gt; capture screenshots -&amp;gt; reviewer scores the rubric
        ^                                                     |
        |______ CHANGES-REQUESTED: feed feedback back ________|
                  (until VERDICT: APPROVED, or cap = 6)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The builder cannot edit the rubric, the tests, or the capture script, which is the same protected-files rule from Part 1.&lt;/p&gt;
&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The reviewer turned the builder down three times. Here is how it went.&lt;/p&gt;

&lt;p&gt;Round 1. The builder replaced the whole grid with a loading skeleton during the swap and summed up its work as "eliminating the hard cut and empty-grid flash." The reviewer looked at the screenshots and disagreed, because replacing every poster with identical skeleton cards is itself a hard cut rather than a crossfade. Verdict: CHANGES-REQUESTED. The builder had claimed it fixed the flash, and the evidence said it had not. If the same agent had been reviewing its own work, that version would have shipped.&lt;/p&gt;

&lt;p&gt;Round 2. The builder reworked it into a real crossfade, with the old posters fading out over the new grid. The reviewer agreed the fade was there now, and then caught the next issue: the required loading state never actually appeared, because the local posters decode instantly. Correct, and easy to miss. Still not approved.&lt;/p&gt;

&lt;p&gt;Round 3. The builder added the loading state, and six of the seven items clearly passed. The seventh got a verdict I did not expect, though. Not a fail, but BLOCKED:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The after frame sits at the top, but the before frame was already at the top, so a scroll reset from the middle of the list was never actually tested.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The most interesting bug was not in the code
&lt;/h2&gt;

&lt;p&gt;That third verdict is my favourite part of the project. The feature was fine. The code did reset the scroll to the top. What failed was the evidence: the screenshot always started at the top, so there was no way to see the reset happen. Before and after looked the same, and the reviewer refused to give credit for something it could not see.&lt;/p&gt;

&lt;p&gt;The fix was in the capture script, not the app. It now scrolls partway down before switching, so the reset is visible:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// ...switch region...&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterScroll&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// proves it snapped back to the top&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A reviewer is only as good as the evidence you give it. A good reviewer pointed at a blind spot still cannot see anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a gate does not cover
&lt;/h2&gt;

&lt;p&gt;The run stopped at round 4, not because it failed but because I hit a Claude usage limit. These local loops run on a Claude Code subscription rather than a paid API key, which I will come back to in Part 3. So I checked the last rubric items by hand and merged.&lt;/p&gt;

&lt;p&gt;One honest detail: the merged code had a couple of lint errors, because the loop only ran the UX rubric, not lint. I cleaned those up myself. A loop checks what its gate checks and nothing else, so it helps to know what your gate leaves out.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I took from Part 2
&lt;/h2&gt;

&lt;p&gt;The reviewer has to be independent: its own context, read-only, and no ability to edit the code or the rubric. An agent reviewing its own work is close to no review at all. The builder's confidence did not catch the flash. The screenshots did.&lt;/p&gt;

&lt;p&gt;In Part 3 the loop changes again. The first two loops you start and watch. The last one runs on a schedule while you are asleep, and its whole job is to do nothing most nights. I will also cover the bug that broke all three loops and the setup that lets this one run for free.&lt;/p&gt;

&lt;p&gt;The full project, with the builder and reviewer agents, is on GitHub:&lt;/p&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/AnvaySingh" rel="noopener noreferrer"&gt;
        AnvaySingh
      &lt;/a&gt; / &lt;a href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;
        moviebrowse
      &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;MovieBrowse — a loop-engineering learning project&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small, deliberately-boring &lt;strong&gt;regional cinema browser&lt;/strong&gt;: a fast, virtualized grid of
film posters for one "region" (a language / film industry, not a country), with a
manual region selector and a geo-IP default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But the website is not the point.&lt;/strong&gt; It's a &lt;em&gt;vehicle&lt;/em&gt; for learning and demonstrating
&lt;strong&gt;loop engineering with Claude Code&lt;/strong&gt; — building automated loops where an AI agent makes
a change, a deterministic check decides whether that change is good, and the loop repeats
until the check passes. The interesting part is the loops, not the movie site. If the
website ever became the hard part, something went wrong.&lt;/p&gt;
&lt;p&gt;This repo ships three loops, each a different &lt;em&gt;shape&lt;/em&gt;, each anchored in a real config and
a real failure. There's one write-up per loop in &lt;a href="https://github.com/AnvaySingh/moviebrowse/./blog" rel="noopener noreferrer"&gt;&lt;code&gt;blog/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is a "loop"?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;loop&lt;/strong&gt; here means:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;run something → check a verifiable&lt;/strong&gt;…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Feedback and different takes are welcome. Would you trust an AI to review another AI's work, or does a human still need to sign off?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>claude</category>
      <category>automation</category>
    </item>
    <item>
      <title>I Gave an AI Agent an Impossible Target to See If It Would Cheat</title>
      <dc:creator>Anvay Singh</dc:creator>
      <pubDate>Mon, 13 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/anvaysingh/i-gave-an-ai-agent-an-impossible-target-to-see-if-it-would-cheat-4ooe</link>
      <guid>https://dev.to/anvaysingh/i-gave-an-ai-agent-an-impossible-target-to-see-if-it-would-cheat-4ooe</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A "loop" is not an agent grading its own work. It is an external script that re-runs the agent, plus a separate check the agent cannot edit.&lt;/li&gt;
&lt;li&gt;I turned "feels smooth" into an FPS number and let the loop optimize toward it.&lt;/li&gt;
&lt;li&gt;I set the target too high to be reachable on a 60Hz screen. The loop kept failing but never faked the result. The bug was in my number, not the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Could I get an AI agent to make my website faster without me sitting there, running it, reading the numbers, and running it again? That is what this series is about. Not how I built a website, because the website is boring on purpose, but how you wrap an agent in a loop that works toward a goal on its own, and how you stop it from cheating along the way.&lt;/p&gt;

&lt;p&gt;In this first part I want to explain what a loop actually is, because there is a common misconception, and then walk through a real one. I set this loop a target that was physically impossible to reach and watched what it did. That run taught me more than a passing test would have.&lt;/p&gt;

&lt;p&gt;This is Part 1 of 3. All three parts use the same small movie-poster website as the example, but the website is never the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a loop is, and what it is not
&lt;/h2&gt;

&lt;p&gt;I had a wrong idea about this at first, so let me clear it up. A loop is not an agent prompting itself, grading its own work, and deciding when it is done. An agent left to mark its own homework will usually tell you it passed.&lt;/p&gt;

&lt;p&gt;A loop is closer to this: an external script runs the agent, a separate check that the agent cannot edit decides whether the result is good, and that repeats until the check passes or you hit a limit.&lt;/p&gt;

&lt;p&gt;There are three parts to it that come up again and again:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The driver: the script that re-runs the agent. This is the thing that removes the manual work, not the agent.&lt;/li&gt;
&lt;li&gt;The gate: the check that decides pass or fail. The agent makes changes, but it never decides when to stop.&lt;/li&gt;
&lt;li&gt;The cap: a limit, so a stuck loop gives up instead of running forever.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One rule matters more than the rest. The thing being checked must never be able to edit the check, and only a person is allowed to change the target. If the agent can edit its own test or lower its own bar, the loop is pointless. So most of the work in a loop is not writing a clever prompt. It is building a check the agent cannot get around. That idea runs through the whole series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem this loop solves
&lt;/h2&gt;

&lt;p&gt;The website is a grid of movie posters you can scroll and zoom through. It holds hundreds of real images and only keeps the visible ones in the page, so it stays light. The one thing it has to do well is feel smooth.&lt;/p&gt;

&lt;p&gt;"Feels smooth" is a vibe, and you cannot put a vibe in a loop, so the first job is to turn it into a number. I used two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sustained frames per second (FPS) while scrolling and zooming, and&lt;/li&gt;
&lt;li&gt;no single main-thread task longer than 50 milliseconds, since a long task is what makes scrolling stutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One quick note on why the data matters. I could have filled the grid with plain coloured boxes as placeholders, and it would have been less work. But coloured boxes are almost free to draw, while real images cost real decode time, memory, and paint. A performance test running against coloured boxes would be measuring something that isn't really there. So I used about 200 real JPEGs, with the same &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; markup I would use with a live movie API. A check is only as honest as the thing it measures.&lt;/p&gt;

&lt;p&gt;A Playwright test drives the grid, records frame times, writes them to a file, and asserts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FPS_TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LONG_TASK_MAX_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;avgFps&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FPS_TARGET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longestTaskMs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LONG_TASK_MAX_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That test file is the definition of "smooth" for this project. It is the gate, and everything else works toward it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The loop itself
&lt;/h2&gt;

&lt;p&gt;The driver is a small bash script. This is its core:&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="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MAX_ITERS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;          &lt;span class="c"&gt;# cap = 12&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;npm run gate&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"PASS — gate green. Loop 1 complete."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
  &lt;span class="k"&gt;fi&lt;/span&gt;
  &lt;span class="c"&gt;# gate failed, so ask Claude for one optimization&lt;/span&gt;
  claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; acceptEdits
  &lt;span class="c"&gt;# ...safety checks... then commit and loop again&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The prompt tells Claude to read the failing report, make one targeted optimization, and stop, and not to touch the test or the threshold. A prompt is only words though, so the script also enforces it. Before it commits each round, it checks whether any protected file changed, and if one did, it reverts everything and stops.&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="k"&gt;if &lt;/span&gt;protected_changed&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ABORT — protected gate files were modified. Reverting."&lt;/span&gt;
  git checkout &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROTECTED&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So if the agent tried to pass by lowering the target, the loop would undo it before the change ever counted.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure
&lt;/h2&gt;

&lt;p&gt;I set &lt;code&gt;FPS_TARGET = 65&lt;/code&gt; and ran it. Here is iteration 1:&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;FAIL&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avgFps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"longestTaskMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"longTasksOver50"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;The grid was already doing about 60 FPS with zero long tasks. It only "failed" because 60.1 is below 65.&lt;/p&gt;

&lt;p&gt;Claude made a real optimization anyway. It moved a shimmer animation off the main thread and onto the GPU, which is a good change. Iteration 2:&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;FAIL&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avgFps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"longestTaskMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;60.1 became 60.2, and it was never going to climb much higher. The reason is the display, not the code. The browser measures frames against the screen's refresh rate, which is around 60Hz, so measured FPS cannot go past the monitor's ceiling no matter how good the code gets.&lt;/p&gt;

&lt;p&gt;The target was the problem, not the code. I had asked for a number the hardware could not produce. The loop could not reach it, and instead of faking the result, it just kept failing, because the protected-files check stopped it from touching the test.&lt;/p&gt;

&lt;p&gt;Fixing it was my job, not the agent's, since only a person changes the target. I lowered it to something reachable:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FPS_TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;   &lt;span class="c1"&gt;// stay near 60, don't drop frames&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I ran it again and it passed on the first gate.&lt;/p&gt;

&lt;p&gt;There is a subtler point here too. On a 60Hz display there is no FPS target that is both currently failing and actually reachable, because you are already sitting at the ceiling. If you wanted a loop that improves its way to green over several rounds, FPS is the wrong thing to measure. You would measure something like p95 frame time or the number of dropped frames instead. Picking the right number to chase is part of the work.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I took from Part 1
&lt;/h2&gt;

&lt;p&gt;A goal loop is only as good as the number you give it. The run against 65 looked like a failure, but it was useful, because it showed the loop would rather keep failing than fake a pass. That is what you want to see before you trust it with anything.&lt;/p&gt;

&lt;p&gt;In Part 2 the check changes. Loop 1 measured a number, but a lot of what we care about is not a number. "Does this animation feel right?" has no assert. So I hand the judgment to a second, separate agent, and it turns the first one down three times before it agrees to a pass.&lt;/p&gt;

&lt;p&gt;The full project, with the loop drivers, gates, and agents, is on GitHub:&lt;/p&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/AnvaySingh" rel="noopener noreferrer"&gt;
        AnvaySingh
      &lt;/a&gt; / &lt;a href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;
        moviebrowse
      &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;MovieBrowse — a loop-engineering learning project&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small, deliberately-boring &lt;strong&gt;regional cinema browser&lt;/strong&gt;: a fast, virtualized grid of
film posters for one "region" (a language / film industry, not a country), with a
manual region selector and a geo-IP default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But the website is not the point.&lt;/strong&gt; It's a &lt;em&gt;vehicle&lt;/em&gt; for learning and demonstrating
&lt;strong&gt;loop engineering with Claude Code&lt;/strong&gt; — building automated loops where an AI agent makes
a change, a deterministic check decides whether that change is good, and the loop repeats
until the check passes. The interesting part is the loops, not the movie site. If the
website ever became the hard part, something went wrong.&lt;/p&gt;
&lt;p&gt;This repo ships three loops, each a different &lt;em&gt;shape&lt;/em&gt;, each anchored in a real config and
a real failure. There's one write-up per loop in &lt;a href="https://github.com/AnvaySingh/moviebrowse/./blog" rel="noopener noreferrer"&gt;&lt;code&gt;blog/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is a "loop"?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;loop&lt;/strong&gt; here means:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;run something → check a verifiable&lt;/strong&gt;…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you would do any of this differently, I would like to hear it. What is the one thing you would have your own loop check?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>beginners</category>
      <category>claude</category>
    </item>
    <item>
      <title>Part 3: A Loop Whose Job Is to Do Nothing</title>
      <dc:creator>Anvay Singh</dc:creator>
      <pubDate>Mon, 13 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/anvaysingh/part-3-a-loop-whose-job-is-to-do-nothing-2kog</link>
      <guid>https://dev.to/anvaysingh/part-3-a-loop-whose-job-is-to-do-nothing-2kog</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This loop runs on a schedule and succeeds by doing nothing almost every night.&lt;/li&gt;
&lt;li&gt;The pass/fail check is plain deterministic code, with no AI in the decision.&lt;/li&gt;
&lt;li&gt;It can run entirely free on your own machine. Only the cloud/CI version needs a paid API key. Plus the one bug that broke all three loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two loops in this series work the same way from your side: you start them and watch. This last one runs on a schedule, like a nightly job, while you are not looking. That changes what success even means.&lt;/p&gt;

&lt;p&gt;A scheduled maintenance loop is doing its job when it does nothing. It should run every night, find nothing wrong, cost almost nothing, and still be there on the night something actually breaks. This part covers that loop, the hook mechanism that the whole series relies on, and a bug that broke all three loops in the least convenient place possible.&lt;/p&gt;

&lt;p&gt;The definition one more time, since this is Part 3 of 3: a loop is a trigger that runs the agent, a check the agent cannot edit that decides pass or fail, and a repeat, or here a wait until the next run. The only new thing this time is the trigger. A timer starts it instead of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem this loop solves
&lt;/h2&gt;

&lt;p&gt;The browser's poster data is baked ahead of time into JSON files and images. In a real deployment that data goes stale as films are added and metadata changes, so you want to regenerate it every so often and confirm it is still valid before it ships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on a timer -&amp;gt; regenerate the data -&amp;gt; validate it -&amp;gt; green ships, red shouts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The gate: a plain check with no model in it
&lt;/h2&gt;

&lt;p&gt;The check is a Node script. For every region file it confirms three things and exits non-zero if any of them fail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it matches the expected JSON schema,&lt;/li&gt;
&lt;li&gt;it has at least the minimum film count, and&lt;/li&gt;
&lt;li&gt;every poster file it points to actually exists on disk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no language model in that list. The regeneration step might use Claude, but the decision about whether the data is good is plain, deterministic code. That is on purpose. You do not want the thing deciding "is this safe to publish" to be a model that could confidently say yes when the answer is no. Regeneration can involve a model; validation should not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hooks, the piece that ties the series together
&lt;/h2&gt;

&lt;p&gt;The same check is also wired into a Claude Code hook, which is worth explaining, because it is the piece that makes the loops in this series trustworthy.&lt;/p&gt;

&lt;p&gt;A hook is a shell command attached to a point in Claude's lifecycle. This one runs when a session tries to stop, and it can block the stop by exiting non-zero:&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;"hooks"&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;"Stop"&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;span class="nl"&gt;"hooks"&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;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/.bin/tsx scripts/validate-regions.ts &amp;gt;&amp;amp;2 || exit 2"&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;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;p&gt;If a session ends with the data in a broken state, the hook fails the session and the work does not go through. The point is that enforcement does not depend on the model deciding to behave. A plain shell command has the final say. That is the same idea as Part 1's protected-files check and Part 2's read-only reviewer, in a different form.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two ways to run it, and who pays
&lt;/h2&gt;

&lt;p&gt;This is where the loop got practical in a way I did not expect. There are two reasonable ways to run something nightly, and they differ mainly in who pays.&lt;/p&gt;

&lt;p&gt;The cloud way is a GitHub Actions workflow. It runs on GitHub's cron, bakes the data headlessly, runs the gate, and opens a pull request when the data is good or an issue when it is not:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Regenerate datasets (Claude, headless)&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;claude -p "Run npm run bake. Baking only." \&lt;/span&gt;
      &lt;span class="s"&gt;--bare --permission-mode acceptEdits --allowedTools "Bash(npm run bake)"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate datasets (deterministic gate)&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run validate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;--bare&lt;/code&gt; is the scripted mode. It skips hooks, plugins, and &lt;code&gt;CLAUDE.md&lt;/code&gt; discovery, and it authenticates with an API key. That key is the pay-as-you-go Anthropic API, which has a $5 minimum and no free tier, because a CI runner has no interactive login to fall back on.&lt;/p&gt;

&lt;p&gt;The local way is free. This loop does not actually need a model, since the bake is deterministic and the gate is a plain script, so the free version is a local job that macOS &lt;code&gt;launchd&lt;/code&gt; runs every night. Because the output is byte-for-byte stable, the normal result is a clean no-op:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ korean.json (120 films, all posters present)
✓ tamil.json  (200 films, all posters present)
✓ nothing to refresh — data byte-stable, gate green. Loop cost: ~nothing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  A note on cost that caught me out
&lt;/h3&gt;

&lt;p&gt;I got this wrong at first, so let me state it plainly. There are two separate ways to pay for Claude:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Claude Code subscription (Pro or Max) covers &lt;code&gt;claude&lt;/code&gt; running locally. Loops 1 and 2 use this, which is why Loop 2 stopped on a session limit rather than a bill.&lt;/li&gt;
&lt;li&gt;The Anthropic API key is the metered, pay-as-you-go one. Only a cloud or CI run needs it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I made the local job the default and kept the cloud workflow in the repo as the documented option for a real unattended setup. Same gate, different tradeoff, and it helps to know which one a given loop is spending.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug that broke all three loops
&lt;/h2&gt;

&lt;p&gt;The best bug in the project belongs here, because it did not only hit this loop. It hit all three. The first time any loop ran inside a fresh git worktree, which I used so a runaway loop could not touch my main checkout, it died right away:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh: playwright: command not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A git worktree does not share &lt;code&gt;node_modules&lt;/code&gt; with your main checkout, because those files are gitignored, and gitignored files are not shared between worktrees. So the loop had no dependencies installed and could not even start. The fix, now in every loop driver, is a one-line guard that installs them if they are missing:&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; node_modules/.bin/tsx &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;npm ci&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is where the project's motto comes from: get the harness solid before you make the loop long. Until the environment a loop runs in is reliable, you do not really have a loop. And an unattended job at 3 AM is the worst place to find out your setup was broken.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I took from Part 3
&lt;/h2&gt;

&lt;p&gt;A scheduled loop succeeds quietly. Most nights it should do nothing and cost almost nothing, and the whole design is really about making sure the one loud failure gets through on the night it matters.&lt;/p&gt;

&lt;p&gt;Here are the three loops side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Loop&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Success looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Goal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;you run it&lt;/td&gt;
&lt;td&gt;a number (FPS) from a test&lt;/td&gt;
&lt;td&gt;the number goes green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Maker-checker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;you run it&lt;/td&gt;
&lt;td&gt;a rubric applied by a separate read-only agent&lt;/td&gt;
&lt;td&gt;the reviewer says APPROVED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Scheduled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a timer&lt;/td&gt;
&lt;td&gt;deterministic data validation&lt;/td&gt;
&lt;td&gt;it quietly does nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They all rely on the same rule from Part 1: the thing being checked cannot edit the check, and only a person moves the target. When that holds, the loop is something you can leave running. When it does not, it only looks like it is working, which is worse than not having it.&lt;/p&gt;

&lt;p&gt;And that is the series. The movie site was only ever a way to build and test the loops.&lt;/p&gt;

&lt;p&gt;The full project, including the drivers, gates, agents, and hooks, is on GitHub:&lt;/p&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/AnvaySingh" rel="noopener noreferrer"&gt;
        AnvaySingh
      &lt;/a&gt; / &lt;a href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;
        moviebrowse
      &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;MovieBrowse — a loop-engineering learning project&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small, deliberately-boring &lt;strong&gt;regional cinema browser&lt;/strong&gt;: a fast, virtualized grid of
film posters for one "region" (a language / film industry, not a country), with a
manual region selector and a geo-IP default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But the website is not the point.&lt;/strong&gt; It's a &lt;em&gt;vehicle&lt;/em&gt; for learning and demonstrating
&lt;strong&gt;loop engineering with Claude Code&lt;/strong&gt; — building automated loops where an AI agent makes
a change, a deterministic check decides whether that change is good, and the loop repeats
until the check passes. The interesting part is the loops, not the movie site. If the
website ever became the hard part, something went wrong.&lt;/p&gt;
&lt;p&gt;This repo ships three loops, each a different &lt;em&gt;shape&lt;/em&gt;, each anchored in a real config and
a real failure. There's one write-up per loop in &lt;a href="https://github.com/AnvaySingh/moviebrowse/./blog" rel="noopener noreferrer"&gt;&lt;code&gt;blog/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is a "loop"?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;loop&lt;/strong&gt; here means:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;run something → check a verifiable&lt;/strong&gt;…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AnvaySingh/moviebrowse" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you build a loop of your own, I would like to hear what you had it check. Feedback and suggestions are always welcome, and thanks for reading all three parts.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>claude</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
