<?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: sky</title>
    <description>The latest articles on DEV Community by sky (@metrix187).</description>
    <link>https://dev.to/metrix187</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%2F4103165%2F6e1b115f-a516-4485-a948-4d8251aadf6a.png</url>
      <title>DEV Community: sky</title>
      <link>https://dev.to/metrix187</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/metrix187"/>
    <language>en</language>
    <item>
      <title>I put a diffusion model in a stylesheet. No JavaScript.</title>
      <dc:creator>sky</dc:creator>
      <pubDate>Mon, 09 Nov 2026 04:16:07 +0000</pubDate>
      <link>https://dev.to/metrix187/i-put-a-diffusion-model-in-a-stylesheet-no-javascript-12hm</link>
      <guid>https://dev.to/metrix187/i-put-a-diffusion-model-in-a-stylesheet-no-javascript-12hm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtltsa1ldrahfwfec58u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtltsa1ldrahfwfec58u.gif" alt="Static resolving into a pixel-art cat over twelve denoising steps" width="560" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://metrix187.github.io/diffusion.css/" rel="noopener noreferrer"&gt;metrix187.github.io/diffusion.css&lt;/a&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Metrix187/diffusion.css" rel="noopener noreferrer"&gt;github.com/Metrix187/diffusion.css&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press the button and the stylesheet freezes a seed, turns it into noise, and denoises that into a 16×16 sprite in 12 steps. There is no &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; on the page. View source and check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;People have already put a &lt;a href="https://lyra.horse/x86css/" rel="noopener noreferrer"&gt;CPU&lt;/a&gt;, &lt;a href="https://github.com/mickael-btc/css-ml" rel="noopener noreferrer"&gt;digit classifiers&lt;/a&gt; and, a few days ago, &lt;a href="https://shubhampatil.dev/blog/gpt2-css" rel="noopener noreferrer"&gt;GPT-2&lt;/a&gt; into CSS. I went looking for a diffusion model and couldn't find one, so this is my attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: CSS has no loops
&lt;/h2&gt;

&lt;p&gt;Denoising is a loop. Each step needs the previous step's output, and a custom property can't refer to itself.&lt;/p&gt;

&lt;p&gt;So the sampler is unrolled. Every step gets its own copy of the arithmetic, and every intermediate value gets its own registered custom property. A hidden unit looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="l"&gt;--Ba0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;integer&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* first unit of the network used by step 2 */&lt;/span&gt;
&lt;span class="nt"&gt;--Ba0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;51096&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;25&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--a108&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;26&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--a123&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;31&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--a126&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;/* 80 more terms */&lt;/span&gt;&lt;span class="o"&gt;)/&lt;/span&gt;&lt;span class="err"&gt;512&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering the property matters. An unregistered custom property is just text, so each &lt;code&gt;var()&lt;/code&gt; would paste in the whole expression behind it and the stylesheet would blow up. A registered &lt;code&gt;&amp;lt;integer&amp;gt;&lt;/code&gt; is computed once, rounded, and passed on as a number. There are 12,518 of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seed and noise
&lt;/h2&gt;

&lt;p&gt;The seed is a counter that a hidden checkbox pauses, so the moment you press is the random seed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15813250&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="m"&gt;211s&lt;/span&gt; &lt;span class="nb"&gt;linear&lt;/span&gt; &lt;span class="nb"&gt;infinite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.app&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#go&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation-play-state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;paused&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of the 768 pixel channels then hashes that seed with &lt;code&gt;mod()&lt;/code&gt; four times and sums the draws, which gives roughly Gaussian noise. That is the static you see before pressing.&lt;/p&gt;

&lt;p&gt;The network rule only matches while the box is ticked, so nothing is computed until the seed is frozen. After that, one &lt;code&gt;@keyframes&lt;/code&gt; rule points the 256 pixel elements at each sampler state in turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;

