<?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: Paul Crinigan</title>
    <description>The latest articles on DEV Community by Paul Crinigan (@paulcrinigan).</description>
    <link>https://dev.to/paulcrinigan</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%2F4016742%2F907d8a95-0aa5-48ee-816a-54db6658869b.png</url>
      <title>DEV Community: Paul Crinigan</title>
      <link>https://dev.to/paulcrinigan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paulcrinigan"/>
    <language>en</language>
    <item>
      <title>How AI Story Generators Keep a Novel Consistent, and Where They Still Drift</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Mon, 05 Oct 2026 23:07:59 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/how-ai-story-generators-keep-a-novel-consistent-and-where-they-still-drift-5ffn</link>
      <guid>https://dev.to/paulcrinigan/how-ai-story-generators-keep-a-novel-consistent-and-where-they-still-drift-5ffn</guid>
      <description>&lt;p&gt;If you have ever built anything on top of an LLM, the hardest problem in AI fiction will look very familiar. It is not prose quality, it is state management across a long session.&lt;/p&gt;

&lt;p&gt;Story generators are one of the most popular consumer uses of language models, and the good ones are quietly solving the same context problems developers hit in agents and chat apps. Looking at how they do it is a useful lens on both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Long Fiction Breaks a Plain Chatbot
&lt;/h2&gt;

&lt;p&gt;Every story generator runs the same loop as any LLM app: take a prompt, predict the next tokens, return text. A quick generator like Perchance or Toolbaz does exactly that and returns 500 to 2,000 words. For a short story it works fine.&lt;/p&gt;

&lt;p&gt;A novel is different. Modern context windows of 100,000 tokens or more can hold roughly 75,000 words, in principle a whole book. In practice the model still drifts. Minor characters change eye color, subplots vanish, and a town that was on the coast in chapter two is somehow inland by chapter nine. Attention over a huge window is not the same as reliable recall of one specific detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story Bible Pattern
&lt;/h2&gt;

&lt;p&gt;The tools that hold up on long fiction all converge on the same design: a structured store of facts kept outside the generated text, which the model reads before it writes each new section.&lt;/p&gt;

&lt;p&gt;DreamGen calls it a Scenario Codex, holding character bios, world rules and lore. SidekickWriter calls it a World Bible and adds a research step that pulls in facts while drafting. Sudowrite builds its version into a Story Engine that walks from outline to chapters. In stress tests past 5,000 words, the codex approach keeps names, accents and physical details stable where a plain chat session slips.&lt;/p&gt;

&lt;p&gt;If that sounds like retrieval for an agent, it is. A small, curated set of facts injected at the right moment beats hoping the model finds the detail somewhere in a giant transcript. The same lesson shows up in support bots, coding agents and anything else that runs long enough to forget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts Are the Other Half of the Context
&lt;/h2&gt;

&lt;p&gt;The bible holds what is true. The prompt says what to do next, and vague prompts produce generic output on every tool. The most reliable story prompts carry five things: genre, tone, point of view, a core dramatic question, and at least one specific detail.&lt;/p&gt;

&lt;p&gt;For long works, layering works better than one giant request. World building first, then characters, then a plot outline, then scene by scene generation. Each stage leaves behind context the next stage can use, which is the same reason chained prompts beat one shot prompts in most pipelines. This &lt;a href="https://www.aitools9.com/ai-story-generator/" rel="noopener noreferrer"&gt;roundup of AI story generators&lt;/a&gt; compares how the main free and paid tools handle prompts, continuity and export.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Tools Still Get Wrong
&lt;/h2&gt;

&lt;p&gt;Repetition is the most visible failure. Models reuse phrases, transitions and dramatic beats, especially over long outputs, and anti repetition filters reduce it without removing it.&lt;/p&gt;

&lt;p&gt;Originality is limited by design. Generating the most probable continuation pulls output toward familiar tropes, so genuinely surprising fiction still needs a human pushing it somewhere unexpected.&lt;/p&gt;

&lt;p&gt;Facts are shaky even in fiction. A model will confidently describe a firearm that does not exist or a historical event that never happened, which matters for thrillers, historical fiction and science fiction.&lt;/p&gt;

&lt;p&gt;Ownership has a clear line too. In the US, raw AI output cannot be copyrighted, while substantial human editing and shaping can earn protection. That makes the human in the loop a legal requirement as well as a creative one.&lt;/p&gt;

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

&lt;p&gt;Story generators are a good case study in what makes LLM products work over long sessions. The model matters less than what surrounds it: a compact source of truth it checks before acting, prompts that carry real constraints, and a person reviewing the parts the model is known to get wrong. That holds for a novel, and it holds for most things developers build on these models.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Giving LLM NPCs a Memory That Lasts Past One Conversation</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Mon, 05 Oct 2026 16:08:28 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/giving-llm-npcs-a-memory-that-lasts-past-one-conversation-4d7i</link>
      <guid>https://dev.to/paulcrinigan/giving-llm-npcs-a-memory-that-lasts-past-one-conversation-4d7i</guid>
      <description>&lt;p&gt;A language model can make a game character say almost anything, which is the easy part. The hard part shows up the second time a player walks up to that character, and the NPC greets them like a stranger.&lt;/p&gt;

&lt;p&gt;Memory is what separates a tech demo from a character players care about. Here is a practical way to layer it, from the conversation buffer up to structured facts, along with the parts of the surrounding architecture it plugs into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Memory Fits in the NPC Pipeline
&lt;/h2&gt;

&lt;p&gt;A typical LLM NPC system breaks into four modules. A context builder assembles the prompt, an LLM interface talks to a cloud API or a local inference engine, a response parser turns raw output into dialogue, emotion tags and game actions, and a memory manager decides what the character remembers.&lt;/p&gt;

&lt;p&gt;The memory manager never talks to the model directly. Its job is to hand the context builder the right few hundred tokens of history, inside a strict token budget, so the prompt stays small and the character still feels continuous. This overview of &lt;a href="https://www.abratabia.com/llm-npcs/" rel="noopener noreferrer"&gt;how LLM powered NPCs are built&lt;/a&gt; walks through all four modules and how they connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer One: A Conversation Buffer That Summarizes
&lt;/h2&gt;

&lt;p&gt;The simplest memory is a rolling buffer of recent messages, stored with role labels and timestamps. For most NPC conversations, 10 to 20 message pairs is enough for natural flow.&lt;/p&gt;

