<?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: Sunitha Eswaraiah</title>
    <description>The latest articles on DEV Community by Sunitha Eswaraiah (@esunitha).</description>
    <link>https://dev.to/esunitha</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%2F4022627%2Fad76c9c0-8ac3-43ff-8aeb-c6a51210819b.jpg</url>
      <title>DEV Community: Sunitha Eswaraiah</title>
      <link>https://dev.to/esunitha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/esunitha"/>
    <language>en</language>
    <item>
      <title>What I learned building an agent pipeline — and where I stopped</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:29:08 +0000</pubDate>
      <link>https://dev.to/esunitha/what-i-learned-building-an-agent-pipeline-and-where-i-stopped-3efk</link>
      <guid>https://dev.to/esunitha/what-i-learned-building-an-agent-pipeline-and-where-i-stopped-3efk</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 8 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The runaway
&lt;/h2&gt;

&lt;p&gt;One afternoon I watched an agent spend three hours and 138 dollars trying to edit a single file.&lt;/p&gt;

&lt;p&gt;It was the Builder — the agent whose job was to take a theme spec and turn my slot machine's React code into a themed variant. It had opened the main game component, a 1,900-line file, and started making small surgical edits. One of them broke the syntax. The model ran the compiler itself, read the error, tried to fix it, broke something else, and kept going — nothing in the loop forced it to stop on a failed build. By the time I killed it, it had made 868 calls to the model. The file was a wreck: two &lt;code&gt;export default&lt;/code&gt; statements, duplicate &lt;code&gt;return (...)&lt;/code&gt; blocks, orphaned JSX floating between them.&lt;/p&gt;

&lt;p&gt;Nothing about that failure was exotic. The agent did exactly what I told it to do. The problem was what I told it to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the factory was
&lt;/h2&gt;

&lt;p&gt;The short version, if you didn't read &lt;a href="https://dev.to/esunitha/the-slot-machine-that-turned-into-an-agentic-game-factory-45i4"&gt;post 1&lt;/a&gt;: I built a real slot machine on AWS to learn the iGaming domain, then turned the theming into a pipeline of agents. Describe a theme in a sentence, and six agents in sequence design a spec, generate icons and a background, rewrite the code, test it in a browser, and deploy it. Designer, Image-Gen, Background-Gen, Builder, Tester, Deployer.&lt;/p&gt;

&lt;p&gt;Five of those six behaved. The Builder was the one that kept me up. Every other agent in the pipeline had a clean output — a spec, a set of images, a test report, a deployed URL. The Builder's output was a code change to a large, interconnected React app, and code changes are where a model's mistakes compound instead of just being wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that mattered most
&lt;/h2&gt;

&lt;p&gt;The Builder's task was: take the casino's &lt;code&gt;SlotGame.js&lt;/code&gt; and edit it to match the theme. The file was 1,900 lines. The model couldn't do it reliably — it guessed at whitespace, patched the wrong one of twenty near-identical calls, broke the parse with unescaped apostrophes, and each miss compounded.&lt;/p&gt;

&lt;p&gt;The insight I was slow to reach: the model was bad at editing that file because &lt;em&gt;the file was hard to edit&lt;/em&gt;. It mixed theme-specific content into structural code. So I stopped asking the model to edit it at all.&lt;/p&gt;