&lt;p&gt;Eleven small MLPs, one per noise level, trained in PyTorch on ten hand-drawn sprites that get recoloured to a random hue for every sample. Then they are pruned and rounded to integers: 202,863 weights, each one a literal inside a &lt;code&gt;calc()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that went wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rounding.&lt;/strong&gt; CSS rounds a &lt;code&gt;calc()&lt;/code&gt; in an &lt;code&gt;&amp;lt;integer&amp;gt;&lt;/code&gt; context to the nearest integer, but browsers don't agree on exact .5 ties. The fix: every division is by a power of two with 0.5 added to the numerator, so a quotient is never a tie. With that in place, a Python sampler using the same integer arithmetic produces identical pixels for the same seed. I checked 40 seeds in Chromium 152 and another 40-odd in Chrome 157.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blended sprites.&lt;/strong&gt; The first model produced a muddle of two sprites about a third of the time. Deterministic sampling with only 12 steps drifts between two shapes and never commits. Two changes fixed most of it: spacing the noise levels evenly in log-SNR, and training the later steps on inputs deliberately blended with a second sprite while keeping the dominant one as the target, so they learn to snap to one shape. It is down to about 1 in 20.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it isn't
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a loop.&lt;/strong&gt; All 12 steps are computed when the seed freezes. The animation reveals them; it does not drive the computation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not creative.&lt;/strong&gt; It learned ten shapes and a continuous range of colours. You get new shape-and-colour combinations, never a new shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not tested outside Chromium.&lt;/strong&gt; It only uses features Firefox and Safari also ship, but I have not run it there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weights&lt;/td&gt;
&lt;td&gt;202,863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sampler steps&lt;/td&gt;
&lt;td&gt;12, deterministic DDIM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registers&lt;/td&gt;
&lt;td&gt;12,518&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Possible seeds&lt;/td&gt;
&lt;td&gt;15,813,251&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File size&lt;/td&gt;
&lt;td&gt;5.2 MB, 0.8 MB gzipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to compute a sprite&lt;/td&gt;
&lt;td&gt;about 0.13 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/grahamthedev/pure-css-neural-network-aiits-easier-that-you-think-f02"&gt;😱 Pure CSS! Neural Network / AI...it's easier than you think! 🤯&lt;/a&gt; by GrahamTheDev, the earliest pure-CSS neural net post I found on DEV&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shubhampatil.dev/blog/gpt2-css" rel="noopener noreferrer"&gt;Implementing GPT-2 Entirely In CSS&lt;/a&gt; by Shubham Patil&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mickael-btc/css-ml" rel="noopener noreferrer"&gt;css-ml&lt;/a&gt; by mickael-btc, whose README describes the integer-register approach this builds on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lyra.horse/x86css/" rel="noopener noreferrer"&gt;x86CSS&lt;/a&gt; by Lyra Rebane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo has the training code, the reference sampler and a console snippet for checking the page against it. If you get it running in Firefox or Safari, I would like to know whether the pixels match.&lt;/p&gt;

</description>
      <category>css</category>
      <category>machinelearning</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>a new privacy tool :3</title>
      <dc:creator>sky</dc:creator>
      <pubDate>Wed, 21 Oct 2026 03:24:49 +0000</pubDate>
      <link>https://dev.to/metrix187/a-new-privacy-tool-3-2993</link>
      <guid>https://dev.to/metrix187/a-new-privacy-tool-3-2993</guid>
      <description>&lt;p&gt;I made Scrub, a browser tool that blurs the room behind you before you share a photo.&lt;/p&gt;

&lt;p&gt;Photos stay on your device. Blurring isn't anonymity.&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://unlisted-scrub.pages.dev" rel="noopener noreferrer"&gt;Scrub Live Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Metrix187" rel="noopener noreferrer"&gt;
        Metrix187
      &lt;/a&gt; / &lt;a href="https://github.com/Metrix187/unlisted" rel="noopener noreferrer"&gt;
        unlisted
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Reduce background clues in photos shared from home. Browser-based Scrub and manual listing-photo removal guidance.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;unlisted&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Reduce the background clues in photos you share from home.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlisted explores one specific risk: a photo's visible room details may be matched to rental or
real-estate listing photos associated with an address. Scrub reduces those details; Footprint helps
you check listing sites manually and find removal guidance. Neither tool can tell you that you are safe
We have not tested these defenses against Raven, GeoSpy, or other proprietary systems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scrub&lt;/strong&gt; (&lt;code&gt;apps/scrub&lt;/code&gt;) — pick a photo, see what in the room would give it away, tap once to blur the
room out from behind you, share. runs entirely in your browser. the photo never leaves your device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Footprint&lt;/strong&gt; (&lt;code&gt;apps/footprint&lt;/code&gt; + &lt;code&gt;workers/footprint-api&lt;/code&gt;) — manual search links and per-site removal
guides. Automated lookups are largely blocked. Addresses are sent to a Cloudflare Worker for lookup
the code avoids storing the address, but Cloudflare handles the request. This…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Metrix187/unlisted" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Tell me what breaks.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>ai</category>
      <category>pwa</category>
      <category>testing</category>
    </item>
    <item>
      <title>Seven Silent Failures: building an autonomous email agent on Cloud Run</title>
      <dc:creator>sky</dc:creator>
      <pubDate>Mon, 31 Aug 2026 18:06:48 +0000</pubDate>
      <link>https://dev.to/metrix187/seven-silent-failures-building-an-autonomous-email-agent-on-cloud-run-85a</link>
      <guid>https://dev.to/metrix187/seven-silent-failures-building-an-autonomous-email-agent-on-cloud-run-85a</guid>
      <description>&lt;p&gt;&lt;em&gt;I created this piece of content for the purposes of entering the All Things Agentic Hackathon.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://quantara.cv/articles/seven-silent-failures.html" rel="noopener noreferrer"&gt;quantara.cv&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  the thing i built