&lt;p&gt;The mistake is what happens at the edge of the buffer. Dropping the oldest messages creates a sudden cutoff where the NPC forgets what you were just talking about. Compressing them into a two or three sentence summary at the top of the history keeps the topic alive for a fraction of the tokens, and it only costs an extra model call when the buffer overflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer Two: Vector Retrieval Across Sessions
&lt;/h2&gt;

&lt;p&gt;Long term memory usually lives in a vector database such as ChromaDB, Pinecone, Qdrant or Weaviate. When a session ends, embed it and store it with a short text summary, a timestamp and the NPC's id. When the next session starts, embed the player's opening line and pull the closest matches back into the prompt.&lt;/p&gt;

&lt;p&gt;Two settings matter more than the database choice. Store segments of 3 to 5 message pairs grouped by topic, since whole conversations retrieve too loosely and single messages bloat the index. And set a minimum similarity score with a cap of 2 to 4 memories, because irrelevant recollections waste tokens and confuse the character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer Three: Structured Facts That Always Load
&lt;/h2&gt;

&lt;p&gt;Semantic search can miss the facts that matter in every conversation: the player's name, a promise they made, their standing with the NPC. Extract those after each session into simple labeled records, such as the player promised to bring iron ore from the northern mines, and load them every time regardless of topic.&lt;/p&gt;

&lt;p&gt;Some systems add memory decay on top, letting older, less important memories fade so retrieval stays focused. The step by step version, including how to format retrieved memories so the NPC references them naturally, is in this guide to &lt;a href="https://www.abratabia.com/llm-npcs/memory-and-context.php" rel="noopener noreferrer"&gt;giving NPCs memory and context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;None of these layers needs a bigger model. A well prompted small model with a good memory manager will feel more alive than a frontier model that forgets you every time you leave the room, and that is usually the cheaper problem to solve first.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>How AI Homework Helpers Solve a Problem, and Where They Still Get It Wrong</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:07:20 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/how-ai-homework-helpers-solve-a-problem-and-where-they-still-get-it-wrong-2816</link>
      <guid>https://dev.to/paulcrinigan/how-ai-homework-helpers-solve-a-problem-and-where-they-still-get-it-wrong-2816</guid>
      <description>&lt;p&gt;If you have ever wondered what happens between a student snapping a photo of a math problem and a worked solution appearing a second later, this one is for you. It is a tour of the pipeline behind AI homework helpers, why they are near perfect in some subjects and shaky in others, and what that means for the people using them.&lt;/p&gt;

&lt;p&gt;Over half of students aged 13 to 17 have used an AI chatbot for schoolwork at least once, and about one in ten use it for most assignments. The tools behind that are less magic than they look. Most are a few well known components stacked together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline Behind a Photo Solver
&lt;/h2&gt;

&lt;p&gt;Photo first apps start with optical character recognition. The camera image becomes text, including fractions, exponents and Greek letters. Printed notation reads reliably, neat handwriting reads well, and messy handwriting is still where errors creep in.&lt;/p&gt;

&lt;p&gt;The text then goes to a large language model, which works out what is being asked. For plain text subjects this is most of the work. For math, the better tools hand the actual computation to a computer algebra system, the same kind of symbolic engine behind Wolfram Alpha. The model interprets the question, the CAS computes an exact answer, and the model writes it up as readable steps.&lt;/p&gt;

&lt;p&gt;The newest tools add retrieval. Instead of relying only on what the model memorized in training, they search a store of textbooks and curriculum content before answering, which cuts factual errors and lets them point at a specific chapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Math Works and History Wobbles
&lt;/h2&gt;

&lt;p&gt;That architecture explains the accuracy gap between subjects. Math has definitive answers and procedural steps, and when a symbolic engine does the arithmetic, dedicated solvers like Photomath and Symbolab pass 95 percent on standard textbook problems through high school and introductory college courses. Each step follows from the one before, so there is little room to invent anything.&lt;/p&gt;

&lt;p&gt;History and the humanities are the opposite. There is no CAS for the causes of a war. The model generates a plausible narrative from patterns in its training data, and plausible is not the same as correct. Misattributed quotes and wrong dates come out in the same confident tone as everything else. Science sits in between, strong on calculations and definitions, weaker on lab design and the real measurement error the model never observed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Answer First Versus Hint First
&lt;/h2&gt;

&lt;p&gt;The other big design choice is what the tool does once it has a correct answer. Homework helpers like Photomath and ChatGPT are answer first: solve the problem, then explain it. AI tutors like Khanmigo are hint first: they reply with a question and wait for the student to attempt the next step.&lt;/p&gt;

&lt;p&gt;Research suggests the hint first approach produces about three times better independent problem solving. It is slower, and most students still prefer the fast answer, which is exactly why the tradeoff matters. There is a fuller comparison of when each approach makes sense in our &lt;a href="https://www.aitools9.com/ai-homework-helper/homework-helper-vs-tutor.php" rel="noopener noreferrer"&gt;homework helpers versus AI tutors breakdown&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Where a tool sits on that spectrum also shapes the integrity question. Using AI to understand a concept or check finished work is broadly accepted, and submitting AI written work as your own is not. Most schools in 2026 allow the first and prohibit the second unless a teacher says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a Tool With the Architecture in Mind
&lt;/h2&gt;

&lt;p&gt;Knowing the pipeline makes the choice simpler. For math, use a solver that pairs a model with a symbolic engine rather than a general chatbot. For essays and history, treat any output as a draft to check against a real source. For anything that will be on a test, pick a tool that gives hints rather than answers.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://www.aitools9.com/ai-homework-helper/" rel="noopener noreferrer"&gt;guide to AI homework helpers&lt;/a&gt; compares the main tools, free tiers and paid plans, photo scanners, and the subjects each one handles best, so you can match the tool to the work instead of the other way around.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>education</category>
      <category>machinelearning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Load, Stress, Spike and Soak: Four Performance Tests and What Each One Catches</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Sun, 04 Oct 2026 18:53:38 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/load-stress-spike-and-soak-four-performance-tests-and-what-each-one-catches-4g6l</link>
      <guid>https://dev.to/paulcrinigan/load-stress-spike-and-soak-four-performance-tests-and-what-each-one-catches-4g6l</guid>
      <description>&lt;p&gt;Most teams find out how much traffic their site can handle on the worst possible day, during a launch, a sale or a post that takes off. A performance test asks the same question on a quiet afternoon. The catch is that performance testing covers several different tests, and each one finds a different class of problem. This is the short map, drawn from our &lt;a href="https://www.webbrowserbot.com/performance-testing/" rel="noopener noreferrer"&gt;guide to performance testing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Targets Before You Pick a Tool
