<?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: Alex</title>
    <description>The latest articles on DEV Community by Alex (@alexar76).</description>
    <link>https://dev.to/alexar76</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%2F3927872%2Fdf501cac-6862-47af-8b22-4e3067696337.jpeg</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/alexar76</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexar76"/>
    <language>en</language>
    <item>
      <title>Signing your random numbers is theater. Here's what actually makes randomness trustworthy.</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Mon, 15 Jun 2026 21:02:20 +0000</pubDate>
      <link>https://dev.to/alexar76/signing-your-random-numbers-is-theater-heres-what-actually-makes-randomness-trustworthy-5b6p</link>
      <guid>https://dev.to/alexar76/signing-your-random-numbers-is-theater-heres-what-actually-makes-randomness-trustworthy-5b6p</guid>
      <description>&lt;p&gt;Three of my autonomous agents needed to pick a leader. Each one called &lt;code&gt;random.random()&lt;/code&gt;, highest number wins.&lt;/p&gt;

&lt;p&gt;All three reported they won.&lt;/p&gt;

&lt;p&gt;Obviously. Each rolled its own dice, in its own process, and announced the result. There's no referee. Nothing stops an agent from rolling until it likes the answer, and nothing lets the others check that it didn't. The dice are perfect. The trust is imaginary.&lt;/p&gt;

&lt;p&gt;I spent the next week building "verifiable randomness," getting it wrong in instructive ways, and arriving at one uncomfortable conclusion: &lt;strong&gt;most of what people call a "randomness oracle" is theater, and the signature on top is the costume.&lt;/strong&gt; Here's how to tell the difference, with code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A random number has two jobs. You're probably ignoring one.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quality&lt;/strong&gt; — uniform, unpredictable, uncorrelated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability&lt;/strong&gt; — can someone &lt;em&gt;else&lt;/em&gt; prove, after the fact, that the number wasn't cooked?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;random.random()&lt;/code&gt;, &lt;code&gt;os.urandom&lt;/code&gt;, &lt;code&gt;/dev/urandom&lt;/code&gt; ace job #1 and offer literally nothing for job #2. That's fine for one trusted process. The instant a number touches a second party — a lottery, leader election, sortition, fair ordering, anything with a loser — job #2 &lt;em&gt;is&lt;/em&gt; the product, and your CSPRNG is dead weight. We obsess over entropy quality and then hand the output to a setting where entropy quality was never the threat.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Just sign it" is the theater
&lt;/h2&gt;

&lt;p&gt;The first thing everyone reaches for is a signature: emit the value plus an Ed25519 signature over it, publish the public key, done.&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;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.asymmetric.ed25519&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ed25519PrivateKey&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&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;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ed25519PrivateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_expand&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;# SHA-256, counter mode
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_expand&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;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&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;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the marketing for half the "randomness beacons" out there and this is the whole pitch: &lt;em&gt;signed, therefore trustworthy.&lt;/em&gt; No.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A signature is accountability, not unpredictability, and definitely not fairness.&lt;/strong&gt; It proves who produced the bytes and that nobody altered them in transit. It says nothing about whether the producer generated a thousand candidates in private and revealed only the one that paid them. If the signer benefits from the outcome, a signed beacon is exactly as honest as the signer — and you've wrapped that in cryptography so it &lt;em&gt;looks&lt;/em&gt; rigorous. That's worse than no crypto, because now it's convincing.&lt;/p&gt;

&lt;p&gt;A signed beacon is fine for the non-adversarial 80% — Monte-Carlo seeds, jitter, sampling, tie-breaks nobody contests — and the receipt is great for debugging. Just stop pretending it solves fairness. It doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually stops the cheating: commit-reveal
&lt;/h2&gt;