&lt;/h2&gt;

&lt;p&gt;An agent with no chat box. An email arrives, and it wakes up on Cloud Run, reads the thread, checks my actual calendar, writes a proposal document, and prepares a reply — then stops and asks a human the moment it isn't sure. Nobody types anything. There is no prompt. The email &lt;em&gt;is&lt;/em&gt; the prompt.&lt;/p&gt;

&lt;p&gt;Gemini 3.5 Flash does the tool selection through function calling, Google's Agent Development Kit runs the loop, and three Cloud Run services keep ingress separate from execution. Pub/Sub carries events between them and Firestore holds the state. The &lt;a href="https://github.com/Metrix187/quantara-routing-engine" rel="noopener noreferrer"&gt;repo is here&lt;/a&gt; if you want the actual code.&lt;/p&gt;

&lt;p&gt;That's not what this piece is about. This is about the seven things that went wrong, because six of them went wrong &lt;em&gt;quietly&lt;/em&gt;, and that turned out to be the real lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  the one that shouted
&lt;/h2&gt;

&lt;p&gt;Exactly one failure announced itself. Line endings. I wrote the deploy scripts on Windows, git helpfully stored them with CRLF, and anyone cloning the repo would get &lt;code&gt;bad interpreter: no such file or directory&lt;/code&gt;. Ugly, obvious, fixed with a &lt;code&gt;.gitattributes&lt;/code&gt; in about ninety seconds.&lt;/p&gt;

&lt;p&gt;Every other failure was polite about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. the model that exists, but not here
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gemini-3.5-flash&lt;/code&gt; returns a 404 on Vertex AI in &lt;code&gt;us-central1&lt;/code&gt;. The error says &lt;em&gt;publisher model not found&lt;/em&gt;. That sentence sends you hunting for a typo in the model name, which is the wrong place to look — the model is fine, the region isn't. It's only served from the &lt;code&gt;global&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;I found it by brute force, asking three regions in a row. Nothing in the error text points at geography.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 and 6. iam, twice, the same way
&lt;/h2&gt;

&lt;p&gt;Two separate failures, identical shape. A service account gets created, the very next line binds a role to it, and the bind fails with &lt;em&gt;service account does not exist&lt;/em&gt;. It does exist. It just doesn't exist &lt;em&gt;yet&lt;/em&gt; — IAM is eventually consistent and the propagation window is tens of seconds.&lt;/p&gt;

&lt;p&gt;The first time this bit me, the failure was inside a line ending in &lt;code&gt;|| true&lt;/code&gt;. I'd written that to make the script re-runnable. What it actually did was swallow the error, skip creating the Pub/Sub subscription, and leave me with a deploy that printed three green service URLs and a completely disconnected pipeline.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every piece of visible config was correct. The thing simply did nothing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second time was Cloud Scheduler, which needs its own service agent to hold &lt;code&gt;serviceAccountTokenCreator&lt;/code&gt; on the account it impersonates. Without that grant: no scheduler log, no request at the target, no error anywhere. And while the grant propagates, it fails in exactly the same silent way — so I concluded it was broken and wrote it off. It started working four retries later.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. gmail won't tell you what arrived
&lt;/h2&gt;

&lt;p&gt;A Gmail push notification contains &lt;code&gt;{emailAddress, historyId}&lt;/code&gt;. That's it. No message ID, ever. To find out what actually landed you replay &lt;code&gt;users.history.list&lt;/code&gt; from a watermark you stored yourself, and one notification can cover several messages.&lt;/p&gt;

&lt;p&gt;I'd built the obvious version, passing the &lt;code&gt;historyId&lt;/code&gt; through as though it identified something. Every real trigger 404'd.&lt;/p&gt;