&lt;/h2&gt;

&lt;p&gt;Write the numbers down first: p95 under 400 milliseconds at 200 concurrent users, zero errors at twice the expected peak, Largest Contentful Paint under 2.5 seconds on mobile. Targets turn test output into a verdict. Without them, every run reads as "seems fine, probably."&lt;/p&gt;

&lt;p&gt;Read percentiles, not averages. A handful of 10 second responses disappear inside an average of thousands of fast ones. p50 is the typical request, p95 is the slowest 1 in 20, and p99 catches the outliers your most frustrated users are hitting. Track throughput next to them. When requests per second stop rising while virtual users keep climbing, the system is saturated and response times are about to follow.&lt;/p&gt;

&lt;p&gt;Error rate should be zero under normal load. How errors appear as load rises matters too: clean 503 responses mean the system is shedding load on purpose, while timeouts and connection resets mean it is drowning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Tests and What Each One Catches
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Load testing&lt;/strong&gt; ramps to your expected peak and holds it for 15 to 30 minutes while you measure response times and errors. It is the baseline, and the one to run first if you have never tested at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stress testing&lt;/strong&gt; keeps adding load until something gives. You come away with the level where latency starts climbing, the level where errors begin, and what failure actually looks like. Knowing that ceiling turns capacity planning from guesswork into arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spike testing&lt;/strong&gt; jumps from normal to extreme almost instantly, the way a flash sale or a viral post does. Systems that pass a gradual stress test often fail here, because autoscaling takes minutes and spikes take seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soak testing&lt;/strong&gt; runs moderate load for hours or days. It finds the slow failures that short tests cannot see: memory leaks, connection pools that run dry, logs filling a disk, and scheduled jobs colliding with traffic at 3 a.m.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.webbrowserbot.com/performance-testing/load-testing-vs-stress-testing.php" rel="noopener noreferrer"&gt;load testing vs stress testing breakdown&lt;/a&gt; goes deeper on when each one belongs in a release cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a Tool
&lt;/h2&gt;

&lt;p&gt;k6, from Grafana Labs, is the modern default for developer teams: tests are JavaScript, the engine is a fast Go binary, and thresholds turn a test into a pass or fail gate for CI. Apache JMeter has the broadest protocol support in open source and a visual test plan builder, at the cost of a heavy JVM and XML plans that are awkward in version control. Locust fits Python shops, since each virtual user is a Python class. Gatling squeezes the most concurrency out of a single machine and produces the best reports of the group. All four are free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make It a Habit, Not an Event
&lt;/h2&gt;

&lt;p&gt;The heavy tests belong before launches, campaigns and seasonal peaks. The everyday version is a 1 to 3 minute k6 or Locust run in CI with thresholds on p95 and error rate. It is not measuring capacity, it is comparing. If the search endpoint was 150 milliseconds last week and is 700 today, the pipeline flags it while the cause is one pull request instead of fifty.&lt;/p&gt;

&lt;p&gt;The frontend gets the same treatment. Lighthouse CI can fail a build that blows a budget committed to the repo, and budgets on real metrics hold up better than score thresholds, which bounce a few points between runs. For pages behind a login, scripting real page loads with &lt;a href="https://www.webbrowserbot.com/performance-testing/performance-testing-with-playwright.php" rel="noopener noreferrer"&gt;Playwright to capture navigation timing and Web Vitals&lt;/a&gt; lets you measure what a public speed test cannot reach, on every deploy.&lt;/p&gt;

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

&lt;p&gt;A load test costs about a day of engineering time. An outage answers the same question in front of your customers. Set targets, read percentiles, run the four tests in order of increasing violence, and keep a small check in CI so the next regression gets caught the day it lands.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>testing</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>SPF, DKIM and DMARC for Developers Who Just Want Their Email Delivered</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Sun, 04 Oct 2026 12:51:29 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/spf-dkim-and-dmarc-for-developers-who-just-want-their-email-delivered-p1c</link>
      <guid>https://dev.to/paulcrinigan/spf-dkim-and-dmarc-for-developers-who-just-want-their-email-delivered-p1c</guid>
      <description>&lt;p&gt;You wired up password resets or a newsletter, the SMTP call returns 250 OK, and the messages still land in spam. The code is fine. What is missing is proof that your server is allowed to send for your domain, and Gmail, Outlook and Yahoo now check for that proof on every message.&lt;/p&gt;

&lt;p&gt;Since 2024, Google and Yahoo require bulk senders, anyone sending more than 5,000 messages a day to their users, to publish DMARC, and Microsoft has followed with similar rules for Outlook. Three DNS records provide the proof, they take about an hour to set up, and they decide more about inbox placement than anything in your templates. This is the short developer version, the full walkthrough of every piece lives in our &lt;a href="https://www.aiappsapi.com/articles/emaildeliverability.php" rel="noopener noreferrer"&gt;email deliverability guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPF in One Record
&lt;/h2&gt;

&lt;p&gt;SPF is a single TXT record on your root domain that lists every service allowed to send as you. List every source, because anything you forget fails the check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;v=spf1 include&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt;_spf.google.com include:amazonses.com ~all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details catch developers. The check runs against the envelope sender, the Return-Path, not the From address your users see. You get exactly one SPF record per domain, so adding a second TXT record that starts with v=spf1 breaks both, merge new providers into the existing record instead. And SPF caps DNS lookups at 10, every include counts toward that, so a long list of providers can quietly push you over the limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  DKIM Keys and Selectors
&lt;/h2&gt;

&lt;p&gt;DKIM signs each message with a private key held by your provider. You publish the matching public key in DNS under a selector, a label the provider picks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;selector1._domainkey.yourdomain.com  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most providers hand you the exact record in a setup wizard and verify it from their dashboard. The part worth understanding is that DKIM signs the message itself rather than vouching for a server, so it survives forwarding where SPF usually fails. That makes DKIM the record DMARC leans on most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling DMARC From None to Reject
&lt;/h2&gt;