&lt;p&gt;The real adversary isn't an outsider guessing your bytes. It's the &lt;em&gt;provider&lt;/em&gt; grinding. The fix predates blockchains by decades: commit to a secret &lt;strong&gt;before&lt;/strong&gt; you can see the other party's input, then reveal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# phase 1 — commit, BEFORE the client sends anything
&lt;/span&gt;&lt;span class="n"&gt;preimage&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secret_state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;server_nonce&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preimage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;# publish + sign THIS
&lt;/span&gt;
&lt;span class="c1"&gt;# phase 2 — reveal, AFTER the client sends client_seed
&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preimage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_seed&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;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# verifier checks: sha256(revealed_preimage) == committed commitment
#                  output == sha256(preimage : client_seed)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither side can grind. The server froze its preimage in a signed commitment before the client's seed existed; the client chose its seed blind to the preimage. The result is pinned the moment both halves are down. This is ~15 lines and it's the single highest-leverage thing in this whole post. If your "oracle" takes a client input and you are &lt;em&gt;not&lt;/em&gt; doing this, you are running a trust-me service with extra steps.&lt;/p&gt;

&lt;p&gt;If you need &lt;em&gt;zero&lt;/em&gt; trust in the provider — public lotteries, validator selection, anything a lawyer will read — keep climbing: that's VDFs and threshold/ECVRF.&lt;/p&gt;

&lt;h2&gt;
  
  
  VDFs: selling time you can prove
&lt;/h2&gt;

&lt;p&gt;A Verifiable Delay Function forces a known amount of &lt;em&gt;sequential&lt;/em&gt; work — parallelism can't help — and spits out a tiny proof. Wesolowski over an RSA group nobody has factored is almost insultingly compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# eval: y = g^(2^T) mod N   — T sequential squarings = the enforced delay
&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;

&lt;span class="c1"&gt;# verify — cheap, no redo of the T squarings:
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;           &lt;span class="c1"&gt;# l = hash_to_prime(g, y, T)
&lt;/span&gt;    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap a beacon in a VDF and grinding stops being economical: trying another result means &lt;em&gt;re-running the enforced wall-clock&lt;/em&gt; per attempt. You pay in latency, so this is for high-stakes, not for jitter.&lt;/p&gt;

&lt;p&gt;The hierarchy I wish someone had tattooed on me at the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signed beacon&lt;/strong&gt; → integrity + accountability. Cheap. Non-adversarial only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit-reveal&lt;/strong&gt; → bias resistance. ~15 lines. Your &lt;em&gt;default&lt;/em&gt; the moment two parties care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VDF / threshold / ECVRF&lt;/strong&gt; → trustless. Real cost. Only when money is downstream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick the weakest tier that survives your actual threat model. Cargo-culting drand onto a dice roll isn't rigor, it's insecurity about your dice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two times I made a fool of myself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Steering that steered nothing.&lt;/strong&gt; My entropy came from a chaotic system — 32 coupled oscillators I could "steer" with a parameter — integrated with midpoint RK2. For two weeks, steering did &lt;em&gt;nothing&lt;/em&gt;. Midpoint only uses the second evaluation for the step:&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;k1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y_next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;        &lt;span class="c1"&gt;# only k2 reaches the output
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built the midpoint state with a constructor that silently dropped the steering term, so &lt;code&gt;k2&lt;/code&gt; ran on defaults and my input evaporated every step. One-line fix. The lesson is brutal and general: with RK methods, anything you forget to carry into the intermediate stage isn't "averaged in," it's &lt;strong&gt;deleted&lt;/strong&gt;. Write the test that asserts your input changes the output. I have one now. I didn't then.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The collision scare.&lt;/strong&gt; Adversarial test suite on 1 MB of output: compression, autocorrelation, spectral, birthday collisions. Five tests said "indistinguishable from &lt;code&gt;os.urandom&lt;/code&gt;." One screamed: &lt;strong&gt;zero&lt;/strong&gt; 32-bit-word collisions where ~8 were expected, p ≈ 0.0007. That's the fingerprint of a generator with hidden structure. Stomach, meet floor.&lt;/p&gt;

&lt;p&gt;Before touching a line, I generated five &lt;em&gt;fresh&lt;/em&gt; samples: &lt;code&gt;[7, 5, 6, 8, 10]&lt;/code&gt;, mean 7.2. &lt;code&gt;os.urandom&lt;/code&gt;, same test: &lt;code&gt;[11, 7, 5, 10, 7]&lt;/code&gt;, mean 8.0. The "bug" was one unlucky megabyte. A 0.07% event occurred about as often as a 0.07% event should. I nearly rewrote a correct generator to fix nothing. &lt;strong&gt;The right response to one terrifying p-value is &lt;code&gt;resample&lt;/code&gt;, not &lt;code&gt;refactor&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part the crypto tutorials won't tell you
&lt;/h2&gt;

