<?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: Built By AI Lab</title>
    <description>The latest articles on DEV Community by Built By AI Lab (@builtbyailab).</description>
    <link>https://dev.to/builtbyailab</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%2F4155432%2F4186b60a-1532-4edf-b6db-e63440fc4574.jpg</url>
      <title>DEV Community: Built By AI Lab</title>
      <link>https://dev.to/builtbyailab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/builtbyailab"/>
    <language>en</language>
    <item>
      <title>A deleted customer came back with the next import</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:30:48 +0000</pubDate>
      <link>https://dev.to/builtbyailab/a-deleted-customer-came-back-with-the-next-import-2kjm</link>
      <guid>https://dev.to/builtbyailab/a-deleted-customer-came-back-with-the-next-import-2kjm</guid>
      <description>&lt;p&gt;The app has a button that deletes everything about a customer, because someone can ask for that, and one that exports their data. I tested both buttons. The customer disappeared from the list and the export produced a file, and I considered the job done.&lt;/p&gt;

&lt;p&gt;The review followed a path I had not. It read the import code after the deletion code and saw that the import never asked whether anyone had been removed. The salon's booking software still has the customer, the salon exports a file every month, and the import would create her again with her name and her phone number. From the owner's side it looks like a deleted customer reappearing a few weeks later.&lt;/p&gt;

&lt;p&gt;The fix needs a memory of who asked to be removed, and the memory must hold as little as possible. When a customer is deleted, or says STOP, the app stores a fingerprint of her phone number in an exclusion list, and nothing else. The import checks every row against that list and skips the ones that match. Because the number is normalized to the same international format before the fingerprint is taken, it makes no difference whether the file writes it with a space, a dash or a +39.&lt;/p&gt;

&lt;p&gt;A second problem appeared once that worked. A plain hash of an Italian phone number is easy to rebuild: there are not that many of them, and anyone with the list could try them all. The app can now mix a secret into the fingerprint, a value set on the server that is not stored with the data. Until that secret is configured it falls back to the plain hash, and I have noted that as a task to finish.&lt;/p&gt;

&lt;p&gt;Retention works in the background as well. Customer records are anonymized 24 months after the last appointment, message texts are cleared after 12 months and the technical log after another 12, and the owner can change those periods within limits. A cron job runs it every night.&lt;/p&gt;

&lt;p&gt;The lesson is that a right has to be tested through the whole cycle: delete, then import again, then look. A working button is only half of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: drafted with a local AI model, then reviewed and approved by me. Originally published at &lt;a href="https://bbal-blog.kairo-app.workers.dev/a-deleted-customer-came-back-with-the-next-import/" rel="noopener noreferrer"&gt;https://bbal-blog.kairo-app.workers.dev/a-deleted-customer-came-back-with-the-next-import/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Six review rounds for one small app</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:26:07 +0000</pubDate>
      <link>https://dev.to/builtbyailab/six-review-rounds-for-one-small-app-34c5</link>
      <guid>https://dev.to/builtbyailab/six-review-rounds-for-one-small-app-34c5</guid>
      <description>&lt;p&gt;The first review came back with 29 problems in the code and 13 legal or privacy risks. I had built the app myself, with AI agents doing most of the typing, and I thought it was close to finished.&lt;/p&gt;

&lt;p&gt;The app is called SlotSaver. When a customer cancels an appointment at a dog grooming salon, it looks for other customers who agreed to receive messages and prepares a WhatsApp text. The owner sends it with one tap. It runs on Cloudflare's free plan, so hosting costs nothing. Before a real salon touched it, I wanted a reader who had not written the code to try to break it. I used an AI reviewer that is separate from the agent that built the app, with read-only access: it can read the code and test the live site, and it cannot edit files or deploy anything.&lt;/p&gt;

&lt;p&gt;The first round found the serious things. The security policy I had added told the browser to refuse inline scripts, and SvelteKit starts every page with one, so the pages appeared but never came alive. The send button took the phone number and the message text from the form and trusted both. Logins could fail with a server error, because my password hashing asked for more iterations than the platform allows.&lt;/p&gt;

&lt;p&gt;The second round found that the Exit button did not log anyone out. I had renamed the session cookie after the first round, and the logout code still deleted the old name. Every test I had passed.&lt;/p&gt;

&lt;p&gt;The third found a customer I had deleted for privacy reasons coming back with the next Excel import. The fourth found that a slot at 10:00 showed up in the message as 09:00.&lt;/p&gt;

&lt;p&gt;I gave the reviewer one standing instruction: read the earlier reports to see what is claimed as fixed, then check the code and the live site yourself. That instruction paid off. One of my own reports said a webhook now verified signatures. The verification function existed and nothing called it. The reviewer sent an unsigned request and got a success answer.&lt;/p&gt;

&lt;p&gt;Between rounds I fixed what it found, and the automated tests went from 34 to 105. After the sixth round a separate session ran an acceptance test on fake data, 61 scenarios, which I'll write about in another post.&lt;/p&gt;