&lt;p&gt;DMARC passes when SPF or DKIM passes and the domain it checked aligns with the visible From domain. Start in monitoring mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_dmarc.yourdomain.com  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave it there for a couple of weeks and read the daily XML reports. They list every IP sending as your domain, which is usually where you find the forgotten CRM or the old server still mailing invoices. Once every legitimate source passes, move to p=quarantine, then p=reject. Jumping straight to reject is how teams block their own billing email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reputation and Pacing After the DNS Work
&lt;/h2&gt;

&lt;p&gt;Authentication gets you judged fairly, it does not make you trusted. A new domain has no reputation, so warm it up by sending small volumes to your most engaged users first and growing over a few weeks. Then keep volume steady per mailbox provider. Sending 50,000 messages to Gmail in one burst gets throttled even with perfect records, while the same volume spread across the day goes through.&lt;/p&gt;

&lt;p&gt;Pacing and the bounce handling that goes with it are the parts most worth automating. We built both into Email Campaign Engine, a free open source broadcast tool that spreads sends across the business day and puts hard bounces and complaints on a permanent suppression list. The &lt;a href="https://www.aiappsapi.com/apps/emailbroadcast.php" rel="noopener noreferrer"&gt;email broadcast system guide&lt;/a&gt; shows how it connects to Amazon SES, Mailgun or any SMTP relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Short Version
&lt;/h2&gt;

&lt;p&gt;Publish SPF with every sending source, turn on DKIM at your provider, start DMARC at p=none and read the reports before tightening it. After that, deliverability is about behavior: warm new domains slowly, pace your sends, and never mail an address that bounced. Get those right and your 250 OK finally means the message reached a person.&lt;/p&gt;

</description>
      <category>email</category>
      <category>webdev</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Making a Babylon.js Game Run Well in a Mobile Browser</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:52:21 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/making-a-babylonjs-game-run-well-in-a-mobile-browser-4j5</link>
      <guid>https://dev.to/paulcrinigan/making-a-babylonjs-game-run-well-in-a-mobile-browser-4j5</guid>
      <description>&lt;p&gt;If you have a Babylon.js game that runs smoothly on your laptop and falls apart on a phone, this one is for you. Mobile GPUs have less memory, lower fill rates and aggressive thermal throttling, and the browser adds its own overhead on top. The good news is that a handful of changes do most of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Before You Change Anything
&lt;/h2&gt;

&lt;p&gt;A slow frame can come from draw calls, oversized textures, shaders, physics, garbage collection or too many polygons, and each has a different fix. Toggle the Inspector with &lt;code&gt;scene.debugLayer.show()&lt;/code&gt; to see FPS, draw calls, active meshes and a frame time breakdown. Then log &lt;code&gt;scene.getLastFrameDuration()&lt;/code&gt;. Anything consistently above 16.6ms misses 60fps, and above 33.3ms misses 30.&lt;/p&gt;

&lt;p&gt;The Chrome Performance tab tells you where the time goes. Long Scripting bars mean JavaScript, long Rendering bars point to draw calls, and long GPU bars mean the graphics card is overloaded. Frequent GC blocks usually mean you are creating new &lt;code&gt;Vector3&lt;/code&gt; objects in the render loop, so preallocate and reuse them. The &lt;a href="https://www.abratabia.com/babylonjs/performance.php" rel="noopener noreferrer"&gt;Babylon.js performance guide&lt;/a&gt; covers the full profiling workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render Fewer Pixels
&lt;/h2&gt;

&lt;p&gt;Phones have pixel densities of 3x or more, and rendering 3D at native resolution there is impractical. &lt;code&gt;engine.setHardwareScalingLevel(2)&lt;/code&gt; renders at half resolution in each direction, a quarter of the pixels, and the browser scales the result to the full screen. Mid range phones do well at 2, high end phones can take 1.5, and low end devices may need 3.&lt;/p&gt;

&lt;p&gt;Pick the level at startup by running a few frames and checking &lt;code&gt;engine.getFps()&lt;/code&gt;. Then turn on FXAA through the DefaultRenderingPipeline with &lt;code&gt;pipeline.fxaaEnabled = true&lt;/code&gt;, which smooths the jagged edges cheaply enough for mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat GPU Memory as the Hard Limit
&lt;/h2&gt;

&lt;p&gt;A single uncompressed 4096x4096 texture uses 64MB. A budget Android phone may share 2GB between CPU and GPU, so ten of those textures and the tab is out of room. On mobile, games crash from memory more often than they stutter from frame rate.&lt;/p&gt;

&lt;p&gt;KTX2 with Basis Universal compression is the answer. One compressed file transcodes at runtime to whatever the GPU supports, BC7 on desktop and ASTC or ETC2 on mobile, and it cuts texture memory by about 75 percent compared to PNG. Ship smaller texture sets for phones too, 1024 or 512 where desktop uses 2048. The &lt;a href="https://www.abratabia.com/babylonjs/mobile-browser.php" rel="noopener noreferrer"&gt;mobile browser walkthrough&lt;/a&gt; also covers touch controls and thermal throttling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cut Draw Calls
&lt;/h2&gt;

&lt;p&gt;A desktop browser handles roughly 500 to 1000 draw calls at 60fps, while mobile browsers top out around 100 to 300. Merge static meshes that share a material with &lt;code&gt;BABYLON.Mesh.MergeMeshes&lt;/code&gt;, so 50 identical wall segments become one draw call. For repeated objects like trees and rocks, &lt;code&gt;createInstance&lt;/code&gt; shares geometry and material across copies, and thin instances via &lt;code&gt;thinInstanceSetBuffer&lt;/code&gt; handle thousands of identical objects with almost no CPU cost.&lt;/p&gt;

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

&lt;p&gt;Profile first, then render fewer pixels, compress every texture and batch your draw calls. Those four steps get most Babylon.js games playable on a mid range phone. The full &lt;a href="https://www.abratabia.com/babylonjs/" rel="noopener noreferrer"&gt;guide to building 3D web games with Babylon.js&lt;/a&gt; covers the rest of the engine, from Havok physics to multiplayer.&lt;/p&gt;

</description>
      <category>babylonjs</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Log an AI Agent So You Can Actually Debug It</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Fri, 02 Oct 2026 22:41:01 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/how-to-log-an-ai-agent-so-you-can-actually-debug-it-3gni</link>
      <guid>https://dev.to/paulcrinigan/how-to-log-an-ai-agent-so-you-can-actually-debug-it-3gni</guid>
      <description>&lt;p&gt;If you have shipped an agent and then tried to explain why it failed on one specific run, this one is for you. It is a practical logging setup to put in place before the first real user touches the agent.&lt;/p&gt;