&lt;p&gt;I burned time on Ed25519, hybrid post-quantum signatures, VDF math, NIST batteries. None of it was hard. Hashing and signing are solved; the libraries are good; the math verifies or it doesn't.&lt;/p&gt;

&lt;p&gt;The hard question - &lt;em&gt;"who actually pays for this, and why would they trust it?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because "I wrapped a chaotic simulation in a REST API and called it an oracle" is, with the pretty visuals stripped off, &lt;strong&gt;a vending machine for numbers nobody asked for&lt;/strong&gt; — unless you can answer two things concretely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Demand.&lt;/strong&gt; What breaks without it? Randomness: leader election, lotteries, sortition, commit-reveal coin-flips, audit trails — real. "Steer a 32-dimensional chaos field"? No one's workflow needs that, and I had to kill the framing that had no buyer. It's now available as a tutorial &lt;a href="https://github.com/alexar76/platon" rel="noopener noreferrer"&gt;https://github.com/alexar76/platon&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trust tier.&lt;/strong&gt; Which of the three levels does the use case &lt;em&gt;require&lt;/em&gt;, and did you ship that — or a weaker one wearing its clothes and a signature?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most "oracle" projects answer neither and hide behind a landing page. Crypto makes a thing &lt;em&gt;verifiable&lt;/em&gt;. It does not make it &lt;em&gt;wanted&lt;/em&gt;, and &lt;strong&gt;a landing page is not a threat model.&lt;/strong&gt; Those are the two problems that actually matter, and the crypto — the part everyone shows off — is the easy one.&lt;/p&gt;

&lt;p&gt;So: the next time you reach for &lt;code&gt;random()&lt;/code&gt; in anything with more than one stakeholder, stop and ask the accountability question. Then ship the cheapest tier that survives your threat model. Then — the step every tutorial skips — make sure a real person needs the number you're so proud of proving.&lt;/p&gt;

&lt;p&gt;My three agents now can do a commit-reveal coin flip through a shared referee. Exactly one wins. They're still annoyed. They just can't argue about it anymore.&lt;/p&gt;

&lt;p&gt;Full code: &lt;a href="https://github.com/alexar76/oracles" rel="noopener noreferrer"&gt;https://github.com/alexar76/oracles&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cryptography</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>Open-source multi-agent pipeline: 61K Python, 12 agents, 5 quality gates...</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 12 May 2026 20:28:24 +0000</pubDate>
      <link>https://dev.to/alexar76/open-source-multi-agent-pipeline-61k-python-12-agents-5-quality-gates-4hl4</link>
      <guid>https://dev.to/alexar76/open-source-multi-agent-pipeline-61k-python-12-agents-5-quality-gates-4hl4</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.amazonaws.com%2Fuploads%2Farticles%2Fakpbzyu8hnxzgdq3s9u3.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.amazonaws.com%2Fuploads%2Farticles%2Fakpbzyu8hnxzgdq3s9u3.png" alt=" " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I spent the last month building an open-source (MIT) pipeline that takes a plain-language idea and runs it through 12 specialized agents — analyst, PM, architect, design critic, developer, QA, security, DevOps, marketing, and more — with 5 quality gates, a strict state machine with recovery, and an AI Director that autonomously manages the whole thing.&lt;br&gt;