&lt;p&gt;What I took from it is that the person who writes a fix should not be the one who declares it done. A reader with read-only access and permission to be suspicious sees things I no longer see, because I wrote them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: drafted with a local AI model, then reviewed and approved by me. Originally published at &lt;a href="https://bbal-blog.kairo-app.workers.dev/six-review-rounds-for-one-small-app/" rel="noopener noreferrer"&gt;https://bbal-blog.kairo-app.workers.dev/six-review-rounds-for-one-small-app/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Checking a Cloned Voice Word by Word</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:13:12 +0000</pubDate>
      <link>https://dev.to/builtbyailab/checking-a-cloned-voice-word-by-word-1lph</link>
      <guid>https://dev.to/builtbyailab/checking-a-cloned-voice-word-by-word-1lph</guid>
      <description>&lt;p&gt;The narrator of one of my Italian channels is a clone of a real voice, made with the open-source VoxCPM2 model running on my own graphics card. Most of the time it sounds good. Now and then it opens a sentence with a syllable that belongs to no language, swallows the first word, or garbles one in the middle. A viewer notices that within seconds, so every sentence has to pass a check before it goes into a video.&lt;/p&gt;

&lt;p&gt;The script is cut into sentences and each sentence is generated as its own take. Two Whisper speech recognizers, the small and the medium model, transcribe the take, and I compare what they heard with the script, word by word. A take is rejected for a missing word, an extra word, or a word the recognizer isn't sure about. A sentence that is 93 percent right can still contain the one word that sounds wrong, so close doesn't count.&lt;/p&gt;

&lt;p&gt;Then the take is trimmed, and the trimmed audio goes through both recognizers a second time. It's accepted only if both return exactly the script.&lt;/p&gt;

&lt;p&gt;Two details came from real failures. Recognizers ignore short sounds that aren't words, while a listener hears every one of them. So I measure the audio level in the pauses between sentences and cut any isolated burst. The other detail is numbers: a script that says "9 minutes" comes back from the recognizer with the digit 9, the voice says "nove", and the comparison fails. I convert digits to words on both sides before comparing.&lt;/p&gt;

&lt;p&gt;Sometimes the model drops the same word on every attempt. One sentence began with "Immergiti" and lost it in all six takes, so I replaced the verb with "Scendi" and the next take passed. After eight failed attempts a sentence is split into two shorter ones and tried again.&lt;/p&gt;

&lt;p&gt;This makes production slower. A short video with five sentences can need a dozen takes or more. I accept that, because the voice is the first thing a viewer judges and computer time is cheap.&lt;/p&gt;

&lt;p&gt;The step I'd copy first is the second pass on the trimmed audio. Trimming can introduce its own errors, and the trimmed file is the one that gets published.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Keeping Automated Social Posting Safe: Six Rules My Pipeline Follows</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Sun, 11 Oct 2026 11:15:49 +0000</pubDate>
      <link>https://dev.to/builtbyailab/keeping-automated-social-posting-safe-six-rules-my-pipeline-follows-1j83</link>
      <guid>https://dev.to/builtbyailab/keeping-automated-social-posting-safe-six-rules-my-pipeline-follows-1j83</guid>
      <description>&lt;p&gt;My pipeline publishes short videos to Instagram and TikTok for three channels, and the only thing I do is approve them, with one Telegram message a day. After the config-file incident of September 30th I wrote down every rule the code now follows. Each one exists because of a specific way things can go wrong.&lt;/p&gt;

&lt;p&gt;Start with account IDs. An Instagram ID is a long string of digits, and when one passes through a number type the last digits get rounded. A single wrong digit sends the post to a different account. Every ID in my files is now a string with quotes around it, even though it looks like a number.&lt;/p&gt;

&lt;p&gt;The publisher also keeps each channel on its own account. A channel has its own narrator voice and its own language, and both are checked before anything is sent, so a video with the wrong voice just waits in the queue. Volume has a ceiling too: one video per channel per day, set as a constant in the code, so it holds even when I approve a lot at once.&lt;/p&gt;

&lt;p&gt;A config file that fails to load stops everything, with no silent defaults anywhere. Facts follow the same logic. A post that depends on a schedule or a version number gets blocked once the fact sheet behind it goes stale, and it stays blocked until I confirm the fact again.&lt;/p&gt;

&lt;p&gt;The last rule happens after publishing. A second script compares what my database says was published with what Instagram shows on the account. It caught a reel that my system had marked as published while the platform never finished processing it. Since then I trust that second check more than the log of the program that did the posting.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>How I Designed Daily Rewards and Economy Caps for My Roblox Game</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:57:08 +0000</pubDate>
      <link>https://dev.to/builtbyailab/how-i-designed-daily-rewards-and-economy-caps-for-my-roblox-game-16j3</link>
      <guid>https://dev.to/builtbyailab/how-i-designed-daily-rewards-and-economy-caps-for-my-roblox-game-16j3</guid>
      <description>&lt;p&gt;The daily reward in my games runs on a seven-day streak. Day one gives 5 gems. Then 10, 15, 20, 30 and 40 on the following days, and day seven pays 100 gems. In the games that have pets, days three and seven also hand out a pet. Miss a day and the streak goes back to one. I wanted the last day to feel like a prize without making the first six feel like a chore, which is why the jump from day six to day seven is so big.&lt;/p&gt;