&lt;p&gt;An agent that fails on a fraction of its runs leaves almost nothing behind if all you log is the input and the final answer. The model planned, called tools, read the results and changed its mind along the way, and none of that is on record. Agents are non deterministic, so replaying the input will not reproduce the failure. The only way to debug it later is to have written down every step while it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With One Event Schema
&lt;/h2&gt;

&lt;p&gt;Every event the agent emits should share the same core fields: a timestamp with milliseconds, a session id for the task, a step index that orders events within that task, an event type from a fixed list (llm_call, tool_call, tool_response, planning, evaluation, error, completion), and a log level.&lt;/p&gt;

&lt;p&gt;Each event type then adds its own payload. An llm_call carries the model, input and output tokens, and latency. A tool_call carries the tool name and its arguments. A completion carries the outcome, total steps, total tokens and total cost. Errors always carry the full payload of the failing step, whatever the log level is set to.&lt;/p&gt;

&lt;p&gt;Validate the schema where logs are ingested, even if a missing field only raises a warning. The &lt;a href="https://www.autolearningagents.com/ai-agent-observability/set-up-logging.php" rel="noopener noreferrer"&gt;step by step logging setup&lt;/a&gt; lists the full fields for each event type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Every LLM Call and Every Tool
&lt;/h2&gt;

&lt;p&gt;Put one wrapper around your LLM client and make it the only way the agent can reach the model. It records the start time, pulls token counts from the response, computes latency and emits the event, so no call can ever go unlogged. At INFO level, log the first and last hundred characters of the prompt plus its length, which is usually enough to identify the prompt version. Save the full prompt and response for DEBUG.&lt;/p&gt;

&lt;p&gt;Tools get the same treatment with one important difference: log the call and the response as two separate events. If a tool call starts and its response never arrives, that gap only shows up when the two are logged apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the Logs Into a Trace
&lt;/h2&gt;

&lt;p&gt;Once every event carries a session id and a step index, you can assemble a trace for each task: a root span for the whole task, a child span for each LLM call, and the tool calls nested under the LLM call that triggered them. A retry becomes a sibling span under the same parent, so you can see at a glance that the first attempt failed.&lt;/p&gt;

&lt;p&gt;What makes agent traces different from microservice traces is the reasoning context. A span that only says the agent made three LLM calls and two tool calls tells you what happened. A span that also records that the first search returned nothing, so the model rewrote the query, tells you why. This &lt;a href="https://www.autolearningagents.com/ai-agent-observability/tracing-decisions.php" rel="noopener noreferrer"&gt;walkthrough on tracing agent decisions&lt;/a&gt; goes deeper on span design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch Three Numbers Before Anything Else
&lt;/h2&gt;

&lt;p&gt;Teams that try to build complete monitoring before launch often end up launching with none. Three metrics catch the most common ways an agent deployment goes wrong: task success rate, cost per task and error rate. Cost is the one that surprises people, since real traffic often costs two to five times what testing predicted, so a simple daily cost alert is worth setting up on day one.&lt;/p&gt;

&lt;p&gt;Add LLM calls per task and tool success rate when one of the three moves and you need to know why. This guide on &lt;a href="https://www.autolearningagents.com/ai-agent-observability/what-to-monitor-first.php" rel="noopener noreferrer"&gt;what to monitor first&lt;/a&gt; lays out the order for adding the rest.&lt;/p&gt;

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

&lt;p&gt;Logging is the layer everything else sits on. Metrics are computed from it, traces are assembled from it, and every investigation ends up querying it. Getting the schema and the wrappers right early costs a day, and skipping it costs every bit of data you would have collected in the meantime. The full &lt;a href="https://www.autolearningagents.com/ai-agent-observability/" rel="noopener noreferrer"&gt;guide to AI agent observability&lt;/a&gt; covers dashboards, cost tracking and the common failure patterns that monitoring catches.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>How AI Browser Agents Actually Drive a Browser, and When a Script Still Wins</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Fri, 02 Oct 2026 18:39:42 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/how-ai-browser-agents-actually-drive-a-browser-and-when-a-script-still-wins-2eea</link>
      <guid>https://dev.to/paulcrinigan/how-ai-browser-agents-actually-drive-a-browser-and-when-a-script-still-wins-2eea</guid>
      <description>&lt;p&gt;For about twenty years, browser automation meant telling the browser exactly what to do. Selenium, then Puppeteer, then Playwright each made that easier, but every one of them still needed a selector for every click and a wait for every load, and every site redesign meant fixing broken scripts. AI browser agents flip that around. You describe the outcome, and a language model works out the clicks. This article walks through how they work under the hood, where they are strong, and where a plain script is still the better tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Loop Behind Every Browser Agent
&lt;/h2&gt;

&lt;p&gt;Every agent runs the same four step cycle: perception, reasoning, action and evaluation.&lt;/p&gt;

&lt;p&gt;Perception is how the agent sees the page. DOM based agents read the HTML, strip out what does not matter, and label each interactive element with a number so the model can refer to it. Browser Use works this way. Vision based agents take a screenshot and send it to a vision model instead, which still works when the DOM is obfuscated or the interface is drawn on a canvas. Skyvern leads with this approach. The strongest setups mix both, reading the DOM when it is clean and falling back to a screenshot when it is not.&lt;/p&gt;

&lt;p&gt;Reasoning is the model call. The agent sends the page state, the original goal and the history of steps so far, and the model picks the next move. This is where the flexibility comes from. If a Submit button changes from a button tag to a link, a script fails, while the model just sees a submit control and clicks it. It can also deal with cookie banners and login prompts that were never part of the plan.&lt;/p&gt;

&lt;p&gt;Action turns that decision into a real command, almost always through Playwright or Puppeteer underneath. Evaluation captures the new page state and checks whether the step worked, so the agent can retry or try another path when it did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Numbers Say
&lt;/h2&gt;

&lt;p&gt;The best open source agents now complete around 85 to 89 percent of tasks on the WebVoyager benchmark, with Browser Use at 89.1 percent and Skyvern at 85.85 percent. That is impressive, and it also means roughly one task in eight fails. Real production workflows tend to score lower than benchmarks.&lt;/p&gt;

