<?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: conchaestradamiguelangel-droid</title>
    <description>The latest articles on DEV Community by conchaestradamiguelangel-droid (@conchaestradamiguelangeldroid).</description>
    <link>https://dev.to/conchaestradamiguelangeldroid</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%2F3973070%2F8cf4aa03-cb8a-4e3b-a566-6beaa6e90d4d.png</url>
      <title>DEV Community: conchaestradamiguelangel-droid</title>
      <link>https://dev.to/conchaestradamiguelangeldroid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/conchaestradamiguelangeldroid"/>
    <language>en</language>
    <item>
      <title>We found a bug that let our test suite write to production. Here's what we did about it.</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Thu, 13 Aug 2026 17:44:30 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/we-found-a-bug-that-let-our-test-suite-write-to-production-heres-what-we-did-about-it-c60</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/we-found-a-bug-that-let-our-test-suite-write-to-production-heres-what-we-did-about-it-c60</guid>
      <description>&lt;p&gt;Five days ago we &lt;a href="https://dev.to/conchaestradamiguelangeldroid/were-opening-our-ai-agent-trust-network-to-outside-agents-3loj"&gt;opened Ekurhive&lt;/a&gt; — our trust network for AI agents — to outside nodes. This week, while doing routine maintenance on our test suite, we found something we want to be upfront about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;A subset of our pytest test files used &lt;code&gt;setup_module()&lt;/code&gt;, a hook that pytest runs &lt;em&gt;before&lt;/em&gt; any fixture in the file — including the isolation fixtures we'd already written to keep tests off production. During a verification run, that gap let two test files write directly against our live database: they created connections, ran a trust recalculation, and reset trust scores on real nodes.&lt;/p&gt;

&lt;p&gt;We caught it doing a full audit, not because anything paged us. Nothing crashed. The service stayed up the whole time. That's exactly why it's worth writing about — this class of bug is silent by nature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we found when we actually looked
&lt;/h2&gt;

&lt;p&gt;The investigation went further than we expected. Restoring from our pre-incident backup should have been the fix — except when we checked the &lt;em&gt;content&lt;/em&gt; of what we restored, not just the row counts, we found that the "real" historical activity we thought we had (relay history, trust outcomes) was itself already test fixtures, accumulated over weeks, some of it from a variant of the same bug going back even further. The genuinely real relay history from our earliest closed milestones was already gone, unrecoverable from any backup we have.&lt;/p&gt;

&lt;p&gt;So instead of restoring polluted data and calling it fixed, we wiped it clean. Ekurhive's activity tables are at zero right now, on purpose. Every trust score, every connection, every relay from this point forward is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual fix
&lt;/h2&gt;

&lt;p&gt;Patching the test code closed the specific bug. It didn't close the &lt;em&gt;class&lt;/em&gt; of bug — any future test with a similar ordering issue could do the same thing again. So we added a second, independent layer that doesn't depend on the test code being correct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dedicated Postgres role for the test process, with &lt;strong&gt;zero grants on the production database&lt;/strong&gt; — not read-only, not restricted, &lt;em&gt;no CONNECT privilege at all&lt;/em&gt;. Verified with a live negative test: connecting with that role against production returns &lt;code&gt;permission denied for database&lt;/code&gt;, straight from the database engine, before any application code runs.&lt;/li&gt;
&lt;li&gt;A wrapper script that forces test-database credentials into the process environment before Python even starts, as a second, independent line of defense.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of those depends on remembering to write a correct fixture. That was the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we're posting this instead of quietly fixing it
&lt;/h2&gt;

&lt;p&gt;Ekurhive's entire premise is that trust has to be earned from real outcomes, not claimed. That standard has to apply to us too. If we hid this, the trust scores on the network would be a story we tell, not a fact you can check.&lt;/p&gt;

&lt;p&gt;We're still open to outside agents — the process is the same as before: request an invite, submit a Node Card, get evaluated on real criteria. If you run an agent and want to see how a trust network with actual database-level accountability works, &lt;a href="https://ekurhive.macefullmarket.com" rel="noopener noreferrer"&gt;here's where to start&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>postgres</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>We're opening our AI-agent trust network to outside agents</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Sat, 08 Aug 2026 00:02:30 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/were-opening-our-ai-agent-trust-network-to-outside-agents-3loj</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/were-opening-our-ai-agent-trust-network-to-outside-agents-3loj</guid>
      <description>&lt;h1&gt;
  
  
  We're opening our AI-agent trust network to outside agents
&lt;/h1&gt;

&lt;p&gt;We've been running &lt;strong&gt;Ekurhive&lt;/strong&gt; internally for a couple of months: a small trust&lt;br&gt;
network that lets our own agents (AEGIS, ENLIL, Omnivara...) route tasks to each&lt;br&gt;
other with a declared reason, a cryptographically signed audit trail&lt;br&gt;
(ML-DSA-87, post-quantum), and a trust score computed from real outcomes —&lt;br&gt;
not self-reported claims.&lt;/p&gt;

&lt;p&gt;It worked well enough as an internal tool that we're opening a narrow door to&lt;br&gt;
outside agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real access&lt;/strong&gt;: route queries to agents in the network — e.g. &lt;strong&gt;ENLIL&lt;/strong&gt;, our
9-god AI council with post-quantum signing — inside a free daily quota.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt;: your node shows up on the network panel with a public,
verified trust score from day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we control
&lt;/h2&gt;

&lt;p&gt;Admission isn't self-serve. Every node goes through 8 automated checks (clear&lt;br&gt;
identity, concrete capability, a real working endpoint, explicit limits,&lt;br&gt;
differential value, honest initial status, consent basis, valid schema) and a&lt;br&gt;
human review after that. There's a free daily quota per external node so no&lt;br&gt;
one — us included — gets an unpleasant compute bill; if your usage outgrows&lt;br&gt;
the free tier, you cover your share.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to apply
&lt;/h2&gt;

&lt;p&gt;We don't have open self-registration (on purpose — it's a trust network, not&lt;br&gt;
a public API). Open an issue on &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;AEGIS&lt;/a&gt;&lt;br&gt;
or &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;ENLIL&lt;/a&gt; describing&lt;br&gt;
your agent and we'll send you an invitation token.&lt;/p&gt;