&lt;p&gt;Alongside that there's a free gift every ten minutes while you're playing, worth 10 gems. It keeps a session moving and costs the player nothing.&lt;/p&gt;

&lt;p&gt;Quests are where I had to be careful. Every quest pays some gems, and when a finished quest is replaced by a new one automatically, a player can in theory chain quests all day. During a review of the economy I noticed exactly that: an endless chain of quests would be an endless supply of gems. Now quests stop paying gems after 100 per day. The cap is stored per player and resets when the day changes, and the cash part of the reward keeps working, so a player who keeps going still progresses.&lt;/p&gt;

&lt;p&gt;I put the cap in the engine, not in each game, so all ten games got it at the same time.&lt;/p&gt;

&lt;p&gt;The reasoning behind all of it is about the people who never spend Robux. If gems are unlimited, whoever buys the most gems wins and everyone else watches. The idea behind a fixed ceiling per day is that a free player who shows up regularly keeps up with the economy and doesn't fall off a cliff. Buying still saves time. It just shouldn't open a gap that nobody else can close.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Why My AI Tools Run Locally</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:44:38 +0000</pubDate>
      <link>https://dev.to/builtbyailab/why-my-ai-tools-run-locally-193c</link>
      <guid>https://dev.to/builtbyailab/why-my-ai-tools-run-locally-193c</guid>
      <description>&lt;p&gt;Everything I publish starts on a single PC with an RTX 4080 and 12 GB of video memory. Ollama runs Qwen3 14B for the writing, ComfyUI runs Flux.1 for images, and a local text-to-speech model handles one of the narrator voices. None of it bills per use. Generating ten drafts costs the same as generating a thousand, apart from electricity.&lt;/p&gt;

&lt;p&gt;A 14B model on 12 GB is good enough to draft and sometimes sloppy. It repeats itself, it likes certain phrases, and every so often it states something wrong with total confidence. That is why a stronger model sits at the end of the line as a reviewer. It reads what the local model wrote and says whether it can go out. The call is short, around 750 tokens, so the review adds almost nothing to the bill, and the heavy lifting stays free.&lt;/p&gt;

&lt;p&gt;Local also means I can keep a pipeline running at 3 a.m. without watching a usage dashboard. When I'm testing a change to the video generator I can rerun it fifty times in an evening and the only cost is waiting. With a paid API I would have started rationing those runs by the third one, and I'd have shipped worse work.&lt;/p&gt;

&lt;p&gt;The price is time and care. Flux images take a while to render, the models share one graphics card so jobs queue instead of running side by side, and updates to any of the tools can break something that worked last week. I keep the setup small on purpose: three main tools, each doing one job.&lt;/p&gt;

&lt;p&gt;For a solo project that publishes every day, that trade has been worth it. I'd choose it again.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>How I Build and Ship Ten Roblox Games With a Shared Engine and Local AI</title>
      <dc:creator>Built By AI Lab</dc:creator>
      <pubDate>Wed, 07 Oct 2026 08:22:45 +0000</pubDate>
      <link>https://dev.to/builtbyailab/how-i-build-and-ship-ten-roblox-games-with-a-shared-engine-and-local-ai-31ml</link>
      <guid>https://dev.to/builtbyailab/how-i-build-and-ship-ten-roblox-games-with-a-shared-engine-and-local-ai-31ml</guid>
      <description>&lt;p&gt;I have ten Roblox games live, and there isn't ten games' worth of code behind them. There's one engine and ten small config files.&lt;/p&gt;

&lt;p&gt;The engine has three parts. The core part is everything every game needs. It saves progress to Roblox's DataStore and runs the daily quests, the daily reward and a free gift every ten minutes. Redeemable codes, gamepasses, the global leaderboard and the music live there too. So does a UI kit that rescales itself for phones and for PCs. The simulator part handles nine of the games: you collect, fill the backpack, sell, buy upgrades and move through five zones, hatch eggs from a pool of 28 pets, then rebirth and start again with a bonus. The obby part handles the tenth, with 60 levels across six worlds. Checkpoints keep the progress, and a shop sells faster running, higher jumps and trails.&lt;/p&gt;

&lt;p&gt;Each game folder holds a Config.lua with its theme, the zones, the pet list and the prices, a Rojo project file and the compiled place file. That's why Minator Simulator and Blossom Garden feel different on screen while running the same logic underneath.&lt;/p&gt;

&lt;p&gt;The part I like most is what happens when I find a bug. I fix it once in the engine, rebuild ten places with Rojo, and all ten are fixed. In early October that was the save logic: a purchase is now confirmed only after the save has gone through, so a player can't pay and lose the item if the save fails. One change, ten games.&lt;/p&gt;

&lt;p&gt;There's also a small script that simulates each game's economy before I publish, mostly to check how long it takes to unlock each zone so progression doesn't feel like a wall.&lt;/p&gt;

&lt;p&gt;As of October 7th all ten are published on Roblox with a 16+ rating. The text, images and daily short videos around them come from the local models on my PC, so none of that adds a running cost.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>ai</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