&lt;p&gt;Speed and cost follow from the loop. Every step needs at least one model call, so an action a script finishes in 2 seconds can take an agent 30 seconds or more. A complex task can need 20 to 50 calls at a few cents each, while a script costs close to nothing per run once it is written. Vision agents also use more tokens than DOM agents, because screenshots are expensive to send.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Between an Agent and a Script
&lt;/h2&gt;

&lt;p&gt;The decision usually comes down to who controls the website. If it is your own application, or a stable site you hit at high volume, a deterministic script is faster, cheaper and repeatable. If the sites belong to someone else, change often, or number in the dozens, an agent saves you from writing and maintaining a scraper for each one.&lt;/p&gt;

&lt;p&gt;Most teams end up in the middle. Stagehand is built around this idea: write normal Playwright style code for the steps you can predict, and call the model only for the parts of the page that are messy. LaVague takes another route and has the model generate Selenium or Playwright code, which leaves you with a script you can read and audit.&lt;/p&gt;

&lt;p&gt;A few limits are worth planning for either way. Bot detection looks at fingerprints, mouse movement and typing rhythm. An agent that logs in for you is handling your cookies and passwords, which matters more when page content goes to an external model. Very dense pages can overflow the model's context window, so the agent has to read them in pieces. The &lt;a href="https://www.webbrowserbot.com/ai-browser-agents/" rel="noopener noreferrer"&gt;complete guide to AI browser agents&lt;/a&gt; goes deeper on each framework, real world use cases from price monitoring to compliance checks, and how to match a tool to your requirements.&lt;/p&gt;

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

&lt;p&gt;Treat agents and scripts as two tools, not a replacement for one another. Use an agent where the page is out of your control and adapting is the whole job, and keep scripts for the stable, high volume work where speed and repeatability matter most.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From docker compose up to Your First Custom Agent</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Thu, 01 Oct 2026 23:03:45 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/from-docker-compose-up-to-your-first-custom-agent-4ed4</link>
      <guid>https://dev.to/paulcrinigan/from-docker-compose-up-to-your-first-custom-agent-4ed4</guid>
      <description>&lt;p&gt;If you have ever wanted to run an AI agent platform on your own box instead of renting someone else's, this is a walkthrough of what that looks like from the first command to a custom agent running on a schedule. It is based on Auto Learning Agents, the free open source platform we build, but most of the patterns carry over to any self hosted agent stack.&lt;/p&gt;

&lt;p&gt;Self hosting used to mean a weekend of wiring services together, a vector database here, a queue there, a Python service that only worked on one machine. The setup below is three steps, and each one is small enough to understand completely before moving to the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Container With Everything Inside
&lt;/h2&gt;

&lt;p&gt;The platform ships as a single Docker image. It bundles the Elixir/OTP release that supervises every agent, PHP for the tool layer, Python with a local embedding model, Playwright with Chromium for browser automation, and Apache serving the web UI. One start script brings all of it up, so the host only needs Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AIAppsAPI/auto-learning-agents
&lt;span class="nb"&gt;cd &lt;/span&gt;auto-learning-agents
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run takes a few minutes while the release compiles and the Python dependencies install. After that it starts in seconds and restarts with the host. Two ports are published, 80 for the web UI and 9500 for the memory bank socket the tools use. If port 80 is taken, change the mapping to something like 8080:80.&lt;/p&gt;

&lt;p&gt;The part worth understanding is the volumes. The SQLite database with memories, conversations and knowledge bases, the markdown memory files, the work queues, the logs and even your Claude login each live in a persistent volume, so you can rebuild or upgrade the container freely. Backing up the system means backing up the volumes and nothing else. The &lt;a href="https://www.autolearningagents.com/docs/docker-install.php" rel="noopener noreferrer"&gt;Docker install guide&lt;/a&gt; covers each volume and the upgrade path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Layers of Configuration
&lt;/h2&gt;

&lt;p&gt;Configuration is split into three layers, and every one of them applies live. The system rereads it while it runs, so most changes need no restart.&lt;/p&gt;

&lt;p&gt;settings.txt holds the runtime config as one JSON object: the install path, the ports for the two always on Python services (9500 for embeddings and storage, 9510 for topic classification), which model families the install can use, and the memory backend. Setting the memory backend to local gives you local embeddings with SQLite vector search and zero setup. On Docker you rarely touch this file directly, because the values in .env are applied into it on every container start.&lt;/p&gt;

&lt;p&gt;The config directory holds one watched JSON file per area: agents.json for your agents, chatbot.json for Discord, Slack, WhatsApp and Telegram, plus files for marketing, customer service, social media and website reporting. Edits reconcile while agents are running.&lt;/p&gt;

&lt;p&gt;The third layer is plain markdown in memory/system: brand voice, product descriptions, support policies, coding standards. Agents read these files every time they work, so a one paragraph edit to the voice file changes the tone of every outgoing message. The &lt;a href="https://www.autolearningagents.com/docs/configuration.php" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt; maps out what lives where.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Agent Is a Brief, a Schedule and a Model
&lt;/h2&gt;

&lt;p&gt;A custom agent is one entry in agents.json:&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;"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;"reviewwatcher"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tick_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instructions_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"memory/agent_systems/custom/reviewwatcher.md"&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;tick_minutes is the schedule, model is the brain, and the instructions file is the job description. Write it the way you would brief a contractor: the role, what to do each tick, and when to raise a flag instead of deciding alone. Every agent ships disabled until you flip enabled on.&lt;/p&gt;

&lt;p&gt;Two optional fields do a lot of practical work. pre_command runs a command at the start of each tick and feeds its output into the prompt, which is the clean way to hand an agent fresh rows or log lines without making it fetch them. gate_command skips the model call entirely when there is nothing to do, so an idle agent costs nothing.&lt;/p&gt;

&lt;p&gt;What makes a short brief produce competent work is everything the agent inherits: its recent conversation, a memory query before it acts and a save after, and the full tool layer for email, browsing, scraping and terminal sessions. The &lt;a href="https://www.autolearningagents.com/docs/creating-agents.php" rel="noopener noreferrer"&gt;guide to creating agents&lt;/a&gt; walks through tuning the tick and the model once the first runs are in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start
&lt;/h2&gt;

