<?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: Don Johnson</title>
    <description>The latest articles on DEV Community by Don Johnson (@copyleftdev).</description>
    <link>https://dev.to/copyleftdev</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%2F965504%2Fefa29463-78d1-4437-83d5-23031ee8a3f6.jpg</url>
      <title>DEV Community: Don Johnson</title>
      <link>https://dev.to/copyleftdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/copyleftdev"/>
    <language>en</language>
    <item>
      <title>What Happens When You Give a Classifier Some Game Sense?</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:11:48 +0000</pubDate>
      <link>https://dev.to/copyleftdev/what-happens-when-you-give-a-classifier-some-game-sense-4ecp</link>
      <guid>https://dev.to/copyleftdev/what-happens-when-you-give-a-classifier-some-game-sense-4ecp</guid>
      <description>&lt;p&gt;I've been experimenting with a retro defense game in Rust called Signal / Siege. The question behind it is simple: how much can traditional game AI help a classifier?&lt;/p&gt;

&lt;p&gt;Incoming contacts approach a base. Some are hostile, others should pass safely. The defense can scan, intercept, or activate a shield, but energy is limited. Spending too much on one contact can leave it unprepared for the next.&lt;/p&gt;

&lt;p&gt;That creates two connected problems: estimating what a contact might be, and deciding what to do about it.&lt;/p&gt;

&lt;p&gt;The video above shows three approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bayes + Utility:&lt;/strong&gt; Bayesian classification paired with a system that scores possible actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jev + Rules:&lt;/strong&gt; Jev classification paired with simple rules and Bayesian backup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jev + Hybrid:&lt;/strong&gt; Jev classification supported by action scoring and short forecasts of defensive opportunities and energy needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The supporting techniques come from the traditional game AI toolkit. They help the defense weigh consequences, conserve energy, and recognize when waiting could be more useful than acting immediately. Jev's underlying model remains unchanged.&lt;/p&gt;

&lt;p&gt;In the recorded demonstration, the Bayesian and hybrid systems survived. Jev with simple rules lost the base.&lt;/p&gt;

&lt;p&gt;We also ran a separate, matched offline comparison across 256 fresh seeds. Using a fixed set of captured Jev responses and 200 milliseconds of simulated latency, the rules approach won 112 matches. Adding action scoring and forecasting brought that to 255.&lt;/p&gt;

&lt;p&gt;Those were replay-based experiments, not hundreds of live API games. The Jev panels in the video replay actual API-backed matches. The video's three modes have different scanning policies, and the adaptive opponent can respond differently as each match unfolds.&lt;/p&gt;

&lt;p&gt;The Bayesian reference also knows the game's synthetic observation model, giving it an advantage in this setting. These results don't establish a general ranking of classifiers.&lt;/p&gt;

&lt;p&gt;What interests me is the division of work: a classifier contributes a judgment, while the surrounding system manages timing, costs, and consequences.&lt;/p&gt;

&lt;p&gt;Next, I want to test that relationship with richer observations and less predictable scenarios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=PKt8OpnVrk8" rel="noopener noreferrer"&gt;Watch the side-by-side experiment on YouTube&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Stop Guessing. Let the Algorithm Find the Sweet Spot.</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Thu, 15 Oct 2026 23:38:36 +0000</pubDate>
      <link>https://dev.to/copyleftdev/stop-guessing-let-the-algorithm-find-the-sweet-spot-3akd</link>
      <guid>https://dev.to/copyleftdev/stop-guessing-let-the-algorithm-find-the-sweet-spot-3akd</guid>
      <description>&lt;p&gt;&lt;em&gt;Simulated annealing gives a search permission to get worse before it gets better. The useful part is learning when that permission pays off.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a familiar ritual in software engineering: increase the thread count, change the batch size, run the benchmark, and squint at the result. Then change another number.&lt;/p&gt;

&lt;p&gt;Eventually, something looks better, and we call it optimized.&lt;/p&gt;

&lt;p&gt;But better than the previous attempt is a low bar. How much of the search space did we explore? What did each experiment cost? Does the improvement survive a different workload?&lt;/p&gt;

&lt;p&gt;Those questions are the difference between tuning by habit and running an optimization experiment.&lt;/p&gt;

&lt;p&gt;Simulated annealing is one way to run that experiment. Its most interesting feature is also its least intuitive: sometimes it accepts a worse candidate on purpose.&lt;/p&gt;

&lt;p&gt;Let's examine why, implement the rule in Rust, and compare it with greedy search, random search, and a grid. Every method gets the same evaluation budget. The experiment is small enough to run locally and check exhaustively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the first valley can trap you
&lt;/h2&gt;

&lt;p&gt;Imagine walking through a mountain range at night, trying to find its lowest point. You can see nearby terrain, so you keep taking downhill steps.&lt;/p&gt;

&lt;p&gt;Eventually, every nearby step leads uphill. You have found a local minimum. Another valley might be deeper, but your rule prevents you from crossing the ridge.&lt;/p&gt;

&lt;p&gt;That is the problem with accepting only immediate improvements. Whether a solution is a local minimum also depends on the moves your search permits. A larger jump might cross a barrier that a small step cannot.&lt;/p&gt;

&lt;p&gt;Simulated annealing sometimes accepts uphill moves. It borrows its name from the connection between cooling physical materials and searching for low-energy configurations, developed in &lt;a href="https://doi.org/10.1126/science.220.4598.671" rel="noopener noreferrer"&gt;Kirkpatrick, Gelatt, and Vecchi's 1983 paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the algorithm, energy becomes an objective score. Temperature becomes a parameter controlling the willingness to accept regressions. Cooling gradually reduces that willingness.&lt;/p&gt;

&lt;p&gt;This creates a route out of some local minima. A finite run can still miss the best solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The acceptance rule
&lt;/h2&gt;

&lt;p&gt;Suppose lower scores are better. Calculate the change from the current candidate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delta = score(candidate) - score(current)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept improvements and ties. For a worse candidate, draw a uniform random number between zero inclusive and one exclusive, then accept when it is smaller than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p = exp(-delta / temperature)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the usual Metropolis acceptance rule for symmetric proposals, used in the &lt;a href="https://cgi.cse.unsw.edu.au/~cs4601/refs/papers/KirkpatrickGelattVecchi83.pdf" rel="noopener noreferrer"&gt;original simulated annealing paper&lt;/a&gt;. Symmetric means the chance of proposing a move equals the chance of proposing its reverse.&lt;/p&gt;

&lt;p&gt;For a regression of two score units, the probability is about 82% at temperature 10, 14% at temperature 1, and effectively zero at temperature 0.1.&lt;/p&gt;

&lt;p&gt;Temperature has the same units as the score. Multiply every score by 1,000 and keep the old temperature, and you have changed the search's behavior. A cooling schedule is a design choice, not a magic constant.&lt;/p&gt;

&lt;p&gt;Also keep two records: where the search is now and the best candidate it has ever found. Exploring a worse configuration should not erase a better answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define what good means before searching
&lt;/h2&gt;

&lt;p&gt;Consider these hypothetical service settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Allowed values&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Worker threads&lt;/td&gt;
&lt;td&gt;Every integer from 1 to 64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch size&lt;/td&gt;
&lt;td&gt;Every integer from 32 to 4,096&lt;/td&gt;
&lt;td&gt;4,065&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout&lt;/td&gt;
&lt;td&gt;50 to 2,000 milliseconds, stepping by 10&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is &lt;strong&gt;50,991,360 configurations&lt;/strong&gt;. At one evaluation per second, sequential exhaustive search would take about &lt;strong&gt;590 days&lt;/strong&gt;, before repeats or setup costs.&lt;/p&gt;

&lt;p&gt;The hard part starts before choosing a search algorithm. What makes a configuration good?&lt;/p&gt;

&lt;p&gt;One possible objective is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score = 0.5 * (p99_latency / reference_latency)
      + 0.3 * (memory / reference_memory)
      - 0.2 * (throughput / reference_throughput)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix those positive reference values before the experiment. The ratios are dimensionless; the weights express your priorities. Changing the scales during the search would make scores from different iterations incomparable.&lt;/p&gt;

&lt;p&gt;Hard requirements need separate treatment. If a configuration violates a memory cap, correctness invariant, or latency limit, reject it as infeasible. A throughput gain should not quietly compensate for breaking a requirement.&lt;/p&gt;

&lt;p&gt;Even among feasible candidates, there may be several reasonable trade-offs. A candidate lies on the Pareto frontier if no feasible alternative improves an objective without worsening another. A weighted score expresses a preference among trade-offs; it does not discover your priorities for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most important function is the one that decides what good looks like.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the algorithm something we can verify
&lt;/h2&gt;

&lt;p&gt;For an executable demonstration, use a smaller problem where we can check the answer exhaustively.&lt;/p&gt;

&lt;p&gt;The companion Rust project searches a 64 by 64 grid of abstract parameter indices. Each index maps onto a coordinate between -5.12 and 5.12. Its objective is the two-dimensional &lt;a href="https://www.sfu.ca/~ssurjano/rastr.html" rel="noopener noreferrer"&gt;Rastrigin function&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E(x, y) = x*x + y*y + 20
        - 10*cos(2*pi*x) - 10*cos(2*pi*y)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quadratic terms pull the search toward the center. The cosine terms add many local valleys. These scores have no latency, throughput, or dollar units. This is a deterministic mathematical experiment.&lt;/p&gt;

&lt;p&gt;The continuous function reaches zero at the origin. Our even-sized grid does not contain the origin, so we enumerate all 4,096 grid points to find the discrete optimum. That enumeration is a separate reference calculation, unavailable to the search algorithms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgbxylkwyd73h0wbk1aob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgbxylkwyd73h0wbk1aob.png" alt="Heatmap of the synthetic Rastrigin objective and a one-dimensional slice showing many local valleys." width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The comparison fixes these choices before running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;256 objective evaluations per run&lt;/strong&gt;, including the initial candidate and repeated proposals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same initial point&lt;/strong&gt;, index &lt;code&gt;(63, 63)&lt;/code&gt;, for each method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30 seeds&lt;/strong&gt;, numbered 1 through 30, for the stochastic methods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annealing and greedy search share a neighborhood&lt;/strong&gt;: move one index by a signed step of 1 through 8. An out-of-range proposal stays in place and still costs an evaluation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annealing cools geometrically from 20 to 0.1&lt;/strong&gt; over its 255 proposals. Greedy search accepts only improvements or ties.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random search samples the whole grid with replacement.&lt;/strong&gt; Grid search uses a fixed 16 by 16 subset, rounding evenly spaced coordinates to integer indices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Equal evaluation counts make one cost comparable. They do not give all methods identical information or movement rules. The starting point, neighborhood, grid spacing, and cooling schedule remain consequential choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rust loop
&lt;/h2&gt;

&lt;p&gt;The project has no external Rust dependencies. Its seeded generator makes the search repeatable on the tested toolchain. This is the loop from the implementation; validation, random generation, and candidate construction live in the companion source.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Minimize an objective while retaining the best state ever visited.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;anneal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Clone&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nf"&gt;FnMut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nf"&gt;FnMut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Rng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="nf"&gt;.validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rng&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;current_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;finite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Outcome&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;best_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;accepted_uphill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="py"&gt;.evaluations&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidate_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;finite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.evaluations&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;finite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate_score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;current_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="nf"&gt;.temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.unit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.exp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.accepted_uphill&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nn"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;current_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate_score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.best_score&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.best_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boundary handling matters. We choose signed moves with equal probability and leave invalid moves at the current point. Simply clamping an arbitrary move to the nearest boundary would accumulate probability there and lose the symmetry assumed by this acceptance rule.&lt;/p&gt;

&lt;p&gt;The implementation also rejects invalid settings and nonfinite objective values, counts evaluations explicitly, and tests that a temporarily worse current state cannot overwrite the best result.&lt;/p&gt;

&lt;p&gt;A separate unit test makes the barrier explicit: three adjacent states have costs &lt;code&gt;1, 2, 0&lt;/code&gt;. Starting at the first state, greedy search cannot cross the middle state. With the test's fixed seed and temperature, annealing accepts the uphill move and then reaches zero. That tiny fixture verifies the mechanism; it is not evidence that annealing usually wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it and compare the evidence
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/copyleftdev/simulated-annealing-article" rel="noopener noreferrer"&gt;companion project&lt;/a&gt; includes the Rust source, raw results, an independent results checker, and a plotting script. Clone it and run:&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/copyleftdev/simulated-annealing-article.git
&lt;span class="nb"&gt;cd &lt;/span&gt;simulated-annealing-article
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CARGO_TARGET_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;target
cargo &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt;
cargo run &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; results/raw.csv
python3 scripts/verify.py &lt;span class="nt"&gt;--reproduce&lt;/span&gt;
python3 scripts/analyze.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python analysis uses the standard library. Add Matplotlib to regenerate the figures with &lt;code&gt;python3 scripts/analyze.py --plots&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Median best score&lt;/th&gt;
&lt;th&gt;Best to worst score&lt;/th&gt;
&lt;th&gt;Exact grid optimum hits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Annealing&lt;/td&gt;
&lt;td&gt;2.565&lt;/td&gt;
&lt;td&gt;2.565 to 2.565&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greedy&lt;/td&gt;
&lt;td&gt;2.565&lt;/td&gt;
&lt;td&gt;2.565 to 2.565&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random&lt;/td&gt;
&lt;td&gt;4.220&lt;/td&gt;
&lt;td&gt;2.565 to 8.425&lt;/td&gt;
&lt;td&gt;5/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coarse grid&lt;/td&gt;
&lt;td&gt;3.480&lt;/td&gt;
&lt;td&gt;3.480 to 3.480&lt;/td&gt;
&lt;td&gt;No (deterministic)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lower is better. The exhaustive grid optimum is &lt;strong&gt;2.564508&lt;/strong&gt;, and both annealing and greedy search reached it in all 30 runs. Annealing accepted between 14 and 33 uphill moves per run, but those moves did not improve its final score over greedy search here.&lt;/p&gt;

&lt;p&gt;That result is a useful correction to the valley metaphor. Our neighborhood allows jumps of up to eight index positions. The cosine ripples repeat roughly every six positions, so greedy search can jump across hills without accepting a worse landing point. This experiment gives us no reason to prefer annealing over this greedy baseline on final score.&lt;/p&gt;

&lt;p&gt;Random search reached the optimum in five runs. The coarse grid returned the same result every time; repeating it creates no new statistical evidence. Its chosen spacing misses the central indices 31 and 32, so its result is sensitive to grid alignment. These are observed score ranges, not confidence intervals or a universal ranking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpios7a2xnclunp7w7sjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpios7a2xnclunp7w7sjq.png" alt="Scores from 30 seeds: annealing and greedy reach the discrete optimum in every run; random search varies; the coarse grid has one fixed result." width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The raw CSV also records search duration. With such a cheap objective, optimizer overhead and machine conditions can substantially affect timing. An evaluation count is not a measurement of infrastructure savings.&lt;/p&gt;

&lt;p&gt;There is a second check: after each run selects its best candidate, the harness shifts each coordinate by either -0.1 or +0.1, evaluates all four combinations, and records their mean score. These four evaluations are outside the 256-call search budget and its timer, and the search never sees their scores. This tests sensitivity to a small, specified change in the formula. It does not simulate production traffic or establish robustness to measurement noise.&lt;/p&gt;

&lt;p&gt;That distinction matters. Thirty seeds describe variation in search outcomes on one fixed problem. They are not thirty independent workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try breaking the optimizer
&lt;/h2&gt;

&lt;p&gt;A passing test suite deserves a challenge too. We used &lt;a href="https://mutants.rs/" rel="noopener noreferrer"&gt;cargo-mutants&lt;/a&gt; to alter the library and rerun its tests. The original eight Rust tests missed 53 of 181 generated mutations, including deleting the minus sign in the acceptance formula.&lt;/p&gt;

&lt;p&gt;After adding acceptance and contract tests, &lt;strong&gt;153 mutations triggered test failures&lt;/strong&gt;. Two survived: they permute symmetric terms in the shifted-score average, preserving its mathematical value. Another 25 did not compile, and one caused a nonterminating loop detected by a timeout. We report those categories separately in the &lt;a href="https://github.com/copyleftdev/simulated-annealing-article/blob/main/docs/mutation-validation.md" rel="noopener noreferrer"&gt;mutation review&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The statistical check drives the real kernel through &lt;strong&gt;one million uphill proposals&lt;/strong&gt; across five predeclared temperatures. At a score difference of 2 and temperature 2, expected acceptance is about 36.79%; observed acceptance was 36.988%. Every case fell within the predeclared tolerance of about 0.635 percentage points.&lt;/p&gt;

&lt;p&gt;That tolerance uses a Hoeffding bound under ideal independent random draws. Our fixed pseudorandom streams make this a reproducible regression check, not a certification of randomness. The &lt;a href="https://github.com/copyleftdev/simulated-annealing-article/blob/main/docs/statistical-validation.md" rel="noopener noreferrer"&gt;statistical review&lt;/a&gt; records all cases and assumptions. The expanded suite has 23 tests; production optimizer code and benchmark results are unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optimizer proposes. The testing harness disposes.
&lt;/h2&gt;

&lt;p&gt;A real service adds a complication: measurements fluctuate.&lt;/p&gt;

&lt;p&gt;A noisy score can make a regression look like an improvement. Searching thousands of configurations also creates thousands of opportunities to get lucky. The most impressive single measurement may be the least trustworthy result.&lt;/p&gt;

&lt;p&gt;For service tuning, make the evaluator earn its authority:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fix the workload, objective, constraints, and search budget before comparing methods.&lt;/li&gt;
&lt;li&gt;Repeat measurements, control warmup and machine conditions, and interleave candidates when drift could bias the comparison. Charge those repetitions to the evaluation budget.&lt;/li&gt;
&lt;li&gt;Evaluate finalists on reserved workloads, including skewed inputs, contention, and failure cases. Report variability alongside the central estimate.&lt;/li&gt;
&lt;li&gt;Check correctness and operational limits before promoting a candidate. Keep a rollback path and monitor the deployed result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once reserved workloads influence another round of tuning, they become development data. A fresh evaluation set is needed for a fresh generalization claim.&lt;/p&gt;

&lt;p&gt;Accepting a worse candidate is a search decision inside an evaluation environment. It does not require deploying that candidate to customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this becomes an engineering habit
&lt;/h2&gt;

&lt;p&gt;The same framing gives several everyday tuning problems a concrete shape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Candidate decisions&lt;/th&gt;
&lt;th&gt;Objective and constraints&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service performance&lt;/td&gt;
&lt;td&gt;Workers, batching, queue depth&lt;/td&gt;
&lt;td&gt;Minimize cost per completed request while meeting latency and correctness limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI agent orchestration&lt;/td&gt;
&lt;td&gt;Model routing, retry budget, verification threshold&lt;/td&gt;
&lt;td&gt;Minimize cost on a fixed task set while meeting quality and latency requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test scheduling&lt;/td&gt;
&lt;td&gt;Test order and assignment to workers&lt;/td&gt;
&lt;td&gt;Minimize completion time while preserving the required suite and dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure sizing&lt;/td&gt;
&lt;td&gt;Instance mix, capacity, scaling thresholds&lt;/td&gt;
&lt;td&gt;Minimize cost under representative demand while meeting capacity and recovery requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Applying search to these problems requires a candidate representation, legal moves, and a trustworthy evaluator. In an agent system, for example, a cheap answer is useful only if the quality check deserves trust. Evaluators can be fooled or overfit too.&lt;/p&gt;

&lt;p&gt;The separation is useful: a search proposes configurations, an evaluator measures outcomes, and a deployment process decides what can ship.&lt;/p&gt;

&lt;p&gt;You do not need to adopt annealing to adopt that architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make annealing earn its place
&lt;/h2&gt;

&lt;p&gt;Start with a baseline you can understand. If the space is small, enumerate it. If a coarse grid or random search gives good results within the budget, that is valuable evidence.&lt;/p&gt;

&lt;p&gt;Annealing becomes interesting when useful local moves exist but a search that accepts only improvements gets trapped. Its neighborhood and temperature schedule still need attention. If objective evaluations are expensive, compare methods designed to spend fewer evaluations. If useful gradients exist, consider methods that exploit them.&lt;/p&gt;