&lt;p&gt;I moved the theme-specific text into a config file the component reads at runtime:&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="c1"&gt;// before: baked into the component, edited by the model&lt;/span&gt;
&lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🎉 JACKPOT! You won &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;spinData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; credits! 🎉`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// after: read from a config the component loads&lt;/span&gt;
&lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;themeConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;winMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jackpot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spinData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;win&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the Builder's job for that file was: copy it unchanged, and write a &lt;code&gt;theme-config.json&lt;/code&gt; next to it. Generating a JSON config from a spec is ordinary Python — no model involved. There is no 1,900-line edit anymore.&lt;/p&gt;

&lt;p&gt;I made the config back-compatible — with no theme specified, the defaults are the original strings, so the untouched casino renders identically. That mattered, because the casino is a live thing and I wasn't going to break it to make theming easier.&lt;/p&gt;

&lt;p&gt;The result: the Builder became almost entirely deterministic. Copy the components verbatim, generate the config, run the color and font and API passes in plain code. The only step left for the model was optional cosmetic CSS — and even that was still the flakiest step in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I observed across all six agents
&lt;/h2&gt;

&lt;p&gt;Each agent taught something specific (posts 3-7 cover those), but patterns emerged across the whole pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this pipeline, generation was reliable; editing was not.&lt;/strong&gt; The Designer generating a spec from a conversation: reliable. Image-Gen producing icons from prompts: reliable. The Builder patching a large, interleaved file with an exact-string-match tool: fragile. Models can edit effectively with better tooling — AST-aware editors, bounded context, validation loops. But with the tools I had, every time I moved work from "edit this" to "generate that," reliability improved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit, inspectable handoffs made debugging possible.&lt;/strong&gt; Every stage's output was a file on disk — a JSON spec, a set of PNGs, a test report. When a build came out wrong, I could open the intermediate files and see exactly where the pipeline diverged. The requirement isn't "files specifically" — it's that each handoff is durable, immutable, and something you can inspect without running anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human gates belong where mistakes are costly.&lt;/strong&gt; Three approval gates in the pipeline: the spec, the build plan, and the deploy. The spec because a bad spec poisons five stages downstream. The build plan because code modifications are hard to inspect after the fact. The deploy because shipping has external impact and rollback — while possible — carries risk. Every other stage ran without asking, because its output was cheap to redo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt caching changes the economics.&lt;/strong&gt; The Builder's 868-call runaway cost $138 at Sonnet's list rates, &lt;em&gt;with&lt;/em&gt; Bedrock's prompt caching enabled — each call paid a fraction of the full input cost for the conversation prefix it had already seen. Without caching, the same run would have been far more expensive. Caching made agent loops economically viable for normal runs. It also masked how expensive a runaway was becoming until an hour had passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn caps are the first safety measure, not the last.&lt;/strong&gt; Before I understood why the Builder was failing, I added a sixty-turn cap. It didn't make the agent smarter — it made its worst case bounded. The capped runs that hit the limit aborted in minutes instead of hours and cost single digits instead of triple. Bound the loop before you trust the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the deterministic core without the model.&lt;/strong&gt; After the Builder redesign, I ran the whole deterministic phase — copy, config, code passes — with the model stubbed out, and compiled the result. It found four real bugs for zero dollars. Every part of an agent pipeline that doesn't require the model is testable like ordinary code. Pull work out of the model's reach not only for reliability, but so you can test it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest ceiling
&lt;/h2&gt;

&lt;p&gt;The pipeline runs end to end. It produces themed, deployed, playable games from a sentence. That's real.&lt;/p&gt;

&lt;p&gt;It's also not a product. Here's what's still true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Icon consistency isn't solved.&lt;/strong&gt; Each image generation call is independent. Thirty icons come back in thirty slightly different styles. Prompt engineering narrows the range; it doesn't eliminate the variance. The games look themed. They don't look art-directed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Tester's navigation is fragile.&lt;/strong&gt; Vision-as-judge works — a model looking at a screenshot can tell you whether the game looks right. But the model guessing CSS selectors to drive Playwright breaks often enough to produce false negatives. Accessible labels and stable test hooks would fix this. I haven't shipped that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cosmetic CSS pass still wobbles.&lt;/strong&gt; Even after shrinking the model's job to "optionally adjust shadows and glows," it still hits the turn cap on some themes. The deterministic build is the floor. The model polish is unreliable upward pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I stopped there. The deterministic Builder was proven — it produced a themed, compiling build with no model involvement. The lessons had stopped being about slot machines and started being about agents in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  What transfers
&lt;/h2&gt;

&lt;p&gt;Five things, distilled from months of building this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decide what should be an agent, not what could be.&lt;/strong&gt; If a step can be generation instead of surgery, make it generation. Restructure the target before you blame the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bound every model loop before you trust it.&lt;/strong&gt; Unbounded &lt;code&gt;while&lt;/code&gt; plus a paid API is a liability, not a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make every handoff explicit and inspectable.&lt;/strong&gt; Files, versioned objects, whatever — as long as you can look at what one stage produced without running the next one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back-compat is how you refactor a live thing without fear.&lt;/strong&gt; Defaults that reproduce the original behavior let you change everything around them safely. The casino still works without a theme config; that's what made the redesign safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know when the lessons have outpaced the project.&lt;/strong&gt; That's the signal to bank them and move on.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The game factory was a learning project. What it taught me about scoping agent work, managing cost, and designing for inspectability carries into whatever I build next. The factory itself stays where it is — functional, proven for what it does, but not something I'm polishing further.&lt;/p&gt;

&lt;p&gt;The next thing is something different: applying what I learned here to a problem that isn't themed slot machines. The agent patterns transfer. The domain won't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the final post in the game-factory series. The earlier posts cover each agent individually: &lt;a href="https://dev.to/esunitha/how-a-single-game-evolved-into-a-game-factory-5763"&gt;the factory overview&lt;/a&gt;, &lt;a href="https://dev.to/esunitha/the-designer-agent-a-spec-from-a-conversation-4nba"&gt;Designer&lt;/a&gt;, &lt;a href="https://dev.to/esunitha/the-image-agents-prompt-to-png-34d0"&gt;Image agents&lt;/a&gt;, &lt;a href="https://dev.to/esunitha/the-builder-agent-the-code-modifier-that-fought-me-5ch0"&gt;Builder&lt;/a&gt;, &lt;a href="https://dev.to/esunitha/the-tester-agent-visual-qa-with-playwright-and-a-vision-model-256m"&gt;Tester&lt;/a&gt;, &lt;a href="https://dev.to/esunitha/the-deployer-agent-and-the-multi-tenant-backend-1h63"&gt;Deployer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>aws</category>
      <category>lessons</category>
    </item>
    <item>
      <title>The Deployer agent and the multi-tenant backend</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:14:07 +0000</pubDate>
      <link>https://dev.to/esunitha/the-deployer-agent-and-the-multi-tenant-backend-1h63</link>
      <guid>https://dev.to/esunitha/the-deployer-agent-and-the-multi-tenant-backend-1h63</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 7 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate that says no
&lt;/h2&gt;

&lt;p&gt;The Deployer is the last thing in the pipeline. Every other agent builds toward it: the Designer produces a spec, Image-Gen generates icons, Builder patches the React code, Tester plays the built game in a real browser. All of that work flows into a single question: does the Deployer run?&lt;/p&gt;

&lt;p&gt;The answer isn't always yes. The Deployer reads the Tester's report before it touches anything. If the Tester flagged the build as broken — spin didn't work, win didn't register, login failed — the Deployer refuses to start. Not with an error. Just a refusal. The build exists on disk but it won't ship.&lt;/p&gt;

&lt;p&gt;That refusal is the thing I'm most glad I built. Every automated pipeline I've seen either skips pre-deploy testing entirely or tests in a way that doesn't check whether the product actually works. The Tester runs a real browser, plays the game, and looks at screenshots to decide if it passed. The Deployer treats that judgment as a hard gate.&lt;/p&gt;

&lt;p&gt;What follows, if the gate opens, is the part that involves AWS.&lt;/p&gt;

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

&lt;p&gt;The Deployer takes a built React app and ships it to AWS using SAM — the Serverless Application Model, which is CloudFormation with a better syntax for serverless resources. The frontend ends up behind CloudFront, served from S3. The backend it points to is a serverless stack: API Gateway in front, Lambda functions handling game logic, DynamoDB storing player data and leaderboard entries.&lt;/p&gt;

&lt;p&gt;It doesn't deploy silently. Before anything changes, it creates a changeset — CloudFormation's dry-run mechanism, which calculates what will be modified without actually modifying it — and shows it to me. If the changeset looks wrong, I can say no. This is the same "agent proposes, human approves" pattern that runs through the whole pipeline: the Designer waits for approval on the spec, the Builder shows its plan before touching code, the Deployer confirms before it ships.&lt;/p&gt;

&lt;p&gt;The deploy has two halves. The backend goes through &lt;code&gt;sam build&lt;/code&gt; and &lt;code&gt;sam deploy&lt;/code&gt; — I show the changeset first, then run with the flag that skips the interactive confirmation, because the review already happened. The frontend is a separate step: &lt;code&gt;aws s3 sync&lt;/code&gt; uploads the React build to the theme's S3 bucket, then a CloudFront invalidation busts the CDN cache so the new version is immediately live.&lt;/p&gt;

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

&lt;p&gt;The obvious outcome is live games at real URLs. Two themed variants are deployed as of this writing: ancient-egypt-slots and norse-mythology-slots, both playable, both with functioning leaderboards and real DynamoDB tables behind them.&lt;/p&gt;

&lt;p&gt;The less obvious outcome — and the one that took more work — is the backend architecture underneath both of them.&lt;/p&gt;

&lt;p&gt;The original casino had a single-tenant backend. Every Lambda function, every DynamoDB table, every API Gateway route was built for one game. When I started generating themed variants, the straightforward path would have been to deploy a separate backend stack per theme: ancient-egypt-slots gets its own Lambdas, norse-mythology-slots gets its own Lambdas, every future theme gets another full stack.&lt;/p&gt;

&lt;p&gt;That's expensive and it's unmanageable. Five themes means five stacks to maintain, five DynamoDB configurations to keep in sync, five deployments every time I fix a bug in the spin logic. The better structure was a shared backend with every read and write keyed by a &lt;code&gt;theme_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I forked the original casino backend into a multi-tenant version: one serverless stack — deployed to eu-west-1 — that serves every themed game at once. A new theme doesn't need its own backend. It registers as another tenant of the shared one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;Two categories of failure. They're quite different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The multi-tenancy migration was a real migration.&lt;/strong&gt; "Add a &lt;code&gt;theme_id&lt;/code&gt; field" sounds like an afternoon. It's not, when the system you're adding it to was designed around one tenant and every path through it assumes that implicitly. I found cases where &lt;code&gt;theme_id&lt;/code&gt; was being passed correctly to Lambda but wasn't making it into the DynamoDB query, so a spin in one game would sometimes read credit data from another. I found cases where the validation checked that the field was present but not whether it matched any known theme. Finding those cases meant writing tests that ran both themes simultaneously and checked for data bleed — not a standard integration test shape.&lt;/p&gt;

&lt;p&gt;The migration also carried its own risk, separate from any theming work. DynamoDB doesn't let you alter an existing table's primary key, so I created new tables with &lt;code&gt;theme_id&lt;/code&gt; as the partition key and migrated the application's queries to use them. On a development backend with no real player data, that was straightforward. In production with existing players, the same migration would mean careful data backfill and dual-read logic during the transition.&lt;/p&gt;

&lt;p&gt;The lesson: making a system multi-tenant is a migration, not a configuration. Plan for it the way you'd plan for any schema change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFormation fails in its own language.&lt;/strong&gt; The Deployer is the stage where the application code becomes irrelevant to the failure. A bug in the React build surfaces in the Tester, not here. If the Deployer fails, the failure is CloudFormation's, and CloudFormation doesn't speak Python or JavaScript.&lt;/p&gt;

&lt;p&gt;I hit two of these. The first was a duplicate logical ID: two resources in the same SAM template with the same name. CloudFormation's error for this doesn't say "you have duplicate logical IDs." It surfaces as a schema validation error, and tracing it back to the specific duplicate took longer than it should have. The second was a &lt;code&gt;ResourceExistenceCheck&lt;/code&gt; — a pre-flight check that validates whether a referenced resource exists before CloudFormation even creates a changeset. I had a reference to a resource the stack expected to already be there, and the check failed before a single resource was created or modified. The error arrived at changeset creation time rather than at deployment, which was useful, but still required reading CloudFormation event logs to understand.&lt;/p&gt;

&lt;p&gt;Neither failure was in the application code. Both required CloudFormation documentation rather than anything else in the project's stack. The deploy step speaks a different dialect than every other step in the pipeline.&lt;/p&gt;

&lt;p&gt;The good part: the test-report gate held every time. One build had a broken win state — reel symbols were rendering but win evaluation wasn't triggering. The Deployer refused it. I fixed the Builder's output, the Tester passed it, and the Deployer ran. The gate did exactly what it was supposed to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Three things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate on real QA output, not on compilation.&lt;/strong&gt; "The build compiled" is not evidence that the product works. A Tester that runs a browser, plays the actual game, and produces a structured report — which checks passed, which failed, screenshots for the failures — gives the Deployer something meaningful to block on. That check takes a few minutes. A broken game on a live URL is worse than a deploy that didn't run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key a shared backend by tenant ID — for low-stakes variants.&lt;/strong&gt; For these themed games — same code, same owner, no compliance boundaries — one backend stack keyed by &lt;code&gt;theme_id&lt;/code&gt; is simpler to operate than five isolated stacks. Every request carries the tenant key, every read and write is scoped to it, and adding a new variant is a registration step rather than a stack provisioning step. This wouldn't be the right call if tenants were separate customers with different security requirements. For variants of one game under one operator, it's the lighter path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget separately for infrastructure failures.&lt;/strong&gt; The Deployer is the first stage in this pipeline where the failure mode isn't in the Python or the React. CloudFormation errors don't look like application errors, don't point to line numbers in your code, and debugging them means learning CloudFormation's event model and pre-flight check behavior. That's a different skill than everything else in the pipeline requires. If you're building an automated deploy step, expect to spend time on this that feels orthogonal to the application work. It is orthogonal. It's also unavoidable.&lt;/p&gt;

&lt;p&gt;The pipeline ends here. The chain runs end-to-end — one sentence in, one deployed game out, with a human approving three decisions along the way. Whether the result is good enough to call a product is a different question, and the answer is honest: not yet. The lessons from why are the subject of the final post.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/the-tester-agent-visual-qa-with-playwright-and-a-vision-model-256m"&gt;the Tester agent&lt;/a&gt;. Next: the final post — observations and lessons.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloudformation</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Tester agent — visual QA with Playwright and a vision model</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:04:43 +0000</pubDate>
      <link>https://dev.to/esunitha/the-tester-agent-visual-qa-with-playwright-and-a-vision-model-256m</link>
      <guid>https://dev.to/esunitha/the-tester-agent-visual-qa-with-playwright-and-a-vision-model-256m</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 6 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ran the ancient Egypt variant through the pipeline for the first time and it passed everything. The Builder produced a clean build. The icons loaded. The themed strings were in the code. Then I ran the Tester, and it told me the login form was broken.&lt;/p&gt;

&lt;p&gt;The form wasn't broken.&lt;/p&gt;

&lt;p&gt;That failure told me more about where automated testing goes wrong than months of reading could have. It's where this post starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Tester agent is
&lt;/h2&gt;

&lt;p&gt;The Tester is the QA stage in a pipeline: Designer, Image-Gen, Background-Gen, Builder, Tester, Deployer. Its job is to take the freshly built game, run it in a real browser, and decide whether it works before the Deployer touches anything. The Deployer reads the test report and refuses to proceed if the verdict is &lt;code&gt;fail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mechanically: the agent spins up the built game as a local subprocess. It runs &lt;code&gt;npm install&lt;/code&gt;, then &lt;code&gt;npm start&lt;/code&gt; in the output directory, waits for port 3000 to respond, and launches a Playwright browser session. From there it runs a fixed battery of eleven functional checks: navigate to the app, complete the auth flow, place spins, verify themed strings appear, check for console errors, confirm that every API call carries the right theme ID. Then three visual checks.&lt;/p&gt;

&lt;p&gt;The unusual part is how it judges those visual results. After the functional checks, it takes three screenshots — idle page, mid-spin, settled-after-spin — and sends each to a vision-capable model with a rubric:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does the title and color palette visibly match the spec? Are the symbols and patterns panel readable? Is the tutorial modal absent?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The vision model scores the screenshot from 1 to 5 and lists any issues it can see. Playwright can assert on visibility and image dimensions, but my existing assertions didn't cover aesthetic defects — a background that loaded as a flat fallback color, a color palette that was technically present but visually wrong. A model looking at the actual screenshot catches those without writing a specific assertion for each one.&lt;/p&gt;

&lt;p&gt;Two models share the work. Haiku handles the main orchestration loop: executing the checklist, clicking buttons, reading console output, filling forms. Sonnet handles the nuanced visual judgment on the idle screenshot, where theme fidelity matters most. The simpler visual checks (mid-spin, post-spin) also use Haiku, because the bar there is lower.&lt;/p&gt;

&lt;p&gt;At the end the agent calls a terminal tool, &lt;code&gt;mark_test_complete&lt;/code&gt;, with a verdict and a summary. That gets written to &lt;code&gt;.test-report.json&lt;/code&gt;:&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;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"functional"&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;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&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;"visual"&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;"average_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.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;"evaluations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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;"findings"&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 verdict is &lt;code&gt;fail&lt;/code&gt;, the Deployer reads that and stops. The game doesn't ship.&lt;/p&gt;

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

&lt;p&gt;On a working build, the Tester runs the full happy path. It registers a fresh smoke-test user with a unique email each run (to avoid conflicts with previous runs), logs in, dismisses the tutorial modal, places a spin, waits for the animation to settle, and checks that a win or no-win message appears. It reads console output for errors and reads network traffic to verify every backend call carries the right theme ID. It runs up to 20 spins watching for themed strings: "Pharaoh's Fortune" for ancient Egypt, "Valhalla Jackpot" for Norse mythology.&lt;/p&gt;

&lt;p&gt;The vision evaluation caught things I didn't expect. On one early build, the theme colors were correct but the background image hadn't loaded. The page was technically functional, the DOM had no errors, but the game looked wrong. The vision model flagged it with a score of 2 and the specific note that the background was a flat fallback color. A DOM assertion would have passed that build.&lt;/p&gt;

&lt;p&gt;That's what the Tester is genuinely good at: looking at the actual rendered page and deciding whether it looks right.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;The failure mode was in the navigation, not the judgment.&lt;/p&gt;

&lt;p&gt;To drive the game, the agent had to find and interact with elements: fill the registration form, click the spin button, read result messages. The model did this by guessing selectors. It tried placeholder text, role attributes, visible text labels, CSS class patterns. Most of the time it got close enough. Sometimes it didn't.&lt;/p&gt;

&lt;p&gt;The registration form was the worst offender. The form has five fields, and two of them are plain &lt;code&gt;input[type="text"]&lt;/code&gt; — one for player name, one for country. There is nothing in the element's type to distinguish them. To fill both correctly, the agent has to know to target &lt;code&gt;input[type="text"]&lt;/code&gt; with &lt;code&gt;nth=0&lt;/code&gt; for the first and &lt;code&gt;nth=1&lt;/code&gt; for the second. If it guesses wrong, the wrong field gets filled, the form submits with country empty, the browser's HTML5 validation silently blocks the submit, and nothing happens. No error. No network request. Just silence — which the agent records as a test failure.&lt;/p&gt;

&lt;p&gt;Twice the run reported "couldn't find field X" when field X was there and working. Both times the automation had broken on its own selector guess, not on anything wrong in the product.&lt;/p&gt;

&lt;p&gt;This is the worst kind of test failure. A real failure tells you something is broken in the product. A false negative tells you something is broken in the test infrastructure. Because the output looks the same, you don't always know which you're looking at immediately. I spent time debugging the auth form before I realized the selector was wrong.&lt;/p&gt;

&lt;p&gt;The pattern matters: vision-as-judge was the reliable half; LLM-guessed-selector navigation was the fragile half.&lt;/p&gt;

&lt;p&gt;Looking at a screenshot and deciding whether a slot machine matches an ancient Egypt theme is exactly the kind of ambiguous judgment a language model handles well. It can reason about color fidelity, symbol legibility, layout, atmosphere. Reverse-engineering a CSS selector for a button it has never seen the source of is a different task. It's brittle, failure-prone, and not something the model is particularly good at.&lt;/p&gt;

&lt;p&gt;The fix is structural. The form fields should have accessible labels — &lt;code&gt;aria-label&lt;/code&gt; or associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements — which also makes the game more accessible. Where a stable user-facing locator doesn't exist, the Builder should stamp &lt;code&gt;data-testid&lt;/code&gt; attributes: &lt;code&gt;data-testid="spin-button"&lt;/code&gt;, &lt;code&gt;data-testid="credit-display"&lt;/code&gt;. Then the Tester queries by label or role first, falls back to testid, and only guesses as a last resort. That gives the automation stable hooks across every theme variant.&lt;/p&gt;

&lt;p&gt;I haven't shipped that yet. It's on the backlog. The evidence for it is sitting in the test logs: two false negatives on builds that were actually working.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Use a vision model as a complementary judge of a rendered UI. It catches aesthetic defects that specific assertions weren't written for: wrong colors, layout that's technically valid but visually broken, a background that loaded as a fallback. It's not a replacement for deterministic assertions — it's probabilistic and occasionally inconsistent. But it covers the gap between "the DOM is correct" and "the game looks right."&lt;/p&gt;

&lt;p&gt;But don't make the model navigate by guessing selectors. That's where it breaks. Navigation requires knowing the exact shape of the DOM, and a model with no source access is guessing. Every wrong guess is a potential false negative.&lt;/p&gt;

&lt;p&gt;The design that works: give the automation stable, semantic hooks — testid attributes, ARIA roles, predictable form structure — and let the model focus on the judgment calls. Make the thing easy to drive, and reserve the model's cleverness for the evaluation.&lt;/p&gt;

&lt;p&gt;For any pipeline where one agent builds an artifact and the next agent tests it, the cleanest version of this is a contract between the two. The Builder promises to produce certain testable hooks. The Tester relies on them. Both agents get simpler. Failures become easier to localize.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/the-builder-agent-the-code-modifier-that-fought-me-5ch0"&gt;the Builder agent&lt;/a&gt;. Next: the Deployer agent.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>playwright</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Builder agent — the code-modifier that fought me</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:51:02 +0000</pubDate>
      <link>https://dev.to/esunitha/the-builder-agent-the-code-modifier-that-fought-me-5ch0</link>
      <guid>https://dev.to/esunitha/the-builder-agent-the-code-modifier-that-fought-me-5ch0</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 5 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three hours and no result
&lt;/h2&gt;

&lt;p&gt;I was watching the terminal scroll when I noticed the agent had been running for over an hour. Build attempts, compiler rejections, patch attempts. Same file, same error pattern, a new proposed fix each time.&lt;/p&gt;

&lt;p&gt;By the time I stopped it, the call count was 868. Three hours had elapsed. The file it had been working on had two &lt;code&gt;export default&lt;/code&gt; statements, two &lt;code&gt;return (...)&lt;/code&gt; blocks — one of them orphaned in JSX that didn't close — and a mix of correctly-themed and original-themed strings sitting next to each other like the model had started several attempts and stopped mid-thought. The React compiler had been rejecting it for the last hundred calls at least.&lt;/p&gt;

&lt;p&gt;At list rates for the model, that run cost about $138.&lt;/p&gt;

&lt;p&gt;The Builder hadn't done anything wrong by its own rules. There were no rules that said stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Builder is
&lt;/h2&gt;

&lt;p&gt;The Builder is the fourth agent in the pipeline. By the time it runs, the Designer has produced a spec, Image-Gen has generated themed icons, and Background-Gen has produced a background image. The Builder's job is to take all of that and wire it into the actual code.&lt;/p&gt;

&lt;p&gt;The Builder runs a flat &lt;code&gt;while&lt;/code&gt; loop. Call the model, dispatch the tool it chooses, feed the result back, repeat until the model calls &lt;code&gt;mark_build_complete&lt;/code&gt; or something goes wrong enough to abort. The loop itself is eight lines. What makes it interesting is the toolset inside it.&lt;/p&gt;

&lt;p&gt;The Builder has more tools than any other agent in the pipeline. It can read a file, write a file, list the files in a directory, copy a file, patch a file (find an exact string and replace it), and call the terminal tool. Six operations, each corresponding to a real step in the job. The variety matters: most of the build is copy operations, some of it is writes, a small slice is targeted patches.&lt;/p&gt;

&lt;p&gt;There are two human gates. Before the agent touches any code, it proposes a plan and waits for approval. When the build finishes, it presents the output and waits again.&lt;/p&gt;

&lt;p&gt;In the version that burned $138, there was no mandatory compile gate. The model could see compiler output — it was running &lt;code&gt;npm run build&lt;/code&gt; itself via the terminal tool, reading the errors, and attempting patches — but nothing &lt;em&gt;stopped the loop&lt;/em&gt; when compilation failed. The model just kept trying. I added a hard compile gate later: compilation runs after the model signals done, errors feed back for a bounded number of fix rounds, and if it doesn't converge, the build rolls back. But that structure didn't exist during the failure I'm about to describe.&lt;/p&gt;

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

&lt;p&gt;The casino codebase is a React application backed by serverless AWS functions. Every symbol in the game, every color, every win message is baked into the source code. The Builder's job was to fork it into a themed variant: copy the structural files unchanged, swap the colors and fonts and API endpoints to match the spec, and patch the main game component so the reels, win messages, and tutorial reflected the theme.&lt;/p&gt;

&lt;p&gt;For an ancient Egypt variant: gold and lapis tones replacing the original palette, hieroglyph names replacing cloud service names in the symbol configuration, the win message changed from something about AWS architecture to something about pharaohs. Same code structure, different content surface.&lt;/p&gt;

&lt;p&gt;The copy-and-patch pattern worked well for small, isolated files. The CSS config, the sound file manifest, the theme metadata — all clean boundaries. The Builder handled them without complaint.&lt;/p&gt;

&lt;p&gt;The main game component was a different case entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The file was too big to edit safely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SlotGame.js&lt;/code&gt; was about 1,900 lines. It held the spin logic, the win-calculation state machine, the reel animation handling — and scattered throughout all of that: theme-specific strings. Win messages embedded in conditional branches. Tutorial copy baked into JSX. Symbol references in a dozen different places, each with slightly different surrounding syntax.&lt;/p&gt;

&lt;p&gt;I had already moved the color swap, the font swap, and the static string substitutions into plain Python — deterministic passes that ran before the model got involved. That reduced the patch surface significantly. It wasn't enough.&lt;/p&gt;

&lt;p&gt;The remaining patches still ran into the problem the patch tool created: to change a line, the model had to reproduce the surrounding block exactly. Every bracket, every indentation level, every blank line before and after the target. On a 1,900-line file, the model didn't have a reliable mental model of every whitespace detail. It guessed. Sometimes correctly. Often close enough to fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Themed names broke the parser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Symbol names in a spec sometimes contained apostrophes. A symbol called &lt;code&gt;Chef's Trio&lt;/code&gt; sounds reasonable in a design document. Dropped into a single-quoted JavaScript string, it breaks the parse. The model didn't consistently escape these. The compiler would reject the file, the agent would read the error, and in fixing the apostrophe it would shift an indentation level, which broke a different thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each failed patch made the file worse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every edit left the file slightly different from what the model remembered. The model patched based on what it had seen from a prior read — but after each change, the file no longer matched that snapshot. So the model was proposing patches against a version that no longer existed on disk. The exact-string match would fail because the surrounding context had changed, and the model — not knowing what the file currently looked like — would guess at what the block might be now and try again, often wrong.&lt;/p&gt;

&lt;p&gt;There was a way to bound this: force a full re-read of the current file before every patch. I hadn't done that. The token cost of re-reading 1,900 lines on every turn seemed wasteful on the runs that were going well. On the runs that weren't, it would have caught the drift early.&lt;/p&gt;

&lt;p&gt;Duplicate structures were the signature failure. When the exact-match patch tool rejected the model's target string, the model would fall back to rewriting larger sections of the file via the write tool — sometimes inserting the replacement alongside the original instead of replacing it, producing two copies of the same block. Duplicate &lt;code&gt;export default&lt;/code&gt; statements. Two &lt;code&gt;return (...)&lt;/code&gt; calls. Orphaned JSX floating between structural blocks that no longer connected to anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The loop had no ceiling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;868 calls. Three hours. Over 413 million cached input tokens as the model re-read the same growing conversation on every call. About $138 at list rates for Sonnet — and that was &lt;em&gt;with&lt;/em&gt; prompt caching enabled. Bedrock's caching meant each call charged a fraction of the full input cost. Without it, the bill would have been an order of magnitude worse. Caching made the Builder viable on normal runs; it also made the worst-case run cheap enough per-call that I didn't notice it was spiraling until an hour in.&lt;/p&gt;

&lt;p&gt;I knew abstractly that an unbounded loop backed by a paid API was a risk. Knowing it abstractly and watching it happen are different experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the fix looked like, in outline
&lt;/h2&gt;

&lt;p&gt;The eventual solution wasn't about making the model more careful. It was about removing the reason to edit that file at all.&lt;/p&gt;

&lt;p&gt;The component was fragile to patch because it mixed structural code with content. Theme-specific strings were not isolated — they were woven into the spin logic and the win-calculation branches. The fix was to separate them: move the theme-specific text into a config file the component reads at runtime, so the Builder's job for that component becomes generating a JSON file rather than patching 1,900 lines of JSX.&lt;/p&gt;

&lt;p&gt;Generating a JSON config from a spec is ordinary Python — a function reads the spec fields and serializes them into the shape the component expects. No model involvement, no string matching, no file editing. The component reads the config at runtime. The model never touches the component at all.&lt;/p&gt;

&lt;p&gt;I'm not going to explain that fix in full here — the full redesign and what it teaches about which parts of a pipeline should involve a model at all is the subject of the final post in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Two things, both short.&lt;/p&gt;

&lt;p&gt;A code-modifying agent is only as safe as the size and shape of what you ask it to edit. Small files with a clean boundary between structure and content are workable targets. Large files with interleaved concerns are not. If your agent is patching a big file and behaving badly, examine the file before you examine the model.&lt;/p&gt;

&lt;p&gt;Any model loop backed by a paid API needs a hard turn cap before you trust it with a real run. Not added later as a safety measure — as a condition of running at all. The cap doesn't improve the agent's reasoning. It makes the worst-case run affordable rather than surprising.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/the-image-agents-prompt-to-png-34d0"&gt;the image agents&lt;/a&gt;. Next: the Tester agent. The full redesign and the general lesson — about what should and shouldn't be an agent — is the final post in this series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>react</category>
    </item>
    <item>
      <title>The image agents — prompt to PNG</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:34:29 +0000</pubDate>
      <link>https://dev.to/esunitha/the-image-agents-prompt-to-png-34d0</link>
      <guid>https://dev.to/esunitha/the-image-agents-prompt-to-png-34d0</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 4 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Icons are what people look at
&lt;/h2&gt;

&lt;p&gt;You can theme fonts, colors, win messages, and sound effects. Change all of it and the game still reads like the original with a skin on it. Swap the icons — the actual symbols spinning in the reels — and it reads like a different game. The casino template I built by hand uses cloud service logos. Replace those with golden scarabs and ankhs and it becomes an Egyptian game. Keep the logos and give it an Egyptian color scheme and it doesn't.&lt;/p&gt;

&lt;p&gt;A full theme has around thirty symbols. Each needs to be small enough to read at reel size, distinctive enough to tell apart mid-spin, and consistent enough that they look like they came from the same place. Getting that by hand for every theme is exactly what I wanted to avoid.&lt;/p&gt;

&lt;p&gt;So two agents handle the visual layer: Image-Gen and Background-Gen. They're the shortest story in the pipeline — almost identical code, real results, and one failure mode I still haven't fully solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two agents, one loop
&lt;/h2&gt;

&lt;p&gt;The Designer's spec carries everything the image agents need. Each symbol entry has an &lt;code&gt;icon_prompt&lt;/code&gt; — a short text description the Designer wrote to describe that symbol's appearance. The spec also carries a single &lt;code&gt;background_prompt&lt;/code&gt; for the full-page background.&lt;/p&gt;

&lt;p&gt;Image-Gen reads the spec, loops through every symbol, and for each one calls Stable Image Core on Bedrock with the symbol's prompt. It gets a PNG back, resizes it to 256×256 (the size the reels expect), and writes it into the app's &lt;code&gt;public/images&lt;/code&gt; folder. After the icons are approved, it seeds DynamoDB — putting each symbol into a table the game queries at runtime to know which icons to load.&lt;/p&gt;

&lt;p&gt;Background-Gen does the same process exactly once, for the background image.&lt;/p&gt;

&lt;p&gt;That's the scope. I grouped them in one post because splitting them into two would mean writing the same agent story twice. They share the same architecture, the same failure modes, and the same lessons. The only thing different is the count of outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs
&lt;/h2&gt;

&lt;p&gt;The happy path is simple. Stable Image Core returns the image as base64; I decode it, resize it, write it to disk. A thirty-symbol run takes a few minutes.&lt;/p&gt;

&lt;p&gt;The resize step looks like a footnote but it isn't. Stable Image Core returns full-resolution PNGs — much larger than the 256×256 the reel layout expects. At native size the images overflow their containers and the game renders incorrectly. No model parameter controls the output dimensions. It's a plain post-download resize call in Python, on every image.&lt;/p&gt;

&lt;p&gt;The fallback is deliberate. Image generation calls fail: timeouts, model errors, prompts that get rejected. A single failed icon shouldn't crash a thirty-symbol run. A single broken icon means a known gap you can regenerate later. So the agent catches per-call failures, logs them, substitutes a placeholder image, and keeps going. The build finishes with a gap rather than a crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it broke
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consistency across a set is the real problem.&lt;/strong&gt; Each call to the model is independent. It has no memory of the previous call. Generate thirty icons in a loop and you get thirty independent draws from the same distribution — which means thirty different art styles.&lt;/p&gt;

&lt;p&gt;The scarab might come back painterly and textured. The ankh lands flat and geometric. The pyramid looks like concept art from a different project. All technically correct against their prompts. None of them obviously from the same game.&lt;/p&gt;

&lt;p&gt;You can reduce this with prompt engineering. I added a style string to every call — "pixel art, dark background, warm palette, consistent line weight" — and it narrows the range. Keeping symbol counts lower helps. But I didn't solve it. Icon visual quality stayed the main limitation of the factory. One good icon is a solved problem. A coherent set of thirty isn't, not with independent calls to a generative model.&lt;/p&gt;

&lt;p&gt;The games ship looking like themed games. They don't look like art-directed games. That's the honest description of the ceiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The path failure was invisible.&lt;/strong&gt; Generated images land in &lt;code&gt;public/images&lt;/code&gt;. The image agents write them correctly. But when deployed under a non-root path, referencing the background in CSS with a plain &lt;code&gt;url('/images/background.png')&lt;/code&gt; silently fails — the browser doesn't find the file. No error, no 404 in the console; the CSS rule parses fine, the image just doesn't appear.&lt;/p&gt;

&lt;p&gt;The correct way is a JavaScript inline style using &lt;code&gt;process.env.PUBLIC_URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;backgroundImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`url(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/images/background.png)`&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That resolves correctly across deployment paths. The fix is a one-liner. Finding it cost more time than that, because nothing in the output told me where to look. Technically a Builder problem — but discovered while debugging why generated images weren't showing up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The size mismatch was boring but necessary.&lt;/strong&gt; Images downloaded fine, the folder looked right, and the game was broken. I had to open the game in a browser to find out something was wrong, then trace back to the image dimensions. The resize step was ten minutes of work; discovering it needed to exist took longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Text-to-image models are strong at generating individual images. They're weak at consistency across a batch. That isn't a limitation of any specific model — it's what you get when each call samples independently. If you're generating a set of related images, plan for this before you start.&lt;/p&gt;

&lt;p&gt;The options: put a strong, specific style string in every prompt and repeat it verbatim; use a fixed seed if the model supports it; generate one reference image first and use img2img for the rest; or set an explicit acceptance that the set will have variance and decide early what "good enough" looks like. Don't let the consistency problem surface when you're looking at thirty finished icons that don't cohere.&lt;/p&gt;

&lt;p&gt;Own the boring post-processing in plain code. Resize, path wiring, fallback handling — none of that belongs in a prompt or a model setting. The model's job is to produce a good image. Your code's job is to put it in the right place at the right size and handle the call that fails. Keep those two responsibilities separate and both stay simple.&lt;/p&gt;

&lt;p&gt;The fallback pattern generalizes beyond image generation: in any pipeline loop over independent, replaceable items, a per-item failure is recoverable. Catch it at the item level, substitute something visible (not silent), and let the build finish. A gap you can fill later is better than a crash you have to start over from — as long as QA can see the gaps.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/the-designer-agent-a-spec-from-a-conversation-4nba"&gt;the Designer agent&lt;/a&gt;. Next: the Builder agent.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>aws</category>
      <category>bedrock</category>
    </item>
    <item>
      <title>The Designer agent — a spec from a conversation</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:24:11 +0000</pubDate>
      <link>https://dev.to/esunitha/the-designer-agent-a-spec-from-a-conversation-4nba</link>
      <guid>https://dev.to/esunitha/the-designer-agent-a-spec-from-a-conversation-4nba</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 3 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The only agent that talks back
&lt;/h2&gt;

&lt;p&gt;Every agent in this pipeline reads files and writes files. The Designer is the exception. You describe a theme — "Norse mythology slots" — and it asks you things. What's the tone: whimsical or dark? Should the high-value symbols be characters or objects? Are there specific win phrases that should feel right for the setting? It consults the original casino's design constraints, checks a worked example, and runs back and forth with you until it has enough to produce a single JSON file.&lt;/p&gt;

&lt;p&gt;That file is the contract. It doesn't deploy anything. It doesn't write any code. It describes the game — symbols with weights and prompts, a color palette, winning patterns, UI strings, fonts, sounds, a background prompt — and every later stage reads it. Image-Gen, Background-Gen, Builder, Tester, Deployer: they all start from that file.&lt;/p&gt;

&lt;p&gt;That sounds like a narrow job. It turned out to be the most consequential one in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent is
&lt;/h2&gt;

&lt;p&gt;The Designer uses the Bedrock Converse API, the conversational format where you maintain a message history and pass it back on every turn. No framework. A Python class, the API, and three tools.&lt;/p&gt;

&lt;p&gt;The tools define the job more precisely than the system prompt does.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate_game_spec&lt;/code&gt; is the terminal tool. When the model calls it, the conversation ends and the spec is ready for human approval. Its input schema is the contract: &lt;code&gt;theme_id&lt;/code&gt;, &lt;code&gt;theme_name&lt;/code&gt;, &lt;code&gt;symbols&lt;/code&gt; (each with a name, a weight from 1 to 10, a category, and an &lt;code&gt;icon_prompt&lt;/code&gt;), &lt;code&gt;color_palette&lt;/code&gt;, &lt;code&gt;win_conditions&lt;/code&gt;, &lt;code&gt;patterns&lt;/code&gt;, &lt;code&gt;ui_strings&lt;/code&gt;, &lt;code&gt;fonts&lt;/code&gt;, &lt;code&gt;sound_theme&lt;/code&gt;, &lt;code&gt;background_prompt&lt;/code&gt;. I defined that schema once and every downstream agent reads against it.&lt;/p&gt;

&lt;p&gt;The other two tools are read-only. &lt;code&gt;read_blueprint&lt;/code&gt; lets the model inspect the original casino's design constraints without those constraints being inlined in the system prompt. &lt;code&gt;read_example_spec&lt;/code&gt; returns a worked example so the model can see what a correct, complete spec looks like. Both tools exist so the model can look things up on demand. Without them, the system prompt would need to carry several thousand words of context on every single turn — expensive, and stale the moment anything in the casino changes.&lt;/p&gt;

&lt;p&gt;The conversation follows a short arc. You describe a theme. The model asks clarifying questions for anything underspecified. It reads the blueprint and example as needed. When it has enough, it calls &lt;code&gt;generate_game_spec&lt;/code&gt;. The proposed spec appears, you read it, and you approve, reject, or ask for changes. Approved, it goes to disk as &lt;code&gt;specs/&amp;lt;theme_id&amp;gt;-spec.json&lt;/code&gt;. Everything else reads from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the spec is actually doing
&lt;/h2&gt;

&lt;p&gt;The file is a few kilobytes of JSON. It doesn't feel load-bearing until you watch what depends on it.&lt;/p&gt;

&lt;p&gt;Image-Gen reads &lt;code&gt;symbols[].icon_prompt&lt;/code&gt; to generate each reel icon. If those prompts are vague — "a cool wizard thing" instead of "an ornate golden wizard hat against a dark purple sky, fantasy illustration style" — the icons come out generic. Builder reads &lt;code&gt;color_palette&lt;/code&gt; and patches the casino's CSS variables. It reads &lt;code&gt;ui_strings&lt;/code&gt; to replace the displayed text. It reads &lt;code&gt;patterns&lt;/code&gt; to configure which symbol combinations win. Deployer reads &lt;code&gt;theme_id&lt;/code&gt; to name the stack. The whole pipeline is downstream of whatever the Designer committed to disk.&lt;/p&gt;

&lt;p&gt;A bad spec is quiet for a long time. The Designer doesn't know whether the prompts it wrote will produce good icons. Builder doesn't know whether the colors will look right in the browser. Tester is the first automated stage that catches it — and by then you're four stages in.&lt;/p&gt;

&lt;p&gt;I reduced the risk slightly by making the example spec a tool result rather than a fixed block in the system prompt. The model can compare its proposed output against a well-formed reference before calling &lt;code&gt;generate_game_spec&lt;/code&gt;. That helped. It didn't eliminate the problem. Reviewing the spec carefully before approving it — actually reading each symbol prompt, not just skimming the palette — is the single most valuable thing you can do in the whole pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;Three things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The recursion problem.&lt;/strong&gt; The Designer was the first agent I wrote, and I wrote it with a recursive response handler. When the model calls a tool, &lt;code&gt;_process_response&lt;/code&gt; feeds the result back by calling itself:&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;# Original Designer: recurse on every tool call
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_process_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&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;tool_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generate_game_spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool_input&lt;/span&gt;          &lt;span class="c1"&gt;# done
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;append_tool_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_process_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# recurse
&lt;/span&gt;
&lt;span class="c1"&gt;# All later agents: flat while loop
&lt;/span&gt;&lt;span class="k"&gt;while&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&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;stop_reason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_turn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="c1"&gt;# dispatch tool, append result, keep going
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursion only goes a few levels deep in practice — the model rarely chains more than two or three tool calls in one turn. It never actually crashed. But it made the code harder to reason about, harder to log each step, and harder to interrupt cleanly. Builder, Tester, and Deployer all use the flat &lt;code&gt;while&lt;/code&gt; loop because it's obvious where the iteration happens. The Designer is the one I wrote first and never went back to fix.&lt;/p&gt;

&lt;p&gt;The first agent you build teaches you the shape. You just have to notice the lesson before you ship the next five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garbage in, garbage out — delayed.&lt;/strong&gt; The spec is the contract, so the weakest spec produces the weakest game. A symbol with &lt;code&gt;"icon_prompt": "mystical thing"&lt;/code&gt; gets an icon that looks like clip art. A win condition with no context gets an awkward label in the UI. Neither of those failures shows up until three or four stages later, and the root is back in the spec file. There is no automated check between "Designer approved" and "game looks bad" because the intermediate artifacts — icon files, styled code — are all valid even when the prompts that generated them were weak. The only mitigation I found was discipline at the approval gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The approval gate confused the web UI.&lt;/strong&gt; The pipeline originally ran in the terminal. Human-in-the-loop meant &lt;code&gt;input()&lt;/code&gt;. When I later put it behind a web UI, every Designer turn became a form in the browser — and the Designer's loop has two distinct kinds of turns: conversational turns where the model is asking a clarifying question, and approval turns where it has produced a spec. In the terminal, context makes these obvious. In the web UI, both appeared as the same text box, and users kept trying to approve a question as if it were a finished spec. I had to go back and tag each turn type explicitly — &lt;code&gt;CONVERSATION&lt;/code&gt; vs &lt;code&gt;APPROVAL&lt;/code&gt; — so the UI could render them differently and route the response correctly. The lesson: "a human in the loop" has a shape, and &lt;code&gt;input()&lt;/code&gt; hides that shape until you try to put it somewhere real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Two things, both concrete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the boundary artifact explicit and inspectable.&lt;/strong&gt; The spec file is not a convenience — it's the reason failures are diagnosable. When a game comes out wrong, I open the spec and the problem is usually visible. If the Designer had handed off implicitly — passing state in memory, writing values into a database you'd need tooling to query — inspection becomes a project instead of a glance. A flat file on disk is not sophisticated, and that's the point. When each stage's output is a file, you can look at it, diff it, and hand it to someone else without explaining an API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a flat loop for anything conversational.&lt;/strong&gt; Recursion reads naturally when you're writing it. A &lt;code&gt;while&lt;/code&gt; loop is easier to reason about, has no depth limit, is easier to add logging to, and is easier to interrupt cleanly. I reached for the while loop on every agent after the first. Go back and apply the lesson to the first one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/how-a-single-game-evolved-into-a-game-factory-5763"&gt;the factory overview&lt;/a&gt;. Next: the image agents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>aws</category>
      <category>bedrock</category>
    </item>
    <item>
      <title>How a single game evolved into a game factory</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:09:50 +0000</pubDate>
      <link>https://dev.to/esunitha/how-a-single-game-evolved-into-a-game-factory-5763</link>
      <guid>https://dev.to/esunitha/how-a-single-game-evolved-into-a-game-factory-5763</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 2 of 8 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One sentence in, one game out
&lt;/h2&gt;

&lt;p&gt;The game factory is a pipeline of agents that turns a one-sentence theme into a deployed, playable slot machine. I type "ancient Egypt treasure slots," approve a few decisions along the way, and some minutes later there is a themed game on a URL: golden scarab reels, a papyrus background, win messages about pharaohs, a working leaderboard. I didn't draw an icon, write a line of React, or touch a console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/esunitha/the-slot-machine-that-turned-into-an-agentic-game-factory-45i4"&gt;Post 1&lt;/a&gt; was the single slot machine I built by hand to learn the iGaming domain. This post is how that one game became a line that produces variants of it on demand — what the six agents are, how they hand off, and where a human still stands in the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a line of agents, not one
&lt;/h2&gt;

&lt;p&gt;The obvious version is a single clever agent: "here's a theme, here's the codebase, make me a game." I didn't build that, and the reason matters.&lt;/p&gt;

&lt;p&gt;A slot machine variant isn't one task. It's a spec decision, then image generation, then code changes, then a browser test, then a deploy — each with different tools, different failure modes, and different places a human wants to look before continuing. A single agent doing all of it holds too much in its head, fails in ways that are hard to localize, and gives you nothing to inspect between steps. So I split it into a pipeline, where each stage is a small agent with one job and a clean output the next stage can consume.&lt;/p&gt;

&lt;p&gt;An agent here is nothing fancy: a model in a loop, given a set of tools and a condition that ends the loop. Call the model, let it call a tool, feed the result back, repeat until it signals done. No framework — just the Bedrock API and Python, on purpose, so I understood every part. The interesting engineering isn't in the loop. It's in scoping each agent's job so the loop actually terminates well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six stages
&lt;/h2&gt;

&lt;p&gt;The pipeline runs in order. Each stage reads what came before and writes something the next stage needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                one sentence in
                │
                ▼
        ┌────────────────┐
        │ Designer       │ ──▶ the spec (a JSON contract)
        └────────────────┘
                │
                ▼
        ┌────────────────┐
        │ Image-Gen      │ ──▶ symbol icons
        └────────────────┘
                │
                ▼
        ┌────────────────┐
        │ Background-Gen │ ──▶ the page background
        └────────────────┘
                │
                ▼
        ┌────────────────┐
        │ Builder        │ ──▶ the themed React code
        └────────────────┘
                │
                ▼
        ┌────────────────┐
        │ Tester         │ ──▶ a pass / fail QA report
        └────────────────┘
                │
                ▼
        ┌────────────────┐
        │ Deployer       │ ──▶ a live URL
        └────────────────┘
                │
                ▼
        a deployed, playable game
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Designer.&lt;/strong&gt; You describe a theme; the Designer asks questions and produces a spec — a single JSON file that every later stage reads. Symbols and their weights, a color palette, winning patterns, UI strings, fonts. It's the contract for the whole build:&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;"theme_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ancient-egypt-slots"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbols"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scarab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scarab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;"color_palette"&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;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#C8A24B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#1A0F0A"&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;"win_names"&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;"three_of_a_kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PHARAOH'S FORTUNE"&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;2. Image-Gen.&lt;/strong&gt; For each symbol in the spec, it generates an icon from a prompt, then resizes it to the size the reels expect. The reels stop being cloud-service logos and start being scarabs and ankhs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Background-Gen.&lt;/strong&gt; The same idea, once, for the page background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Builder.&lt;/strong&gt; The one that turns the casino's React code into &lt;em&gt;this theme's&lt;/em&gt; code. This is the code-modification agent, and it's the one that fought me hardest — enough that it gets its own post and a redesign story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Tester.&lt;/strong&gt; It runs the built game in a real browser with Playwright, takes screenshots at each step, and a vision model judges whether the game actually plays — do the reels spin, does a win display correctly, does the UI look right. Automated QA that judges by looking, not by asserting on the DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Deployer.&lt;/strong&gt; It builds the app and deploys it to AWS — the frontend to CloudFront, wired to a serverless backend — and it refuses to run unless the Tester's report says the game passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the human stands
&lt;/h2&gt;

&lt;p&gt;The line isn't fully autonomous, deliberately. Between stages there are approval gates: the Designer proposes a spec and waits for a yes; the Builder proposes a plan before it changes code; the Deployer confirms before it ships. The pattern is &lt;em&gt;agent proposes, human approves&lt;/em&gt;. It keeps a person in the loop at exactly the points where a wrong decision is expensive to undo, without making them do the work.&lt;/p&gt;

&lt;p&gt;The handoffs are boring on purpose. The spec is a file on disk. The icons and background are files on disk. The test result is a file on disk. Each agent reads the artifacts of the ones before it. Boring handoffs are inspectable handoffs — when a build looks wrong, I can open the spec and the images and see exactly what the next stage was handed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If you're building something that turns a prompt into a real artifact, the shape worth copying isn't "one agent that does everything." It's a &lt;strong&gt;pipeline of narrow agents&lt;/strong&gt;: each with a single job, a plain-file output the next stage reads, and a human gate wherever a mistake is costly. It localizes failure — when a game comes out wrong, I know &lt;em&gt;which&lt;/em&gt; stage to blame, because I can see what it produced. And it lets you replace or harden one stage without touching the others, which is the whole reason the Builder could get torn down and rebuilt later without disturbing the five agents around it.&lt;/p&gt;

&lt;p&gt;Each stage gets its own post next, in order, same three questions each time: what it is, what it did, and what went wrong.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/esunitha/the-slot-machine-that-turned-into-an-agentic-game-factory-45i4"&gt;the slot machine that became a factory&lt;/a&gt;. Next: the Designer agent.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>The slot machine that turned into an agentic game factory</title>
      <dc:creator>Sunitha Eswaraiah</dc:creator>
      <pubDate>Thu, 09 Jul 2026 12:39:00 +0000</pubDate>
      <link>https://dev.to/esunitha/the-slot-machine-that-turned-into-an-agentic-game-factory-45i4</link>
      <guid>https://dev.to/esunitha/the-slot-machine-that-turned-into-an-agentic-game-factory-45i4</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 1 of 7 in the game-factory series.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;There is a slot machine I built and deployed to AWS, and it looks, at first glance, like a novelty. The reels are filled with cloud service icons instead of cherries and sevens, a leaderboard tracks players in real time, and every so often a small quantum badge lights up in the corner of the screen for reasons that aren't obvious until you look under the hood. It has a tournament mode for live events, a hosted "big screen" leaderboard view for a projector, and a real-time risk gauge that most people don't expect to see on a slot machine at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvkjdd0vh0o644ebhmvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvkjdd0vh0o644ebhmvr.png" alt="AWS Slots — reels, responsible gaming monitor, and live leaderboard" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;AWS Slots — reels, responsible gaming monitor, and live leaderboard
 &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Underneath that surface, it behaves like a real one. Random number generation with a defined design, return-to-player math, session tracking, bet escalation detection — all of the mechanics that make a slot machine a slot machine, implemented the way an operator would implement them. What it is not, deliberately, is gambling. There is no real money anywhere in the system: no deposit path, no cash-out, nothing a player can lose beyond a number on a screen. The cloud icons are the tell. Nobody wagers actual currency for the chance to line up three Lambdas.&lt;/p&gt;

&lt;p&gt;That distinction — a slot machine built with the seriousness of a real product, but without the thing that would make it one — is the reason this project exists, and it's where the story actually starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I've worked with several clients in the iGaming space, and I came into that work knowing almost nothing about the industry itself. I had no real opinions about &lt;a href="https://en.wikipedia.org/wiki/Return_to_player" rel="noopener noreferrer"&gt;return-to-player&lt;/a&gt; percentages (the share of wagered money a machine pays back over time), volatility curves, or how a jurisdiction certifies a &lt;a href="https://en.wikipedia.org/wiki/Random_number_generation" rel="noopener noreferrer"&gt;random number generator&lt;/a&gt;. I was walking into a domain with its own vocabulary, its own regulators, and its own math, and I needed to close that gap quickly.&lt;/p&gt;

&lt;p&gt;Reading through documentation and sitting in on product demos told me what the industry talks about, but it didn't tell me how any of it actually works. That gap is where I usually end up building something — I learn a domain by trying to reproduce its hardest problems in code, not by reading about them. So I set out to build a slot machine that behaved like a real one: a random number generator designed the way a regulator would expect, &lt;a href="https://en.wikipedia.org/wiki/Responsible_gaming" rel="noopener noreferrer"&gt;responsible gaming&lt;/a&gt; features (tools and detection systems meant to catch harmful play before it escalates) implemented the way an operator is required to implement them, and session tracking structured the way a behavioral analytics team would actually query it. If I could get all of that working end to end, I would understand the industry from the inside rather than from a deck.&lt;/p&gt;

&lt;p&gt;The AWS icons weren't just theming — they were the mechanic. Winning patterns in the game are architecture patterns: land two matching services and you score, land three of a kind and you score more, and the jackpot is landing a coherent AWS architecture across the reels. The symbols had to mean something for that to work, and cloud services were the natural fit. It also meant the game could teach a little AWS architecture on the side, almost as a byproduct of playing it.&lt;/p&gt;

&lt;p&gt;Once I started, the project kept growing on its own logic. Learning about rule-based responsible gaming detection led me to want to try the ML-based version. Basic session tracking turned into a full analytics pipeline once I understood why operators care about it. A mentor of mine even suggested quantum-seeded randomness and then built the service himself, which gave me a real quantum RNG to integrate into a live spin path. What began as "understand how slot machines work" became "build the smallest complete iGaming platform I can, and treat every layer of it as if it had to survive a real audit."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the game does
&lt;/h2&gt;

&lt;p&gt;Strip away the infrastructure and the game itself is simple to describe: you spin, you bet, you try to land a pattern.&lt;/p&gt;

&lt;p&gt;The basic win is matching symbols — two of a kind pays a little, three of a kind pays more. The interesting win is the jackpot, and it isn't a random combination. It's a real AWS architecture. Land S3 feeding Lambda feeding DynamoDB across the reels, for instance, and you've built something that actually deploys. The game keeps a small library of valid patterns like this, and hitting one is worth more than any three-of-a-kind. That was the whole point of choosing cloud services as symbols in the first place — the jackpot teaches the shape of a real system as a side effect of chasing it.&lt;/p&gt;

&lt;p&gt;Sitting next to the reels is a live risk gauge, and this is the part that surprises people who've never thought about what's actually required of a gambling product. Every spin gets checked against a set of behavioral rules — are you spinning faster than a person normally would, are your bets climbing after a loss, have you been at this for half an hour straight. On top of that rule-based layer, a machine learning model periodically reassesses your session and produces a risk score, and the gauge moves accordingly. Because there's no real money in this version, nothing actually stops you when the gauge goes red — but the mechanism is the same one a real operator would use to intervene, and building it taught me more about responsible gaming than any article could have.&lt;/p&gt;

&lt;p&gt;The badge in the corner is the quantum tell. Most spins draw their randomness from a standard, well-trusted source. Some spins, when a separate service is healthy and responsive, draw it from an actual quantum circuit instead — the service my mentor built, seeding the randomness from &lt;a href="https://aws.amazon.com/braket/" rel="noopener noreferrer"&gt;Amazon Braket&lt;/a&gt; (AWS's managed quantum computing service). I integrated it into the game as an experiment in whether quantum-sourced entropy could serve a live spin without anyone noticing the difference except for the badge itself.&lt;/p&gt;

&lt;p&gt;And the whole thing isn't limited to solo play. Event mode turns it into something you can run in a room: players join with a QR code, a shared leaderboard updates live on a projector, and a tournament structure lets you run timed competitive rounds rather than open-ended play. That mode is what made the game night possible in the first place.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(A full technical writeup — Lambda counts, RNG seeding mechanics, security layers, analytics pipeline — is in the appendix at the end of this post, for anyone who wants the implementation detail.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;I ran the first version at an office game night. Booked a room, put the leaderboard up on a screen, and let people join by scanning a QR code so nobody had to type anything to get in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwii0bs2p9lxc68gj897r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwii0bs2p9lxc68gj897r.jpg" alt="Demoing the tournament mode to a small group, live leaderboard on screen" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Demoing the tournament mode to a small group, live leaderboard on screen
 &lt;p&gt;&lt;/p&gt;

&lt;p&gt;It landed better than I expected. A small crowd gathered around the screen — people watching the reels spin, waiting to see if the badge would light up, watching the leaderboard reshuffle as new players joined and started competing for the top spot. The room reacted to it the way a room reacts to an actual game, which told me the work had crossed some threshold from a personal exercise into something that functioned as a real product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnr0au1p1nf7s9exepzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnr0au1p1nf7s9exepzi.png" alt="The tournament's live leaderboard projected during a later game night" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;The tournament's live leaderboard projected during a later game night
 &lt;p&gt;&lt;/p&gt;

&lt;p&gt;The tournament mode scaled past that first small room, too. I ran a later event with a full leaderboard projected for a much bigger group, timed rounds, and a proper prize structure at the end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Far4hw9aumzx0t0rycnyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Far4hw9aumzx0t0rycnyb.png" alt="Presenting final tournament results and prize winners" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Presenting final tournament results and prize winners
 &lt;p&gt;&lt;/p&gt;

&lt;p&gt;The game is still running today. Real production deployment, a real Cognito user pool, real DynamoDB tables that have held up under actual use. The player count is modest, but every one of them has a genuine account and a genuine leaderboard entry, and the system hasn't needed to be rebuilt to support any of it.&lt;/p&gt;

&lt;p&gt;More importantly, for the reason I started, the learning stuck. I can reason about RNG design, responsible gaming obligations, session tracking, behavioral analytics, and tournament operations from having built each of those pieces myself, not from having read about them. When I'm in a conversation with someone who actually knows this industry, I understand what they mean.&lt;/p&gt;

&lt;p&gt;And there was one more thing I noticed, standing at that first game night watching the reels spin — something that felt obvious the moment I saw it, but hadn't occurred to me at all before then.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came next
&lt;/h2&gt;

&lt;p&gt;Here's the realization, in one sentence: almost everything that made this a &lt;em&gt;slot machine&lt;/em&gt; was infrastructure, and almost nothing that made it &lt;em&gt;this&lt;/em&gt; slot machine was.&lt;/p&gt;

&lt;p&gt;The thirty-four functions, the machine learning, the quantum RNG, the six layers of security, the analytics pipeline — months of work, and none of it specific to this particular game. What was specific to this game was small by comparison: a set of cloud icons that could have been any set of images, a handful of hex codes for the palette, win patterns in a database table, sound files in a folder.&lt;/p&gt;

&lt;p&gt;Which raised an obvious question: if the infrastructure stayed exactly as it was and only the theme changed, could I produce an entirely different slot machine — wizards and wands instead of cloud icons, or iron thrones, or football clubs — without touching any of the hard parts again?&lt;/p&gt;

&lt;p&gt;Doing that by hand would just be a customization service, not a factory, and it wouldn't have taught me anything new. The version I actually wanted to build was one where the variation itself was automatic — where I could describe a theme in a single sentence and have something else produce the specification, generate the artwork, patch the code, test the result, and deploy it, with no manual editing anywhere in the loop.&lt;/p&gt;

&lt;p&gt;That project became game-factory: a pipeline of six agents — Designer, Image-Gen, Background-Gen, Builder, Tester, and Deployer — that takes a theme prompt and turns it into a themed clone of this casino, deployed to its own address. It works. Two themed variants are live as of this writing. Getting there took a lot of wrong turns, and those wrong turns turned out to be the more interesting story.&lt;/p&gt;

&lt;p&gt;The next six posts in this series are about those wrong turns and the patterns that came out of fixing them. If you're building agents that modify code, generate assets, or check their own work, some of what I learned along the way should transfer directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: technical implementation
&lt;/h2&gt;

&lt;p&gt;The section above stays at the level a player or a curious reader needs. This is the level an engineer would want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tp64kul8caoxqm1bgvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tp64kul8caoxqm1bgvb.png" alt="Presenting final tournament results and prize winners" width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;AWS Slots Casino — solution architecture
 &lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend.&lt;/strong&gt; A React application, hosted on S3 behind CloudFront. Several distinct modes in the same codebase: solo play, tournament mode with a warmup phase and timed rounds, an "event" mode for hosting live sessions, an admin dashboard for credit resets and pattern approvals, and a big-screen leaderboard view for a projector. Fifty-plus AWS service icons make up the symbol pool. Three ways to win — two of a kind, three of a kind, or a valid AWS architecture across the reels for the jackpot — each with its own payout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend.&lt;/strong&gt; Thirty-four AWS Lambda functions written in Python, organized by responsibility: authentication (registration, login, verification, password recovery), game logic (spins, credit resets, leaderboard), machine learning (behavioral event tracking, risk score inference), events and tournaments (creation, joining, spinning within an event, ending a round), and admin (analytics, feedback, pattern approval). Amazon API Gateway sits in front of all of it, with a WAF web ACL applying &lt;a href="https://owasp.org/www-project-top-ten/" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt; rule sets (the industry-standard list of common web application vulnerabilities), IP reputation filtering, and rate limiting, plus its own throttling — 30 req/s sustained, 100 burst. &lt;a href="https://aws.amazon.com/cognito/" rel="noopener noreferrer"&gt;Amazon Cognito&lt;/a&gt; (AWS's managed user identity service) handles identity, tokens, and password recovery. DynamoDB holds players, sessions, behavioral events, tournaments, event participants, icon metadata, and winning patterns, all as configurable data rather than anything hardcoded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RNG design.&lt;/strong&gt; In production, the spin function calls Python's &lt;code&gt;secrets.randbelow&lt;/code&gt;, drawing from the OS's cryptographically secure entropy pool — sufficient for a real product. A second path exists for experimentation, running the quantum service my mentor built. That service runs on ECS Fargate in Rust, and on startup submits 32-qubit circuits to Amazon Braket, using either the SV1 simulator or IonQ hardware when it's available. Each qubit gets a Hadamard gate and then a measurement, and each measurement collapses into a zero or a one — genuine quantum randomness. Those bits seed a &lt;a href="https://datatracker.ietf.org/doc/html/rfc8439" rel="noopener noreferrer"&gt;ChaCha20&lt;/a&gt; stream cipher (a fast, well-vetted algorithm for expanding a small amount of true randomness into a much larger stream of it) that fills a 100MB entropy pool, enough for roughly 12.5M spins, with a background monitor topping it off whenever it drops below 20%. The spin function calls this service with a 500ms timeout and falls back transparently to the standard path if anything is slow or unhealthy. A field in the response, &lt;code&gt;entropySource&lt;/code&gt;, tells the frontend which path served the spin — that's what lights up the quantum badge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsible gaming.&lt;/strong&gt; Two layers running side by side. Rule-based, on every spin: evaluates against six patterns — spins under 6s apart, bets spiking above 5x the running average, 3x bet increases immediately after a loss, credits dropping below 10, sessions past 30 minutes, and long streaks of rapid consecutive spins. Any of those firing writes an event to a behavioral table in DynamoDB. Model-based, every 10 spins: a function pulls the last 30 days of a player's session data, computes 26 behavioral features across three groups — temporal (session duration, frequency), financial (bet sizing, credit depletion), and behavioral (impulsivity, loss-chasing) — and sends them to an &lt;a href="https://xgboost.readthedocs.io/" rel="noopener noreferrer"&gt;XGBoost&lt;/a&gt; model (a widely used, tree-based machine learning algorithm) hosted on &lt;a href="https://aws.amazon.com/sagemaker/" rel="noopener noreferrer"&gt;Amazon SageMaker&lt;/a&gt; (AWS's managed machine learning platform), which returns a risk score from zero to a hundred driving the live gauge, with four levels from low to critical. There's no real money in the system, so the gauge is a demonstration rather than an intervention — in a real product, high or critical scores would trigger deposit limits or a referral to support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security.&lt;/strong&gt; A defense-in-depth model across six layers, arranged so that if one fails, the next one is still standing. Edge: CloudFront and a WAF handling OWASP rules, IP reputation, and HTTPS enforcement, backed by AWS Shield Standard. Network: API Gateway throttling and CORS restricted to the production domain. Identity: Cognito verifying &lt;a href="https://jwt.io/introduction" rel="noopener noreferrer"&gt;JWTs&lt;/a&gt; (signed tokens used to prove a logged-in user's identity on every request) against its JWKS endpoint, brute-force lockout, short-lived access tokens. Application: every credit and win calculation runs server-side, admin endpoints check the caller's JWT against an allowlist, players can only ever read their own data. Data: encryption at rest, per-function IAM roles scoped to exactly the tables each one needs, public access blocked on every S3 bucket. Detection: CloudWatch alarms, X-Ray tracing on every Lambda, CloudFront access logs flowing into S3. None of it arrived at once — each layer got added as the domain's actual threat model became clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics.&lt;/strong&gt; The behavioral events table is the source of truth. Every night at 2AM UTC, a scheduled export job scans the table and writes the day's events into S3 as Parquet, where Athena queries it with SQL and QuickSight turns it into dashboards — player behavior over time, risk score distributions, tournament participation, session duration trends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tournament operations.&lt;/strong&gt; Before a live event, a warmup script provisions 10 instances of Lambda concurrency on the event-spin function so cold starts don't interrupt play, then tears the concurrency down afterward so the cost disappears with it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://REPLACE-WITH-POST-URL/02-what-is-an-agent" rel="noopener noreferrer"&gt;What is an agent, actually?&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Also useful: &lt;a href="https://REPLACE-WITH-POST-URL/07-scoping-cheat-sheet" rel="noopener noreferrer"&gt;Agent scoping — the cheat sheet&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>igaming</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