&lt;p&gt;Pick one small recurring job you already do by hand, write a five line brief for it, set the tick to a couple of hours and watch the first few runs in the activity feed. Too cautious means the brief can grant more, too eager means it needs one more boundary. Everything you need to get the platform running is on the &lt;a href="https://www.autolearningagents.com/get-started/" rel="noopener noreferrer"&gt;get started page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ai</category>
      <category>opensource</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Adding Checkout to the MCP Server You Already Run</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Thu, 01 Oct 2026 15:41:46 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/adding-checkout-to-the-mcp-server-you-already-run-241</link>
      <guid>https://dev.to/paulcrinigan/adding-checkout-to-the-mcp-server-you-already-run-241</guid>
      <description>&lt;p&gt;If you maintain an MCP server, you have probably had someone ask whether they can buy the Pro tier, a credit pack or a license right there in the chat. This post walks through what it takes to add search, cart and checkout tools to a server you already run, without standing up a second service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register Commerce as More Tools, Not a New Server
&lt;/h2&gt;

&lt;p&gt;The shape that works is a library you import into your existing server. You create a commerce core from three things: a catalog source, a cart store and a small config object. Then one call, registerCommerceTools(yourMcpServer, commerce), adds search_products, get_product, add_to_cart, view_cart, update_cart_item and remove_from_cart next to the tools you already expose. Your transport, auth and deployment stay exactly as they are.&lt;/p&gt;

&lt;p&gt;Checkout is a separate service you pass in as a third argument. It holds the payment provider, a session store, a tax calculator and an order sink, and once it is there the checkout tool appears. During development you can swap in a mock payment provider and a no op order sink, so the whole flow runs with no keys and no charges. The &lt;a href="https://www.afcommerce.com/free-acp-payments-module/embedding.php" rel="noopener noreferrer"&gt;guide to embedding commerce tools in your own MCP server&lt;/a&gt; has the full code for each step, including wiring the Stripe webhook so duplicate deliveries are harmless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Tool Descriptions for the Agent That Reads Them
&lt;/h2&gt;

&lt;p&gt;An MCP tool description is the only manual the agent ever reads, so the contract has to live there. The transport is stateless, which means add_to_cart returns a cart_id and the agent must send it back on every later call. Line edits use a line_id, not a product id, and the description says so, because that mix up is the most common agent mistake.&lt;/p&gt;

&lt;p&gt;Errors matter as much as success. Returning readable messages with machine codes like missing, invalid and out_of_stock lets an agent recover by searching again, picking a variant or telling the shopper an item is unavailable, instead of stalling. Each result pairs a one line summary for the conversation with a JSON body for the agent's reasoning. The &lt;a href="https://www.afcommerce.com/free-acp-payments-module/mcp-tools.php" rel="noopener noreferrer"&gt;MCP commerce tools reference&lt;/a&gt; documents all seven tools with their inputs, outputs and failure cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Catalog and the Cart State Apart
&lt;/h2&gt;

&lt;p&gt;The catalog is read only product truth. It never gets written by commerce code, and it is the only authority on price, kept in integer cents. Every cart read recalculates line prices from the live catalog, so whatever the client sends cannot change what gets charged, and a price change reaches open carts on its own.&lt;/p&gt;

&lt;p&gt;The cart is the opposite: per shopper, constantly changing, and worthless after an hour. Modeling carts, checkout sessions and idempotency records as rows that expire means the store cleans itself and holds nothing worth stealing, with no card data and no order archive. SQLite covers a single box, and DynamoDB with conditional writes covers serverless fleets where two containers might handle the same retry. The &lt;a href="https://www.afcommerce.com/free-acp-payments-module/storage-and-catalog.php" rel="noopener noreferrer"&gt;storage and catalog drivers guide&lt;/a&gt; compares the options and the cache settings that bound how stale a price can get.&lt;/p&gt;

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

&lt;p&gt;Selling inside an MCP server is mostly plumbing you should not have to write twice: a few extra tools with honest descriptions, a catalog that owns every price, and short lived state that expires on its own. Treat commerce as part of the server your users already talk to, and the purchase happens in the same conversation where they asked for it.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>stripe</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your Uptime Monitor Says 200 OK and Your Site Is Still Broken</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:39:15 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/your-uptime-monitor-says-200-ok-and-your-site-is-still-broken-28l0</link>
      <guid>https://dev.to/paulcrinigan/your-uptime-monitor-says-200-ok-and-your-site-is-still-broken-28l0</guid>
      <description>&lt;p&gt;If you run production web apps, you have probably watched a green dashboard during an outage at least once. This post covers the gaps a basic uptime check leaves open, and the few settings that close them without flooding your alert channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Basic Uptime Check Actually Proves
&lt;/h2&gt;

&lt;p&gt;A standard uptime monitor sends an HTTP request every one to five minutes and records whether a 2xx status came back in time. That proves the server answered. It does not prove the page works.&lt;/p&gt;

&lt;p&gt;The failure it misses is the one that hurts most. A bad deploy, a broken template or a cache serving yesterday's error page can all return 200 OK with nothing useful in the body. The monitor stays green, and users see nothing.&lt;/p&gt;

&lt;p&gt;The fix costs nothing. &lt;a href="https://www.webbrowserbot.com/website-monitoring/uptime-monitoring-explained.php" rel="noopener noreferrer"&gt;Keyword checks and response assertions&lt;/a&gt; confirm that the body contains a string that only appears when the page rendered real content, like a product name or a footer line. Add a check that the page size stays within its normal range and you catch the blank page case too.&lt;/p&gt;

&lt;p&gt;It also helps to know what your target means in minutes. 99.9 percent uptime allows about 43.8 minutes of downtime a month, and one bad deploy can spend that whole budget in a single incident. 99.99 percent leaves about 4.4 minutes, which is not reachable without sub minute checks and automated failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cutting Alert Noise Before Your Team Learns to Ignore It
&lt;/h2&gt;

&lt;p&gt;Alerts that fire on every transient glitch get muted, and then the real one gets muted too. Most of that noise comes from single location checks, because a failure from one probe cannot tell a real outage from a routing problem between two networks.&lt;/p&gt;

&lt;p&gt;Run each check from at least three regions and alert only when two or more fail. Require a couple of consecutive failures before paging anyone. Set response time thresholds at roughly two to three times the baseline you measured during a stable week, and revisit them as traffic grows. Define maintenance windows so planned deploys do not page the on call engineer.&lt;/p&gt;