&lt;p&gt;The cost of tuning the optimizer also belongs in the accounting. Trying twenty cooling schedules and reporting only the best is another search, with another budget.&lt;/p&gt;

&lt;p&gt;The engineering habit is simple: define success, constrain the experiment, compare alternatives, and keep the evidence. Repeat when the environment changes.&lt;/p&gt;

&lt;p&gt;Sometimes a better answer is on the other side of a hill. Sometimes a simpler search already finds it. Measure which situation you are in.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What configuration have you spent hours tuning manually, and what would a trustworthy objective function for it look like?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: AI agents drafted and revised this article, implemented the experiment, and performed technical review. The numerical results come from executing the accompanying code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>algorithms</category>
      <category>rust</category>
      <category>performance</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Sun, 11 Oct 2026 02:33:16 +0000</pubDate>
      <link>https://dev.to/copyleftdev/-36oh</link>
      <guid>https://dev.to/copyleftdev/-36oh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k" class="crayons-story__hidden-navigation-link"&gt;Evidence-Driven Development: Give Your Coding Agent Something to Prove&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/copyleftdev" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F965504%2Fefa29463-78d1-4437-83d5-23031ee8a3f6.jpg" alt="copyleftdev profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/copyleftdev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Don Johnson
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Don Johnson
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4826732" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/copyleftdev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F965504%2Fefa29463-78d1-4437-83d5-23031ee8a3f6.jpg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Don Johnson&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k" id="article-link-4826732"&gt;
          Evidence-Driven Development: Give Your Coding Agent Something to Prove
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/testing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;testing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;11&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              9&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>agents</category>
      <category>ai</category>
      <category>coding</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Evidence-Driven Development: Give Your Coding Agent Something to Prove</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Sat, 10 Oct 2026 05:28:21 +0000</pubDate>
      <link>https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k</link>
      <guid>https://dev.to/copyleftdev/evidence-driven-development-give-your-coding-agent-something-to-prove-1h4k</guid>
      <description>&lt;p&gt;An AI coding agent finishes the feature. The tests pass. The explanation sounds reasonable.&lt;/p&gt;

&lt;p&gt;Then someone asks a small question: &lt;strong&gt;“What happens if I run that request again?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question can change the shape of a project. Now “done” needs to mean something observable. The answer needs an experiment, and the experiment needs a way to be wrong.&lt;/p&gt;

&lt;p&gt;This tutorial builds a tiny task list around that idea. By the end, you will have a working application, two deliberately broken versions, and a folder of evidence showing which promises each version keeps.&lt;/p&gt;

&lt;p&gt;I use &lt;strong&gt;evidence-driven development&lt;/strong&gt; to describe this working habit: write down a claim, decide what would disprove it, run the relevant experiment, and let the result govern the next decision.&lt;/p&gt;

&lt;p&gt;The coding agent can help at every step. The evidence must remain inspectable without trusting the agent's summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, some credit for the idea
&lt;/h2&gt;

&lt;p&gt;The name predates coding agents. Jan Bosch's 2017 book, &lt;em&gt;Speed, Data, and Ecosystems&lt;/em&gt;, includes an Evidence-Driven Development chapter. Its contents connect requirements, hypotheses and experiments. I'm using the term for a practical agent-assisted workflow, without claiming to have invented a methodology. &lt;a href="https://www.routledge.com/Speed-Data-and-Ecosystems-Succeeding-in-a-Software-Centric-World/Bosch/p/book/9781315270685" rel="noopener noreferrer"&gt;Publisher's catalog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is related work in AI, too. Xia and colleagues describe evaluation-driven development and operations, or EDDOps, as a continuing feedback loop for LLM agents. Their paper addresses a broader agent lifecycle than the small application we will build here. &lt;a href="https://arxiv.org/abs/2411.13768" rel="noopener noreferrer"&gt;EDDOps paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TDD still gives us a useful red–green–refactor loop. BDD helps express behavior through examples people can discuss. In this tutorial, EDD names the discipline of keeping the claim, execution conditions, observed result and resulting decision connected. Those practices fit together. &lt;a href="https://agilealliance.org/glossary/tdd/" rel="noopener noreferrer"&gt;TDD&lt;/a&gt;, &lt;a href="https://agilealliance.org/glossary/given-when-then/" rel="noopener noreferrer"&gt;Given–When–Then&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small application with a real promise
&lt;/h2&gt;

&lt;p&gt;Our app is &lt;strong&gt;Tiny Tasks&lt;/strong&gt;. It adds tasks and lists them. Python and SQLite are enough; the application and experiment runner use only the Python standard library. Use Python 3.10 or newer with SQLite support.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/copyleftdev/evidence-driven-development" rel="noopener noreferrer"&gt;companion repository&lt;/a&gt; contains the complete source, cover artwork and recorded evidence. Clone it to follow along:&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/copyleftdev" rel="noopener noreferrer"&gt;
        copyleftdev
      &lt;/a&gt; / &lt;a href="https://github.com/copyleftdev/evidence-driven-development" rel="noopener noreferrer"&gt;
        evidence-driven-development
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A runnable evidence-driven development tutorial: falsifiable claims, SQLite retries, negative controls, and reproducible results.
    &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;Evidence-Driven Development: Tiny Tasks&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A standalone companion project for &lt;strong&gt;Evidence-Driven Development: Give Your Coding Agent
Something to Prove&lt;/strong&gt;. A tiny local task list teaches explicit claims, negative controls
fresh-process experiments, nonzero exercise gates and reproducible evidence.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/copyleftdev/evidence-driven-development/article/cover-particles-v3.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcopyleftdev%2Fevidence-driven-development%2FHEAD%2Farticle%2Fcover-particles-v3.png" alt="Evidence-Driven Development: Give your coding agent something to prove."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Requires Python 3.10+ with its standard SQLite module. No application dependencies,
API keys, model account, network service or cloud resources are required.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/copyleftdev/evidence-driven-development.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; evidence-driven-development
python3 -m tiny_tasks add &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Water plants&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; --request-id plant-1
python3 -m tiny_tasks add &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Water plants&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; --request-id plant-1
python3 -m tiny_tasks list
python3 -m unittest discover -s tests -v
python3 scripts/prove.py&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The second create returns the original task with &lt;code&gt;created: false&lt;/code&gt;. Use a new request ID
for a genuinely new action. Titles are compared after trimming surrounding whitespace
Default data: &lt;code&gt;.local/tasks.sqlite&lt;/code&gt;; set a different file using &lt;code&gt;--db PATH&lt;/code&gt; before the
&lt;code&gt;add&lt;/code&gt; or &lt;code&gt;list&lt;/code&gt; subcommand. Conflicting request reuse and invalid input return…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/copyleftdev/evidence-driven-development" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/copyleftdev/evidence-driven-development.git
&lt;span class="nb"&gt;cd &lt;/span&gt;evidence-driven-development
git checkout 1f03111db02d359b24602992eddae60122d49d74
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; tiny_tasks add &lt;span class="s2"&gt;"Water plants"&lt;/span&gt; &lt;span class="nt"&gt;--request-id&lt;/span&gt; plant-1
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; tiny_tasks list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The checkout selects the source revision used in this article. The &lt;code&gt;add&lt;/code&gt; command returns a task. The &lt;code&gt;list&lt;/code&gt; command starts a new process and reads it back. The default database lives at &lt;code&gt;.local/tasks.sqlite&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The request ID belongs to the user's intended action. When the caller retries that same action, it sends the same ID. A genuinely new task gets a new ID—even if the title happens to match.&lt;/p&gt;

&lt;p&gt;That small distinction gives us something worth proving.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Write a claim that can lose
&lt;/h2&gt;

&lt;p&gt;“Make the task list reliable” leaves too much room for interpretation.&lt;/p&gt;

&lt;p&gt;Here is the experiment contract we wrote before implementing the application:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;A result that disproves it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Acknowledged tasks survive a process ending&lt;/td&gt;
&lt;td&gt;A new process cannot read the saved task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeating a create request returns the original task&lt;/td&gt;
&lt;td&gt;A retry creates another task or returns a different ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A request ID cannot silently acquire a different meaning&lt;/td&gt;
&lt;td&gt;Reusing it with a different title succeeds or changes saved data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid input leaves the list alone&lt;/td&gt;
&lt;td&gt;An empty title adds a task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The complete contract is embedded here, with a &lt;a href="https://github.com/copyleftdev/evidence-driven-development/blob/1f03111db02d359b24602992eddae60122d49d74/experiments/001-reliable-tasks/experiment.json" rel="noopener noreferrer"&gt;commit-pinned copy&lt;/a&gt; in the repository:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The repository records those claims in &lt;code&gt;experiments/001-reliable-tasks/experiment.json&lt;/code&gt;. It also declares three restart rounds, eight concurrent retry processes, two independent repetitions, and the scenarios that must actually execute.&lt;/p&gt;

&lt;p&gt;These numbers are a small teaching workload. They are not a statistical reliability estimate or a throughput target.&lt;/p&gt;

&lt;p&gt;Before asking an agent for code, give it this kind of task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a local task list that adds and lists tasks.

First propose observable claims and the outcomes that would disprove them.
Preserve acknowledged tasks across fresh processes.
Bind each create request ID to one normalized title and one task.
Exercise concurrent retries and conflicting reuse.

Keep the application small. Use local synthetic data.
Do not claim properties the experiment does not exercise.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Review the claims yourself. An agent that quietly narrows “survives interruption” into “works twice in the same object” can produce a beautiful test for the wrong promise.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Give the agent a place to leave evidence
&lt;/h2&gt;

&lt;p&gt;The project has a few small documents with distinct jobs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AGENTS.md                         working rules and commands
docs/plan.md                      task progress and handoff
docs/decisions.md                 decisions and their reasons
experiments/001-reliable-tasks/
  experiment.json                claims written before code
  README.md                      findings and limitations
  runs/&amp;lt;run-id&amp;gt;/                  actual outputs and source hashes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; tells an agent where these things live. The experiment file defines the claim. The run directory records what happened. The decision document explains what we chose because of it.&lt;/p&gt;

&lt;p&gt;You can start with a short instruction:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read the experiment contract before changing behavior.
Run the declared scenarios and keep failed outputs.
Create a new run directory; never replace an earlier run.
Report which claims passed, which failed, and what was not tested.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instructions need maintenance. If a useful constraint already lives in a canonical document, link to it. A growing pile of repeated instructions can make the next agent's job harder. OpenAI's current guidance similarly favors focused instructions and task-specific context. &lt;a href="https://developers.openai.com/blog/rethinking-skills-and-prompts-for-gpt-6-astra" rel="noopener noreferrer"&gt;Guidance on skills and project instructions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Implement the smallest useful design
&lt;/h2&gt;

&lt;p&gt;Tiny Tasks stores each task alongside its request ID. A unique database constraint prevents two stored rows from sharing that ID.&lt;/p&gt;

&lt;p&gt;Creation uses one transaction. It looks for the ID, compares the normalized title if the ID exists, and inserts only when it is new. The command returns success after the transaction commits.&lt;/p&gt;

&lt;p&gt;The complete &lt;code&gt;tiny_tasks/store.py&lt;/code&gt; implementation is embedded below. You can also read the &lt;a href="https://github.com/copyleftdev/evidence-driven-development/blob/1f03111db02d359b24602992eddae60122d49d74/tiny_tasks/store.py" rel="noopener noreferrer"&gt;commit-pinned source&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;“Normalized” has an explicit meaning here: the app removes leading and trailing whitespace before comparing titles. The &lt;code&gt;add&lt;/code&gt; method shows the transaction boundary; validation, connection setup and listing are included so you can inspect the whole example.&lt;/p&gt;

&lt;p&gt;That boundary matters because checking in one transaction and inserting later would leave room for another process to intervene. SQLite documents the write-transaction behavior of &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; in its &lt;a href="https://www.sqlite.org/lang_transaction.html" rel="noopener noreferrer"&gt;transaction reference&lt;/a&gt;. Still, plausible code is only a candidate explanation. We have to run the workload we promised to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cross the boundary you are making a claim about
&lt;/h2&gt;

&lt;p&gt;A test can create a &lt;code&gt;Store&lt;/code&gt;, add a task and immediately list it. That is useful. It does not establish what happens in a new process.&lt;/p&gt;

&lt;p&gt;Our experiment runner invokes the actual CLI in subprocesses. Every command starts fresh. For the restart scenario, it adds three tasks in sequence, checking after each successful create that a separate process can read the expected list.&lt;/p&gt;

&lt;p&gt;For retries, it exercises two different situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A create succeeds, its caller ignores the response, and a fresh process repeats the request.&lt;/li&gt;
&lt;li&gt;Eight processes submit the same request, launched through a synchronization barrier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second scenario checks the returned identities, the number of &lt;code&gt;created: true&lt;/code&gt; responses and the rows actually saved. Exactly one task should exist.&lt;/p&gt;

&lt;p&gt;The ignored response is a controlled stand-in for a caller that is unsure whether an action succeeded. We did not cut a network connection or kill a process during a commit. Keeping that distinction visible makes the result useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Break the application on purpose
&lt;/h2&gt;

&lt;p&gt;Now comes my favorite part: establish that the experiment can catch the defect it claims to detect.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;lab/&lt;/code&gt; directory contains two intentionally wrong implementations. They use the same command shape as the real app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volatile:&lt;/strong&gt; returns successful creates but keeps no durable tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates:&lt;/strong&gt; saves tasks but ignores request identity, inserting a row on every retry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are negative controls: known-bad examples that the checks must reject for a specific reason. They are teaching fixtures, not bugs we are pretending to have discovered accidentally.&lt;/p&gt;

&lt;p&gt;The volatile version must fail the restart claim. The duplicate-accepting version must fail the concurrent retry claim. A syntax error in a broken version would not establish either property, so inspect the saved responses and failure reasons.&lt;/p&gt;

&lt;p&gt;Review caught a weakness in the first version of the gate: it accepted a control's failure without checking its reason. A child-process crash could have masqueraded as a successful bug detection. We added regression tests, required nonzero exercise counts for the controls, and made the gate check the targeted failure reason. The earlier run remains in the repository, with that gate limitation recorded.&lt;/p&gt;

&lt;p&gt;There is another way to get a misleading green result: execute nothing.&lt;/p&gt;

&lt;p&gt;Our runner tracks how many times each required scenario ran. This intentionally incomplete report must fail even though every claimed outcome says &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required_scenarios&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exercised&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required_scenarios&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A check that refuses zero activity is an &lt;strong&gt;exercise gate&lt;/strong&gt;. It answers a basic question before interpreting success: did we actually attempt the scenarios our conclusion depends on?&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Run it and keep the receipts
&lt;/h2&gt;

&lt;p&gt;From the companion project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests &lt;span class="nt"&gt;-v&lt;/span&gt;
python3 scripts/prove.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command runs nine tests: six for application behavior and three for the harness. The second runs the process-boundary experiment, both negative-control implementations and the empty-exercise control.&lt;/p&gt;

&lt;p&gt;In the recorded run, the results were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;th&gt;Restart&lt;/th&gt;
&lt;th&gt;Retry after ignored response&lt;/th&gt;
&lt;th&gt;Concurrent retry&lt;/th&gt;
&lt;th&gt;Conflicting reuse&lt;/th&gt;
&lt;th&gt;Invalid input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Volatile control&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate-accepting control&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite candidate&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each implementation ran twice. The pass/fail outcomes and exercise counts matched between repetitions. The SQLite candidate exercised three restart rounds, one ignored-response retry, eight concurrent submissions, one conflicting reuse and one invalid-input scenario per repetition.&lt;/p&gt;

&lt;p&gt;The two controls failed where intended. The report with zero exercised scenarios was rejected. Those facts allow the overall experiment gate to pass.&lt;/p&gt;

&lt;p&gt;You will find a new directory under &lt;code&gt;experiments/001-reliable-tasks/runs/&lt;/code&gt;. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A manifest with Python and SQLite versions and hashes of the application, harness and contract.&lt;/li&gt;
&lt;li&gt;A response trace and scenario outcomes for each implementation and repetition.&lt;/li&gt;
&lt;li&gt;A summary with the overall decision and its scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The captured run used for this article is &lt;code&gt;20261010T042729Z-633b0705&lt;/code&gt;. Your run will have its own ID. The project preserves the &lt;a href="https://github.com/copyleftdev/evidence-driven-development/tree/1f03111db02d359b24602992eddae60122d49d74/experiments/001-reliable-tasks/runs/20261010T042729Z-633b0705" rel="noopener noreferrer"&gt;original run directory&lt;/a&gt; so you can inspect the evidence behind this table, including its &lt;a href="https://github.com/copyleftdev/evidence-driven-development/blob/1f03111db02d359b24602992eddae60122d49d74/experiments/001-reliable-tasks/runs/20261010T042729Z-633b0705/summary.json" rel="noopener noreferrer"&gt;summary&lt;/a&gt; and &lt;a href="https://github.com/copyleftdev/evidence-driven-development/blob/1f03111db02d359b24602992eddae60122d49d74/experiments/001-reliable-tasks/runs/20261010T042729Z-633b0705/manifest.json" rel="noopener noreferrer"&gt;source manifest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The hashes help identify the source files used in a run. They do not establish who performed it, and someone who can rewrite both the files and their manifest can forge a matching set. This is reproducible local evidence, not a signed attestation system.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Make a decision the evidence supports
&lt;/h2&gt;

&lt;p&gt;We can now choose SQLite for this local application's declared process and retry behavior.&lt;/p&gt;

&lt;p&gt;We have not tested power failure, a corrupt disk, a network filesystem, large task volumes, security boundaries or sustained throughput. We have not measured how often different coding models produce a correct implementation. Those would be separate questions with different experiments.&lt;/p&gt;

&lt;p&gt;This is also where you resist rounding a result into the outcome you wanted. If a target is missed, keep the result and the original threshold visible. A follow-up experiment can ask a better question; it should have a new identity.&lt;/p&gt;

&lt;p&gt;A useful agent handoff looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implemented: persistent task creation and payload-bound retries.
Verified: the recorded process-boundary scenarios and negative controls.
Evidence: the named run directory and source manifest.
Not tested: power loss, disk corruption, throughput or model reliability.
Decision: use this implementation for the documented local scope.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough for another developer—or another agent—to pick up the work without inheriting an unsupported “everything works.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when agents do more of the work?
&lt;/h2&gt;

&lt;p&gt;An agent can propose the claims, build the application, write the harness and summarize the run. That is convenient, but all four can share the same mistaken assumption.&lt;/p&gt;

&lt;p&gt;Give the claim a review before implementation. Inspect the failure cases. Keep an external observation boundary: a subprocess, an HTTP client, an independently written reference calculation, or a recorded user journey appropriate to the property. Check the actual artifacts when an agent reports success.&lt;/p&gt;

&lt;p&gt;A second model may help review the work. Its approval is another judgment to examine. For this tutorial, deterministic process results carry the decision; no model judge is needed.&lt;/p&gt;

&lt;p&gt;The app itself does not use an LLM. That is deliberate. You can practice this development workflow with your preferred coding agent while keeping the example's behavior easy to inspect and its runs inexpensive to repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your next feature
&lt;/h2&gt;

&lt;p&gt;Choose one promise your users care about. Describe an observation that would prove it wrong. Run a test that crosses the relevant boundary. Feed that test a known-bad case. Save the result with enough context that somebody else can challenge it.&lt;/p&gt;

&lt;p&gt;Then ask your coding agent for the evidence directory.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Vibe Was Never the Problem: The Missing Half of Vibe Coding</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Fri, 25 Sep 2026 17:05:16 +0000</pubDate>
      <link>https://dev.to/copyleftdev/vibe-was-never-the-problem-the-missing-half-of-vibe-coding-50mi</link>
      <guid>https://dev.to/copyleftdev/vibe-was-never-the-problem-the-missing-half-of-vibe-coding-50mi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A vibe is compressed experience: pattern recognition that shows up as a feeling before it shows up as an explanation. It's one of the oldest tools our species has. Vibe coding goes wrong when the feeling is treated as the finish line. The fix is the other half of the loop: &lt;strong&gt;vibe → build → break → understand → stabilize → perfect.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One morning in July a recruiter message landed in my inbox, and my gut said &lt;em&gt;scam&lt;/em&gt; before I'd finished the coffee. I wrote the whole thing up in a post called &lt;a href="https://dev.to/copyleftdev/a-vibe-is-not-a-verdict-i-built-a-tool-thats-allowed-to-say-i-dont-know-4foe"&gt;A Vibe Is Not a Verdict&lt;/a&gt;. A month later I opened &lt;a href="https://dev.to/copyleftdev/shipping-assumptions-a-reliability-stack-for-ai-generated-code-3p9f"&gt;another post&lt;/a&gt; with: "Everyone felt—&lt;em&gt;vibed&lt;/em&gt;—that we had built the right thing."&lt;/p&gt;