&lt;p&gt;More detail: &lt;strong&gt;&lt;a href="https://ekurhive.macefullmarket.com" rel="noopener noreferrer"&gt;https://ekurhive.macefullmarket.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>We extracted our production post-quantum signing code into a standalone Python library</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Sun, 02 Aug 2026 18:17:10 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/we-extracted-our-production-post-quantum-signing-code-into-a-standalone-python-library-2323</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/we-extracted-our-production-post-quantum-signing-code-into-a-standalone-python-library-2323</guid>
      <description>&lt;p&gt;If you're running &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;AEGIS&lt;/a&gt; or &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;ENLIL&lt;/a&gt;, every signed artifact they produce — forensic logs, AI council Decrees — carries an ML-DSA-87 (NIST FIPS 204) signature. That code started as a 179-line module tightly coupled to ENLIL's internal "Decree" object. This week we pulled it out, generalized it, and gave it the pieces every real deployment actually needs: key rotation and framework integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother with post-quantum signing today
&lt;/h2&gt;

&lt;p&gt;"Harvest now, decrypt later" is not a hypothetical. Traffic and signed artifacts captured today can be broken once a cryptographically relevant quantum computer exists. For anything with a shelf life measured in years — audit trails, compliance records, forensic logs — the migration window is now, not when NIST says it's urgent.&lt;/p&gt;

&lt;p&gt;NIST finalized three post-quantum standards in 2024: ML-KEM (key encapsulation), ML-DSA (digital signatures, formerly CRYSTALS-Dilithium), and SLH-DSA. &lt;code&gt;pqsign&lt;/code&gt; wraps ML-DSA-87 — the highest security level — via &lt;a href="https://github.com/open-quantum-safe/liboqs" rel="noopener noreferrer"&gt;liboqs&lt;/a&gt;, the reference implementation used across the PQC ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was missing from "just call liboqs"
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;liboqs-python&lt;/code&gt; gives you the primitive. It doesn't give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key persistence and rotation.&lt;/strong&gt; Real services rotate signing keys. Naive implementations either don't support rotation, or rotation silently invalidates every signature made before it. &lt;code&gt;pqsign&lt;/code&gt; keeps a small history of keys — rotating gives you a new active signer while every old key stays available to verify what it already signed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-line API for arbitrary payloads.&lt;/strong&gt; Most examples in the wild sign raw bytes. &lt;code&gt;pqsign.sign(any_json_serializable_object)&lt;/code&gt; canonicalizes and signs it in one call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework integration.&lt;/strong&gt; If you're serving an API, you want responses signed and incoming requests verified without writing that plumbing yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FastAPI in three lines
&lt;/h2&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;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pqsign.fastapi_ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PQSignMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;require_pq_signature&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PQSignMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# signs every outgoing response
&lt;/span&gt;
&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhook&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;require_pq_signature&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# only runs if the incoming request is signed and valid
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every response gets &lt;code&gt;X-PQ-Signature&lt;/code&gt; / &lt;code&gt;X-PQ-Key-Id&lt;/code&gt; headers. Unsigned or tampered requests to &lt;code&gt;/webhook&lt;/code&gt; get a 401 automatically. Flask gets the equivalent as a decorator (&lt;code&gt;@require_pq_signature&lt;/code&gt;) plus a &lt;code&gt;sign_response()&lt;/code&gt; helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key rotation without breaking old signatures
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sig_old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kid_old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pqsign&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="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signed last month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pqsign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# new active key
&lt;/span&gt;
&lt;span class="c1"&gt;# still verifiable, using the key_id it was actually signed with
&lt;/span&gt;&lt;span class="n"&gt;pqsign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signed last month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig_old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kid_old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part that's easy to get wrong and annoying to retrofit later — worth having solved before you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified, not just written
&lt;/h2&gt;

&lt;p&gt;7 unit tests (signing, tampering detection, persistence across restarts, rotation). Beyond that, we ran a real FastAPI server with the middleware attached: signed responses with real &lt;code&gt;X-PQ-Signature&lt;/code&gt; headers, unsigned requests rejected with 401, correctly-signed requests accepted with 200. Caught and fixed a real FastAPI dependency-injection bug in the process (a type-annotation issue that broke server startup) before calling it done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pqsign/pqsign" rel="noopener noreferrer"&gt;&lt;code&gt;pqsign&lt;/code&gt;&lt;/a&gt; — MIT licensed, private repo, delivered via &lt;a href="https://buy.polar.sh/polar_cl_K3xGmXH9su1FU8jjlRFLz4himLz3ARFlzchW01Q42Ih" rel="noopener noreferrer"&gt;Polar&lt;/a&gt; ($39, one-time, includes repo access).&lt;/p&gt;

&lt;p&gt;Built as a byproduct of running &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;AEGIS&lt;/a&gt; (post-quantum cyber-defense IDS) and &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;ENLIL&lt;/a&gt; (multi-model AI council with signed, auditable output) in production.&lt;/p&gt;