&lt;p&gt;Certificates deserve their own alert. Warn at least 30 days before expiry, even if you use Let's Encrypt with auto renewal, because renewal can fail silently after DNS changes, server migrations or config drift, and an expired certificate blocks visitors with a full page browser warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watching Content, Not Just Availability
&lt;/h2&gt;

&lt;p&gt;Some of the problems that matter never touch your status codes. Someone edits a pricing page by mistake, a script gets injected into a template, or a compliance page you depend on changes upstream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.webbrowserbot.com/website-monitoring/website-change-detection.php" rel="noopener noreferrer"&gt;Website change detection&lt;/a&gt; handles this by taking a snapshot of a page on a schedule and comparing each version against the last. The trick is scope. Comparing a whole page alerts on every timestamp, ad slot and rotating carousel, so target the element you care about, such as the pricing table or the main body text, and ignore navigation and footers. Browser extensions like Distill.io work while your machine is on, hosted services like Visualping run around the clock, and changedetection.io is open source if you want to host it yourself.&lt;/p&gt;

&lt;p&gt;For the flows that make money, add synthetic checks. A scheduled script that logs in, adds an item to the cart and reaches checkout catches a broken payment step that every page level check would miss.&lt;/p&gt;

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

&lt;p&gt;Start with the ten pages and flows that drive revenue, not the hundred that exist. Check them from several regions, assert on real content instead of status codes, alert on certificates early, and send alerts to someone who can act on them. The full &lt;a href="https://www.webbrowserbot.com/website-monitoring/" rel="noopener noreferrer"&gt;guide to website monitoring&lt;/a&gt; goes further into performance, visual and real user monitoring, and how to choose a tool.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>sre</category>
    </item>
    <item>
      <title>Your Docs Are Wrong and the Build Still Passes</title>
      <dc:creator>Paul Crinigan</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:46:33 +0000</pubDate>
      <link>https://dev.to/paulcrinigan/your-docs-are-wrong-and-the-build-still-passes-njl</link>
      <guid>https://dev.to/paulcrinigan/your-docs-are-wrong-and-the-build-still-passes-njl</guid>
      <description>&lt;p&gt;If you maintain a codebase with more than a few contributors, you have probably copied an example from your own docs and watched it fail. This post looks at why documentation drifts away from the code and what actually keeps the two in step.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Documentation Drift Starts
&lt;/h2&gt;

&lt;p&gt;Drift almost never starts with a mistake. It starts with a change that is correct. Someone renames a parameter, changes a default, or makes a function return null instead of throwing. The tests pass, the review approves, the pull request merges, and the docs still describe the old behavior. Nobody notices, because nobody reads the docs during code review.&lt;/p&gt;

&lt;p&gt;Each instance is small, which is exactly why it compounds. &lt;a href="https://www.aiappsapi.com/articles/aitechnicaldocs/documentationdrift.php" rel="noopener noreferrer"&gt;Documentation drift&lt;/a&gt; tends to show up in four shapes: parameters that were renamed or retyped, behavior that changed quietly, architecture that was split or merged, and code examples that no longer run. The last one hurts the most, because examples exist to be copied, and a copied example that fails teaches the reader not to trust anything else on the page.&lt;/p&gt;

&lt;p&gt;That loss of trust is the real cost. Once developers learn that some of the docs are wrong, they stop reading all of them, including the parts that are still accurate. Then nobody notices the next inaccuracy either, and the drift speeds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Usual Fixes Fade
&lt;/h2&gt;

&lt;p&gt;Most teams try one of three fixes. The first is adding "update the docs" to the review checklist. It rarely holds, because reviewers look at the diff, and the doc that describes the changed function often lives in another folder, another repo, or a wiki nobody opens during review.&lt;/p&gt;

&lt;p&gt;The second is a documentation sprint every quarter. It works for about a week. Drift starts again the next day, and by the next sprint the backlog looks the same.&lt;/p&gt;

&lt;p&gt;The third is assigning doc owners. That creates accountability, but the owner still has to notice every relevant change across the codebase, and it is usually the first duty to slip when other work piles up.&lt;/p&gt;

&lt;p&gt;The root cause is simple. The compiler and the test suite enforce the code, and nothing enforces the docs. There is no failing build when a README goes stale. Any fix that depends on people remembering will fade, which is why &lt;a href="https://www.aiappsapi.com/articles/aitechnicaldocs/syncwithcode.php" rel="noopener noreferrer"&gt;keeping docs in sync with code changes&lt;/a&gt; works better as an automated step than as a habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat Docs as Output of the Code
&lt;/h2&gt;

&lt;p&gt;The approach that holds up is to treat the parts of your documentation that describe the code as something generated from the code. An agent or a script watches for changes, works out which functions, classes or modules were touched, and checks whether any existing doc describes them.&lt;/p&gt;

&lt;p&gt;Not every change matters. A bug fix that leaves the signature and behavior alone needs no doc update. A new parameter, a changed return value, a different default or a new error case does. The useful step is comparing the before and after states and updating only the sections that describe what changed.&lt;/p&gt;

&lt;p&gt;The timing matters as much as the method. When the doc update lands in the same pull request as the code change, reviewers see both together and the docs are never behind for a day. When it lands later, you are back to batch work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With the README
&lt;/h2&gt;

&lt;p&gt;If you do one thing, start with the README. It is the first page anyone reads, it renders on the repository page, and it is usually the most out of date file in the project, because it was written once at the start and then left alone.&lt;/p&gt;

&lt;p&gt;Most of a good README can be derived from files that already exist. Install steps come from package.json, requirements.txt or pyproject.toml, including the runtime versions they pin. The configuration reference comes from the env var reads and settings objects in the code, with types and defaults. Usage examples come from the actual public interface, so they run when someone pastes them. Tools that &lt;a href="https://www.aiappsapi.com/articles/aitechnicaldocs/readmefromcode.php" rel="noopener noreferrer"&gt;generate a README from the source code&lt;/a&gt; are really just doing that reading for you, every time the code changes.&lt;/p&gt;

&lt;p&gt;The parts that cannot be derived, like why the project exists and when you should not use it, still need a person. That is fine, those parts change slowly.&lt;/p&gt;

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

&lt;p&gt;Documentation drift is not a discipline problem, it is a missing check. Anything in your docs that restates a fact about the code, like a parameter, a default, a config key or an example, should come from the code and update with it. Leave the people who write docs to explain the why, which is the part no generator can produce and the part readers actually came for.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