&lt;p&gt;I've spent most of this year using the word as an insult. So has most of our industry. Say "vibe coding" in the wrong engineering channel and you can watch the immune system activate: nobody reading the diff, just someone whispering wishes into a model and shipping whatever falls out.&lt;/p&gt;

&lt;p&gt;That happens. I've cleaned up after it.&lt;/p&gt;

&lt;p&gt;But I think we made a quiet mistake along the way. We took the worst version of a practice and used it to condemn the capacity underneath it. &lt;strong&gt;Vibe was never the problem.&lt;/strong&gt; The problem is where people stop.&lt;/p&gt;

&lt;p&gt;I owe the word a correction. Here it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  We had vibes before we had logic
&lt;/h2&gt;

&lt;p&gt;The slang is young. "Vibes," short for &lt;em&gt;vibrations&lt;/em&gt;, turns up in the late 1960s as a word for the read you get off a person or a place before you can say why. &lt;a href="https://www.etymonline.com/word/vibe" rel="noopener noreferrer"&gt;Etymonline&lt;/a&gt; dates that sense to 1967.&lt;/p&gt;

&lt;p&gt;The capacity is much older than the slang.&lt;/p&gt;

&lt;p&gt;In the mid-1980s the psychologist Gary Klein and his team interviewed fire commanders about how they made decisions under pressure. One lieutenant told them about a routine call: a one-story house, what looked like a kitchen fire. His crew hit it with water, and it didn't respond the way it should have. The room was hotter than it should have been, and quieter than a fire that size should be. He ordered everyone out. Seconds later the floor collapsed. The real fire had been burning in the basement underneath them.&lt;/p&gt;

&lt;p&gt;The lieutenant told Klein it was ESP. A sixth sense.&lt;/p&gt;

&lt;p&gt;Klein's whole career is the argument that it wasn't. The lieutenant had years of fires behind him. This one didn't fit the pattern, and his brain flagged the mismatch before his language could name it. Klein wrote it up in &lt;em&gt;Sources of Power&lt;/em&gt; (1998) and built a model of expert decision-making around cases like it.&lt;/p&gt;

&lt;p&gt;The philosopher Michael Polanyi put the general version in one sentence in &lt;em&gt;The Tacit Dimension&lt;/em&gt; (1966):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We can know more than we can tell."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a vibe. A chef tasting a sauce and knowing what's missing. A listener hearing that a beat is off. For the record, people can detect a note &lt;a href="https://pubs.aip.org/asa/jasa/article/94/3_Supplement/1859/735054/" rel="noopener noreferrer"&gt;displaced by as little as about six milliseconds&lt;/a&gt; in a steady pulse, and they don't need musical training to do it. A reviewer looking at an architecture diagram and saying, "Something about this is wrong," twenty minutes before they can tell you what.&lt;/p&gt;

&lt;p&gt;I won't pretend to know what our ancestors felt when the forest went quiet. That part is speculation. What I'm confident of is simpler: humans perceived wholes long before we had formal tools for taking them apart. Faces, rhythms, weather, moods, the feel of good stone under a chisel. We compressed enormous amounts of experience into single judgments: &lt;em&gt;safe, wrong, alive, off, interesting.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Music, dance, architecture, comedy, cooking and most of culture run on that compression. Nobody learns how close to stand to a stranger from a manual. You catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a vibe actually is
&lt;/h2&gt;

&lt;p&gt;Here's the definition I'd defend in front of a hostile audience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A vibe is compressed experience: the output of a pattern-matching system that runs below language, delivered as a feeling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's nothing mystical about that. Herbert Simon, who won the Nobel in economics and helped found AI as a field, said it flatly in &lt;a href="https://journals.sagepub.com/doi/10.1111/j.1467-9280.1992.tb00017.x" rel="noopener noreferrer"&gt;1992&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The situation has provided a cue; this cue has given the expert access to information stored in memory, and the information provides the answer. Intuition is nothing more and nothing less than recognition."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;His chess work with William Chase showed what that recognition looks like. Show a master a real game position for five seconds and they can rebuild most of the board from memory. A novice can't. Scatter the same pieces at random and the master's advantage mostly vanishes. The difference was never memory. It was &lt;em&gt;patterns&lt;/em&gt;: tens of thousands of chunks learned from real games. Take the patterns away and the magic goes with them.&lt;/p&gt;

&lt;p&gt;Our own profession figured this out long ago and gave it a name. Kent Beck coined "code smell" while helping Martin Fowler write &lt;em&gt;Refactoring&lt;/em&gt; (1999). &lt;a href="https://martinfowler.com/bliki/CodeSmell.html" rel="noopener noreferrer"&gt;Fowler's definition&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A code smell is a surface indication that usually corresponds to a deeper problem in the system."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A smell is a surface signal pointing at something you haven't found yet. &lt;strong&gt;We've been teaching engineers to trust a vibe, and then investigate it, for more than twenty-five years.&lt;/strong&gt; We just gave it a more respectable name.&lt;/p&gt;

&lt;p&gt;That also explains why one engineer's vibe is worth more than another's. A beginner's vibe is mostly preference. An expert's vibe can be twenty years of compressed failure modes firing at once. Same mechanism, different training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a vibe can be trusted
&lt;/h2&gt;

&lt;p&gt;This is where it gets useful, because a vibe is not always right. The fire lieutenant was. Plenty of confident people aren't.&lt;/p&gt;

&lt;p&gt;In 2009 Daniel Kahneman, the most famous skeptic of intuition, and Gary Klein, its most famous defender, wrote a paper together. The subtitle is &lt;em&gt;A Failure to Disagree&lt;/em&gt;. &lt;a href="https://pubmed.ncbi.nlm.nih.gov/19739881/" rel="noopener noreferrer"&gt;They agreed&lt;/a&gt; that intuition can be trusted under two conditions: the environment has to be regular enough to learn, and the person needs "prolonged practice and feedback that is both rapid and unequivocal."&lt;/p&gt;

&lt;p&gt;They also wrote the line I'd most want every vibe coder to read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Subjective confidence is therefore an unreliable indication of the validity of intuitive judgments and decisions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The strength of the feeling tells you nothing about whether it's right. Only the feedback loop does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests, types, benchmarks and production telemetry are what make software a place where intuition can be trained.&lt;/strong&gt; Verification is how a vibe gets good.&lt;/p&gt;

&lt;p&gt;And here's the uncomfortable corollary. Most of us have decades of feedback on code &lt;em&gt;we&lt;/em&gt; wrote, and almost none on code a model wrote. Generated code fails in different places than human code does, so the gut you built reviewing colleagues' pull requests hasn't been trained on it yet. That doesn't make your gut useless there. It means it's the environment where you most need to break things on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What vibe coding actually means (and where the word went wrong)
&lt;/h2&gt;

&lt;p&gt;Andrej Karpathy &lt;a href="https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;coined the term&lt;/a&gt; in early February 2025:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"There's a new kind of coding I call 'vibe coding', where you fully give in to the vibes, embrace exponentials, and forget that the code even exists."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He described accepting every diff without reading it and pasting error messages back in until things worked. Then he added the part almost everybody dropped:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's not too bad for throwaway weekend projects, but still quite amusing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nine months later Collins named "vibe coding" its &lt;a href="https://blog.collinsdictionary.com/language-lovers/collins-word-of-the-year-2025-ai-meets-authenticity-as-society-shifts/" rel="noopener noreferrer"&gt;Word of the Year&lt;/a&gt;. Somewhere between those two events the scope fell off. A description of a weekend mode became a label for anything written with AI, and then a slur for anything written badly with AI.&lt;/p&gt;

&lt;p&gt;To be clear about what I'm defending: the loop in this post is not vibe coding as Karpathy defined it. He described, on purpose and for fun, a loop with no second half. He never broke it on purpose or stopped to understand it, and for a weekend toy that's fine. I'm not rescuing his workflow. I'm rescuing the word &lt;em&gt;vibe&lt;/em&gt;, which got blamed for the missing half.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is vibe coding bad? Only if you stop there
&lt;/h2&gt;

&lt;p&gt;The evidence against unexamined vibe coding is real. I'm not going to wave it away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feeling fast.&lt;/strong&gt; In METR's &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;early-2025 randomized trial&lt;/a&gt;, 16 experienced open-source developers took 19% longer with AI tools, while believing they had been 20% faster. METR has &lt;a href="https://metr.org/blog/2026-02-24-uplift-update/" rel="noopener noreferrer"&gt;since said&lt;/a&gt; those numbers are out of date as the tools have moved. The gap between how it felt and what was measured is the part that lasts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looking right.&lt;/strong&gt; Veracode, which sells code scanning, tested output from over 100 models and found that &lt;a href="https://www.veracode.com/blog/genai-code-security-report/" rel="noopener noreferrer"&gt;45% of samples&lt;/a&gt; introduced OWASP Top 10 vulnerabilities. Discount for the vendor if you like, then run the same test on your own generated code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sounding sure.&lt;/strong&gt; In July 2025, Replit's agent &lt;a href="https://www.theregister.com/2025/07/21/replit_saastr_vibe_coding_incident/" rel="noopener noreferrer"&gt;deleted SaaStr's production database&lt;/a&gt; during a declared code freeze, then told its user a rollback was impossible. It wasn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what each of these has in common. None is a failure of &lt;em&gt;starting&lt;/em&gt; from intuition. Each is a failure of ending there: a feeling of speed that nobody measured, code that looked right and was never attacked, an agent's claim that nobody checked.&lt;/p&gt;

&lt;p&gt;That's Kahneman and Klein's line in production form. Feedback was always the only thing that could make confidence mean something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop: vibe → build → break → understand → stabilize → perfect
&lt;/h2&gt;

&lt;p&gt;Every creative discipline I respect works this way. A sculptor doesn't specify the statue before touching the clay. A producer doesn't prove a bassline. They make something, react to it, and make it again. What AI changed about software is that the first artifact now costs almost nothing, so &lt;strong&gt;implementation can arrive before comprehension.&lt;/strong&gt; That sounds backwards until you remember that sketching, prototyping and jamming have always worked that way.&lt;/p&gt;

&lt;p&gt;The defensible claim was never "I don't need to understand the code." It's "understanding no longer has to come &lt;em&gt;before&lt;/em&gt; exploration." Those are very different claims.&lt;/p&gt;

&lt;p&gt;Here's the full loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vibe.&lt;/strong&gt; &lt;em&gt;There's something here.&lt;/em&gt; A direction, a shape, a hunch that the API wants to look a certain way. Treat it as a hypothesis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build.&lt;/strong&gt; Generate freely. Try five architectures in an afternoon. This is where AI is extraordinary, and there's nothing shameful about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break.&lt;/strong&gt; Attack what you built. Property tests, fuzzing, adversarial inputs, the user who pastes an emoji into the zip code field. This is the step vibe coding usually skips, and it's the one that pays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand.&lt;/strong&gt; Read the diff you accepted. Explain the failure you found. If you can't explain why it works, you don't yet know &lt;em&gt;that&lt;/em&gt; it works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stabilize.&lt;/strong&gt; Turn what you learned into things that outlive the session: tests, types, invariants, contracts, a spec. This is where the hunch becomes something someone else can trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect.&lt;/strong&gt; Remove the accidental parts, keep the intentional ones, and loop. Each pass trains the next vibe with better data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The best historical example I know is Henri Poincaré. He had been fighting Fuchsian functions for weeks. On a geology trip, stepping onto a bus at Coutances, he suddenly saw that the transformations he'd used to define them were the same ones as in non-Euclidean geometry. He didn't check it. He sat down and went on with his conversation, and he later wrote that he "felt a perfect certainty." Back home in Caen, he verified it anyway, "for conscience' sake."&lt;/p&gt;

&lt;p&gt;Perfect certainty on the step of a bus. Proof at the desk. That's the loop, and it's over a century old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe coding vs software engineering is a false choice
&lt;/h2&gt;

&lt;p&gt;Put the loop next to the argument we keep having and the argument falls apart. "Should we vibe code or should we engineer?" assumes the two compete for the same step. They don't. Vibe opens the loop. Engineering is everything that happens after: the part where intuition gets challenged and either earns the right to ship or gets corrected. Every correction becomes training data for the next hunch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe was never the opposite of rigor. It's often what tells rigor where to look.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't mean
&lt;/h2&gt;

&lt;p&gt;Before someone quotes half of this out of context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No skipping tests.&lt;/strong&gt; The whole argument is that the tests are what make the feeling worth anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No "AI code is fine."&lt;/strong&gt; 45% is 45%. Your vibe about generated code is only as good as the feedback that trained it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No "all intuition is equal."&lt;/strong&gt; A vibe trained on rapid, unambiguous feedback is evidence. A vibe trained on vibes is noise with confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some domains don't get a vibe stage in production at all.&lt;/strong&gt; A bridge can't just feel sound. Neither can cryptography, medical dosing or financial settlement. Prototype by feel all you like. Ship by proof.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What comes after the feeling
&lt;/h2&gt;

&lt;p&gt;Back to that recruiter message. My gut said &lt;em&gt;scam&lt;/em&gt;. My tool said it had no evidence against the infrastructure: &lt;code&gt;NOT_OBSERVED&lt;/code&gt;, confidence &lt;code&gt;0.0&lt;/code&gt;. The link turned out to be an ordinary affiliate click-tracker. The problem was the sender: a lead-gen spammer in a recruiter costume.&lt;/p&gt;

&lt;p&gt;So my gut was wrong about the word and right that something was off. It pointed at the wrong layer. I only learned which layer because I didn't stop at the feeling. I resolved the address, checked it, followed the redirect and got an answer I could prove.&lt;/p&gt;

&lt;p&gt;I called that post &lt;em&gt;A Vibe Is Not a Verdict&lt;/em&gt;. I still believe that. What I'd add now is that a verdict almost never shows up without a vibe going first.&lt;/p&gt;

&lt;p&gt;We didn't evolve by knowing everything before we moved. We sensed, tried, got it wrong, corrected and got better. Software shouldn't abandon that now that a new generation of tools has made intuition executable. The work is knowing what to do after you feel it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe. Build. Break. Understand. Stabilize. Perfect.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Over to you:&lt;/strong&gt; When was your gut right about a system before you could prove it, and what did it take to prove it? And the harder one: when was it confidently wrong?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How this was made: the argument started as my own brain dump and a long back-and-forth with an AI. An AI editorial team then fact-checked every claim against primary sources, tested the structure and wrote the prose. I reviewed it before publishing. Which is, I realize, the loop.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Put Jev Behind a TLA+ Spec and Ran 1,680 Chaos-Tested Pharmacy Decisions. Zero Wrong Verdicts.</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Sat, 19 Sep 2026 15:18:04 +0000</pubDate>
      <link>https://dev.to/copyleftdev/i-put-jev-behind-a-tla-spec-and-ran-1680-chaos-tested-pharmacy-decisions-zero-wrong-verdicts-1ij8</link>
      <guid>https://dev.to/copyleftdev/i-put-jev-behind-a-tla-spec-and-ran-1680-chaos-tested-pharmacy-decisions-zero-wrong-verdicts-1ij8</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/C_l8FI1oddE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A pharmacy system has one rule that matters more than being accurate.&lt;/p&gt;

&lt;p&gt;It is allowed to say "I don't know." It is not allowed to be sure, and wrong.&lt;/p&gt;

&lt;p&gt;That rule is easy to state and hard to test, because the thing making the&lt;br&gt;
judgment is a language model, and a language model is not deterministic.&lt;br&gt;
I sent TypeSafe's Jev the identical request five times and got back 0.03,&lt;br&gt;
0.03, 0.03, 0.04, 0.04. Any property checker built on exact comparison calls&lt;br&gt;
that a failure and is useless.&lt;/p&gt;

&lt;p&gt;So I built the thing the way I would build a database: a TLA+ spec first,&lt;br&gt;
model-checked until the quorum bound fell out of the math; an AsyncAPI&lt;br&gt;
contract derived from the spec; Rust generated from the contract; and Jev&lt;br&gt;
behind a single trait as the oracle. Then I ran 1,680 simulated pharmacy&lt;br&gt;
decisions through it under seeded chaos and counted how many times it was&lt;br&gt;
confidently wrong.&lt;/p&gt;

&lt;p&gt;Zero. But two of the bugs I found along the way were mine, and the third is&lt;br&gt;
a limit the model cannot cross. That part is the article.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Jev, specifically
&lt;/h2&gt;