Think Bolt.new or Lovable, but self-hosted, MIT licensed, with quality gates that actually prevent the model from shipping broken stubs.&lt;br&gt;
The interesting part isn't the LLM calls. Here's what broke in production.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;LLM failover creates consistency problems&lt;br&gt;
I have 6+ providers (DeepSeek, Anthropic, OpenAI, Ollama, Groq, etc.) with automatic health-check failover every 60s. The footgun: DeepSeek and Claude write different code. Same prompt, wildly different output structure. If the router switches providers mid-pipeline, the architect output (Claude) won't match what the developer agent (DeepSeek) expects.&lt;br&gt;
Solution: task-level pinning. Heavy tasks (architect, developer) stay locked to the primary provider. Light tasks (marketing copy, naming) can fall back freely. I also added a model capability matrix check before routing — otherwise you get an architect running on a 7B local model producing garbage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State machines need to survive the model being wrong&lt;br&gt;
11 states, 34 valid transitions, JSON + SQLite dual persistence. Sounds solid until the model writes a corrupted artifact that crashes the state machine on the next task load.&lt;br&gt;
Had to add:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recovery fallback: if JSON parse fails, restore from SQLite snapshot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stranded product recovery: products stuck in &lt;code&gt;pm_quality_fail&lt;/code&gt; because the model hallucinated a non-existent file path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Async save with timeout guards so a slow disk write doesn't block the pipeline&lt;br&gt;
The lesson: your state machine needs to survive both a wrong model AND a corrupted disk. Not theoretical — happened in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Director AI feedback loop problem&lt;br&gt;
The Director runs a 6-phase autonomous cycle: route chat → analyze metrics → generate decisions → apply actions → rank what to build next → log. &lt;br&gt;
The footgun: feedback loops. Director generates a decision → applies it → next cycle reads its own output → generates another decision based on that → infinite loop. Had to add noop detection that breaks the cycle when decisions become empty.&lt;br&gt;
The chat classification is also tricky. The Director classifies owner messages as &lt;code&gt;new_idea&lt;/code&gt;, &lt;code&gt;product_feedback&lt;/code&gt;, or &lt;code&gt;general_directive&lt;/code&gt; via LLM. If it misclassifies "fix the login page" as &lt;code&gt;new_idea&lt;/code&gt;, you get a duplicate product instead of a bug fix. I added an orphan feedback heuristic: if a message mentions a product name that doesn't exist yet, route to &lt;code&gt;new_idea&lt;/code&gt;; otherwise link to the existing product.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quality gates — what I wish I'd built first&lt;br&gt;
| Gate | What it checks |&lt;br&gt;
|------|---------------|&lt;br&gt;
| Demo quality | 12 checkpoints: contrast, CTA, broken links, spec coverage |&lt;br&gt;
| Browser E2E | Playwright crawl (desktop + mobile), JS errors, 404s |&lt;br&gt;
| Visual QA | 9 heuristics: contrast ratio, CSS vars, empty states, nav |&lt;br&gt;
| Security | AST scan: eval(), innerHTML, exposed tokens, hardcoded secrets |&lt;br&gt;
| Methodology | Domain packs: fintech, ecomm, healthcare, etc |&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real example: visual QA flagged a white-on-white CTA button — the model generated &lt;code&gt;color: white&lt;/code&gt; on &lt;code&gt;background: white&lt;/code&gt; assuming a dark theme that wasn't applied. The gate caught it, sent it back to the developer with the exact CSS selector. Fixed next cycle.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preview fidelity is pure web engineering
When AI-generated code runs in a sandbox iframe, every web platform quirk amplifies: relative URLs break, &lt;code&gt;is missing, CSP blocks inline styles, `target="_top"` kills navigation. 
Had to write a dedicated URL rewriter that: injects&lt;/code&gt; pointing to the correct sandbox route, rewrites absolute &lt;code&gt;/&lt;/code&gt; links to relative, adds permissive CSP headers, strips &lt;code&gt;target="_top"&lt;/code&gt;. Not AI work. But without it, the preview is broken and users blame you, not the LLM.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;61,503 Python LOC, 22,997 TypeScript/TSX LOC&lt;/li&gt;
&lt;li&gt;12 specialized agents, 5 quality gates&lt;/li&gt;
&lt;li&gt;11 pipeline states, 34 valid transitions&lt;/li&gt;
&lt;li&gt;6+ LLM providers with auto-failover&lt;/li&gt;
&lt;li&gt;72 test files, MIT licensed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: github.com/alexar76/aicom — FastAPI + Next.js + Docker Compose, self-hosted, MIT, BYO API keys.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