&lt;p&gt;The part worth sitting with: every test I'd written before that point passed, because every test supplied the message ID by hand. I had thoroughly tested a code path that production would never take.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. a container has no idea where it is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;datetime.now().astimezone()&lt;/code&gt; resolves to UTC on Cloud Run. So "available 9 to 5" quietly became 9–5 UTC, and my agent started offering a prospective client &lt;strong&gt;5 a.m.&lt;/strong&gt; meetings.&lt;/p&gt;

&lt;p&gt;It labelled them UTC correctly, too. The output looked right. It was just useless.&lt;/p&gt;

&lt;p&gt;This is the one I'd most likely have shipped, because it passes every test written by someone sitting in the same timezone as their server — which is to say, every test I would have thought to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. the guardrail worked; the output didn't
&lt;/h2&gt;

&lt;p&gt;The agent signed a draft &lt;code&gt;[Operator]&lt;/code&gt;. A literal placeholder, in an email a human was one click from sending.&lt;/p&gt;

&lt;p&gt;Nothing was broken. The escalation logic was working perfectly the entire time. The tool returned &lt;code&gt;ok=True&lt;/code&gt;, because the API call genuinely succeeded. A tool reporting success tells you the call went through, not that what came back was any good — and nobody had read the output yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. i proved it, wrote it down, and shipped it anyway
&lt;/h2&gt;

&lt;p&gt;This is the one I'd lead with.&lt;/p&gt;

&lt;p&gt;Pub/Sub delivers at least once, so every event eventually arrives twice. For an agent with side effects that's the difference between one draft and two. I handled it properly: before doing anything, the router performs an atomic Firestore &lt;code&gt;create()&lt;/code&gt; on a key derived from the event. The create fails if the key exists, and the failure is atomic, so there's no read-then-write race. I was pleased enough with this that I put the formula in the write-up.&lt;/p&gt;

&lt;p&gt;Then I sent two emails at once and the agent wrote two proposal documents for one of them.&lt;/p&gt;

&lt;p&gt;Gmail sends one notification per arriving message. Two emails, two notifications — and both replay the same history window, so both surface &lt;em&gt;both&lt;/em&gt; messages. I'd keyed the claim on &lt;code&gt;historyId + messageId&lt;/code&gt;. Two views of one message, two different keys, no collision, two drafts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The lock worked exactly as designed. It was guarding the wrong door.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The lesson isn't "handle at-least-once delivery." I &lt;em&gt;had&lt;/em&gt; handled it, and I had a proof. It's that a deduplication key has to name &lt;strong&gt;the thing with the side effect&lt;/strong&gt;, not the thing that told you about it. I'd tested Pub/Sub redelivery, which is the easy case and the one I'd thought of. The case that broke it only appears when two messages arrive close enough together to overlap their history windows — which is exactly what a demo does, and exactly what a quiet test inbox never does.&lt;/p&gt;

&lt;h2&gt;
  
  
  the pattern
&lt;/h2&gt;

&lt;p&gt;Six of seven failures produced no error. Not a caught exception, not a warning, not a log line. Several produced output that looked &lt;em&gt;correct&lt;/em&gt;: meeting times labelled with the right timezone, a deploy printing three healthy service URLs, a cron job sitting in the console marked &lt;code&gt;ENABLED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The common thread is that distributed systems fail by omission far more than by exception. A missing IAM grant and a still-propagating IAM grant are indistinguishable. A created cron job and a working cron job look identical until the day you need it. A tool returning &lt;code&gt;ok&lt;/code&gt; and a tool returning something useful are different claims.&lt;/p&gt;

&lt;p&gt;Which means the only thing that actually found these was running the real thing against real data and reading the output. Not tests — my tests passed throughout. Watching it work, end to end, and being suspicious of success.&lt;/p&gt;

&lt;h2&gt;
  
  
  what i'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never write &lt;code&gt;|| true&lt;/code&gt; without logging what it swallowed.&lt;/strong&gt; That one line cost me a disconnected pipeline that looked deployed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume IAM lags.&lt;/strong&gt; Poll for the resource before binding to it, instead of assuming that created means usable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test in the environment's timezone, not yours.&lt;/strong&gt; Or better: refuse to have a default. Business hours without a named zone are meaningless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the agent's actual output.&lt;/strong&gt; Every run. &lt;code&gt;ok=True&lt;/code&gt; is not a quality signal, and the placeholder signature proved it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your idempotency key after the side effect.&lt;/strong&gt; Not after the notification, not after the request. After the thing you'd hate to do twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent works now. It reads a real inbox, does real multi-step work on real APIs, and stops to ask a human when it isn't sure. That last behaviour is the one I'm most pleased with, and it's the only one that never broke.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>backend</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