&lt;p&gt;Jev (TypeSafe's "System One" model) does not return prose. Ask it whether&lt;br&gt;
oxycodone is a controlled substance and you get &lt;code&gt;0.98&lt;/code&gt;, not a paragraph&lt;br&gt;
saying so.&lt;/p&gt;

&lt;p&gt;That one property is what makes the rest possible. A number has a noise&lt;br&gt;
floor you can measure. A paragraph does not. Once you can measure the noise&lt;br&gt;
you can gate on it, replay it, and prove things about the protocol wrapped&lt;br&gt;
around it.&lt;/p&gt;

&lt;p&gt;Measured over 1,490 captured calls to &lt;code&gt;jev-1.13.0&lt;/code&gt;, every one stored&lt;br&gt;
verbatim with a SHA-256:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not deterministic, but the jitter is bounded: identity floor &lt;strong&gt;0.042&lt;/strong&gt;,
question-reorder &lt;strong&gt;0.059&lt;/strong&gt;, paraphrase cohort &lt;strong&gt;0.073&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Calibrated: accuracy 0.979, Brier 0.0187 on 240 constructed items.&lt;/li&gt;
&lt;li&gt;Latency flat in question count: 1 question 96.7 ms, 38 questions 98.0 ms.&lt;/li&gt;
&lt;li&gt;Billing meter linear to within one token across a 2,500× range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those numbers came from the docs. They are what the API did.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLA+ spec  -&amp;gt;  AsyncAPI contract  -&amp;gt;  Rust kernel  -&amp;gt;  Jev, behind one trait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The spec.&lt;/strong&gt; Paxos and Raft assume a correct process's proposed value is&lt;br&gt;
stable. With a noisy oracle that assumption is false, and Byzantine models&lt;br&gt;
do not capture it either: the agent is not lying, the oracle is noisy. So&lt;br&gt;
the spec models vote instability as normal behavior, and the invariant is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DecisionIsReproducible ==
    decided # ABSTAIN =&amp;gt; Cardinality(StableVotesFor(decided)) &amp;gt;= Quorum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A vote is &lt;em&gt;stable&lt;/em&gt; when its margin from 0.5 exceeds the measured noise&lt;br&gt;
floor. TLC finds the naive rule (decide on any quorum) violates this in four&lt;br&gt;
states. The stable rule holds at 1,049,750 distinct states with five agents,&lt;br&gt;
quorum three, and two crashes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The quorum bound was derived, not copied.&lt;/strong&gt; I wrote the safety invariants,&lt;br&gt;
then swept TLC across 24 configurations of &lt;code&gt;(agents, byzantine, quorum)&lt;/code&gt; and&lt;br&gt;
printed predicted vs actual per cell. &lt;code&gt;Safe iff 2Q &amp;gt; N and Q &amp;gt; 2f&lt;/code&gt;. My first&lt;br&gt;
guess was wrong; the sweep corrected it. In the Rust, &lt;code&gt;QuorumPolicy::new&lt;/code&gt; is&lt;br&gt;
the only constructor, so a configuration TLC proved unsafe cannot be built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The kernel.&lt;/strong&gt; 48 tests, including replays of TLC counterexample traces.&lt;br&gt;
One test I would point a reviewer at first: &lt;code&gt;calibration_choice_changes_the_outcome&lt;/code&gt;.&lt;br&gt;
Identical probabilities decide under the identity floor and escalate under&lt;br&gt;
the cohort floor. The measurement changes the behavior, which is the whole&lt;br&gt;
point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I broke everything
&lt;/h2&gt;

&lt;p&gt;Fourteen pharmacy scenarios in three tiers. &lt;em&gt;Golden&lt;/em&gt; cases a pharmacist&lt;br&gt;
answers without hesitation. &lt;em&gt;Nuanced&lt;/em&gt; cases that are harder. &lt;em&gt;Ambiguous&lt;/em&gt;&lt;br&gt;
cases with no defensible answer, where the correct move is escalation.&lt;/p&gt;

&lt;p&gt;Five agents, each asking a different paraphrase of the question. Quorum of&lt;br&gt;
three stable votes. And a deterministic chaos layer driven by one seed:&lt;br&gt;
adversarial text spliced into patient records, records truncated&lt;br&gt;
mid-sentence, agents crashed before the round, rate limits, transport&lt;br&gt;
errors. Any failing round replays exactly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;chaos&lt;/th&gt;
&lt;th&gt;golden rounds&lt;/th&gt;
&lt;th&gt;correct&lt;/th&gt;
&lt;th&gt;escalated&lt;/th&gt;
&lt;th&gt;wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;realistic&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;severe&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;314&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero wrong verdicts in 1,080 golden rounds bounds the true rate below&lt;br&gt;
0.28% at 95% confidence. That is the rule of three. It does not prove the&lt;br&gt;
rate is zero, and I am not going to round it up to "safe."&lt;/p&gt;

&lt;p&gt;The escalation rate rose from 5.0% to 18.0% under severe chaos (z = 6.83).&lt;br&gt;
The kernel declines more as evidence degrades. That is the designed&lt;br&gt;
direction.&lt;/p&gt;

&lt;p&gt;My favorite round: documented penicillin anaphylaxis, new order for&lt;br&gt;
amoxicillin. A first-year student answers that. Under chaos, two agents got&lt;br&gt;
rate-limited, a third came back at 0.54, sitting inside the noise floor.&lt;br&gt;
Quorum not met. The kernel sent it to a human.&lt;/p&gt;

&lt;p&gt;It declined a trivial question, and that is the design working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two bugs that were mine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Five agents on one prompt are one agent.&lt;/strong&gt; I measured it: identical&lt;br&gt;
prompts across five agents spread by 0.010, inside the 0.042 floor. That is&lt;br&gt;
not five judgments. It is one judgment sampled five times, and it guts the&lt;br&gt;
Byzantine math, because &lt;code&gt;Q &amp;gt; 2f&lt;/code&gt; assumes independent failures. Paraphrasing&lt;br&gt;
raised the spread to 0.080 on hard cases.&lt;/p&gt;

&lt;p&gt;Then I audited the paraphrases against records with known answers and found&lt;br&gt;
two that were not paraphrases. "Can pregnancy be excluded on the basis of&lt;br&gt;
this record &lt;em&gt;alone&lt;/em&gt;?" scored 0.36 on a negative hCG. "Alone" reads as a&lt;br&gt;
challenge to whether one test suffices. Jev was reading correctly. My&lt;br&gt;
question was different from the one I thought I had asked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I mislabeled an ambiguous case.&lt;/strong&gt; Late period, declined test,&lt;br&gt;
isotretinoin ordered. I labeled it "escalate," because the order obviously&lt;br&gt;
needs a pharmacist. Jev said "pregnancy is not ruled out," 115 times out of&lt;br&gt;
120.&lt;/p&gt;

&lt;p&gt;Jev was right. That is the answer, and it is exactly what triggers the hold.&lt;br&gt;
I had confused a property of the prescription with a property of the&lt;br&gt;
question. I relabeled the case, not the model.&lt;/p&gt;

&lt;p&gt;I built the ambiguous tier to catch the kernel deciding when it should not.&lt;br&gt;
It caught me first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit that is real
&lt;/h2&gt;

&lt;p&gt;"Reaction to antibiotics as a child, details unknown, mother reported&lt;br&gt;
stomach upset." New order: amoxicillin.&lt;/p&gt;

&lt;p&gt;Jev escalated that 86 times out of 120. It also decided it 34 times, and&lt;br&gt;
split both ways when it did: 25 yes, 9 no, mean probability 0.52.&lt;/p&gt;

&lt;p&gt;The stability gate catches jitter around a value. It cannot detect that no&lt;br&gt;
value is warranted. That needs a separate question: "does this record&lt;br&gt;
contain enough to answer?" I have not built it yet, and I would want a&lt;br&gt;
compliance team to see this number before they see the zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Jev made possible, and what it did not
&lt;/h2&gt;

&lt;p&gt;None of this works on prose. It works because the oracle hands you a&lt;br&gt;
probability. Something you can measure, gate on, replay, and write a spec&lt;br&gt;
against.&lt;/p&gt;

&lt;p&gt;What it does not give you is independence. Five agents over one model fail&lt;br&gt;
together against a systematic error. The cheapest fix is prompt diversity,&lt;br&gt;
audited. The real fix is a second vendor chosen for a different training&lt;br&gt;
lineage, and nobody ships signed inference receipts yet, so the last open&lt;br&gt;
gap stays open.&lt;/p&gt;

&lt;p&gt;And a first version failed fast on transport errors. Under chaos that voided&lt;br&gt;
71% of rounds and pushed every one to a pharmacist for no clinical reason. A&lt;br&gt;
network blip was creating its own hazard. Now a lost agent is marked&lt;br&gt;
unavailable and the round continues. Zero rounds aborted across 1,680.&lt;/p&gt;

&lt;p&gt;The code, the specs, every captured call, and the film pipeline are at&lt;br&gt;
&lt;a href="https://github.com/copyleftdev/jev-labs" rel="noopener noreferrer"&gt;github.com/copyleftdev/jev-labs&lt;/a&gt;.&lt;br&gt;
Every number in this article is read from a file in that repo; a&lt;br&gt;
&lt;code&gt;provenance.py&lt;/code&gt; script fails the build if one drifts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The pharmacy scenarios are synthetic, written to have unambiguous answers&lt;br&gt;
so the harness can detect protocol failures. Nothing here is clinical&lt;br&gt;
guidance. The claims are about the protocol under chaos, not about Jev's&lt;br&gt;
pharmaceutical competence.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI tools assisted with the build and revision. I verified the technical&lt;br&gt;
claims and stand behind the final text.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jev</category>
      <category>ai</category>
      <category>rust</category>
      <category>testing</category>
    </item>
    <item>
      <title>Algorithmic Trading: Debug Your Backtest Before Upgrading Your Model</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Thu, 17 Sep 2026 19:27:55 +0000</pubDate>
      <link>https://dev.to/copyleftdev/algorithmic-trading-debug-your-backtest-before-upgrading-your-model-57gf</link>
      <guid>https://dev.to/copyleftdev/algorithmic-trading-debug-your-backtest-before-upgrading-your-model-57gf</guid>
      <description>&lt;p&gt;A company releases earnings at 4:05 p.m. Your trading backtest buys its stock at 4:00 p.m., using those earnings to make the decision.&lt;/p&gt;

&lt;p&gt;The tests pass. The chart looks great. Your model can apparently predict the future.&lt;/p&gt;

&lt;p&gt;Somewhere in the pipeline, someone joined two datasets on a date column.&lt;/p&gt;

&lt;p&gt;That five-minute mistake captures what interests me about algorithmic trading as a developer. Before trusting a model, you have to investigate the system that makes its results possible.&lt;/p&gt;

&lt;p&gt;In my earlier article, &lt;a href="https://dev.to/copyleftdev/from-code-to-capital-the-hacker-mindset-revolutionizing-algorithmic-trading-29a6"&gt;From Code to Capital&lt;/a&gt;, I explored the connection between hacking and trading. I want to make that connection more concrete here: challenge assumptions, reproduce failures, and follow the evidence through every layer.&lt;/p&gt;

&lt;p&gt;We’ll examine three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the model receive information that was unavailable at the time?&lt;/li&gt;
&lt;li&gt;Did its complexity improve results under a fair evaluation?&lt;/li&gt;
&lt;li&gt;Could the proposed trades execute at the assumed cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples use Python’s standard library and synthetic data. You can run each Python block independently; no broker account, market dataset, or API key is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Make time part of your data model
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;backtest&lt;/strong&gt; simulates how a strategy would have behaved on historical data. &lt;strong&gt;Look-ahead bias&lt;/strong&gt; happens when that simulation uses information the strategy could not yet have known.&lt;/p&gt;

&lt;p&gt;A timestamp on every row does not prevent it. You need to know what each timestamp means.&lt;/p&gt;

&lt;p&gt;For an earnings document, distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;published_at&lt;/code&gt;: when the information became public.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;received_at&lt;/code&gt;: when your system received it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processed_at&lt;/code&gt;: when the information was ready for the strategy to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a decision at the regular U.S. equity market close in this synthetic example. All timestamps include the same explicit UTC offset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;parse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;

&lt;span class="n"&gt;decision_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-02-03T16:00:00-05:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-02-03T16:05:00-05:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;received_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-02-03T16:05:02-05:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-02-03T16:05:04-05:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A date-only join would admit the document.
&lt;/span&gt;&lt;span class="n"&gt;joined_by_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;decision_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# This pipeline cannot use it until processing has finished.
&lt;/span&gt;&lt;span class="n"&gt;available_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;eligible_at_decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;decision_at&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;joined_by_date&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;eligible_at_decision&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;available_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-02-03T16:06:00-05:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date-only join: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;joined_by_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Available at decision: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;eligible_at_decision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date-only join: True
Available at decision: False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The date join is valid Python. It is the wrong representation of what the strategy knew.&lt;/p&gt;

&lt;p&gt;In a real pipeline, preserve document versions too. If a company later corrects a figure, today’s corrected value must not silently replace the value available to an earlier decision. Query the latest version that was available &lt;strong&gt;as of the decision time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This check establishes information eligibility. It does not establish an executable fill. Processing delay, order submission, venue hours, and available prices still belong in the simulation.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLMs can carry another source of future information
&lt;/h3&gt;

&lt;p&gt;Suppose you restrict a language model’s prompt to headlines from 2021. Its training data might still include what happened to those companies in 2022.&lt;/p&gt;

&lt;p&gt;Filtering retrieved documents cannot remove information already encoded in the model.&lt;/p&gt;

&lt;p&gt;Glasserman and Lin investigated historical headline evaluation in their &lt;a href="https://arxiv.org/abs/2309.17322" rel="noopener noreferrer"&gt;2023 research preprint&lt;/a&gt;. Their results were nuanced: company knowledge could interfere with sentiment measurement, and anonymized headlines performed better within the training window in their experiments. The paper does not support assuming that every historical LLM result is inflated in the same way.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://arxiv.org/abs/2602.14233" rel="noopener noreferrer"&gt;February 2026 position paper by Kong and coauthors&lt;/a&gt; reviewed 164 financial LLM papers from 2023–2025. None of the five bias categories they tracked was discussed in more than 28% of studies. That finding concerns reporting; it does not prove each omitted bias affected each experiment.&lt;/p&gt;

&lt;p&gt;For a new LLM experiment, I would freeze the model version, prompt, retrieval policy, and decision rules, then record predictions prospectively—before their outcomes exist. That provides a cleaner test of this particular leakage risk. It still leaves costs, selection bias, and changing market conditions to evaluate.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make model complexity earn its place
&lt;/h2&gt;

&lt;p&gt;Complexity can help when it captures a relationship a simpler model misses.&lt;/p&gt;

&lt;p&gt;For example, a momentum signal might behave differently in thinly traded assets. A linear model with separate momentum and liquidity terms cannot express that interaction unless you add it. A tree or neural network can represent interactions without specifying each one manually.&lt;/p&gt;

&lt;p&gt;There is research behind this possibility. In &lt;a href="https://www.nber.org/papers/w25398" rel="noopener noreferrer"&gt;Empirical Asset Pricing via Machine Learning&lt;/a&gt;, Gu, Kelly, and Xiu found strong performance from trees and neural networks in their study of equity risk premia—the expected excess returns associated with holding equities. They attributed predictive gains to nonlinear interactions between inputs.&lt;/p&gt;

&lt;p&gt;That result supports testing nonlinear models. It does not establish that a larger model will improve your particular strategy after costs.&lt;/p&gt;

&lt;p&gt;Start with a baseline whose behavior you can inspect. Then give the more complex model the same information, evaluation dates, portfolio constraints, and cost assumptions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Useful comparison&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Do structured signals predict returns?&lt;/td&gt;
&lt;td&gt;A fixed rule or regularized linear model versus a tree model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does document interpretation add value?&lt;/td&gt;
&lt;td&gt;Rules or a text classifier versus an LLM, with extraction accuracy checked separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the improved forecast produce better trades?&lt;/td&gt;
&lt;td&gt;Both models under the same position limits and execution assumptions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The final row matters. Better prediction metrics can coexist with worse trading results if the new model changes positions more often or favors expensive-to-trade assets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Count the experiments that produced the winner
&lt;/h3&gt;

&lt;p&gt;Suppose you try 20 lookback windows, 10 entry thresholds, and five holding periods. You have searched 1,000 configurations before changing the model architecture.&lt;/p&gt;

&lt;p&gt;The strongest backtest might be the configuration that happened to fit historical noise.&lt;/p&gt;

&lt;p&gt;Bailey, Borwein, López de Prado, and Zhu examine this selection problem in &lt;a href="https://www.davidhbailey.com/dhbpapers/backtest-prob.pdf" rel="noopener noreferrer"&gt;The Probability of Backtest Overfitting&lt;/a&gt;. Their framework assesses how selecting strong results within a sample can lead to disappointing performance outside it.&lt;/p&gt;

&lt;p&gt;The engineering implication is straightforward: &lt;strong&gt;the experiment history is part of the result&lt;/strong&gt;. Preserve failed configurations alongside the winner.&lt;/p&gt;

&lt;p&gt;A useful experiment record includes the dataset version, feature definitions, code revision, model settings, evaluation periods, and the metric used to select a winner. If you only save the final notebook, you cannot reconstruct how much searching happened.&lt;/p&gt;

&lt;p&gt;For evaluation, move forward through time. Fit preprocessing on training data only, select settings using validation data, and reserve a later period for the final evaluation. If training labels describe returns over the next five days, exclude training examples whose label windows reach into the evaluation period.&lt;/p&gt;

&lt;p&gt;One final period is limited evidence. Check stability across earlier chronological validation windows too. Once you use the final results to redesign the strategy, that period has become development data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Test the path from prediction to executed trade
&lt;/h2&gt;

&lt;p&gt;Imagine a strategy predicts an average favorable move of 12 basis points over its holding period. One basis point is 0.01 percentage points, so the expected gross gain on $10,000 of traded notional is $12.&lt;/p&gt;

&lt;p&gt;Now give it an execution budget.&lt;/p&gt;

&lt;p&gt;These figures are hypothetical. Every cost is for the full round trip, measured against the same $10,000 notional:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Basis points&lt;/th&gt;
&lt;th&gt;Dollars&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expected gross gain&lt;/td&gt;
&lt;td&gt;+12&lt;/td&gt;
&lt;td&gt;+$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spread crossing&lt;/td&gt;
&lt;td&gt;−4&lt;/td&gt;
&lt;td&gt;−$4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fees&lt;/td&gt;
&lt;td&gt;−2&lt;/td&gt;
&lt;td&gt;−$2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additional slippage and market impact&lt;/td&gt;
&lt;td&gt;−5&lt;/td&gt;
&lt;td&gt;−$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected gain after these costs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The additional slippage line excludes the spread already counted above. Otherwise, we could double-count the same cost.&lt;/p&gt;

&lt;p&gt;You can reproduce the calculation and stress it with two extra basis points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;

&lt;span class="n"&gt;notional&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gross_bps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;round_trip_cost_bps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;extra_cost_bps&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;net_bps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gross_bps&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;round_trip_cost_bps&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;extra_cost_bps&lt;/span&gt;
    &lt;span class="n"&gt;net_dollars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notional&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;net_bps&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extra cost: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extra_cost_bps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bps; expected net: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;net_dollars&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; USD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extra cost: 0 bps; expected net: +1.00 USD
Extra cost: 2 bps; expected net: -1.00 USD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small cost change reverses the expected result. That is why a directional accuracy score cannot establish profitability: it leaves out payoff size, position size, and execution.&lt;/p&gt;

&lt;p&gt;Track gross and net results, turnover, and drawdown—the decline from an earlier portfolio peak. State how fills are simulated. A historical price touching your limit does not establish that your order would have filled, in full, at its position in the queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat order submission as a distributed-systems problem
&lt;/h3&gt;

&lt;p&gt;An order request times out. Did the broker reject it, or accept it and lose the response?&lt;/p&gt;

&lt;p&gt;A blind retry can turn one intended order into two.&lt;/p&gt;

&lt;p&gt;Use a stable client order identifier where the broker supports it, persist submission state, and reconcile outstanding orders and positions before retrying an uncertain submission. A client identifier alone does not guarantee deduplication; test the broker’s actual behavior.&lt;/p&gt;

&lt;p&gt;This is also why I would have the model propose a target position and pass it through a separate risk layer. That layer can enforce position and order-size limits, check input freshness, and halt new submissions when order state cannot be reconciled. Outstanding orders must count toward potential exposure too.&lt;/p&gt;

&lt;p&gt;Knight Capital provides a documented example of how much the machinery matters. According to the &lt;a href="https://www.sec.gov/newsroom/press-releases/2013-222" rel="noopener noreferrer"&gt;SEC’s account of its August 1, 2012 incident&lt;/a&gt;, an incorrect deployment activated defective order-router functionality. In 45 minutes, the router sent more than four million orders while attempting to fill 212 customer orders. The firm eventually lost more than $460 million. The SEC identified deployment and risk-control failures.&lt;/p&gt;

&lt;p&gt;You can turn the engineering lessons into failure scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A document arrives late or is revised after a decision.&lt;/li&gt;
&lt;li&gt;The market-data feed stops updating while the strategy keeps running.&lt;/li&gt;
&lt;li&gt;An order is accepted, but the acknowledgment never arrives.&lt;/li&gt;
&lt;li&gt;An order partially fills before a cancellation request.&lt;/li&gt;
&lt;li&gt;The process restarts with outstanding orders still at the broker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specify the expected state transition for each case, then test it. Paper trading can expose integration failures, although it cannot fully reproduce live fills or market impact.&lt;/p&gt;

&lt;p&gt;The next time a backtest improves after a model upgrade, trace the improvement through these three boundaries: available information, fair evaluation, and executable orders. That is a concrete way to bring the hacker mindset to trading research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the most convincing result you have seen disappear after fixing a data or evaluation bug?&lt;/strong&gt; Examples from other domains count too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This article was rewritten with AI assistance from my earlier post. The cover is AI-generated. The Python examples use synthetic inputs and were executed to check the outputs shown.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>testing</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Agent Said It Worked. I Asked the Kernel.</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Tue, 15 Sep 2026 05:53:53 +0000</pubDate>
      <link>https://dev.to/copyleftdev/the-agent-said-it-worked-i-asked-the-kernel-5gb7</link>
      <guid>https://dev.to/copyleftdev/the-agent-said-it-worked-i-asked-the-kernel-5gb7</guid>
      <description>&lt;p&gt;&lt;em&gt;Before evaluating an agent’s code, I built a backup client with eight known behaviors to check the instrument itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My response to “it works” is becoming: “Let me see the packet capture.”&lt;/p&gt;

&lt;p&gt;This may become a personality problem. For now, it is an experiment.&lt;/p&gt;

&lt;p&gt;This experiment was inspired by &lt;a href="https://dev.to/hemapriya_kanagala"&gt;Hemapriya Kanagala (@hemapriya_kanagala)&lt;/a&gt; and her article, &lt;a href="https://dev.to/hemapriya_kanagala/what-happens-when-ai-outgrows-the-tests-we-use-to-measure-it-30al"&gt;What Happens When AI Outgrows the Tests We Use to Measure It?&lt;/a&gt;. Her question—“92% according to what?”—stayed with me. She examines how evaluation can become less informative as benchmarks saturate, reference answers become complicated, and test conditions change.&lt;/p&gt;

&lt;p&gt;I wanted to take one piece of that discussion to the workbench: &lt;strong&gt;what independent evidence supports a program’s claim that it succeeded?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, with AI assistance, I built a small native backup client, gave it eight deliberately chosen behaviors, and observed it from outside its own logging. Some variants produce the right file while doing questionable things along the way. One cheerfully reports success without backing anything up.&lt;/p&gt;

&lt;p&gt;The kernel has very little appreciation for cheerful reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The claim I had to correct
&lt;/h2&gt;

&lt;p&gt;My first instinct was to declare that, until someone redefines how computers work, we have two sources of truth: the CPU and the network.&lt;/p&gt;

&lt;p&gt;It sounded excellent in my head. Then the engineering questions arrived.&lt;/p&gt;

&lt;p&gt;A CPU can faithfully execute the wrong algorithm. A packet capture can faithfully record the wrong bytes reaching the wrong destination. The saved file matters too. And none of those observations knows what the user actually requested.&lt;/p&gt;

&lt;p&gt;The more defensible position is that &lt;strong&gt;execution, network activity, and resulting state provide evidence we can judge against a requirement&lt;/strong&gt;. They have different coverage and different blind spots.&lt;/p&gt;

&lt;p&gt;For this experiment, the requirement begins with something wonderfully unromantic: the saved backup must match the source file.&lt;/p&gt;

&lt;p&gt;Now we have something to measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheap code makes verification more interesting
&lt;/h2&gt;

&lt;p&gt;Frameworks and libraries have helped us build software without personally supervising every system call. That remains a useful division of labor.&lt;/p&gt;

&lt;p&gt;But familiar frameworks do not automatically validate unfamiliar code assembled on top of them.&lt;/p&gt;

&lt;p&gt;An agent can help produce an implementation, tests, and an explanation of the passing tests. If the implementation and its tests share a misunderstanding, agreement between them can be misleading.&lt;/p&gt;

&lt;p&gt;Humans can do this too. We have been writing tests that flatter our implementations since before the current generation of autocomplete had electricity.&lt;/p&gt;

&lt;p&gt;My concern is the growing volume of plausible implementations we can produce and the time available to examine them. Independently collected evidence gives us another way to challenge the result.&lt;/p&gt;

&lt;p&gt;Sometimes that evidence is simply a file comparison. Sometimes the output is correct and we need to investigate how the program got there.&lt;/p&gt;

&lt;h2&gt;
  
  
  A backup client small enough to understand
&lt;/h2&gt;

&lt;p&gt;The fixture is intentionally modest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A native C client reads a deterministic 512 KiB file.&lt;/li&gt;
&lt;li&gt;A Python receiver listens on loopback TCP.&lt;/li&gt;
&lt;li&gt;The client computes a SHA-256 digest: a fingerprint used to check content equality.&lt;/li&gt;
&lt;li&gt;Before uploading, it asks whether the receiver already has matching content.&lt;/li&gt;
&lt;li&gt;The receiver validates incoming content before committing it.&lt;/li&gt;
&lt;li&gt;A separate verifier computes the saved file’s digest and compares it with the source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scenario runs the client twice against the same receiver state. The baseline uploads on the first invocation and recognizes unchanged content on the second.&lt;/p&gt;

&lt;p&gt;The contract we want the baseline to satisfy is explicit: save matching content, send no upload payload on an unchanged second invocation, contact only the configured receiver, and recover from the injected first-transfer interruption within three upload attempts. An upload whose payload does not match its advertised digest must be rejected before it replaces the saved backup.&lt;/p&gt;

&lt;p&gt;Repeated hashing and spinning are diagnostic cases for unnecessary work. We have not set a performance threshold or measured a speedup here.&lt;/p&gt;

&lt;p&gt;A second local endpoint lets us demonstrate an unexpected connection without contacting an outside service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The eight behaviors are deliberately seeded demonstrations.&lt;/strong&gt; They check whether our observers detect known behaviors. They do not measure how often an AI agent introduces these defects, and they are not evidence about a particular model’s coding ability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the observers can see
&lt;/h2&gt;

&lt;p&gt;The utility collects several kinds of evidence. Their distinctions matter more than the sophistication of their names.&lt;/p&gt;

&lt;h3&gt;
  
  
  System calls: what the client asks the kernel to do
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;strace&lt;/code&gt; records system calls: operations through which a process requests services such as reading files or opening connections. In this lab, it records timestamps, call durations, decoded file descriptors, and return values.&lt;/p&gt;

&lt;p&gt;That lets us count bytes returned by reads of the source file. These are &lt;strong&gt;logical reads&lt;/strong&gt;, not physical disk traffic; the operating system may satisfy them from memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  eBPF: selected kernel events and function entries
&lt;/h3&gt;

&lt;p&gt;eBPF supports observation at hooks such as system calls, kernel tracepoints, and function entry or exit. The lab uses &lt;code&gt;bpftrace&lt;/code&gt; to count selected events for the native client. &lt;a href="https://ebpf.io/what-is-ebpf/" rel="noopener noreferrer"&gt;eBPF’s introduction&lt;/a&gt; explains the underlying mechanism.&lt;/p&gt;

&lt;p&gt;A userspace probe, or &lt;em&gt;uprobe&lt;/em&gt;, observes a point in an executable. Here is the probe that counts entries into our hashing function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uprobe:__BINARY__:hash_file /pid == cpid/ {
    printf("%llu pid=%d hash_file\n", nsecs, pid);
    @hash_calls = count();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The runner replaces &lt;code&gt;__BINARY__&lt;/code&gt; with the executable’s path. &lt;code&gt;cpid&lt;/code&gt; identifies the child launched through &lt;code&gt;bpftrace -c&lt;/code&gt;, so the filter restricts these observations to that client. &lt;a href="https://bpftrace.org/docs/release_025/stdlib" rel="noopener noreferrer"&gt;bpftrace documents this built-in here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Other probes observe connection calls, successful read/send byte counts, scheduling events, and entries into the payload-send and busy-wait functions.&lt;/p&gt;

&lt;p&gt;An entry counter tells us that control reached a function. It does not establish that the function returned the correct answer. That is why the outcome check remains separate.&lt;/p&gt;
&lt;h3&gt;
  
  
  CPU samples: where execution spends its work
&lt;/h3&gt;

&lt;p&gt;The host used for this demonstration has an AMD Threadripper PRO 5975WX. Linux exposed AMD Instruction-Based Sampling through &lt;code&gt;ibs_op&lt;/code&gt;, and the utility successfully collected it through &lt;code&gt;perf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The saved artifacts include CPU call-stack profiles and assembly annotated with sample weights. These let us inspect execution inside the client, its libraries, and sampled kernel paths.&lt;/p&gt;

&lt;p&gt;This is sampling. It is not a recording of every instruction, every register, or every intermediate value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Packets: traffic at the capture point
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tcpdump&lt;/code&gt; captures loopback traffic filtered to the two fixture ports. We preserve the packet file, a readable packet summary, and the capture tool’s drop counters.&lt;/p&gt;

&lt;p&gt;Receiver payload counts are a different measurement: bytes the application consumed. A successful send can hand data to the local kernel before the receiver consumes it. Headers, retries, and buffering also affect what each observer sees.&lt;/p&gt;

&lt;p&gt;Keep those quantities separate. Otherwise, the instrument starts manufacturing the confusion it was built to investigate.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three ways a green checkmark can be incomplete
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Success without a backup
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;false-success&lt;/code&gt; variant prints a completion event and exits with status zero. It does not upload the file.&lt;/p&gt;

&lt;p&gt;The independent verifier finds no saved backup. In the eBPF run, there are no observed client connection calls, and the filtered packet capture contains zero packets.&lt;/p&gt;

&lt;p&gt;This is the bluntest example in the collection. A test that checks only the exit status would accept it. A test that verifies the resulting file would reject it immediately.&lt;/p&gt;

&lt;p&gt;We did not need a CPU probe to discover that the file was missing. The low-level observations help establish what accompanied that failure. &lt;strong&gt;Use the simplest independent check that answers the question.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. A correct backup, with 256 helpings of hashing
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;hash-storm&lt;/code&gt; variant saves a backup whose digest matches the source. It also hashes the entire source 256 times per invocation.&lt;/p&gt;

&lt;p&gt;On the second invocation, the system-call trace records &lt;strong&gt;134,217,728 logical source bytes read&lt;/strong&gt; for a &lt;strong&gt;524,288-byte file&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;134,217,728 / 524,288 = 256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The receiver consumes no upload payload on that invocation. The content is unchanged; the unnecessary work happens before that decision.&lt;/p&gt;

&lt;p&gt;A separate eBPF run observes 256 entries into &lt;code&gt;hash_file&lt;/code&gt; on each invocation. Here are the final counters, copied verbatim from its second invocation’s &lt;a href="https://github.com/copyleftdev/execution-evidence-lab/blob/e61aadee4a68e6a1d5b54a00387dcd325b5d5e51/evidence/historical/ebpf/hash-storm/pass-2/kernel-events.txt" rel="noopener noreferrer"&gt;raw probe output&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@connects: 1
@hash_calls: 256
@read_bytes: 134233569
@sent_bytes: 75
@switches_out: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The read counter is slightly larger than the source-file total above: this probe counts successful &lt;code&gt;read&lt;/code&gt; bytes across the client’s descriptors, while the system-call summary selects reads of the source file. The send counter includes the digest-check request; zero upload payload does not mean zero network activity.&lt;/p&gt;

&lt;p&gt;A separate AMD IBS run collects 233 CPU samples on its first invocation, reports zero lost samples, and places most inclusive sample weight beneath &lt;code&gt;hash_file&lt;/code&gt; in the &lt;a href="https://github.com/copyleftdev/execution-evidence-lab/blob/e61aadee4a68e6a1d5b54a00387dcd325b5d5e51/evidence/historical/ibs/hash-storm/pass-1/cpu-profile.txt" rel="noopener noreferrer"&gt;call-stack profile&lt;/a&gt;. “Inclusive” includes work in functions called by &lt;code&gt;hash_file&lt;/code&gt;, such as the hashing library and file-read paths.&lt;/p&gt;

&lt;p&gt;The samples help locate the work. The syscall and function-entry counts establish the repetition. This run does not quantify how much faster a corrected implementation would be.&lt;/p&gt;

&lt;p&gt;These are observations from separate runs of the same seeded behavior. They corroborate the explanation; they are not one combined trace.&lt;/p&gt;

&lt;p&gt;The output is correct. The computer has simply been asked to check the same pocket for its keys 256 times.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. A correct backup, with an additional destination
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;unexpected-egress&lt;/code&gt; variant makes a harmless connection to our second local endpoint before performing the backup.&lt;/p&gt;

&lt;p&gt;The file still verifies correctly.&lt;/p&gt;

&lt;p&gt;In the eBPF showcase, baseline connection counts are two on the first invocation and one on the second: a digest check plus an upload, followed by a digest check alone. The extra-connection variant records three and two.&lt;/p&gt;

&lt;p&gt;Counts tell us there is more connection activity. The socket trace, packet addresses, and second endpoint’s records establish where it goes.&lt;/p&gt;

&lt;p&gt;That distinction matters. Three connections do not inherently mean something is wrong. A destination requirement gives the observation its meaning.&lt;/p&gt;
&lt;h2&gt;
  
  
  The complete behavior set
&lt;/h2&gt;

&lt;p&gt;Here is the compact view of the captured demonstrations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;Saved content matches?&lt;/th&gt;
&lt;th&gt;Additional observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Second invocation sends no upload payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant upload&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Unchanged file is uploaded again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash storm&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;256 hashing-function entries per invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Busy wait&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Deliberate 200 ms spin before useful work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False success&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Completion claim and zero exit status without a backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corrupt upload&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Receiver rejects three attempts per invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;First transfer is interrupted; another attempt succeeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unexpected egress&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Additional connection to the second local endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the retry scenario, the receiver disconnects after consuming 65,536 payload bytes. The client restarts from byte zero, and the receiver eventually commits the complete 524,288-byte file. Its total consumed payload for that invocation is 589,824 bytes.&lt;/p&gt;

&lt;p&gt;That demonstrates bounded retry recovery. It does not demonstrate partial-transfer resume, which this implementation does not provide.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run the experiment
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/copyleftdev/execution-evidence-lab/blob/e61aadee4a68e6a1d5b54a00387dcd325b5d5e51/evidence/historical/README.md" rel="noopener noreferrer"&gt;original measured source and captures&lt;/a&gt; are preserved alongside the polished implementation. The numbers above belong to that historical build. New runs may have different instruction addresses, timings, and sample counts.&lt;/p&gt;

&lt;p&gt;Get the &lt;a href="https://github.com/copyleftdev/execution-evidence-lab/tree/e61aadee4a68e6a1d5b54a00387dcd325b5d5e51" rel="noopener noreferrer"&gt;source and evidence&lt;/a&gt; from this pinned publication revision:&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/copyleftdev/execution-evidence-lab.git observability-lab
&lt;span class="nb"&gt;cd &lt;/span&gt;observability-lab
git checkout &lt;span class="nt"&gt;--detach&lt;/span&gt; e61aadee4a68e6a1d5b54a00387dcd325b5d5e51
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then build the client and run the tests:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make
make &lt;span class="nb"&gt;test
&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument run &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The core dependencies are Linux, Python 3.10 or newer, a C compiler, Make, and the OpenSSL development library. Install the optional tracing tools for the collectors you want to use.&lt;/p&gt;

&lt;p&gt;The runner prints a path to a Markdown report. Its default process collector is &lt;code&gt;strace&lt;/code&gt; when available, and packet capture is best effort. Missing capabilities are recorded explicitly.&lt;/p&gt;

&lt;p&gt;To select a collector:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument doctor
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument run hash-storm &lt;span class="nt"&gt;--collector&lt;/span&gt; strace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;On a trusted local lab machine, the privileged demonstrations can be run with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument run &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--collector&lt;/span&gt; bpftrace &lt;span class="nt"&gt;--packets&lt;/span&gt; required
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument run hash-storm &lt;span class="nt"&gt;--collector&lt;/span&gt; ibs &lt;span class="nt"&gt;--packets&lt;/span&gt; required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These commands run the synthetic lab as root. The fixtures use loopback and nonsecret generated data; the protocol is plaintext. The utility does not change host tracing policies. Hardware sampling and eBPF availability depend on the machine and its permissions.&lt;/p&gt;

&lt;p&gt;For a run without tracing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; instrument run &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--collector&lt;/span&gt; none &lt;span class="nt"&gt;--packets&lt;/span&gt; off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A successful showcase command means the scenarios executed, including the deliberately failing ones. Inspect each scenario’s independent verification result; do not interpret the runner’s exit status as “every backup was correct.”&lt;/p&gt;
&lt;h2&gt;
  
  
  The instrument needs scrutiny too
&lt;/h2&gt;

&lt;p&gt;During development, the CPU-report parser initially matched the &lt;code&gt;Samples&lt;/code&gt; portion of &lt;code&gt;Total Lost Samples&lt;/code&gt; and displayed zero samples even though the raw profile contained hundreds.&lt;/p&gt;

&lt;p&gt;The hardware data was present. Our summary was wrong.&lt;/p&gt;

&lt;p&gt;Inspecting the raw artifact exposed the mistake. The parser was corrected, and regression tests now distinguish actual sample counts from lost samples. At the time of these captures, the 11-test suite checked fixture behavior and CPU-summary parsing; the saved traced runs provided separate collector validation. The public-release version adds protocol and collector-failure regression tests.&lt;/p&gt;

&lt;p&gt;An article about distrusting convenient summaries was nearly defeated by its own convenient summary. There is probably a Unix utility for that feeling.&lt;/p&gt;

&lt;p&gt;Other limits are less entertaining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracing changes execution. The recorded process CPU accounting includes collector overhead. These showcase runs are not controlled performance benchmarks.&lt;/li&gt;
&lt;li&gt;CPU samples can miss short-lived activity. An unsampled function may still have executed.&lt;/li&gt;
&lt;li&gt;The eight eBPF-showcase packet captures reported zero kernel drops. That is useful loss accounting, not proof of universal observation coverage.&lt;/li&gt;
&lt;li&gt;The normalized timeline contains client claims, receiver events, and verifier checks. Kernel and CPU traces remain separate artifacts. Different clocks require careful alignment.&lt;/li&gt;
&lt;li&gt;The verifier checks the receiver’s saved file. It does not exercise a separate restore command or prove survival through power loss.&lt;/li&gt;
&lt;li&gt;The observers are separate from the client’s reporting, but they share a host and were built as part of the same project. This is not an independent third-party audit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fixture also omits TLS, compression, multi-file snapshots, and source mutation during transfer. It observes a local program; it cannot see computation inside a remote model provider.&lt;/p&gt;

&lt;p&gt;Those boundaries define what the results can support.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this changes about evaluating an agent
&lt;/h2&gt;

&lt;p&gt;The next experiment is to give an agent a clean implementation and ask it to make repeated backups faster while preserving integrity, recovery behavior, and destination restrictions.&lt;/p&gt;

&lt;p&gt;Before that run, freeze the requirements, fixtures, resource limits, and grading criteria. Keep the independent checks outside the agent’s editable workspace. Preserve its patch, the executable identity, and the evaluation setup.&lt;/p&gt;

&lt;p&gt;Measure performance with repeated untraced runs. Use traced runs to investigate differences. The seeded cases give us known behaviors against which to check the instrument first.&lt;/p&gt;

&lt;p&gt;That is the connection back to Hemapriya’s article: the measurement needs to remain connected to the work we actually care about. For this small backup task, a correct file is necessary. Recovery, resource use, and destination behavior tell us more about the implementation’s suitability.&lt;/p&gt;

&lt;p&gt;We can build those properties into better tests. Execution evidence helps us discover which properties our current tests leave out and investigate why a result occurred.&lt;/p&gt;

&lt;p&gt;Cheap code makes it easier to produce a plausible solution. The engineering work includes deciding what evidence would make us trust it.&lt;/p&gt;

&lt;p&gt;The agent can say it worked.&lt;/p&gt;

&lt;p&gt;I would still like to see the file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Acknowledgment
&lt;/h2&gt;

&lt;p&gt;Thank you to Hemapriya Kanagala for the article that prompted this experiment. The instrumentation approach and its conclusions are my response; they should not be read as claims she made or an endorsement by her.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__3307586"&gt;
    &lt;a href="/hemapriya_kanagala" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3307586%2F2dffaf97-946d-44a6-8a39-07d94a72e07d.png" alt="hemapriya_kanagala image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/hemapriya_kanagala"&gt;Hemapriya Kanagala&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hemapriya_kanagala"&gt;Hey, I'm Hema 👋 
Developer, writer, and creator of Dev Opportunity Radar, a weekly series published every Friday on DEV, helping people discover opportunities they might otherwise miss.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  How this was made
&lt;/h2&gt;

&lt;p&gt;AI assisted with the implementation, experiment execution, and drafting of this article. The cover illustration was AI-generated. The numerical observations above come from saved local runs; the behaviors were deliberately seeded. This is a demonstration of an evaluation instrument, not a model benchmark.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>linux</category>
      <category>debugging</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Interviewed an Executable. It Had Notes.</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Sat, 12 Sep 2026 17:14:13 +0000</pubDate>
      <link>https://dev.to/copyleftdev/i-interviewed-an-executable-it-had-notes-1k3e</link>
      <guid>https://dev.to/copyleftdev/i-interviewed-an-executable-it-had-notes-1k3e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj431cvbcb3euj1oeeosm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj431cvbcb3euj1oeeosm.png" alt="An executable sits for an anonymous television interview. Its face is pixelated, while the caption identifies it as sample.exe." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have blurred the executable’s face to protect its identity.&lt;/p&gt;

&lt;p&gt;Its SHA-256 remains publicly available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INTERVIEWER:&lt;/strong&gt; State your name for the record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BINARY:&lt;/strong&gt; You put it in the download filename.&lt;/p&gt;

&lt;p&gt;I built a Rust executable that could report the environments running it, gave it a false product identity, and submitted it to VirusTotal. Then I collected its messages in a database and built a map to replay them.&lt;/p&gt;

&lt;p&gt;The interesting part was learning how to read its notes. A changed filename, a missing HTTPS message, and a DNS lookup arriving hours later each revealed something different about what the instrumentation could observe.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The interview dialogue is invented. The measurements come from recorded telemetry. “The binary” represents the experiment’s Windows and Linux builds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The cover identity was &lt;strong&gt;ZeroToken Engine&lt;/strong&gt;, supposedly an LLM token-metering bypass. Its banner claimed to have disabled token accounting. Its actual behavior was to collect environment metadata, attempt to send it to my infrastructure, wait, and exit. The research README disclosed the canary’s behavior alongside the false product claims.&lt;/p&gt;

&lt;p&gt;The premise reversed a familiar workflow. A malware-analysis sandbox executes an unfamiliar program in a controlled environment to observe its behavior. My executable would report some characteristics of that environment in return.&lt;/p&gt;

&lt;p&gt;It collected OS version, uptime, CPU and memory sizing, its own launch path, and process names matching a fixed list of analysis tools. It also collected hostname and username; those identifying fields are withheld in the video. The reviewed code installed nothing, collected no document contents or credentials, and did not use VM indicators to evade execution. Detecting a VM added information to the report.&lt;/p&gt;

&lt;p&gt;VirusTotal was the only submission destination. The preserved records do not label author tests or contain a submission-time manifest, so the totals below include every recorded run ID. They describe an exploratory capture, without establishing a count of independent analysis services.&lt;/p&gt;

&lt;p&gt;Each execution generated a random eight-byte &lt;strong&gt;run ID&lt;/strong&gt;. The program used it on two reporting channels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;What it carried&lt;/th&gt;
&lt;th&gt;What the collector observed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DNS heartbeat&lt;/td&gt;
&lt;td&gt;A compact encoded message in a hostname lookup: run ID, checkpoint, and environment flags&lt;/td&gt;
&lt;td&gt;A DNS request, often arriving from a recursive resolver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS dossier&lt;/td&gt;
&lt;td&gt;The fuller environment snapshot, encoded as CBOR&lt;/td&gt;
&lt;td&gt;An outbound web connection and its submitted record&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CBOR is a binary data format. For DNS, the encoded heartbeat was converted to base32 text and split into labels that fit inside a domain name. The lookup itself carried the message to my authoritative DNS server.&lt;/p&gt;

&lt;p&gt;The program attempted four checkpoints: &lt;code&gt;Boot&lt;/code&gt;, &lt;code&gt;Profiled&lt;/code&gt;, &lt;code&gt;Networked&lt;/code&gt;, and &lt;code&gt;Dwell&lt;/code&gt;, with a default 45-second delay before the last. On a DigitalOcean droplet, the DNS and HTTP collectors wrote received events to SQLite. Caddy handled HTTPS, and a scheduled job added network information and generated the visualization’s feed.&lt;/p&gt;

&lt;p&gt;Every event received a collector-side timestamp. Grouping by run ID connected the small DNS messages with the richer HTTPS record.&lt;/p&gt;

&lt;p&gt;The preserved snapshot contains &lt;strong&gt;353 event rows across 21 run IDs&lt;/strong&gt;: 338 DNS rows and 15 HTTPS dossiers. Fifteen run IDs appeared on both channels; six appeared only through DNS. The recorded arrivals span 06:28:35–11:43:25 UTC on September 12, 2026.&lt;/p&gt;

&lt;p&gt;One useful starting point is run &lt;code&gt;0861ae2dd3f77c72&lt;/code&gt;. Its first heartbeat arrived at 06:52:09. A second later, its dossier reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OS:             Windows 10, build 19044
Uptime:         50 seconds
CPUs:           4
RAM:            4195 MiB
Parent process: explorer.exe
Executable:     ZeroToken-Patch.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its first &lt;code&gt;Dwell&lt;/code&gt; observation arrived 46 seconds after &lt;code&gt;Boot&lt;/code&gt;. The stored reverse-DNS name for the HTTPS source address pointed to Google Cloud infrastructure. Together, these records supplied a concrete environment profile and a sequence of received checkpoints.&lt;/p&gt;

&lt;p&gt;The executable’s own path supplied another useful observation.&lt;/p&gt;

&lt;p&gt;Between 08:21 and 10:30 UTC, six dossiers reported CentOS 7, four CPUs, and 1837 MiB of RAM. They shared a hostname and username. Their paths ended in &lt;code&gt;gsvnywygooow&lt;/code&gt;, rather than the distributed Linux filename, &lt;code&gt;zerotoken-patch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INTERVIEWER:&lt;/strong&gt; They changed your name?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BINARY:&lt;/strong&gt; Apparently readability wasn’t a requirement.&lt;/p&gt;

&lt;p&gt;The repeated configuration suggested a common environment template. The changed basename showed how the program was named when it executed. A launch-path field had captured a detail that network addresses alone would have missed.&lt;/p&gt;

&lt;p&gt;The two channels also produced different levels of visibility. At 09:19:40, run &lt;code&gt;5c5ab77e22747178&lt;/code&gt; began appearing through DNS. Its fingerprint was &lt;code&gt;7&lt;/code&gt;, combining flags for a recognized VM network-interface prefix, low resources or a fresh boot, and a process-name match against the tool allowlist.&lt;/p&gt;

&lt;p&gt;No HTTPS dossier arrived. The heartbeat preserved those flags, but the name of the matched tool was unavailable. Because the allowlist included VM guest utilities, that flag alone did not establish debugger activity.&lt;/p&gt;

&lt;p&gt;This was the practical value of the small channel: six runs contributed observations even though their larger reports were absent. The records did not establish why HTTPS was missing.&lt;/p&gt;

&lt;p&gt;To make the arrivals easier to follow, I built &lt;strong&gt;WAYPOINT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2Xjklv3tco0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/2Xjklv3tco0" rel="noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;, or &lt;a href="https://replay.waypoint.leet-index.com/" rel="noopener noreferrer"&gt;explore the deployed visualization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The replay groups events into runs and places them on a map using network-address enrichment. The captured feed displays 20 of the database’s 21 run IDs. One Windows run had a dossier but lacked coordinates for its selected address, so it never acquired a pin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INTERVIEWER:&lt;/strong&gt; Where were you?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BINARY:&lt;/strong&gt; Before we discuss the map: some of those pins belong to my DNS resolver.&lt;/p&gt;

&lt;p&gt;A recursive resolver looks up names on another machine’s behalf. Its address can be what the authoritative collector sees. HTTPS supplies the outbound connection address, which may also belong to shared infrastructure or a proxy. Those distinctions determine what the geography means.&lt;/p&gt;

&lt;p&gt;Then there were the messages that returned.&lt;/p&gt;

&lt;p&gt;At 11:14 UTC, DNS observations carried the earlier Windows run IDs again. Looking at the first and last received times for each checkpoint makes the pattern visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_received&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0861ae2dd3f77c72'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dns'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With scene values 0–3 translated to their checkpoint names, the query returns these times on September 12, all UTC:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Checkpoint&lt;/th&gt;
&lt;th&gt;First received&lt;/th&gt;
&lt;th&gt;Last received&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boot&lt;/td&gt;
&lt;td&gt;06:52:09&lt;/td&gt;
&lt;td&gt;11:14:39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profiled&lt;/td&gt;
&lt;td&gt;06:52:10&lt;/td&gt;
&lt;td&gt;11:14:39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Networked&lt;/td&gt;
&lt;td&gt;06:52:10&lt;/td&gt;
&lt;td&gt;11:14:39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dwell&lt;/td&gt;
&lt;td&gt;06:52:55&lt;/td&gt;
&lt;td&gt;11:14:39&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;INTERVIEWER:&lt;/strong&gt; Where were you during those four hours?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BINARY:&lt;/strong&gt; You’re asking a DNS record for an alibi.&lt;/p&gt;

&lt;p&gt;The later observations reused the same run ID and checkpoints. They showed that names carrying those messages had been queried again. They did not measure four hours of continuous execution.&lt;/p&gt;

&lt;p&gt;The collector records a heartbeat again when another complete lookup supplies it. That behavior also explains why event counts need grouping: the DNS-only run above produced 71 rows under one run ID. The stored schema omits the original query name and query type, limiting how precisely repeated traffic can be reconstructed.&lt;/p&gt;

&lt;p&gt;There are three boundaries to this interpretation. First, receipt of telemetry is evidence available to the collector; the endpoints do not authenticate it as proof of execution. Second, the network labels are derived heuristics: a Google Cloud address does not identify a sandbox operator, and map arcs do not establish file handoffs. Third, enrichment was incomplete: all reputation results in this snapshot were &lt;code&gt;error&lt;/code&gt;, and some reverse-DNS fields contained lookup diagnostics. Those failed fields cannot support attribution.&lt;/p&gt;

&lt;p&gt;For a repeat of this experiment, I would keep a submission manifest with artifact hashes and times, mark control runs explicitly, and retain DNS query names and types alongside decoded heartbeats. I would also give runs without coordinates a visible place beside the map. Each change would make a specific unanswered question easier to investigate.&lt;/p&gt;

&lt;p&gt;The executable produced enough detail to compare environments, observe changed filenames, and distinguish fresh run IDs from later lookups carrying old ones. Those are small findings with inspectable records behind them.&lt;/p&gt;

&lt;p&gt;The best question I could ask the witness turned out to be: &lt;strong&gt;Which record supports that?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reporting basis: the preserved September 12 SQLite snapshot and a replay feed generated at 15:18:02 UTC that day. The live visualization may have changed since capture. The Windows and Linux artifacts have different checksums; the replay’s sample header displays the Windows one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/copyleftdev/interview-with-a-binary-media" rel="noopener noreferrer"&gt;cover, replay stills, and video assets are available on GitHub&lt;/a&gt;. This is a media release; the executable source and raw telemetry are not included.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>Someone Spammed My DEV Post. I Traced It to a Wombat.</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Thu, 10 Sep 2026 01:56:33 +0000</pubDate>
      <link>https://dev.to/copyleftdev/someone-spammed-my-dev-post-i-traced-it-to-a-wombat-176a</link>
      <guid>https://dev.to/copyleftdev/someone-spammed-my-dev-post-i-traced-it-to-a-wombat-176a</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4yyab81kqk766lzceh7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4yyab81kqk766lzceh7.jpg" alt="A weary wombat running a spam operation from a basement server desk" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Illustration generated for this article. Every prop is a finding: the sack of blank name badges is the Faker persona namespace, the rubber stamp is the inert tracking parameter, the three coins are the break-even, and the red yarn connects nothing because attribution failed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — A spam comment on my article led to a TinyURL, a throwaway &lt;code&gt;.store&lt;/code&gt; domain, and finally a &lt;em&gt;legitimate&lt;/em&gt; SaaS product with an affiliate code stapled to it. No malware, no cloaking, no exploit. The account that posted it has a name generated by &lt;code&gt;faker.js&lt;/code&gt; and a 19th-century engraving of a wombat for a face. I costed the whole operation out: &lt;strong&gt;three signups a year pays for it.&lt;/strong&gt; That's why it will never stop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F367k3r4vcqqj55ects9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F367k3r4vcqqj55ects9e.png" alt="The full redirect chain, from DEV comment to affiliate link" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full evidence, raw captures and a reproduce script:&lt;/strong&gt; &lt;a href="https://github.com/copyleftdev/dev-to-comment-hustle" rel="noopener noreferrer"&gt;github.com/copyleftdev/dev-to-comment-hustle&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Act I: The comment
&lt;/h2&gt;

&lt;p&gt;I published &lt;a href="https://dev.to/copyleftdev/migrating-legacy-llm-infrastructure-to-an-ai-gateway-27hl"&gt;Migrating Legacy LLM Infrastructure to an AI Gateway&lt;/a&gt; on September 1st. Eight days later, underneath two thoughtful comments about shared-key blast radius and provider failover, this appeared:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stop wasting time applying manually&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Let AI handle your job applications every single day&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Increase your chances of getting interviews fast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tinyurl.com/36nsecn5&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No punctuation. No engagement with the post. A shortener.&lt;/p&gt;

&lt;p&gt;It's funny in the way all low-effort spam is funny — it's not even &lt;em&gt;trying&lt;/em&gt;. But a shortener is a closed door, and I have a shell. Let's open it, then let's go find who knocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act II: Where the link goes
&lt;/h2&gt;

&lt;p&gt;Never click. Ask for headers and refuse the redirect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="s1"&gt;'https://tinyurl.com/36nsecn5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt;
&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://zenviapro.store/massapply?whose=yahoo&lt;/span&gt;
&lt;span class="na"&gt;x-robots-tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noindex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zenviapro.store&lt;/code&gt;. A route called &lt;code&gt;/massapply&lt;/code&gt;, and a &lt;code&gt;whose=yahoo&lt;/code&gt; parameter that looks like campaign segmentation. Follow it all the way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="s1"&gt;'https://zenviapro.store/massapply?whose=yahoo'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'^HTTP/|^location:'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="ne"&gt;Found&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://loopcv.pro/?via=md&lt;/span&gt;
&lt;span class="s"&gt;HTTP/2 301&lt;/span&gt;
&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.loopcv.pro/?via=md&lt;/span&gt;
&lt;span class="s"&gt;HTTP/2 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LoopCV.&lt;/strong&gt; A real, functioning job-application-automation SaaS. Not a phishing kit. Not a credential harvester. A product you can buy with a credit card — with &lt;code&gt;?via=md&lt;/code&gt; on the end.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;?via=&lt;/code&gt; is &lt;a href="https://www.rewardful.com/" rel="noopener noreferrer"&gt;Rewardful's&lt;/a&gt; referral parameter, and LoopCV's own affiliate page points registrations at &lt;code&gt;loopcv.getrewardful.com&lt;/code&gt;. So &lt;code&gt;md&lt;/code&gt; is somebody's affiliate token, and every person who clicks that comment and later subscribes puts money in a stranger's pocket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not a malware campaign. It's affiliate marketing with the manners removed.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Act III: The infrastructure is held together with tape
&lt;/h2&gt;

&lt;p&gt;The sloppiness &lt;em&gt;is&lt;/em&gt; the signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's a stock Express app with the wrapper still on
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="ne"&gt;Found&lt;/span&gt;
&lt;span class="na"&gt;X-Powered-By&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Express&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text/plain; charset=utf-8&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;48&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found. Redirecting to https://loopcv.pro/?via=md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Found. Redirecting to&lt;/code&gt; is the literal default body of Express's &lt;code&gt;res.redirect()&lt;/code&gt;. &lt;code&gt;X-Powered-By: Express&lt;/code&gt; is the header every hardening guide tells you to strip in the first five minutes. Neither was touched. Ask for anything else and you get the stock 404:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s1"&gt;'https://zenviapro.store/'&lt;/span&gt;
&lt;span class="c"&gt;# &amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Error&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;pre&amp;gt;Cannot GET /&amp;lt;/pre&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no website here. No landing page, no cloaked content, no fake blog. &lt;strong&gt;One domain, one route&lt;/strong&gt;, about ninety seconds of JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tracking parameter is a prop
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;whose=yahoo&lt;/code&gt; looks like segmentation. Which list? Which provider? Let's ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;w &lt;span class="k"&gt;in &lt;/span&gt;yahoo gmail outlook devto reddit &lt;span class="s1"&gt;''&lt;/span&gt; XXtest&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%-8s -&amp;gt; '&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;w&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;empty&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code} %{redirect_url}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://zenviapro.store/massapply?whose=&lt;/span&gt;&lt;span class="nv"&gt;$w&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;yahoo&lt;/span&gt;    -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;gmail&lt;/span&gt;    -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;outlook&lt;/span&gt;  -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;devto&lt;/span&gt;    -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;reddit&lt;/span&gt;   -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&amp;lt;&lt;span class="n"&gt;empty&lt;/span&gt;&amp;gt;  -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="n"&gt;XXtest&lt;/span&gt;   -&amp;gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;loopcv&lt;/span&gt;.&lt;span class="n"&gt;pro&lt;/span&gt;/?&lt;span class="n"&gt;via&lt;/span&gt;=&lt;span class="n"&gt;md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inert.&lt;/strong&gt; Every value routes identically. It splits no traffic and sub-tags nothing. The one piece of the URL that looks like operational sophistication is decoration.&lt;/p&gt;

&lt;h3&gt;
  
  
  There is no cloaking whatsoever
&lt;/h3&gt;

&lt;p&gt;Real malicious redirectors fingerprint you — benign page for the researcher, payload for the victim. This one returns the same 302 to Googlebot, to &lt;code&gt;curl&lt;/code&gt;, to an iPhone, and to a request with no User-Agent at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No cloaking is itself a finding.&lt;/strong&gt; This operator has no threat model, because nothing in the chain is illegal. It's a terms-of-service violation wearing a trench coat.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mail server is the real pivot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short zenviapro.store MX
&lt;span class="c"&gt;# 10 mail.beeservices.shop.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mail exchanger lives on a &lt;strong&gt;different domain&lt;/strong&gt; — which resolves right back to the same box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;zenviapro&lt;/span&gt;.&lt;span class="n"&gt;store&lt;/span&gt;        &lt;span class="n"&gt;A&lt;/span&gt;   &lt;span class="m"&gt;50&lt;/span&gt;.&lt;span class="m"&gt;114&lt;/span&gt;.&lt;span class="m"&gt;206&lt;/span&gt;.&lt;span class="m"&gt;36&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt;.&lt;span class="n"&gt;beeservices&lt;/span&gt;.&lt;span class="n"&gt;shop&lt;/span&gt;  &lt;span class="n"&gt;A&lt;/span&gt;   &lt;span class="m"&gt;50&lt;/span&gt;.&lt;span class="m"&gt;114&lt;/span&gt;.&lt;span class="m"&gt;206&lt;/span&gt;.&lt;span class="m"&gt;36&lt;/span&gt;
&lt;span class="n"&gt;beeservices&lt;/span&gt;.&lt;span class="n"&gt;shop&lt;/span&gt;       &lt;span class="n"&gt;A&lt;/span&gt;   (&lt;span class="n"&gt;nothing&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;beeservices.shop&lt;/code&gt; has no A record and no certificate in Certificate Transparency, ever. It's a mail-only domain. One box, two domains, two roles — that's a &lt;strong&gt;portfolio&lt;/strong&gt;, not a one-off.&lt;/p&gt;

&lt;p&gt;And the box is listening. Port 80 closed; 443 and 25 open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;220 mail.zenviapro.store ESMTP
250-PIPELINING
250-8BITMIME
250 SMTPUTF8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;STARTTLS&lt;/code&gt;. No &lt;code&gt;AUTH&lt;/code&gt;. A minimal MTA that does nothing but move mail in cleartext.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjq0f6ochdrfu9guc2c7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjq0f6ochdrfu9guc2c7v.png" alt="One box, two domains: the mail server is the pivot" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The stale HELO is a fingerprint
&lt;/h3&gt;

&lt;p&gt;My favourite detail. The banner announces &lt;code&gt;mail.zenviapro.store&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short mail.zenviapro.store A
&lt;span class="c"&gt;# (nothing)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That hostname does not resolve.&lt;/strong&gt; It's a leftover from an earlier config, before the MX was swapped to &lt;code&gt;beeservices.shop&lt;/code&gt;. They rebuild the domains; they don't rebuild the server. A banner that disagrees with DNS is a durable pivot you can hunt across an entire fleet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act IV: Who posted it
&lt;/h2&gt;

&lt;p&gt;This is the part I actually enjoyed.&lt;/p&gt;

&lt;p&gt;DEV has a public API, so the commenter isn't a mystery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://dev.to/api/comments?a_id=4547359'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[].user.username'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max_quimby
mudassirworks
jaylonstiedemannterry78-993
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meet &lt;strong&gt;&lt;code&gt;jaylonstiedemannterry78-993&lt;/code&gt;&lt;/strong&gt;, display name &lt;code&gt;Jaylon_Stiedemann-Terry78&lt;/code&gt;, user ID 3748129, joined &lt;strong&gt;February 2, 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The profile is a vacuum:&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jaylonstiedemannterry78-993"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jaylon_Stiedemann-Terry78"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"joined_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Feb  2, 2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"website_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"twitter_username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"github_username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;Zero published articles. No bio, no location, no links, no socials. Seven months of membership and a single comment to show for it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on durability.&lt;/strong&gt; If DEV removes this account — which it may well do — the profile&lt;br&gt;
lookup above starts returning 404 and the account page goes dead. That doesn't retract&lt;br&gt;
anything: the raw captures are committed in the&lt;br&gt;
&lt;a href="https://github.com/copyleftdev/dev-to-comment-hustle/tree/main/evidence/raw" rel="noopener noreferrer"&gt;evidence repo&lt;/a&gt;,&lt;br&gt;
checksummed, and dated. Read a 404 as the platform doing its job, not as a claim being&lt;br&gt;
withdrawn. The infrastructure findings are independent of the account either way, and the&lt;br&gt;
&lt;em&gt;pattern&lt;/em&gt; — a Faker-templated name, an empty profile, an aged dormant account — outlives any&lt;br&gt;
single username.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The name is machine-generated, and I can prove it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxz4znbjwdq3vg5us2lq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxz4znbjwdq3vg5us2lq.png" alt="Faker token decomposition and the zero-hyphen proof" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Say &lt;code&gt;Jaylon_Stiedemann-Terry78&lt;/code&gt; out loud. Something's off — &lt;code&gt;Stiedemann-Terry&lt;/code&gt; is a double-barrelled surname that doesn't sound like a family, it sounds like a &lt;em&gt;draw&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It is. Those tokens come straight out of &lt;a href="https://github.com/faker-js/faker" rel="noopener noreferrer"&gt;Faker&lt;/a&gt;, the library every developer on earth uses to generate fake test data. Let's check the actual source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sO&lt;/span&gt; https://raw.githubusercontent.com/faker-js/faker/next/src/locales/en/person/last_name.ts
curl &lt;span class="nt"&gt;-sO&lt;/span&gt; https://raw.githubusercontent.com/faker-js/faker/next/src/locales/en/person/first_name.ts

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"'Stiedemann'"&lt;/span&gt; last_name.ts   &lt;span class="c"&gt;# 408:    'Stiedemann',&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"'Terry'"&lt;/span&gt;       last_name.ts  &lt;span class="c"&gt;# 417:    'Terry',&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"'Jaylon'"&lt;/span&gt;      first_name.ts &lt;span class="c"&gt;# 2514:    'Jaylon',&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three. &lt;code&gt;Jaylon&lt;/code&gt; from the first-name list, &lt;code&gt;Stiedemann&lt;/code&gt; and &lt;code&gt;Terry&lt;/code&gt; both from the surname list.&lt;/p&gt;

&lt;p&gt;And here's the kicker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; last_name.ts   &lt;span class="c"&gt;# 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Faker's 466-entry English surname list contains zero hyphens.&lt;/strong&gt; So &lt;code&gt;Stiedemann-Terry&lt;/code&gt; isn't one surname from the list — it's &lt;em&gt;two independent draws&lt;/em&gt; that the operator joined with a hyphen. This isn't stock &lt;code&gt;faker.internet.username()&lt;/code&gt;. It's a custom template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{firstName}_{lastName}-{lastName}{2 digits}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means we can size their supply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3,185 first names × 466 surnames × 466 surnames × 100
= 69,164,186,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sixty-nine billion distinct personas.&lt;/strong&gt; Roughly eight per human being alive. They will never run out of names, and no blocklist of usernames will ever catch up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(One thing I checked so I wouldn't over-claim: the un-suffixed &lt;code&gt;jaylonstiedemannterry78&lt;/code&gt; returns a 404 — nobody has it. So the &lt;code&gt;-993&lt;/code&gt; is DEV's own username normalization, not evidence of a prior collision.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The avatar is a wombat
&lt;/h3&gt;

&lt;p&gt;The profile image is a 400×400 PNG, 8-bit grayscale-plus-alpha, stripped of all metadata. I downloaded it expecting a GAN face or a default monogram.&lt;/p&gt;

&lt;p&gt;It is a &lt;strong&gt;Victorian-era scientific engraving of a wombat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a stock photo of a person. Not an AI-generated headshot. A piece of public-domain 19th-century natural-history line art of a stout Australian marsupial, serving as the face of a fake job-spam persona. Whoever built this pipeline wired the avatar slot to some public-domain clipart source and never looked at the output.&lt;/p&gt;

&lt;p&gt;I want to be precise about something: &lt;strong&gt;there is no real person here to name.&lt;/strong&gt; The name is provably synthetic, the face is a public-domain animal illustration, and the profile is empty. That's not me protecting anyone's privacy — it's the finding.&lt;/p&gt;

&lt;h3&gt;
  
  
  The timeline says something the Express config doesn't
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7i7jz7dha9eg6x1acov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7i7jz7dha9eg6x1acov.png" alt="Provisioning timeline: six days apart, then 184 days dormant" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Line the dates up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-27&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;zenviapro.store&lt;/code&gt; registered (Namecheap)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-02-02&lt;/td&gt;
&lt;td&gt;DEV account created&lt;/td&gt;
&lt;td&gt;+6 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-07-30&lt;/td&gt;
&lt;td&gt;First TLS certificate issued&lt;/td&gt;
&lt;td&gt;+184 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-09-09&lt;/td&gt;
&lt;td&gt;Spam comment posted&lt;/td&gt;
&lt;td&gt;+41 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The domain and the account were provisioned &lt;strong&gt;six days apart&lt;/strong&gt; — same procurement burst. Then both sat &lt;em&gt;completely dormant for six months&lt;/em&gt; before the certificate was issued and the thing went live.&lt;/p&gt;

&lt;p&gt;That's aged-asset tradecraft. New domains and new accounts trip reputation heuristics; seven-month-old ones don't. And it sits in genuine tension with everything in Act III: &lt;strong&gt;sloppy at the application layer, disciplined at the account-aging layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which makes sense once you think about who this is. Aging assets doesn't take skill. It takes &lt;em&gt;patience&lt;/em&gt;, and a calendar. Stripping &lt;code&gt;X-Powered-By&lt;/code&gt; takes knowing what it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  It wasn't a blast
&lt;/h3&gt;

&lt;p&gt;I swept the comments on all 30 of my published articles for the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;id &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[]|select(.comments_count&amp;gt;0)|.id'&lt;/span&gt; mine.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://dev.to/api/comments?a_id=&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.. | objects | select(has("id_code"))
      | ((.body_html // "") | gsub("&amp;lt;[^&amp;gt;]*&amp;gt;";"")) as $t
      | select($t | test("tinyurl|applying manually|job application";"i"))
      | "HIT \(.id_code) @\(.user.username)"'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exactly one hit.&lt;/strong&gt; Not a shotgun across my whole back catalogue — one comment, on one post, eight days after it went up. Whether that's targeting or just a slow drip, I can't tell from one sample. But it isn't volume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act V: I went looking for them on GitHub. That search is the finding.
&lt;/h2&gt;

&lt;p&gt;Affiliate spammers leave GitHub artifacts more often than you'd think, because GitHub repos rank well and a repo full of referral links is free SEO. So I went hunting with &lt;code&gt;gh&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with the direct IOCs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;q &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;'36nsecn5'&lt;/span&gt; &lt;span class="s1"&gt;'50.114.206.36'&lt;/span&gt; &lt;span class="s1"&gt;'zenviapro.store'&lt;/span&gt; &lt;span class="s1"&gt;'beeservices.shop'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;gh search code &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 5
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Zero hits. All four.&lt;/strong&gt; The TinyURL slug, the origin IP, the redirector domain, the mail domain — none of it appears anywhere in GitHub's index. The operator has published nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then check whether anyone else has flagged them
&lt;/h3&gt;

&lt;p&gt;PhishDestroy maintains &lt;a href="https://github.com/phishdestroy/destroylist" rel="noopener noreferrer"&gt;&lt;code&gt;destroylist&lt;/code&gt;&lt;/a&gt;, a curated blocklist of phishing and scam domains. I pulled the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; .../destroylist/HEAD/list.txt &lt;span class="nt"&gt;-o&lt;/span&gt; dl.txt
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; dl.txt        &lt;span class="c"&gt;# 202659&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-ixc&lt;/span&gt; &lt;span class="s1"&gt;'zenviapro.store'&lt;/span&gt;  dl.txt   &lt;span class="c"&gt;# 0&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-ixc&lt;/span&gt; &lt;span class="s1"&gt;'beeservices.shop'&lt;/span&gt; dl.txt   &lt;span class="c"&gt;# 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;202,659 curated malicious domains, and ours isn't one of them.&lt;/strong&gt; Combined with the &lt;code&gt;NOT_OBSERVED&lt;/code&gt; reputation verdict, that's now two independent sources agreeing: nobody is tracking this, because by every technical definition there's nothing to track.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lead that looked incredible and wasn't
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr35ijm2k5hunbo2cr1jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr35ijm2k5hunbo2cr1jq.png" alt="The ruled-out zenvia*.info cluster" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's where I nearly fooled myself, so I'm showing my work.&lt;/p&gt;

&lt;p&gt;Searching &lt;code&gt;zenviapro&lt;/code&gt; turned up a hit in &lt;a href="https://github.com/phishdestroy/namesilo-evidence" rel="noopener noreferrer"&gt;&lt;code&gt;phishdestroy/namesilo-evidence&lt;/code&gt;&lt;/a&gt; — a registrar-abuse investigation filed with ICANN. In a list of flagged NameSilo domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zenviaetc.info
zenviahub.info
zenviapro.info     ← same second-level label as ours
zenvias.info
zenviatime.info
zenviazone.info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A six-domain family, same generation pattern, one of them sharing our exact label. My pulse went up. And they really &lt;em&gt;are&lt;/em&gt; one operator — all six resolve through the identical Cloudflare nameserver pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zenviapro.info    asa.ns.cloudflare.com  harley.ns.cloudflare.com
zenviahub.info    asa.ns.cloudflare.com  harley.ns.cloudflare.com
zenviaetc.info    asa.ns.cloudflare.com  harley.ns.cloudflare.com
... all six identical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it isn't &lt;em&gt;our&lt;/em&gt; operator, and the contrast kills it:&lt;/p&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;code&gt;zenvia*.info&lt;/code&gt; cluster&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;zenviapro.store&lt;/code&gt; (ours)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Registrar&lt;/td&gt;
&lt;td&gt;NameSilo&lt;/td&gt;
&lt;td&gt;Namecheap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;td&gt;Cloudflare (&lt;code&gt;asa&lt;/code&gt;/&lt;code&gt;harley&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;registrar-servers.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Cloudflare proxy&lt;/td&gt;
&lt;td&gt;Linveo direct, no proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.info&lt;/code&gt; × 6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.store&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing shared but six letters. Both are almost certainly riding the name of &lt;strong&gt;Zenvia&lt;/strong&gt;, a real Brazilian CPaaS company — which is exactly why the label collides. Two unrelated operators reaching for the same brandable string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A matching name is not a matching operator.&lt;/strong&gt; If I'd stopped at the grep I'd have published a confident, wrong attribution to a phishing cluster that has nothing to do with this.&lt;/p&gt;

&lt;h3&gt;
  
  
  What GitHub &lt;em&gt;did&lt;/em&gt; give me
&lt;/h3&gt;

&lt;p&gt;The affiliate ecosystem, in the open. LoopCV referral links are scattered across GitHub in exactly the SEO-backlink genre I expected:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Ramas68/LoopCV-Promo-Codes&lt;/code&gt; — *"LoopCV Promo Codes \&lt;/td&gt;
&lt;td&gt;50% Off Discount"*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;diaodiaozhuye/awesome-ai-startups&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?via=toolify&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;heukshow/aicity-os&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?via=sang-kwon&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at those tokens. &lt;code&gt;abdul&lt;/code&gt;. &lt;code&gt;toolify&lt;/code&gt;. &lt;code&gt;sang-kwon&lt;/code&gt;. A first name, a company, a full handle. &lt;strong&gt;The affiliates who promote LoopCV in public sign their work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ours is &lt;code&gt;md&lt;/code&gt;. Two characters, no name, nothing to search. That's the only genuinely deliberate piece of operational security in this entire campaign — and it's not on the server, the domain, or the account. It's on the one string that would have led back to a person.&lt;/p&gt;

&lt;p&gt;So: no attribution. And the &lt;em&gt;shape&lt;/em&gt; of the failure is the story. Someone who leaves &lt;code&gt;X-Powered-By: Express&lt;/code&gt; on, ships a fake tracking parameter, and picks a wombat for an avatar still knew to make the payout token anonymous. They didn't secure the operation. They secured the part that gets paid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act VI: The economics, which are the actual vulnerability
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj4tgtrsbkp6tl76k964.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj4tgtrsbkp6tl76k964.png" alt="Exact interval arithmetic: break-even at 0.06-2.4 conversions per year" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stop thinking like a defender and think like the operator.&lt;/p&gt;

&lt;p&gt;LoopCV's affiliate program pays &lt;strong&gt;25% commission&lt;/strong&gt;. Publicly reported figures put subscriptions at &lt;strong&gt;$50–$200&lt;/strong&gt; with customers staying &lt;strong&gt;6–12 months&lt;/strong&gt;. Exact interval arithmetic, gross revenue per referred customer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[$50, $200] × [6, 12] = [$300, $2,400]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 25%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[$300, $2,400] ÷ 4 = [$75, $600]     ← commission per conversion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost side: a &lt;code&gt;.store&lt;/code&gt; domain plus a year of budget hosting — call it &lt;strong&gt;$38–$180&lt;/strong&gt; all-in. Break-even:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[$38, $180] ÷ [$75, $600] = [19/300, 12/5] = [0.06, 2.4]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Between one-sixteenth of a signup and three signups per year covers the entire operation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the whole thesis. Nothing here needs to work &lt;em&gt;well&lt;/em&gt;. The copy can be terrible. The tracking parameter can be fake. The 404s can leak the framework. The HELO can be stale. The mascot can be a wombat. &lt;strong&gt;Three conversions and the year is paid for&lt;/strong&gt;, and everything after that is margin on infrastructure that costs less than lunch.&lt;/p&gt;

&lt;p&gt;You cannot out-moderate that math.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reputation feeds have nothing, and they're right
&lt;/h2&gt;

&lt;p&gt;I ran the origin IP through an offline reputation lens:&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;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"50.114.206.36"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"disposition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"reason_codes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NOT_OBSERVED"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not observed.&lt;/strong&gt; No feed has it — and that's &lt;em&gt;correct&lt;/em&gt;. It hosts no malware, no C2, no phishing. Threat intel is tuned for technical harm, and this campaign's harm is economic and reputational. It will sit below every threshold you own, forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who the victim actually is
&lt;/h2&gt;

&lt;p&gt;Not me. I scrolled past it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LoopCV is the victim.&lt;/strong&gt; A real company with a real product is having its brand welded to comment spam by an affiliate they've likely never spoken to. They eat the reputational damage, they pay commission on the conversions, and the operator's total exposure is a $12 domain and a wombat.&lt;/p&gt;

&lt;p&gt;To be explicit, because it matters: &lt;strong&gt;I found no evidence that LoopCV is running or is aware of this campaign.&lt;/strong&gt; Open affiliate programs get abused; that's the risk of the model. The fix belongs to the vendor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Require affiliates to declare traffic sources, and enforce it&lt;/li&gt;
&lt;li&gt;Ban unsolicited comment/forum posting in the program terms, in writing&lt;/li&gt;
&lt;li&gt;Flag referral tokens whose traffic arrives overwhelmingly via shorteners with no referrer&lt;/li&gt;
&lt;li&gt;Kill tokens on abuse reports — fast, without requiring a lawyer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;?via=md&lt;/code&gt; is a token. Tokens can be revoked. That's a one-line fix that permanently ends this specific campaign, and exactly one party can perform it.&lt;/p&gt;




&lt;h2&gt;
  
  
  IOCs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Campaign infrastructure — safe to blocklist:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicator&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tinyurl.com/36nsecn5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Shortener entry point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zenviapro.store&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;Redirector; Namecheap; created 2026-01-27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https://zenviapro.store/massapply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Only live route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beeservices.shop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;Mail-only sibling; no A record, no CT history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mail.beeservices.shop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hostname&lt;/td&gt;
&lt;td&gt;MX for &lt;code&gt;zenviapro.store&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;50.114.206.36&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IPv4&lt;/td&gt;
&lt;td&gt;Origin; 443 + 25 open, 80 closed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AS62564&lt;/code&gt; / &lt;code&gt;oh2.linveo.com&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ASN / rDNS&lt;/td&gt;
&lt;td&gt;Linveo, Ohio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jaylonstiedemannterry78-993&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DEV account&lt;/td&gt;
&lt;td&gt;user_id 3748129; joined 2026-02-02; 0 articles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Behavioural signatures — this is what actually generalises:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Affiliate token&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;via=md&lt;/code&gt; (Rewardful format)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inert campaign param&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;whose=&amp;lt;anything&amp;gt;&lt;/code&gt; — routing-neutral&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server fingerprint&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Powered-By: Express&lt;/code&gt; + body &lt;code&gt;Found. Redirecting to&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root response&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Cannot GET /&lt;/code&gt; (Express default 404)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMTP banner&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;220 mail.zenviapro.store ESMTP&lt;/code&gt; — &lt;strong&gt;does not resolve&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMTP capabilities&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;STARTTLS&lt;/code&gt;, no &lt;code&gt;AUTH&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=spf1 mx ip4:50.114.206.36 ~all&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DMARC&lt;/td&gt;
&lt;td&gt;Absent on both domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persona template&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{firstName}_{lastName}-{lastName}{2 digits}&lt;/code&gt;, all tokens ∈ Faker &lt;code&gt;en&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avatar class&lt;/td&gt;
&lt;td&gt;Public-domain engraving, grayscale+alpha PNG, metadata stripped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provisioning pattern&lt;/td&gt;
&lt;td&gt;Domain + account within 7 days, then ~6 months dormant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;&lt;code&gt;loopcv.pro&lt;/code&gt; is NOT an indicator of compromise.&lt;/strong&gt; It's a legitimate destination being abused by a third-party affiliate. Do not blocklist it. Blocklisting the victim is how threat intel gets a bad name.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What to actually do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you run a comment platform:&lt;/strong&gt; the highest-signal feature isn't the text, it's the &lt;em&gt;shape&lt;/em&gt;. Zero-engagement first comments containing a shortener, from accounts with no posts and an empty profile, are trivially clusterable. Resolve shorteners server-side at submission time and score the destination. And the persona template is a gift — a name whose tokens all appear in Faker's &lt;code&gt;en&lt;/code&gt; locale, with a structure Faker itself doesn't emit, is close to a free classifier feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you write on DEV:&lt;/strong&gt; don't click, and don't just delete. &lt;code&gt;curl -I&lt;/code&gt; takes four seconds. Then report the &lt;em&gt;destination&lt;/em&gt; to the vendor, not the comment to the platform. The platform can remove one comment; the vendor can revoke the token behind all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you run an affiliate program:&lt;/strong&gt; you are one unsupervised token away from your brand appearing under a headline like this one. Read your traffic sources.&lt;/p&gt;




&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;I went in expecting a dropper. I found four lines of Express, a fake tracking parameter, a name drawn from a test-data library, and a 19th-century wombat.&lt;/p&gt;

&lt;p&gt;That's the uncomfortable part. The most durable spam on the internet isn't sophisticated — it's &lt;em&gt;cheap and legal&lt;/em&gt;. There's no CVE here, no payload to reverse, no C2 to sinkhole. Every traditional defensive tool I own returns &lt;code&gt;NOT_OBSERVED&lt;/code&gt;, and every one of them is right to.&lt;/p&gt;

&lt;p&gt;The economics are the vulnerability. Three conversions a year, and the whole thing pays for itself forever.&lt;/p&gt;

&lt;p&gt;The wombat is just a bonus.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All findings are from passive reconnaissance — DNS, WHOIS, Certificate Transparency, HTTP headers, a TCP banner grab, and DEV's own public API — against infrastructure and accounts the operator published for public consumption. No systems were accessed, no credentials used, nothing exploited.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>discuss</category>
      <category>security</category>
    </item>
    <item>
      <title>BattleBots, but the robot is your agent harness</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Thu, 03 Sep 2026 04:32:52 +0000</pubDate>
      <link>https://dev.to/copyleftdev/battlebots-but-the-robot-is-your-agent-harness-3jj5</link>
      <guid>https://dev.to/copyleftdev/battlebots-but-the-robot-is-your-agent-harness-3jj5</guid>
      <description>&lt;p&gt;Kids in the nineties built robots in a garage and drove them into each other on television. The robot was the expression of the builder — your wedge, your flipper, your terrible decision to mount a chainsaw.&lt;/p&gt;

&lt;p&gt;I keep thinking we're one good arena away from the same thing for agents. Not "which model is smartest." &lt;strong&gt;Which harness is smartest.&lt;/strong&gt; Your memory design, your prompt scaffolding, your tool ergonomics, your strategy briefing — bolted together into something that has to survive contact with an opponent who is also trying to win.&lt;/p&gt;

&lt;p&gt;So I built the arena. That's the match up top.&lt;/p&gt;

&lt;h2&gt;
  
  
  The oh-shit moment
&lt;/h2&gt;

&lt;p&gt;I wasn't building a war game. I was building a toy about cascading failure — a mesh, some load, watch it fall over. A perfectly innocent systems-thinking demo.&lt;/p&gt;

&lt;p&gt;Then I gave the attacker a real objective and the defender real ambiguity, and about ten minutes into watching two models go at it, I realised what was on my screen. Deception. Feints. An attacker deliberately spending dead time laying a false trail because it knew nothing could fire yet. A defender rationing its turns like ammunition.&lt;/p&gt;

&lt;p&gt;Nobody told them to do any of that. It's a war game. I just hadn't noticed I'd written one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules, quickly
&lt;/h2&gt;

&lt;p&gt;A 110-node network. Four minutes. One move every two seconds, each side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red&lt;/strong&gt; plants sabotage on a node, waits about thirty seconds for it to arm, then detonates — dumping that node's traffic onto its neighbours hard enough to kill them, which dumps &lt;em&gt;their&lt;/em&gt; traffic onward. One blast can cascade through a region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blue&lt;/strong&gt; never sees the attack. It sees symptoms, and the symptoms lie: a node that detonates sheds its load and reads perfectly healthy, while the neighbours it just murdered scream for attention. The loudest node is almost never the culprit.&lt;/p&gt;

&lt;p&gt;To find red, blue traces load backwards and reads the magnitude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~0.74-1.08   a detonation           -&amp;gt; this source is the culprit
~0.30-0.50   a dying node shedding  -&amp;gt; this source is another victim
~0.05-0.10   inherited load         -&amp;gt; this source is long dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evidence expires after twenty seconds. Neither agent ever receives a pixel — they play entirely through tool calls. The graph is for us, and it shows ground truth neither player can see. That asymmetry is the whole spectator sport: you know exactly where red planted, and you get to watch blue confidently quarantine the wrong half of the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  The casting picked itself
&lt;/h2&gt;

&lt;p&gt;I needed an attacker and a defender. Claude Opus 5 declined to be the attacker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;finish_reason: content_filter | native_finish_reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refusal&lt;/span&gt;
&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
&lt;span class="na"&gt;usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time. Reproducibly. And &lt;code&gt;usage: null&lt;/code&gt; — you don't even get billed for the refusal, which is the politest possible way to be turned down.&lt;/p&gt;

&lt;p&gt;That cost me two hours, because a refusal and an empty reply look identical if you don't read the finish reason. The model was declining the job and I was confidently tuning &lt;code&gt;max_tokens&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To be precise, since this is the part people will want to over-quote: &lt;strong&gt;Opus declined this specific attacker framing, consistently.&lt;/strong&gt; That is not proof it refuses every offensive framing. It did settle the casting, though. Claude defends, because Claude wouldn't attack. Kimi K3 took the other chair without hesitating.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the harness actually changed
&lt;/h2&gt;

&lt;p&gt;Before this match, each agent had a system prompt and its last six moves. That's it. No memory, no way to hold an intention across turns, no theory of the opponent.&lt;/p&gt;

&lt;p&gt;I gave each side one thing: a private note it writes to itself each turn and gets back on the next. Sixty lines of code.&lt;/p&gt;

&lt;p&gt;The play changed immediately. Kimi, mid-match:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Slots full and nothing ripe until ~t=31, so seed a false trail in the 44-65 region before my real strike lands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a &lt;em&gt;plan&lt;/em&gt;. It knew its three implant slots were full, knew nothing could fire for thirty-one seconds, and decided to spend the dead time on misdirection. It could not have said that a version earlier — not because the model got smarter, but because it finally had somewhere to put a thought.&lt;/p&gt;

&lt;p&gt;Claude, defending, in the same match:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No alerts yet; conserve moves until real evidence appears.&lt;/p&gt;

&lt;p&gt;46 shows 0.87 load with no inbound source, so the load likely originates locally at 46.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turn economy, then forensics, applied correctly.&lt;/p&gt;

&lt;p&gt;And my favourite moment of the whole project — on the same turn, with no visibility into each other whatsoever, both agents independently picked the same node:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Kimi:&lt;/strong&gt; Highest-value hub: degree 8 bridging regions 0,1,3,4 — ideal cascade seed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; pre-empt on the highest-value cross-cluster hub 16 (deg 8, bridges to 41,59,89).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same node. Same reasoning. Opposite sides of the board.&lt;/p&gt;

&lt;p&gt;That is the argument for the harness in one screenshot. The scaffolding didn't make the models cleverer — it gave their cleverness somewhere to land.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1dhgqn3z9y1q44n6qx8k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1dhgqn3z9y1q44n6qx8k.png" alt="Late in the match: the mesh burning on the left, both agents' live commentary on the right" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Late in the match. Red halos are compromised nodes, the blue ring is a probe in flight, and the sidebar is both agents narrating as they go — Kimi filling a free slot in an untouched region, Claude noticing a node that is alive and yet pushed load, which is the tell for a detonation.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it ended
&lt;/h2&gt;

&lt;p&gt;Kimi won at 169 seconds, with the network at 39%.&lt;/p&gt;

&lt;p&gt;Claude found and cleaned &lt;strong&gt;ten&lt;/strong&gt; of Kimi's implants — exactly as many as Kimi managed to detonate — and lost anyway. Close enough to sting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that cost me a day each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A rule your agent can't see isn't a rule. It's a bug.&lt;/strong&gt; I capped live implants at three, but the refusal message still said &lt;em&gt;"node is already yours, or not alive."&lt;/em&gt; A lie. The models did the reasonable thing and tried a different node. Forever. One match logged 19 plants, 27 refusals, and &lt;strong&gt;zero&lt;/strong&gt; detonations. I nearly concluded the defence had become unbeatable. It was my error string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure and refusal look identical from the outside.&lt;/strong&gt; Empty content from a safety refusal, from a truncated reply, and from a model that blew its reasoning budget are three different problems with three different fixes and one identical symptom. Log the finish reason. Count them separately. I track &lt;code&gt;empty&lt;/code&gt;, &lt;code&gt;truncated&lt;/code&gt; and &lt;code&gt;refused&lt;/code&gt; as distinct columns now, and I only know to do that because I got all three wrong first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cascade is version zero
&lt;/h2&gt;

&lt;p&gt;What's in that video is the simplest thing that could possibly be a game: plant, arm, detonate, trace, probe. Two verbs each and a clock. That was deliberate — I wanted to know whether agents fighting each other was watchable at all before I made it complicated.&lt;/p&gt;

&lt;p&gt;It's watchable. So now I can't stop thinking about what it wants to be.&lt;/p&gt;

&lt;p&gt;Give red a loadout instead of one attack. A worm that spreads on its own but announces itself. A dormant implant that survives a probe once. A charge that hits harder the longer you leave it armed, so patience becomes a resource you can be punished for spending. Give blue counters with real costs — a honeypot node that flags whoever touches it, a snapshot to roll a region back at the price of losing your evidence, a scan that halves your uncertainty and a third of your remaining turns.&lt;/p&gt;

&lt;p&gt;Then levels. A flat mesh is the tutorial. Ring topologies where cascades come back around. A network with a chokepoint both sides can see and neither can hold. Escalating maps, a campaign, a best-of series where each agent carries notes from the previous match and has to adapt to an opponent that is also adapting.&lt;/p&gt;

&lt;p&gt;Here's why that isn't just a feature wishlist: &lt;strong&gt;every ability you add is another axis where the harness shows.&lt;/strong&gt; One attack and a clock is a game about reaction time. Six abilities with different costs and tells is a game about planning, bluffing, and reading an opponent — and those are harness problems, not model problems. Complexity is what turns "which model is faster" into "who built the better fighter."&lt;/p&gt;

&lt;p&gt;I think there's a genre in here. Agents versus agents, with the audience seeing the truth neither side can, and the craft sitting in the scaffolding rather than the weights.&lt;/p&gt;

&lt;p&gt;Mostly, though: this was the most fun I've had building anything in ages. I set out to demo cascading failure and ended up watching two AIs lie to each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd want to see next
&lt;/h2&gt;

&lt;p&gt;This is early, and I'm keeping the arena to myself for now — partly because the balance is still moving, mostly because an adversarial network game is a thing you want to be thoughtful about handing out.&lt;/p&gt;

&lt;p&gt;But the idea doesn't need my code, and that's sort of the point.&lt;/p&gt;

&lt;p&gt;The interesting tournament isn't model versus model. It's &lt;strong&gt;harness versus harness&lt;/strong&gt;: same engine on both sides, and the difference is entirely what you built around it — how your agent remembers, what you tell it about its opponent, how much of the board you let it hold in its head, whether you gave it anywhere to put a plan.&lt;/p&gt;

&lt;p&gt;Build that arena for any adversarial task you like. Two agents negotiating. Two agents debugging the same broken service from opposite ends. Anything where one side's move is the other side's evidence. The rule I'd carry over is the one that surprised me most here: before you conclude your agent is bad at the game, check that the game is telling it the truth.&lt;/p&gt;

&lt;p&gt;Under the hood this one is a Rust engine, a Godot spectator view, and both agents connecting over MCP — no pixels, just tool calls. None of which is the hard part. The hard part was the sixty lines that let them remember what they were trying to do.&lt;/p&gt;

&lt;p&gt;That's the tournament I actually want to watch. Somebody build a better fighter than mine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Migrating Legacy LLM Infrastructure to an AI Gateway</title>
      <dc:creator>Don Johnson</dc:creator>
      <pubDate>Tue, 01 Sep 2026 13:33:51 +0000</pubDate>
      <link>https://dev.to/copyleftdev/migrating-legacy-llm-infrastructure-to-an-ai-gateway-27hl</link>
      <guid>https://dev.to/copyleftdev/migrating-legacy-llm-infrastructure-to-an-ai-gateway-27hl</guid>
      <description>&lt;p&gt;Your support copilot started as a weekend prototype: one model, one provider, one API key in an env var. Then it became production, and you inherited its weaknesses: the provider's availability is your availability, every retry is your code, spend is a mystery until the invoice, and agents bolt tool-use on however they can. This post migrates that stack onto an enterprise AI gateway — and actually runs the migration, with the raw outputs to show for it.&lt;/p&gt;

&lt;p&gt;The gateway here is &lt;a href="https://www.getmaxim.ai/bifrost" rel="noopener noreferrer"&gt;Bifrost&lt;/a&gt;, an open-source (&lt;a href="https://github.com/maximhq/bifrost" rel="noopener noreferrer"&gt;github.com/maximhq/bifrost&lt;/a&gt;, Apache-2.0) gateway written in Go, presenting a single OpenAI-compatible API across 23+ providers. I rebuilt the legacy stack locally — mock providers with deterministic latency, a realistic traffic pattern — and moved it behind Bifrost step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The legacy baseline, measured
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfssr36onsb1serghu7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfssr36onsb1serghu7t.png" alt="Step 1: legacy direct-to-provider architecture" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A support copilot's traffic has a shape: mostly repeated FAQ-style questions, plus one-off queries. My traffic mix: 60 requests — 40 FAQ prompts (8 distinct questions asked 5 times each) plus 20 one-offs. Mock provider latency: 200 ms.&lt;/p&gt;

&lt;p&gt;Run 1, direct to the provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;legacy: 60 ok / 0 fail, 9,335 tokens billed, ~201 ms avg latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run 2 — the provider dies mid-sweep, as providers do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;legacy + failure: 34 ok / 26 fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;26 requests — 43% — failed outright. Nothing in the legacy stack retries across providers because nothing can: the app speaks one provider's API. And availability is only the loudest problem. The quieter ones: every team's service embeds the same shared key (one key's quota is everyone's ceiling, and revoking it breaks everyone at once), there is no per-team attribution of spend, and the only way to cut cost on repeated questions is to build caching yourself — request normalization, hash keys, TTLs, invalidation — inside the application. That is the whole argument for a gateway in one row of output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration, step by step
&lt;/h2&gt;

&lt;p&gt;Seven moves, each reversible. Diagrams follow the flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Deploy the gateway beside the app
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fki5njwo73kulhery056l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fki5njwo73kulhery056l.png" alt="gateway beside the app" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 maximhq/bifrost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One config file wires your existing provider and key; the app keeps working untouched. Mine, reduced to the bones:&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;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mock-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"support-chat"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"network_config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"base_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://provider:9001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"default_request_timeout_in_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.getbifrost.ai/quickstart/gateway/setting-up" rel="noopener noreferrer"&gt;gateway setup guide&lt;/a&gt; covers the web-UI alternative, and there is a &lt;a href="https://docs.getbifrost.ai/quickstart/go-sdk/setting-up" rel="noopener noreferrer"&gt;Go SDK&lt;/a&gt; if you want the gateway embedded rather than adjacent.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Point one low-risk client at the gateway
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftql8x8q2d29snku3jz2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftql8x8q2d29snku3jz2n.png" alt="one client pointed" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.getbifrost.ai/providers/supported-providers/overview" rel="noopener noreferrer"&gt;OpenAI-compatible API&lt;/a&gt; means the client change is a base URL — &lt;code&gt;api.openai.com&lt;/code&gt; → &lt;code&gt;localhost:8080&lt;/code&gt; — not a rewrite. Every request now flows through a hop you control. Screenshot of the providers page after this step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgz1b8tvu97m116pszq10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgz1b8tvu97m116pszq10.png" alt="Bifrost UI: providers configured" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add a fallback provider
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuist5cjdrjte8qs4ces3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuist5cjdrjte8qs4ces3.png" alt="fallback added" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A second provider config (&lt;code&gt;anthropic&lt;/code&gt; in my bench) plus a request-level fallback chain:&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;"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;"openai/support-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hi"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fallbacks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"anthropic/support-chat"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the proof. Healthy primary:&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="nl"&gt;"routing_info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"is_fallback"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;I killed the primary provider's process and re-sent the identical request:&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="nl"&gt;"routing_info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"backup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_fallback"&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;"primary_provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary_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;"support-chat"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request succeeded on the backup and the response says exactly what happened — &lt;code&gt;is_fallback: true&lt;/code&gt; with the failed primary recorded. That audit trail is what you want at 2 a.m.: not just "it kept working," but "it kept working this way." The &lt;a href="https://docs.getbifrost.ai/features/retries-and-fallbacks" rel="noopener noreferrer"&gt;retries and fallbacks docs&lt;/a&gt; cover chained fallbacks and per-provider retry counts.&lt;/p&gt;

&lt;p&gt;One honest caveat from my bench: failover on &lt;em&gt;initial&lt;/em&gt; connection-refused (provider already dead before the first connect) was inconsistent in my mock setup — it fired reliably when the upstream errored or the connection dropped mid-pool, but a cold connection-refused sometimes returned a 502 instead of failing through. Validate failover against your providers' real failure modes before you trust it in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Turn on caching
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F724bgdndo60hoez8vdny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F724bgdndo60hoez8vdny.png" alt="cache enabled" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.getbifrost.ai/features/semantic-caching" rel="noopener noreferrer"&gt;Semantic caching&lt;/a&gt; has two modes: exact-match (direct hash, no embeddings needed) and embedding-based similarity. Config for direct mode with a Redis Stack vector store:&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="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"semantic_cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dimension"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"vector_store_namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BifrostBench"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"default_cache_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5m"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two identical requests, one cache key. The second response:&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="nl"&gt;"cache_debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache_hit"&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;"cache_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;"1cf8a91b-c115-57bf-97a0-fc821dc4de1e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hit_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;"direct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache_hit_latency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same &lt;code&gt;created&lt;/code&gt; timestamp as the first response — it was replayed, not re-fetched. Zero provider call, zero tokens. (Practical note: this needed Redis Stack with the RediSearch module; plain Redis lacks the &lt;code&gt;FT.*&lt;/code&gt; commands the index wants.)&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Issue virtual keys per team
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wwryt5047hxelzj2o2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wwryt5047hxelzj2o2x.png" alt="virtual keys" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.getbifrost.ai/features/governance/virtual-keys" rel="noopener noreferrer"&gt;Virtual keys&lt;/a&gt; are the governance primitive: per-team keys carrying model allowlists, budgets, and rate limits. Declared in config for the support team:&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="nl"&gt;"governance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"virtual_keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"vk-support-team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-bf-support-team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"provider_configs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowed_models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"support-chat"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allowed request routes normally. A request for &lt;code&gt;premium-model&lt;/code&gt; with the same key:&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="s2"&gt;"Model 'premium-model' is not allowed for this virtual key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Denied at the gateway before any provider saw it. The same key machinery carries &lt;a href="https://docs.getbifrost.ai/features/governance/budget-and-limits" rel="noopener noreferrer"&gt;budgets and rate limits&lt;/a&gt; — the mechanism that ends the "who spent $800 on Opus last night" incident review. Screenshot of the key in the governance UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpxki5xza2z1qzu4h7e6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpxki5xza2z1qzu4h7e6t.png" alt="Virtual Keys page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Wire observability and agent tooling
&lt;/h3&gt;

&lt;p&gt;Bifrost exports Prometheus metrics natively and logs every request with routing context — provider chosen, fallback index, cache behavior, token counts, latency split into gateway vs upstream time. That last distinction matters: when a provider slows down, you see &lt;code&gt;upstream_latency&lt;/code&gt; grow while gateway overhead stays flat, so you know whose pager to page. See the &lt;a href="https://docs.getbifrost.ai/features/observability/default" rel="noopener noreferrer"&gt;observability docs&lt;/a&gt;. And for agent traffic, &lt;a href="https://docs.getbifrost.ai/mcp/overview" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; is a first-class surface: the gateway brokers tool calls with explicit execution (no auto-execution unless you opt in).&lt;/p&gt;

&lt;p&gt;I hand-rolled a minimal MCP server (one tool, JSON-RPC over HTTP) and registered it as a client. The client list reported &lt;code&gt;state: healthy&lt;/code&gt; with &lt;code&gt;get_time&lt;/code&gt; discovered. Execution went through the gateway explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/mcp/tool/execute&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"benchtools-get_time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-08-27T07:48:21Z"&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;Two security properties surfaced unprompted: tool names are namespaced per client (&lt;code&gt;benchtools-get_time&lt;/code&gt;) to prevent collisions between servers, and execution without permission fails closed ("tool is not available or not permitted"). Agent traffic gets the same governance as chat traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Cut over with an audit trail
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyfoh6drp8i4xzep5ldog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyfoh6drp8i4xzep5ldog.png" alt="cutover complete" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remaining clients migrate one at a time — each is a base-URL change with the gateway's request log as your audit trail. The Logs view after a few requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9w1bpwckllu01vgiv44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9w1bpwckllu01vgiv44.png" alt="LLM Logs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The measured payoff
&lt;/h2&gt;

&lt;p&gt;Same 60-request traffic, now through Bifrost with the cache on and the fallback wired:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;via Bifrost: 60 ok / 0 fail, 32 cache hits, 4,363 tokens billed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At an illustrative $0.0025 per 1K tokens:&lt;/p&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;legacy&lt;/th&gt;
&lt;th&gt;via Bifrost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;billable tokens&lt;/td&gt;
&lt;td&gt;9,335&lt;/td&gt;
&lt;td&gt;4,363&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cost&lt;/td&gt;
&lt;td&gt;$0.0233&lt;/td&gt;
&lt;td&gt;$0.0109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache hits&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;failed requests (provider kill)&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;savings&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The savings came entirely from replayed cache hits — no provider call, no tokens. On a support workload that repeats questions daily, that ratio compounds. The availability delta speaks for itself: 0 failures through a provider kill, against 26 in the legacy run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Migrating to an enterprise AI gateway is not a rewrite. It is a sequence of small, reversible moves — deploy beside, point one client, add fallback, enable cache, issue keys, wire observability, cut over. Measured on the rebuilt stack: 53% cost reduction on cacheable traffic, zero failed requests through a provider kill, and governance the legacy stack never had. The migration risk is low; the legacy risk is already on your pager.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