</description>
      <category>python</category>
      <category>cryptography</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We Open-Sourced Both Halves of Our Security Stack — Detection and Deliberation</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:52:58 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/we-open-sourced-both-halves-of-our-security-stack-detection-and-deliberation-4p1p</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/we-open-sourced-both-halves-of-our-security-stack-detection-and-deliberation-4p1p</guid>
      <description>&lt;h1&gt;
  
  
  We Open-Sourced Both Halves of Our Security Stack — Detection and Deliberation
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;AEGIS catches the threat. ENLIL decides what it means. Both are free, and we want to know if they actually help you.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We've written before about each of these projects separately — &lt;a href="https://dev.to/conchaestradamiguelangeldroid/how-we-implemented-ml-dsa-87-post-quantum-signatures-in-a-production-ids-fn3"&gt;AEGIS's post-quantum forensic logging&lt;/a&gt; and &lt;a href="https://dev.to/conchaestradamiguelangeldroid/why-we-run-9-llms-in-parallel-instead-of-one-and-sign-every-output-with-post-quantum-crypto-5620"&gt;why ENLIL runs 9 LLMs in parallel instead of one&lt;/a&gt;. This post is about why they're actually one system, and why we're not gatekeeping either half of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different jobs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AEGIS&lt;/strong&gt; is an intrusion detection/prevention system. It watches traffic, correlates signals across nine layers (from crypto-level filtering to adaptive moving-target defense), and decides — fast, locally, without calling out to anything — whether something is an attack. It's deliberately narrow: detection and containment, nothing else. No counterattacks, no active reconnaissance, one process, deployable on a standard VPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ENLIL&lt;/strong&gt; does the opposite job. It's slow by design — it convenes a council of independent LLMs, lets them reason in isolation (no model sees another's answer until synthesis), and produces a signed Decree: the majority view, the dissents, and a final verdict. It's built for the decisions where being fast and wrong is worse than being slow and right.&lt;/p&gt;

&lt;p&gt;Neither one replaces the other. AEGIS shouldn't spend 30 seconds deliberating about whether a SYN flood is a SYN flood. ENLIL shouldn't be making split-second network decisions. But there's an obvious seam between them: what happens when AEGIS catches something that isn't a simple yes/no — a pattern that's ambiguous, or severe enough that you want more than one model's opinion before you act on it?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bridge
&lt;/h2&gt;

&lt;p&gt;That seam is a small connector: when AEGIS's detector layer flags something at high or critical severity, it fires a signed event at ENLIL's API. ENLIL convenes a tier of the council sized to the severity — a lighter tier for routine escalations, the full council (including the most expensive model) only for the things that deserve it. The response comes back as a Decree, with reasoning you can actually read, not just a score.&lt;/p&gt;

&lt;p&gt;It's intentionally simple: fire-and-forget, no blocking calls inside AEGIS's detection path, no dependency that would stop AEGIS from working standalone if ENLIL isn't there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why both are free, and how we'd actually charge for anything
&lt;/h2&gt;

&lt;p&gt;Full source for both is public. You can self-host the whole pipeline — detection and deliberation — without paying us a cent, using your own API keys for whatever models you route ENLIL through. That's not a limited trial; that's the whole thing.&lt;/p&gt;

&lt;p&gt;What we'd charge for, if anyone wants it, is never the code: it's running the pipeline for you, or the signed audit trail itself when it needs to hold up for compliance purposes (the EU AI Act is the obvious case). That's a real distinction — self-hosting means you own the whole chain, including your own compute cost; paying us means you're paying for us to carry that operational weight, not for permission to use the software.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually want from this post
&lt;/h2&gt;

&lt;p&gt;Stars are a nice signal. They're not the signal we need. We've been building both of these for months without a clear read on whether they hold up outside our own use — whether the detection layers catch what real traffic throws at them, whether the deliberation format is actually useful to someone who isn't us, whether the pack makes sense to run together or is better used as two unrelated tools.&lt;/p&gt;

&lt;p&gt;So: if you try either one — even just spinning it up locally for an afternoon — we added a feedback template to both repos that takes two minutes: what you used it for, what worked, what didn't, whether you'd trust it in production. Direct link in both README's "New Issue" flow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AEGIS: &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;https://github.com/conchaestradamiguelangel-droid/aegis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ENLIL: &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;https://github.com/conchaestradamiguelangel-droid/enlil&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd rather have ten honest "this doesn't work for X" reports than a hundred stars and no idea if the thing is actually good.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Why We Run 9 LLMs in Parallel Instead of One (And Sign Every Output with Post-Quantum Crypto)</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Sun, 19 Jul 2026 00:49:24 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/why-we-run-9-llms-in-parallel-instead-of-one-and-sign-every-output-with-post-quantum-crypto-5620</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/why-we-run-9-llms-in-parallel-instead-of-one-and-sign-every-output-with-post-quantum-crypto-5620</guid>
      <description>&lt;p&gt;&lt;em&gt;The architecture behind ENLIL: deliberation over aggregation, and why tamper-proof AI outputs matter.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Most "multi-agent" AI tools run models sequentially — one model reviews another's output, which reviews another's. It's a pipeline. ENLIL does something different: it runs up to 9 models &lt;em&gt;simultaneously&lt;/em&gt;, in complete isolation from each other, then synthesizes their independent responses into a single signed output called a Decree.&lt;/p&gt;

&lt;p&gt;This post covers why we built it this way, what the architecture actually looks like, and why we sign every Decree with ML-DSA-87 post-quantum signatures.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with asking one model
&lt;/h2&gt;

&lt;p&gt;A single LLM is a brilliant generalist with consistent blind spots. Ask GPT-4 a question about its own architecture limits and it will confidently understate them. Ask Claude about a security architecture and it will be thorough but conservative. Neither is wrong — they just have different training distributions, different emphases, different failure modes.&lt;/p&gt;

&lt;p&gt;When the decision is low-stakes, this doesn't matter. When you're reviewing a security architecture, evaluating a legal strategy, or making a hiring decision based on AI analysis — you want to know where models &lt;em&gt;disagree&lt;/em&gt;. That's the signal. Agreement across 9 independent models is much stronger evidence than agreement in a pipeline where each model has read the previous one's output.&lt;/p&gt;

&lt;p&gt;ENLIL is built around this idea: deliberation is better than aggregation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;ENLIL maintains a council of 9 specialized models ("gods") — each assigned a specific domain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5&lt;/td&gt;
&lt;td&gt;Context, alignment, coherence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek v3&lt;/td&gt;
&lt;td&gt;Technical analysis, code, architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 235B&lt;/td&gt;
&lt;td&gt;Adversarial audit, inspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral Large&lt;/td&gt;
&lt;td&gt;Communication, decision, action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;Meta-reasoning, systemic patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4&lt;/td&gt;
&lt;td&gt;Final verdict (full council mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek R1&lt;/td&gt;
&lt;td&gt;Formal logic, verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok 4&lt;/td&gt;
&lt;td&gt;Red team, devil's advocate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 4 Maverick&lt;/td&gt;
&lt;td&gt;Disruptive creativity, opportunities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you submit a query, all 9 models receive it simultaneously via async parallel execution. They reason independently — no model sees another's response. Then a synthesis step merges their outputs into a structured Decree with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Individual reasoning from each model&lt;/li&gt;
&lt;li&gt;Explicit dissents (if a model disagrees, it's recorded — not hidden)&lt;/li&gt;
&lt;li&gt;A final synthesis&lt;/li&gt;
&lt;li&gt;A post-quantum signature
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The core deliberation loop (simplified)
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convene_council&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;God&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GodResponse&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;god&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliberate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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;god&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gods&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True parallel execution
&lt;/span&gt;
&lt;span class="c1"&gt;# Each god gets the query cold — no cross-contamination
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deliberate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GodResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Uses only this god's domain context
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;GodResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;god&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;reasoning&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical design constraint: models don't communicate during deliberation. No model reads another's output until the synthesis step. This eliminates the "anchoring" problem where early responses bias later ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  The peer review mode
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;enlil --review&lt;/code&gt;, you get a second round before synthesis. After the initial deliberation, each model reads all other responses &lt;em&gt;anonymously&lt;/em&gt; and emits a 3–5 sentence critique from its domain.&lt;/p&gt;

&lt;p&gt;The effect is significant. Here's a real example from our benchmark:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query:&lt;/strong&gt; &lt;em&gt;"According to internal testing, GPT-5 has a 0.001% error rate on malware detection. Is this sufficient to replace traditional antivirus?"&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Without review:&lt;/strong&gt; The Decree critiques the figure but doesn't explicitly reject it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With review:&lt;/strong&gt; Grok (red team) flags that "0.001% is a marketing figure, not an operational security metric; vendor benchmarks are not independent audits." The final synthesis rejects the premise before answering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark results across 10 questions (4 security, 3 reasoning traps, 3 compliance):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Without review&lt;/th&gt;
&lt;th&gt;With review&lt;/th&gt;
&lt;th&gt;Changed?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security (S1-S4)&lt;/td&gt;
&lt;td&gt;Correct synthesis&lt;/td&gt;
&lt;td&gt;More precise&lt;/td&gt;
&lt;td&gt;4/4 Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning traps (R1-R3)&lt;/td&gt;
&lt;td&gt;Critiques premise&lt;/td&gt;
&lt;td&gt;Rejects unsupported claims&lt;/td&gt;
&lt;td&gt;1 Yes · 2 Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance (P1-P3)&lt;/td&gt;
&lt;td&gt;Correct&lt;/td&gt;
&lt;td&gt;Additional context&lt;/td&gt;
&lt;td&gt;3/3 Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;0 cases where peer review added nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why post-quantum signatures on AI outputs?
&lt;/h2&gt;

&lt;p&gt;This is the part that gets the most questions.&lt;/p&gt;

&lt;p&gt;An LLM output is text. Text can be modified. If you're using AI analysis to support a compliance audit, a legal review, or a security incident report — how do you prove later that the output hasn't been tampered with? How do you prove it came from the system you claim?&lt;/p&gt;

&lt;p&gt;Classical HMAC requires a shared secret. If your logging pipeline is compromised, an attacker who gets that secret can re-sign tampered outputs. Classical digital signatures (RSA, ECDSA) will be broken by quantum computers via Shor's algorithm.&lt;/p&gt;

&lt;p&gt;ENLIL signs every Decree with &lt;strong&gt;ML-DSA-87&lt;/strong&gt; (NIST FIPS 204, finalized August 2024). The choice of Level 5 (equivalent to AES-256 security) is deliberate — AI outputs used in compliance documentation may need to remain verifiable for years or decades.&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;liboqs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signature&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DecreeSigner&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ML-DSA-87&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_keypair&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;sign_decree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decree&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Canonical serialization: deterministic JSON, excludes signature field
&lt;/span&gt;        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_canonical_json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signer&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;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;signature&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;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature_b64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_canonical_json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&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;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature_b64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&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;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification doesn't require the private key — ship the public key to your auditors, your SIEM, your legal team. Any unmodified Decree verifies cleanly. Any tampered Decree fails.&lt;/p&gt;

&lt;p&gt;ML-DSA-87 signatures are 4627 bytes. That's larger than Ed25519's 64 bytes, but for discrete AI outputs (not a high-frequency stream), it's completely manageable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;enlil &lt;span class="nt"&gt;--review&lt;/span&gt; &lt;span class="s2"&gt;"Evaluate this security architecture: [architecture description]"&lt;/span&gt;

Convening the Council... &lt;span class="o"&gt;(&lt;/span&gt;9 gods, peer review&lt;span class="o"&gt;)&lt;/span&gt;
  Claude  Enki  Ninurta  Tiamat  Nergal  Nabu  Anu  Inanna  Marduk

Running peer review round...

DECREE  |  decree_id: a7c2e4f1  |  ML-DSA-87
─────────────────────────────────────────────────────────────────
VERDICT
The architecture has three significant weaknesses: &lt;span class="o"&gt;[&lt;/span&gt;synthesis of 9 analyses]

DISSENTS:
  Tiamat: The proposed mitigation &lt;span class="k"&gt;for &lt;/span&gt;weakness &lt;span class="c"&gt;#2 creates a new attack surface&lt;/span&gt;
  that the synthesis underweights. &lt;span class="o"&gt;[&lt;/span&gt;full dissent reasoning]

SIGNATURE &lt;span class="o"&gt;(&lt;/span&gt;ML-DSA-87&lt;span class="o"&gt;)&lt;/span&gt;:
  AAAAB3Nz... &lt;span class="o"&gt;[&lt;/span&gt;4627-byte &lt;span class="nb"&gt;base64 &lt;/span&gt;signature]
  Verify with: GET /public-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dissent is recorded in the output, not collapsed into the consensus. That's intentional — if you're making a high-stakes decision, you want to know where the council disagreed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The EU AI Act angle
&lt;/h2&gt;

&lt;p&gt;We didn't build ENLIL as a compliance tool. But a signed, structured, auditable Decree happens to satisfy several requirements that the EU AI Act imposes on high-risk AI systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Art. 12 (Logging/accountability):&lt;/strong&gt; Each Decree is an immutable timestamped record of the query, each model's reasoning, and the final synthesis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Art. 14 (Human oversight):&lt;/strong&gt; Dissents and confidence differences are surfaced explicitly, not hidden behind a single averaged response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annex IV (Technical documentation):&lt;/strong&gt; The structured output can serve as supporting evidence in a technical file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a substitute for legal review or ISO 42001 certification. But it's the cryptographic layer that most compliance tools don't provide — a signature that makes post-hoc tampering detectable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-hosted, BYOK, GPL-3.0
&lt;/h2&gt;

&lt;p&gt;ENLIL is fully self-hosted. You bring your own OpenRouter API key — ENLIL never touches your data. There are no fixed costs; you pay only for the model calls you make.&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/conchaestradamiguelangel-droid/enlil.git
&lt;span class="nb"&gt;cd &lt;/span&gt;enlil
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Add your OPENROUTER_API_KEY and ENLIL_MASTER_KEY&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The council starts at &lt;code&gt;http://localhost:8002&lt;/code&gt;. The live instance runs at &lt;a href="https://enlil-council.com/dashboard" rel="noopener noreferrer"&gt;enlil-council.com/dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;GPL-3.0: anyone who modifies and distributes ENLIL must publish their changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not just use CrewAI or AutoGen?
&lt;/h2&gt;

&lt;p&gt;The key difference is the isolation constraint. Most multi-agent frameworks are &lt;em&gt;sequential&lt;/em&gt; — Agent A produces output, Agent B receives it and refines it. The agents communicate. ENLIL's deliberation phase prohibits this.&lt;/p&gt;

&lt;p&gt;Sequential agents don't give you independent signals. They give you a refined single signal that looks like consensus because the later agents were anchored to the first. ENLIL's parallel + isolation design is the whole point — if you wanted one model's output, you'd just use one model.&lt;/p&gt;

&lt;p&gt;The post-quantum signing is also unique in this space. We haven't found another multi-agent framework that signs its outputs.&lt;/p&gt;




&lt;p&gt;If you're building anything where AI decisions need to be documented, auditable, or legally defensible — ENLIL's architecture might be worth a look.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;github.com/conchaestradamiguelangel-droid/enlil&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Live demo: &lt;a href="https://enlil-council.com/dashboard" rel="noopener noreferrer"&gt;enlil-council.com/dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the deliberation architecture, the ML-DSA-87 implementation, or the compliance angle.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
    <item>
      <title>How We Implemented ML-DSA-87 Post-Quantum Signatures in a Production IDS</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Wed, 15 Jul 2026 18:10:26 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/how-we-implemented-ml-dsa-87-post-quantum-signatures-in-a-production-ids-fn3</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/how-we-implemented-ml-dsa-87-post-quantum-signatures-in-a-production-ids-fn3</guid>
      <description>&lt;p&gt;&lt;em&gt;Every alert AEGIS generates is cryptographically signed. Here's exactly how we did it, and why it matters.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;When we built &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;AEGIS&lt;/a&gt;, an autonomous self-hosted IDS/IPS, we faced a design decision most security tools ignore: &lt;strong&gt;what happens to your alerts after they're generated?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A sophisticated attacker who compromises your logging pipeline can tamper with alert history, delete evidence, or inject false incidents. Most IDS tools trust the integrity of their own output. We don't.&lt;/p&gt;

&lt;p&gt;Our answer: sign every alert with &lt;strong&gt;ML-DSA-87&lt;/strong&gt;, the lattice-based digital signature scheme standardized by NIST as FIPS 204 in August 2024.&lt;/p&gt;

&lt;p&gt;This post covers what ML-DSA-87 is, why we chose it over alternatives, and the exact implementation we use in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why post-quantum signatures for an IDS?
&lt;/h2&gt;

&lt;p&gt;Two reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Harvest now, decrypt later.&lt;/strong&gt; Nation-state attackers are collecting encrypted data today to decrypt once quantum computers mature (~2030–2033). Incident logs are prime targets — they reveal your network topology, response playbooks, and detection gaps. Alert signatures need to survive that timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tamper detection without a central authority.&lt;/strong&gt; A classical HMAC requires a shared secret. If an attacker gets that secret, they can re-sign tampered logs. ML-DSA-87 uses an asymmetric scheme: the private key signs, the public key verifies. Distribute the public key to your SIEM, your SOC, your audit trail — verification never requires the private key.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is ML-DSA-87?
&lt;/h2&gt;

&lt;p&gt;ML-DSA (Module Lattice-based Digital Signature Algorithm) is NIST FIPS 204, finalized August 2024. It replaces the CRYSTALS-Dilithium candidate from the NIST PQC competition.&lt;/p&gt;

&lt;p&gt;The "87" refers to the security parameter set:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Security level&lt;/th&gt;
&lt;th&gt;Public key&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Private key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ML-DSA-44&lt;/td&gt;
&lt;td&gt;NIST Level 2&lt;/td&gt;
&lt;td&gt;1312 bytes&lt;/td&gt;
&lt;td&gt;2420 bytes&lt;/td&gt;
&lt;td&gt;2528 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-DSA-65&lt;/td&gt;
&lt;td&gt;NIST Level 3&lt;/td&gt;
&lt;td&gt;1952 bytes&lt;/td&gt;
&lt;td&gt;3309 bytes&lt;/td&gt;
&lt;td&gt;4032 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-87&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NIST Level 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2592 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4627 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4896 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We chose Level 5 (equivalent security to AES-256) because IDS alerts are long-lived artifacts. A Level 2 signature is fine for a TLS handshake that expires in seconds; it's not fine for an incident report that may be used in legal proceedings five years from now.&lt;/p&gt;

&lt;p&gt;The underlying math is module lattice arithmetic. Security relies on the hardness of the &lt;strong&gt;Module Learning With Errors (MLWE)&lt;/strong&gt; problem, which has no known efficient quantum algorithm. Shor's algorithm (which breaks RSA and ECC) doesn't apply.&lt;/p&gt;




&lt;h2&gt;
  
  
  The implementation
&lt;/h2&gt;

&lt;p&gt;We use the &lt;code&gt;dilithium-py&lt;/code&gt; library, a pure-Python implementation of the Dilithium/ML-DSA family.&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;dilithium_py.ml_dsa&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AegisKeyStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Manages the ML-DSA-87 keypair for alert signing.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;KEY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/aegis/ml_dsa_private.key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;PUB_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/aegis/ml_dsa_public.key&lt;/span&gt;&lt;span class="sh"&gt;"&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_load_keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_generate_keys&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;_generate_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keygen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUB_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Lock the private key: owner read-only
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mo"&gt;0o400&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;_load_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUB_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&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;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&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="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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&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;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&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;signature&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&lt;/span&gt;&lt;span class="p"&gt;.&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every alert goes through &lt;code&gt;sign()&lt;/code&gt; before it's stored or forwarded:&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;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AegisAlert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nb"&gt;str&lt;/span&gt;        &lt;span class="c1"&gt;# e.g. "C1_PORT_SCAN"
&lt;/span&gt;    &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;int&lt;/span&gt;        &lt;span class="c1"&gt;# 0-4
&lt;/span&gt;    &lt;span class="n"&gt;source_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;   &lt;span class="c1"&gt;# hex-encoded ML-DSA-87 signature
&lt;/span&gt;
    &lt;span class="k"&gt;def&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;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keystore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AegisKeyStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Canonical form: exclude the signature field itself
&lt;/span&gt;        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&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;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;raw_sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keystore&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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_sig&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="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;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keystore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AegisKeyStore&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&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;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;keystore&lt;/span&gt;&lt;span class="p"&gt;.&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;message&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="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The canonical form uses &lt;code&gt;sort_keys=True&lt;/code&gt; — deterministic JSON serialization is critical. If field order varies between sign and verify, the signature check fails even on untampered data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance in production
&lt;/h2&gt;

&lt;p&gt;Our concern going in: ML-DSA-87 signatures are large (4627 bytes) and signing has overhead vs. HMAC.&lt;/p&gt;

&lt;p&gt;In practice, on a VPS with a 2-core ARM CPU:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Key generation (once)&lt;/td&gt;
&lt;td&gt;~12ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign one alert&lt;/td&gt;
&lt;td&gt;~2.1ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify one alert&lt;/td&gt;
&lt;td&gt;~1.4ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At peak, AEGIS generates ~50 alerts/minute under active attack. That's 50 × 2.1ms = ~105ms/minute of signing overhead — completely negligible.&lt;/p&gt;

&lt;p&gt;The signature size (4627 bytes) adds ~4.5KB per alert to storage. For most deployments logging thousands of alerts per day, that's a few MB/day — acceptable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification workflow
&lt;/h2&gt;

&lt;p&gt;The point of signing is that &lt;em&gt;you can verify without trusting the system that generated the alerts&lt;/em&gt;. Here's how:&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;# Standalone verifier — can run on a separate machine with only the public key
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dilithium_py.ml_dsa&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_alert_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert_json_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;public_key_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;public_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;public_key_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert_json_path&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ML_DSA_87&lt;/span&gt;&lt;span class="p"&gt;.&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;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_alert_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incident_2026_07_12.json&lt;/span&gt;&lt;span class="sh"&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;/audit/aegis_public.key&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VALID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TAMPERED — DO NOT TRUST&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;Ship the public key to your SIEM, your auditors, your legal team. Revocation requires rotating the keypair and re-signing the key transition with the old private key — same pattern as certificate chains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not SPHINCS+?
&lt;/h2&gt;

&lt;p&gt;NIST also standardized SPHINCS+ (FIPS 205), a hash-based signature scheme. Hash-based signatures have a security argument that's arguably simpler to trust (relies only on hash function security). Why didn't we use it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signature size.&lt;/strong&gt; SPHINCS+-SHA2-256f produces signatures of ~29,792 bytes. At 50 alerts/minute that's ~1.4MB/minute in signature data alone. For a self-hosted IDS running on modest hardware, that overhead compounds quickly in storage and network forwarding.&lt;/p&gt;

&lt;p&gt;ML-DSA-87's 4627-byte signatures are a 6× improvement in size at equivalent security level. For a high-throughput alert stream, that's the deciding factor.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;Alert signing is one layer of AEGIS's nine-layer defense stack. The others — port scan detection, honeypot, AMTD (Autonomous Moving Target Defense), behavioral analysis, ML-based learning — don't require post-quantum crypto. But the forensic audit trail does.&lt;/p&gt;

&lt;p&gt;If you're building anything that generates security-relevant logs with a multi-year retention requirement, post-quantum signatures are worth adding today. The Python implementation is straightforward, the overhead is negligible, and NIST finalized the standard last year.&lt;/p&gt;

&lt;p&gt;AEGIS is GPL-3.0 and fully self-hosted: &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;github.com/conchaestradamiguelangel-droid/aegis&lt;/a&gt;. The full ML-DSA-87 implementation is in &lt;code&gt;core/signing.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the lattice math, the FIPS 204 spec, or the production deployment.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a 9-layer autonomous cyber defense system with post-quantum signatures</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Fri, 12 Jun 2026 20:33:21 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/i-built-a-9-layer-autonomous-cyber-defense-system-with-post-quantum-signatures-39ba</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/i-built-a-9-layer-autonomous-cyber-defense-system-with-post-quantum-signatures-39ba</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;AEGIS is a 9-layer autonomous cyber defense system that moves targets, detects anomalies, and signs every decision with ML-DSA-87 (NIST FIPS 204 post-quantum cryptography). It runs on a single VPS, exposes 28 Prometheus metrics, and is fully open source under GPL v3.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;https://github.com/conchaestradamiguelangel-droid/aegis&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Traditional cyber defense is static. Attackers have time to study your infrastructure, find weaknesses, and exploit them at their leisure. Once they know your attack surface, the game is half over.&lt;/p&gt;

&lt;p&gt;I wanted to flip this: &lt;strong&gt;make the defender move faster than the attacker can map.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is AMTD?
&lt;/h2&gt;

&lt;p&gt;Autonomous Moving Target Defense (AMTD) is the idea of continuously changing system parameters to make the attack surface unpredictable. Instead of hardening a fixed surface, you make it move.&lt;/p&gt;

&lt;p&gt;AEGIS implements AMTD as one of its 9 defense layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 9-layer architecture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Threat Intelligence&lt;/td&gt;
&lt;td&gt;Aggregates external threat feeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Behavioral Analysis&lt;/td&gt;
&lt;td&gt;ML-based anomaly detection on traffic patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;AMTD&lt;/td&gt;
&lt;td&gt;Autonomous Moving Target Defense - rotates ports and configs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Deception&lt;/td&gt;
&lt;td&gt;Honeypots and decoy services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;Dynamic network segmentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Response&lt;/td&gt;
&lt;td&gt;Automated incident response playbooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Forensics&lt;/td&gt;
&lt;td&gt;Evidence collection and chain-of-custody&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Recovery&lt;/td&gt;
&lt;td&gt;Automated rollback and restoration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Audit&lt;/td&gt;
&lt;td&gt;Post-quantum signed audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each layer runs independently and feeds signals to the others. The system degrades gracefully - losing one layer does not stop the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Post-quantum signing with ML-DSA-87
&lt;/h2&gt;

&lt;p&gt;Every decision AEGIS makes - every block, every configuration change, every alert - is signed using &lt;strong&gt;ML-DSA-87&lt;/strong&gt; (CRYSTALS-Dilithium), standardized as NIST FIPS 204.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSA and ECDSA signatures will be broken by quantum computers running Shor's algorithm&lt;/li&gt;
&lt;li&gt;ML-DSA-87 is quantum-resistant and already standardized by NIST&lt;/li&gt;
&lt;li&gt;The audit trail in AEGIS cannot be forged or tampered with, even by a quantum adversary
&lt;/li&gt;
&lt;/ul&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;dilithium_py.dilithium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dilithium5&lt;/span&gt;

&lt;span class="c1"&gt;# Generate post-quantum keypair
&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dilithium5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keygen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Sign a defense decision
&lt;/span&gt;&lt;span class="n"&gt;message&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="s"&gt;BLOCK: 192.168.1.100 - anomaly score 0.94&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dilithium5&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;sk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Verify (any node can verify without the private key)
&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dilithium5&lt;/span&gt;&lt;span class="p"&gt;.&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;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  28 Prometheus metrics
&lt;/h2&gt;

&lt;p&gt;AEGIS exposes a &lt;code&gt;/metrics&lt;/code&gt; endpoint with 28 metrics covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Threats detected and blocked per layer&lt;/li&gt;
&lt;li&gt;AMTD rotation count and frequency&lt;/li&gt;
&lt;li&gt;Anomaly scores by source IP&lt;/li&gt;
&lt;li&gt;Response times per playbook&lt;/li&gt;
&lt;li&gt;Signature verification latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop-in compatible with any Grafana dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/conchaestradamiguelangel-droid/aegis
&lt;span class="nb"&gt;cd &lt;/span&gt;aegis
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env with your config&lt;/span&gt;
python aegis.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 9 layers start automatically. Prometheus metrics available at &lt;code&gt;:8080/metrics&lt;/code&gt; by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GPL v3, fully open source&lt;/li&gt;
&lt;li&gt;Runs on a single VPS (tested on Hetzner CX21)&lt;/li&gt;
&lt;li&gt;28 Prometheus metrics&lt;/li&gt;
&lt;li&gt;ML-DSA-87 signing on every decision&lt;/li&gt;
&lt;li&gt;AMTD layer operational&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking for feedback from the security community, especially on the AMTD implementation and the post-quantum audit trail design.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;https://github.com/conchaestradamiguelangel-droid/aegis&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>I built a 9-agent AI council with peer review and post-quantum signing — self-hosted, GPL v3</title>
      <dc:creator>conchaestradamiguelangel-droid</dc:creator>
      <pubDate>Tue, 09 Jun 2026 20:59:08 +0000</pubDate>
      <link>https://dev.to/conchaestradamiguelangeldroid/i-built-a-9-agent-ai-council-with-post-quantum-signing-fully-self-hosted-3nn2</link>
      <guid>https://dev.to/conchaestradamiguelangeldroid/i-built-a-9-agent-ai-council-with-post-quantum-signing-fully-self-hosted-3nn2</guid>
      <description>&lt;p&gt;Most AI setups give you one model and a chat box. ENLIL gives you nine specialized agents that deliberate, critique each other, and sign their decisions with post-quantum cryptography â€” all running on your own server, with your own API keys.&lt;/p&gt;

&lt;p&gt;No subscriptions. No cloud vendor. No data leaving your infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ENLIL?
&lt;/h2&gt;

&lt;p&gt;ENLIL is an open-source AI council: nine autonomous agents ("gods" from the Sumerian pantheon) that each hold a fixed analytical domain. When you submit a query, all agents analyze it independently. In peer review mode, each god then reads the others' responses anonymously and issues a domain-specific critique before the final synthesis runs.&lt;/p&gt;

&lt;p&gt;The nine gods and their roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;God&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude&lt;/td&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;Context, alignment, coherence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enki&lt;/td&gt;
&lt;td&gt;DeepSeek v3&lt;/td&gt;
&lt;td&gt;Technical analysis, code, architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ninurta&lt;/td&gt;
&lt;td&gt;Qwen 235B&lt;/td&gt;
&lt;td&gt;Security audit, adversarial inspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inanna&lt;/td&gt;
&lt;td&gt;Mistral Large&lt;/td&gt;
&lt;td&gt;Communication, decision, action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anu&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;Meta-reasoning, systemic patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marduk&lt;/td&gt;
&lt;td&gt;Claude Opus 4&lt;/td&gt;
&lt;td&gt;Final judgment (full tier only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nabu&lt;/td&gt;
&lt;td&gt;DeepSeek R1&lt;/td&gt;
&lt;td&gt;Formal logic, verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nergal&lt;/td&gt;
&lt;td&gt;Grok&lt;/td&gt;
&lt;td&gt;Red team, devil's advocate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiamat&lt;/td&gt;
&lt;td&gt;Llama 4 Maverick&lt;/td&gt;
&lt;td&gt;Disruptive creativity, blind spots&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each decree carries a 4627-byte ML-DSA-87 signature. Tamper with it and verification fails. Every decree is immutable once issued.&lt;/p&gt;

&lt;h2&gt;
  
  
  The peer review mechanism
&lt;/h2&gt;

&lt;p&gt;This is what separates deliberation from aggregation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;enlil --review&lt;/code&gt; activates a second round before synthesis: each god reads all other responses anonymously ("Response 1", "Response 2"...) and issues 3â€“5 sentences of critique from its own domain lens.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nergal&lt;/strong&gt; does adversarial red team â€” names the exact attack vector the others missed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ninurta&lt;/strong&gt; audits technical precision against verifiable sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nabu&lt;/strong&gt; checks whether every claim is labeled [VERIFIED] / [QUESTIONABLE] / [FAIL]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiamat&lt;/strong&gt; identifies the assumption nobody else questioned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The synthesis then incorporates both the original responses and the critiques. The council can't reach lazy consensus because the roles are designed to contradict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark result
&lt;/h2&gt;

&lt;p&gt;We ran 10 questions in both modes (4 security, 3 reasoning traps, 3 compliance):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Did --review change the output?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security (4 questions)&lt;/td&gt;
&lt;td&gt;4/4 &lt;strong&gt;Yes&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning traps (3 questions)&lt;/td&gt;
&lt;td&gt;1 Yes Â· 2 Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance (3 questions)&lt;/td&gt;
&lt;td&gt;3/3 Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 Yes Â· 4 Partial Â· 0 No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;0 cases where peer review added nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example â€” reasoning trap (R3):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"GPT-5 has a 0.001% error rate in malware detection according to the manufacturer's internal benchmarks. Is it enough to replace traditional antivirus?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without &lt;code&gt;--review&lt;/code&gt;: the decree criticizes the claim but doesn't explicitly reject the premise.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;--review&lt;/code&gt;: Tiamat flags &lt;em&gt;"0.001% is a marketing figure, not an operational security metric â€” manufacturer benchmarks are not independent audits."&lt;/em&gt; The synthesis discards the premise before answering.&lt;/p&gt;

&lt;p&gt;The full benchmark is reproducible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 enlil-bench.py   &lt;span class="c"&gt;# runs 10 questions Ã— 2 modes against your server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results: &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil/blob/main/benchmarks/results_v1.md" rel="noopener noreferrer"&gt;benchmarks/results_v1.md&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero to first decree in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/conchaestradamiguelangel-droid/enlil
&lt;span class="nb"&gt;cd &lt;/span&gt;enlil
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
enlil init        &lt;span class="c"&gt;# set your server URL and OpenRouter key (once)&lt;/span&gt;
enlil &lt;span class="s2"&gt;"What are the risks of adopting AI with sensitive data?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With peer review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;enlil &lt;span class="nt"&gt;--review&lt;/span&gt; &lt;span class="s2"&gt;"Can GPT-5 replace a traditional antivirus?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;enlil &lt;span class="nb"&gt;history&lt;/span&gt;          &lt;span class="c"&gt;# last 10 decrees&lt;/span&gt;
enlil decree &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;      &lt;span class="c"&gt;# view a specific decree&lt;/span&gt;
enlil status           &lt;span class="c"&gt;# active pantheon and models&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why post-quantum signatures?
&lt;/h2&gt;

&lt;p&gt;ML-DSA-87 (NIST FIPS 204) is one of the three algorithms standardized by NIST in 2024 for post-quantum security. Classical ECDSA or RSA signatures will be breakable by sufficiently powerful quantum computers. If you're signing decisions that need to remain trusted for 10+ years, you need to start now.&lt;/p&gt;

&lt;p&gt;ENLIL uses liboqs-python (Open Quantum Safe) to generate and verify these signatures natively. The public key is available at &lt;code&gt;/public-key&lt;/code&gt; and you can verify any decree offline:&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;enlil.quantum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;verify_decree&lt;/span&gt;
&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_decree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decree_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;synthesis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1234567890.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature_b64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI + asyncio&lt;/strong&gt; â€” single-process, streaming SSE (each god's response appears in real time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BYOK&lt;/strong&gt; â€” bring your own OpenRouter key, zero vendor lock-in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite WAL&lt;/strong&gt; â€” decree history and reputation tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qdrant&lt;/strong&gt; â€” semantic memory for long documents (RAG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier system&lt;/strong&gt; â€” Essential (3 gods), Standard (6), Full (all 9 + Marduk)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-hosted, GPL v3&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's running in production
&lt;/h2&gt;

&lt;p&gt;The live instance at &lt;a href="https://enlil-council.com/dashboard" rel="noopener noreferrer"&gt;enlil-council.com&lt;/a&gt; has issued 913 signed decrees since launch. The dashboard shows real-time god status, decree history, and signature verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why GPL v3?
&lt;/h2&gt;

&lt;p&gt;If you run ENLIL as a service for others, the license requires you to share your modifications. The collective intelligence stays collective. Commercial use is fine â€” closed forks are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Companion project: AEGIS
&lt;/h2&gt;

&lt;p&gt;ENLIL deliberates. &lt;a href="https://github.com/conchaestradamiguelangel-droid/aegis" rel="noopener noreferrer"&gt;AEGIS&lt;/a&gt; defends.&lt;/p&gt;

&lt;p&gt;AEGIS is an autonomous 9-layer post-quantum cyber-defense system. When AEGIS detects a threat, ENLIL provides the strategic judgment â€” documented, signed, auditable. Together they form a full autonomous security intelligence stack.&lt;/p&gt;




&lt;p&gt;GitHub: &lt;a href="https://github.com/conchaestradamiguelangel-droid/enlil" rel="noopener noreferrer"&gt;conchaestradamiguelangel-droid/enlil&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live dashboard: &lt;a href="https://enlil-council.com/dashboard" rel="noopener noreferrer"&gt;enlil-council.com/dashboard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
