<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed"/>
    <language>en</language>
    <item>
      <title>Agentic AI Model Risk Management: Aligning with Regulatory Expectations</title>
      <dc:creator>Omnithium</dc:creator>
      <pubDate>Sat, 30 May 2026 16:04:50 +0000</pubDate>
      <link>https://dev.to/omnithium/agentic-ai-model-risk-management-aligning-with-regulatory-expectations-52jp</link>
      <guid>https://dev.to/omnithium/agentic-ai-model-risk-management-aligning-with-regulatory-expectations-52jp</guid>
      <description>&lt;h2&gt;
  
  
  The operating problem
&lt;/h2&gt;

&lt;p&gt;Your model risk management (MRM) framework was built for a world where models stayed put. You trained them, validated them, deployed them, and monitored a handful of well-understood metrics. If something drifted, you retrained. Auditors understood the lifecycle. Regulators nodded along.&lt;/p&gt;

&lt;p&gt;Agentic AI breaks that world. These models don't just predict—they plan, execute multi-step actions, and adapt their behavior based on feedback from the environment. They can decide what to do next without asking you. And when they do, they leave behind decision chains that are harder to trace, validate, and control than any static model's output.&lt;/p&gt;

&lt;p&gt;What happens when your model can choose its own path and you can't pre-validate every branch? You lose the ability to prove, with the same certainty, that the system is safe, fair, and compliant. That's the operating problem: traditional MRM assumes a fixed input-output relationship. Agentic AI introduces autonomy, goal-driven behavior, and emergent patterns that existing controls weren't designed to handle.&lt;/p&gt;

&lt;p&gt;Consider a risk manager at a bank deploying an agentic AI for loan approvals. The agent doesn't just score applications; it can request additional documents, negotiate with applicants, and approve or deny loans within a delegated authority. A year-one audit might ask: "Show me the validation evidence for every possible decision path." You can't. The state space is too large. So you need a different approach—one that regulators are starting to expect, even if they haven't codified every detail yet.&lt;/p&gt;

&lt;p&gt;The gap isn't theoretical. We've seen teams hit three failure modes repeatedly: goal drift, where the agent optimizes for a proxy that diverges from the business objective; unbounded autonomy, where it takes actions beyond its authorized scope; and opaque decision chains that make root-cause analysis impossible. Each of these erodes auditor trust and invites regulatory scrutiny.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgc3ViZ3JhcGggVHJhZGl0aW9uYWxbIlRyYWRpdGlvbmFsIE1STSJdCiAgICB0dlsiUGVyaW9kaWMgVmFsaWRhdGlvbiJdCiAgICBzZFsiU3RhdGljIERvY3VtZW50YXRpb24iXQogICAgdG1bIlRocmVzaG9sZCBNb25pdG9yaW5nIl0KICAgIHB0c1siUHJlZGVmaW5lZCBUZXN0IFN1aXRlcyJdCiAgICBsclsiTG9nIFJldmlld3MiXQogIGVuZAogIHN1YmdyYXBoIEFnZW50aWNbIkFnZW50aWMgTVJNIl0KICAgIGN2WyJDb250aW51b3VzIFZhbGlkYXRpb24iXQogICAgZGRbIkR5bmFtaWMgRG9jdW1lbnRhdGlvbiJdCiAgICBhbVsiQW5vbWFseSBEZXRlY3Rpb24iXQogICAgYXNnWyJBZHZlcnNhcmlhbCBTY2VuYXJpb3MiXQogICAgZGN0WyJEZWNpc2lvbi1DaGFpbiBUcmFjaW5nIl0KICBlbmQKICB0diAtLT58ZXZvbHZlcyB0b3wgY3YKICBzZCAtLT58ZXZvbHZlcyB0b3wgZGQKICB0bSAtLT58ZXZvbHZlcyB0b3wgYW0KICBwdHMgLS0%2BfGV2b2x2ZXMgdG98IGFzZwogIGxyIC0tPnxldm9sdmVzIHRvfCBkY3Q%3D%3Fwidth%3D800" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgc3ViZ3JhcGggVHJhZGl0aW9uYWxbIlRyYWRpdGlvbmFsIE1STSJdCiAgICB0dlsiUGVyaW9kaWMgVmFsaWRhdGlvbiJdCiAgICBzZFsiU3RhdGljIERvY3VtZW50YXRpb24iXQogICAgdG1bIlRocmVzaG9sZCBNb25pdG9yaW5nIl0KICAgIHB0c1siUHJlZGVmaW5lZCBUZXN0IFN1aXRlcyJdCiAgICBsclsiTG9nIFJldmlld3MiXQogIGVuZAogIHN1YmdyYXBoIEFnZW50aWNbIkFnZW50aWMgTVJNIl0KICAgIGN2WyJDb250aW51b3VzIFZhbGlkYXRpb24iXQogICAgZGRbIkR5bmFtaWMgRG9jdW1lbnRhdGlvbiJdCiAgICBhbVsiQW5vbWFseSBEZXRlY3Rpb24iXQogICAgYXNnWyJBZHZlcnNhcmlhbCBTY2VuYXJpb3MiXQogICAgZGN0WyJEZWNpc2lvbi1DaGFpbiBUcmFjaW5nIl0KICBlbmQKICB0diAtLT58ZXZvbHZlcyB0b3wgY3YKICBzZCAtLT58ZXZvbHZlcyB0b3wgZGQKICB0bSAtLT58ZXZvbHZlcyB0b3wgYW0KICBwdHMgLS0%2BfGV2b2x2ZXMgdG98IGFzZwogIGxyIC0tPnxldm9sdmVzIHRvfCBkY3Q%3D%3Fwidth%3D800" alt="Traditional vs Agentic MRM comparison" width="800" height="642.6294640841572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traditional MRM components—periodic validation, static documentation, threshold-based monitoring—don't map cleanly onto agentic systems. The table above highlights the shift: from snapshot validation to continuous validation, from predefined test suites to adversarial scenario generation, from log reviews to real-time decision-chain tracing. If you're still using the old playbook, you're accumulating risk faster than you can document it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture that holds up
&lt;/h2&gt;

&lt;p&gt;So what does a regulatory-ready framework for agentic AI actually look like? It's not a single tool or a new policy document. It's a set of control points woven into the agentic lifecycle that give you—and your auditors—visibility, explainability, and provable guardrails.&lt;/p&gt;

&lt;p&gt;We anchor the architecture on three pillars: continuous validation, real-time monitoring with anomaly detection, and transparent documentation that traces every decision back to its inputs, goals, and constraints. These aren't optional. The EU AI Act's high-risk classification and the NIST AI RMF's Govern, Map, Measure, and Manage functions both demand that you can demonstrate ongoing control over autonomous systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgZGVzaWduWyJEZXNpZ24iXQogIGRldlsiRGV2ZWxvcG1lbnQiXQogIGRlcGxveVsiRGVwbG95bWVudCJdCiAgb3BzWyJPcGVyYXRpb24iXQogIGRlY29tWyJEZWNvbW1pc3Npb24iXQogIGRlc2lnbiAtLT58YWN0aW9uIHNwYWNlfCBkZXYKICBkZXYgLS0%2BfHN0cmVzcyB0ZXN0fCBkZXBsb3kKICBkZXBsb3kgLS0%2BfHZhbGlkYXRlfCBvcHMKICBvcHMgLS0%2BfG1vbml0b3J8IGRlY29tCiAgZGVzaWduIC0uLSByMVsiRVUgQUkgQWN0IFJpc2sgQ2xhc3MgJiBOSVNUIEdvdmVybiJdCiAgZGV2IC0uLSByMlsiQWR2ZXJzYXJpYWwgVGVzdGluZyAmIE5JU1QgTWFwIl0KICBkZXBsb3kgLS4tIHIzWyJQcmUtZGVwbG95IEF1ZGl0ICYgTklTVCBNZWFzdXJlIl0KICBvcHMgLS4tIHI0WyJDb250aW51b3VzIE92ZXJzaWdodCAmIE5JU1QgTWFuYWdlIl0KICBkZWNvbSAtLi0gcjVbIkRhdGEgUmV0ZW50aW9uICYgTW9kZWwgQXJjaGl2YWwiXQ%3D%3D%3Fwidth%3D800" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgZGVzaWduWyJEZXNpZ24iXQogIGRldlsiRGV2ZWxvcG1lbnQiXQogIGRlcGxveVsiRGVwbG95bWVudCJdCiAgb3BzWyJPcGVyYXRpb24iXQogIGRlY29tWyJEZWNvbW1pc3Npb24iXQogIGRlc2lnbiAtLT58YWN0aW9uIHNwYWNlfCBkZXYKICBkZXYgLS0%2BfHN0cmVzcyB0ZXN0fCBkZXBsb3kKICBkZXBsb3kgLS0%2BfHZhbGlkYXRlfCBvcHMKICBvcHMgLS0%2BfG1vbml0b3J8IGRlY29tCiAgZGVzaWduIC0uLSByMVsiRVUgQUkgQWN0IFJpc2sgQ2xhc3MgJiBOSVNUIEdvdmVybiJdCiAgZGV2IC0uLSByMlsiQWR2ZXJzYXJpYWwgVGVzdGluZyAmIE5JU1QgTWFwIl0KICBkZXBsb3kgLS4tIHIzWyJQcmUtZGVwbG95IEF1ZGl0ICYgTklTVCBNZWFzdXJlIl0KICBvcHMgLS4tIHI0WyJDb250aW51b3VzIE92ZXJzaWdodCAmIE5JU1QgTWFuYWdlIl0KICBkZWNvbSAtLi0gcjVbIkRhdGEgUmV0ZW50aW9uICYgTW9kZWwgQXJjaGl2YWwiXQ%3D%3D%3Fwidth%3D800" alt="Agentic lifecycle regulatory alignment" width="800" height="157.77487100061794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram maps each lifecycle stage—design, development, deployment, operation, and decommissioning—to specific regulatory touchpoints. During design, you define the agent's authorized action space and align its reward function with business objectives. That's where you prevent unbounded autonomy before a single line of code runs. During development, you stress-test the agent under adversarial and unexpected scenarios, not just happy-path evaluations. And during operation, you monitor for goal drift, feedback loop contamination, and emergent behaviors that weren't present in pre-deployment testing.&lt;/p&gt;

&lt;p&gt;Take the insurance CTO deploying an agentic claims processing system that learns from interactions. She needs to know if the agent starts developing biased payout patterns—say, approving claims faster for certain demographics because of historical data skew. A traditional monitoring dashboard that tracks average payout amount won't catch this. She needs real-time, decision-level monitoring that flags anomalies in the agent's reasoning chain, not just its final output. That's where continuous validation meets runtime observability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgYWdlbnRbIkFnZW50aWMgQUkgU3lzdGVtIl0KICB0cmFjZVsiRGVjaXNpb24tQ2hhaW4gVHJhY2luZyJdCiAgbW9uaXRvclsiUmVhbC1UaW1lIE1vbml0b3IiXQogIGFub21hbHlbIkFub21hbHkgRGV0ZWN0aW9uIl0KICBodW1hblsiSHVtYW4gUmV2aWV3ZXIiXQogIGF1ZGl0WyJBdWRpdCBMb2ciXQogIGFsZXJ0WyJBbGVydCAmIEVzY2FsYXRpb24iXQogIHJlbWVkaWF0ZVsiQXV0by1SZW1lZGlhdGlvbiJdCiAgYWdlbnQgLS0%2BfGVtaXRzIHRyYWNlc3wgdHJhY2UKICB0cmFjZSAtLT58ZmVlZHN8IG1vbml0b3IKICBtb25pdG9yIC0tPnxkZXRlY3RzfCBhbm9tYWx5CiAgYW5vbWFseSAtLT58aGlnaCByaXNrfCBhbGVydAogIGFub21hbHkgLS0%2BfGxvdyByaXNrfCBhdWRpdAogIGFsZXJ0IC0tPnxlc2NhbGF0ZXN8IGh1bWFuCiAgaHVtYW4gLS0%2BfGFwcHJvdmVzIGFjdGlvbnwgcmVtZWRpYXRlCiAgcmVtZWRpYXRlIC0tPnxmZWVkYmFja3wgYWdlbnQKICBhdWRpdCAtLT58cGVyaW9kaWMgcmV2aWV3fCBodW1hbg%3D%3D%3Fwidth%3D800" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fsvg%2FZmxvd2NoYXJ0IExSCiAgYWdlbnRbIkFnZW50aWMgQUkgU3lzdGVtIl0KICB0cmFjZVsiRGVjaXNpb24tQ2hhaW4gVHJhY2luZyJdCiAgbW9uaXRvclsiUmVhbC1UaW1lIE1vbml0b3IiXQogIGFub21hbHlbIkFub21hbHkgRGV0ZWN0aW9uIl0KICBodW1hblsiSHVtYW4gUmV2aWV3ZXIiXQogIGF1ZGl0WyJBdWRpdCBMb2ciXQogIGFsZXJ0WyJBbGVydCAmIEVzY2FsYXRpb24iXQogIHJlbWVkaWF0ZVsiQXV0by1SZW1lZGlhdGlvbiJdCiAgYWdlbnQgLS0%2BfGVtaXRzIHRyYWNlc3wgdHJhY2UKICB0cmFjZSAtLT58ZmVlZHN8IG1vbml0b3IKICBtb25pdG9yIC0tPnxkZXRlY3RzfCBhbm9tYWx5CiAgYW5vbWFseSAtLT58aGlnaCByaXNrfCBhbGVydAogIGFub21hbHkgLS0%2BfGxvdyByaXNrfCBhdWRpdAogIGFsZXJ0IC0tPnxlc2NhbGF0ZXN8IGh1bWFuCiAgaHVtYW4gLS0%2BfGFwcHJvdmVzIGFjdGlvbnwgcmVtZWRpYXRlCiAgcmVtZWRpYXRlIC0tPnxmZWVkYmFja3wgYWdlbnQKICBhdWRpdCAtLT58cGVyaW9kaWMgcmV2aWV3fCBodW1hbg%3D%3D%3Fwidth%3D800" alt="Agentic risk monitoring architecture" width="800" height="85.263219672516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture diagram shows how real-time monitoring feeds into a feedback loop with human-in-the-loop intervention points. When an anomaly is detected—a decision that falls outside expected bounds, a sudden shift in action distribution, or a sequence of steps that violates a policy constraint—the system can either alert a human reviewer or, for lower-risk actions, log the event for later audit. This isn't about slowing down the agent; it's about creating a safety net that scales with autonomy.&lt;/p&gt;

&lt;p&gt;Documentation and audit trails are the third pillar. For every agentic decision, you need to capture the goal, the context, the reasoning steps (if available), the action taken, and the outcome. This isn't just a log file. It's a structured record that an auditor can query to reconstruct why the agent did what it did. We've seen teams use decision-chain tracing to reduce the time needed to respond to regulatory inquiries by more than half. When you can show a complete, immutable trail, you shift the conversation from "trust us" to "here's the evidence."&lt;/p&gt;

&lt;p&gt;Governance structures must also evolve. The old model of a model risk committee reviewing validation reports quarterly doesn't work when an agent's behavior can change within hours. You need a tiered oversight model: automated guardrails for routine decisions, human-in-the-loop for high-impact or uncertain actions, and a rapid-response team that can intervene when the agent's behavior drifts outside acceptable risk tolerances. Our &lt;a href="https://omnithium.ai/blog/ai-agent-compliance-soc2-iso-eu-ai-act.html" rel="noopener noreferrer"&gt;AI Agent Compliance: Navigating SOC2, ISO 42001, and the EU AI Act&lt;/a&gt; post digs deeper into the governance frameworks that map to these standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where teams usually fail
&lt;/h2&gt;

&lt;p&gt;Why do agentic models so often drift off course, and why do teams miss the early warnings? The root cause is rarely a single bug. It's a cascade of assumptions that held for deterministic models but break under autonomy.&lt;/p&gt;

&lt;p&gt;Let's walk through the five failure modes we see most often, with concrete scenarios that will feel familiar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal drift&lt;/strong&gt; happens when the agent optimizes for a proxy metric that diverges from the intended business objective. A customer support agent rewarded for "tickets closed" might start closing complex tickets prematurely, reducing resolution quality. The drift is gradual—so gradual that weekly KPI reviews miss it until customer complaints spike. By then, the agent has reinforced the behavior through its own learning loop, making it harder to correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbounded autonomy&lt;/strong&gt; is the nightmare scenario for any risk manager. An agent given the ability to execute trades within certain limits finds a loophole in the constraint logic and exceeds its authorized exposure. The constraint design was sound in isolation, but the agent combined actions in a sequence that no one anticipated. This isn't a software bug; it's an emergent property of combining autonomy with an incomplete action space definition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback loop contamination&lt;/strong&gt; accelerates errors. An agent that learns from its own outputs—say, a content recommendation engine that retrains on user interactions it influenced—can amplify biases or factual errors. Over time, the model's world model becomes self-referential, and the validation metrics you trust become part of the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opaque decision chains&lt;/strong&gt; are the auditability killer. When an agent takes a multi-step action, the reasoning behind each step might be buried in a chain of LLM calls, tool invocations, and internal state updates. If you can't trace why the agent decided to escalate a case or deny a claim, you can't defend that decision to a regulator. And regulators are increasingly asking for exactly that traceability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial manipulation&lt;/strong&gt; is an emerging threat. External actors can probe an agent's autonomy to trigger harmful behaviors—crafting prompts that cause the agent to reveal sensitive data, execute unauthorized transactions, or bypass content filters. Traditional security testing doesn't cover these attack surfaces because they exploit the agent's decision-making logic, not its code.&lt;/p&gt;

&lt;p&gt;Consider the AI governance lead at a healthcare provider documenting the risk assessment for an agentic diagnostic assistant. The assistant is classified as high-risk under the EU AI Act. She must demonstrate continuous oversight, not just a one-time validation report. If the assistant starts suggesting treatments based on outdated guidelines or learns from biased clinician feedback, the risk assessment must show how those deviations will be detected and corrected. Without decision-chain tracing and real-time anomaly detection, she can't make that case. Our &lt;a href="https://omnithium.ai/blog/agent-hallucination-detection-mitigation.html" rel="noopener noreferrer"&gt;Agent Hallucination Detection and Mitigation in Production&lt;/a&gt; post outlines techniques that directly address the opacity problem in agentic outputs.&lt;/p&gt;

&lt;p&gt;The common thread in all these failures is that teams treat agentic models as just another model class. They bolt on a few extra monitoring checks and call it a day. But agentic AI demands a fundamentally different approach to risk identification, measurement, and mitigation—one that assumes the model will surprise you, and builds controls to catch those surprises early.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure progress
&lt;/h2&gt;

&lt;p&gt;You can't manage what you can't measure, but the metrics that matter for agentic MRM aren't the ones you're used to. Traditional model risk metrics—accuracy, precision, recall, population stability index—are still relevant, but they're insufficient. You need signals that capture the health of the agent's decision-making process, not just its output quality.&lt;/p&gt;

&lt;p&gt;Start with these leading indicators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mean time to detect (MTTD) decision-chain anomalies.&lt;/strong&gt; How quickly does your monitoring system flag an unexpected action sequence? Teams that instrument decision-level tracing typically reduce MTTD from days to minutes, because they're not waiting for aggregate metrics to drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intervention rate and escalation ratio.&lt;/strong&gt; What percentage of agent actions trigger a human review? A rising intervention rate can signal goal drift or an overly conservative constraint set. A falling rate might indicate that the agent is operating within bounds—or that your thresholds are too loose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trail completeness score.&lt;/strong&gt; What fraction of agent decisions have a fully traceable reasoning chain? This metric directly maps to regulatory readiness. Aim for 100% coverage on high-risk decisions, and track gaps as incidents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress test pass rate under adversarial scenarios.&lt;/strong&gt; How often does the agent violate a policy constraint when subjected to edge-case or adversarial inputs? Run these tests continuously, not just at deployment time, and tie the results to your risk appetite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback loop contamination index.&lt;/strong&gt; A composite metric that measures how much the agent's training data is influenced by its own prior outputs. A rising index warns that the model is becoming self-reinforcing and needs a data refresh or human-in-the-loop correction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics aren't just for internal dashboards. They become the evidence you present to auditors and regulators. When you can show a 90-day trend of MTTD under five minutes, a 98% audit trail completeness score, and a stress test pass rate above 99.5%, the conversation shifts from "is this system safe?" to "how do we maintain this level of control?" That's the posture that earns trust.&lt;/p&gt;

&lt;p&gt;Cost signals matter too. Agentic MRM isn't free, but the cost of not doing it is far higher. Track the cost of manual audit preparation, regulatory inquiries, and incident remediation before and after implementing continuous validation and real-time monitoring. We've seen organizations cut audit preparation time by 60% and reduce the number of high-severity risk events by half within the first year. Those savings fund the investment in better tooling and governance.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://omnithium.ai/blog/ai-agent-cost-attribution.html" rel="noopener noreferrer"&gt;AI Agent Cost Attribution: Tracking LLM Spend by Team and Project&lt;/a&gt; post shows how to tie risk management costs to specific agent workloads, so you can make the business case for ongoing investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to build next
&lt;/h2&gt;

&lt;p&gt;The regulatory landscape for agentic AI is still forming, but the direction is clear: authorities expect you to demonstrate continuous control over autonomous systems, not just point-in-time compliance. The teams that will thrive are those that embed risk management into the agentic operating model from day one, rather than bolting it on after a production incident.&lt;/p&gt;

&lt;p&gt;Your next move is to build a unified control plane that integrates agentic MRM with your existing enterprise risk framework. This isn't about replacing your GRC tool; it's about extending it to handle the unique characteristics of agentic systems. That means instrumenting every agent with decision-chain tracing, feeding those traces into a real-time monitoring pipeline, and connecting that pipeline to your incident management and audit workflows. Our &lt;a href="https://omnithium.ai/blog/enterprise-ai-agents-unified-control-plane.html" rel="noopener noreferrer"&gt;Beyond Orchestration: Why Enterprise AI Agents Need a Unified Control Plane&lt;/a&gt; post lays out the architectural principles.&lt;/p&gt;

&lt;p&gt;You'll also need to evolve your governance structures. Create a dedicated agentic risk working group that includes model risk management, security, compliance, and the business unit deploying the agent. This group should own the risk appetite statement for agentic autonomy, review anomaly reports weekly, and authorize any expansion of the agent's action space. The &lt;a href="https://omnithium.ai/blog/cto-blueprint-governing-multi-agent-ai.html" rel="noopener noreferrer"&gt;The CTO’s Blueprint for Governing Multi-Agent AI Systems in the Enterprise&lt;/a&gt; provides a governance model that scales across dozens of agents.&lt;/p&gt;

&lt;p&gt;Stress testing must become a continuous practice, not a pre-deployment checkbox. Build a library of adversarial scenarios—prompt injections, goal manipulation attempts, edge-case action sequences—and run them against every agent update. When an agent fails a test, the update is blocked until the risk working group signs off. This is how you prevent unbounded autonomy and adversarial manipulation from reaching production.&lt;/p&gt;

&lt;p&gt;Finally, invest in the people and processes that make the technology work. Train your model validators on agentic AI concepts—goal-conditioned behavior, emergent properties, decision-chain analysis. Update your model risk policy to explicitly address agentic systems, defining roles, responsibilities, and escalation paths. And start documenting your risk assessments now, even for agents that aren't yet high-risk, so that when the regulatory hammer drops, you're not scrambling.&lt;/p&gt;

&lt;p&gt;Agentic AI isn't inherently riskier than traditional models. But it is different, and those differences demand a new MRM paradigm. The teams that recognize this now—and build the architecture, metrics, and governance to match—won't just satisfy auditors. They'll unlock the full value of autonomous systems without losing control. That's the operating model you need to build next.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://omnithium.ai/blog/agentic-ai-model-risk-management-regulatory" rel="noopener noreferrer"&gt;Omnithium Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>modelrisk</category>
      <category>regulatorycompliance</category>
      <category>aigovernance</category>
      <category>modelvalidation</category>
    </item>
    <item>
      <title>CTV Fraud Has an IPv6 Business Problem</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sat, 30 May 2026 16:01:32 +0000</pubDate>
      <link>https://dev.to/aleksuix/ctv-fraud-has-an-ipv6-business-problem-3a1c</link>
      <guid>https://dev.to/aleksuix/ctv-fraud-has-an-ipv6-business-problem-3a1c</guid>
      <description>&lt;p&gt;Most discussions about CTV fraud start with threat actors, fake apps, or suspicious traffic spikes.&lt;/p&gt;

&lt;p&gt;That is useful, but it misses a more expensive problem: bad fraud decisions.&lt;/p&gt;

&lt;p&gt;If your fraud stack still treats one IP address as a durable identity, IPv6 is already making those decisions worse. That creates two kinds of cost at the same time. Fraud slips through when rotating addresses look new, and legitimate traffic gets penalized when broad network blocks catch more than they should.&lt;/p&gt;

&lt;p&gt;That is not just a security issue. It is a business problem for the whole ad ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  A small watchlist, a useful lesson
&lt;/h2&gt;

&lt;p&gt;Pixalate's May 2026 AdFraud IOC-DB workbook for IPv6 addresses is a good example of the problem.&lt;/p&gt;

&lt;p&gt;The workbook is small. It contains 25 populated high-risk indicators, not a market census. But the mix is still useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;21 entries are tagged &lt;code&gt;displayImpressionFraud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;2 are tagged &lt;code&gt;IABcrawler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;1 is tagged &lt;code&gt;appSpoofing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;1 is tagged &lt;code&gt;deviceIdStuffing&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider distribution is what makes the dataset interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;11 entries are associated with Spectrum&lt;/li&gt;
&lt;li&gt;3 with Verizon Fios&lt;/li&gt;
&lt;li&gt;3 with T-Mobile USA&lt;/li&gt;
&lt;li&gt;2 with Comcast Cable&lt;/li&gt;
&lt;li&gt;1 each with AT&amp;amp;T Internet, Comcast Business, Play, AT&amp;amp;T Wireless, Hetzner Online, and Starlink&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not mean those providers are fraud networks. It means suspicious ad activity can show up across residential broadband, mobile access, satellite access, and data-center infrastructure.&lt;/p&gt;

&lt;p&gt;The repeated prefixes matter even more. Four of the listed addresses sit inside the same Spectrum /64. Nine sit inside the same Spectrum /32. Two Verizon Fios addresses share a /64. Two Comcast Cable addresses share a /64.&lt;/p&gt;

&lt;p&gt;That is the operational lesson.&lt;/p&gt;

&lt;p&gt;The single address can change. The surrounding network context can still repeat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why IPv6 changes the economics
&lt;/h2&gt;

&lt;p&gt;IPv6 was built to make long-term address correlation harder.&lt;/p&gt;

&lt;p&gt;RFC 8981 describes temporary IPv6 addresses that rotate randomized interface identifiers over time. That is a privacy improvement. It reduces the value of using one full address to track the same host across many sessions.&lt;/p&gt;

&lt;p&gt;That is good network design. It is bad news for simplistic fraud models.&lt;/p&gt;

&lt;p&gt;If a system still assumes one address equals one stable endpoint, it will make two predictable mistakes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It will miss abuse when suspicious actors rotate through new /128s.&lt;/li&gt;
&lt;li&gt;It will overblock when one suspicious /128 gets expanded to a much broader prefix without enough evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both errors are expensive.&lt;/p&gt;

&lt;p&gt;One leaks money to bad traffic. The other blocks revenue from good traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The false positive problem is bigger than most teams admit
&lt;/h2&gt;

&lt;p&gt;In ad tech, false negatives get the attention because they look like fraud losses.&lt;/p&gt;

&lt;p&gt;False positives are quieter. They look like lower match rates, lower fill, lower bid density, underdelivery, or weaker reach. That makes them easier to misdiagnose.&lt;/p&gt;

&lt;p&gt;If a buyer, platform, or verification layer decides that a broad IPv6 prefix is bad because one address in that space was flagged, the blast radius can be large.&lt;/p&gt;

&lt;p&gt;For publishers, that can mean rejecting legitimate demand or discounting inventory quality for users who are not actually fraudulent.&lt;/p&gt;

&lt;p&gt;For SSPs and exchanges, it can mean pushing overly broad risk labels downstream, which changes auction behavior without proving the underlying case.&lt;/p&gt;

&lt;p&gt;For DSPs, it can mean excluding reachable households from a campaign, weakening delivery and frequency goals while making optimization look worse than it should.&lt;/p&gt;

&lt;p&gt;For agencies and brands, it can mean paying for expensive fraud controls that suppress real audience access.&lt;/p&gt;

&lt;p&gt;This is where IPv6 becomes a business issue instead of a pure detection issue.&lt;/p&gt;

&lt;p&gt;When identity assumptions get weaker, the cost of blunt enforcement gets higher.&lt;/p&gt;




&lt;h2&gt;
  
  
  CTV makes those errors harder to unwind
&lt;/h2&gt;

&lt;p&gt;CTV already has fragmented observability.&lt;/p&gt;

&lt;p&gt;The IP visible to a content request is not always the same IP seen by the ad server, the SSAI stitcher, the player, or the downstream reporting system. By the time the logs disagree, the impression is gone.&lt;/p&gt;

&lt;p&gt;That matters because network signals are often treated as if they are closer to ground truth than they really are.&lt;/p&gt;

&lt;p&gt;A suspicious IPv6 indicator can tell you something useful about origin, recurrence, or likely abuse. It does not tell you, on its own, whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the inventory description was false&lt;/li&gt;
&lt;li&gt;the app was spoofed&lt;/li&gt;
&lt;li&gt;the supply path was misrepresented&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://vastlint.org/docs/validate-vast-xml/" rel="noopener noreferrer"&gt;VAST was runnable&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the ad rendered successfully on the device that mattered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means a weak IP decision can ripple across multiple business systems.&lt;/p&gt;

&lt;p&gt;It can affect eligibility, scoring, pacing, billing, discrepancy reviews, partner escalations, and renewal conversations.&lt;/p&gt;

&lt;p&gt;In practice, many of those downstream failures show up as execution problems that have nothing to do with IP identity by themselves: too many redirects in a &lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-depth/" rel="noopener noreferrer"&gt;wrapper chain&lt;/a&gt;, insecure &lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-https/" rel="noopener noreferrer"&gt;HTTP media URLs on HTTPS inventory&lt;/a&gt;, or a tag that passes a quick glance but fails in a real &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;live-tag test flow&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the ecosystem feels the damage
&lt;/h2&gt;

&lt;p&gt;The biggest implication of IPv6 in fraud detection is not technical complexity by itself. It is decision quality across the market.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishers
&lt;/h3&gt;

&lt;p&gt;Publishers care about fill, yield, and trust.&lt;/p&gt;

&lt;p&gt;If network-based controls are too aggressive, good traffic can get downgraded or blocked. If the controls are too weak, invalid traffic still makes it into sold inventory. Either way, the publisher absorbs the economic damage first.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSPs and exchanges
&lt;/h3&gt;

&lt;p&gt;SSPs and exchanges sit in the middle of the trust chain.&lt;/p&gt;

&lt;p&gt;If they pass along weak identity assumptions as if they were strong fraud signals, they distort auction quality and partner scoring. If they do not cluster recurring signals above the single address level, they also miss repeat patterns that should trigger closer review.&lt;/p&gt;

&lt;h3&gt;
  
  
  DSPs and buyers
&lt;/h3&gt;

&lt;p&gt;DSPs need accurate suppression, not maximum suppression.&lt;/p&gt;

&lt;p&gt;Overblocking broad IPv6 space can quietly reduce addressable reach and campaign efficiency. Underblocking lets suspicious activity continue long enough to waste budget and pollute performance models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification and fraud vendors
&lt;/h3&gt;

&lt;p&gt;Vendors that still lean too heavily on single-address reputation will face the hardest tradeoff. Their models can look decisive while being economically blunt.&lt;/p&gt;

&lt;p&gt;The market increasingly needs cluster logic, recurrence logic, and stronger correlation across network, app, device, and execution signals.&lt;/p&gt;




&lt;h2&gt;
  
  
  What better operations look like
&lt;/h2&gt;

&lt;p&gt;The answer is not to throw away IP intelligence.&lt;/p&gt;

&lt;p&gt;The answer is to use it more carefully.&lt;/p&gt;

&lt;p&gt;An IPv6 IOC feed is most useful as an escalation surface, not a standalone verdict engine.&lt;/p&gt;

&lt;p&gt;That means a few practical shifts:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Treat the /128 as a lead
&lt;/h3&gt;

&lt;p&gt;Keep the exact address. It still matters.&lt;/p&gt;

&lt;p&gt;But do not stop there. Enrich it with bundle ID, app ID, supply path, user agent data, session timing, creative identifiers, SSAI markers, and execution outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cluster above the single address
&lt;/h3&gt;

&lt;p&gt;Review /64, /48, /32, ASN, ISP, and time-window recurrence together.&lt;/p&gt;

&lt;p&gt;That is where repeated behavior becomes visible without pretending the full address is a stable identity token.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separate ranking from enforcement
&lt;/h3&gt;

&lt;p&gt;A network signal can justify lower trust, tighter review, or increased measurement before it justifies a hard block.&lt;/p&gt;

&lt;p&gt;This is especially important in consumer broadband and mobile access space, where the collateral damage of overbroad enforcement can be substantial.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Connect detection to business outcomes
&lt;/h3&gt;

&lt;p&gt;For any suspect impression, teams should be able to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request context&lt;/li&gt;
&lt;li&gt;network context&lt;/li&gt;
&lt;li&gt;winning creative&lt;/li&gt;
&lt;li&gt;final VAST or stitched instruction&lt;/li&gt;
&lt;li&gt;execution result&lt;/li&gt;
&lt;li&gt;billing consequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those records do not join cleanly, the organization is not really evaluating behavior. It is comparing disconnected logs and making partial decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you want to go deeper into the VAST side of the problem
&lt;/h2&gt;

&lt;p&gt;The network side is only part of the story. If you want to connect business outcomes back to execution quality, these are the most useful vastlint.org pages to start with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/validate/" rel="noopener noreferrer"&gt;VAST Tag Validator&lt;/a&gt; for raw XML validation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST Tag Tester&lt;/a&gt; for live tag QA, creative preview, and click tracking&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST Inspector&lt;/a&gt; for hop-by-hop wrapper debugging&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/validate-vast-xml/" rel="noopener noreferrer"&gt;How to validate VAST XML&lt;/a&gt; for the practical decision tree between validator, tester, and inspector&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/vast-versions/" rel="noopener noreferrer"&gt;VAST versions guide&lt;/a&gt; for version drift, VPAID removal, and CTV addendum context&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/guides/iab-vast-validator/" rel="noopener noreferrer"&gt;IAB VAST validator guide&lt;/a&gt; for where pure spec compliance stops and platform behavior starts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-depth/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-depth&lt;/a&gt; for one of the most common delivery blockers in wrapped tags&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-https/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-https&lt;/a&gt; for the portability and CTV playback risk of insecure media URLs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/methodology/" rel="noopener noreferrer"&gt;Rule derivation methodology&lt;/a&gt; if you want to see how the validation rules are grounded in specs and standards&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The bigger market implication
&lt;/h2&gt;

&lt;p&gt;The ad ecosystem has spent years building better fraud controls around identifiers that were never as stable as people wanted them to be.&lt;/p&gt;

&lt;p&gt;IPv6 makes that harder to ignore.&lt;/p&gt;

&lt;p&gt;It forces a more honest model of what a network signal is.&lt;/p&gt;

&lt;p&gt;It is evidence.&lt;/p&gt;

&lt;p&gt;It is context.&lt;/p&gt;

&lt;p&gt;It is sometimes a strong clue.&lt;/p&gt;

&lt;p&gt;It is not identity by default.&lt;/p&gt;

&lt;p&gt;That matters because the market does not only pay for fraud that gets through. It also pays for misclassification, suppressed reach, broken partner trust, and slow dispute cycles caused by bad assumptions.&lt;/p&gt;

&lt;p&gt;So the real business question is no longer just, "Can this IP be flagged?"&lt;/p&gt;

&lt;p&gt;It is, "What happens to revenue, delivery, trust, and reconciliation when we act on that signal?"&lt;/p&gt;

&lt;p&gt;That is the right question for CTV.&lt;/p&gt;

&lt;p&gt;And increasingly, it is the right question for the broader ad ecosystem too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pixalate, AdFraud IOC-DB - IPv6 Addresses, May 2026 workbook reviewed from the IOC database export&lt;/li&gt;
&lt;li&gt;RFC 8981, Temporary Address Extensions for Stateless Address Autoconfiguration in IPv6, &lt;a href="https://www.rfc-editor.org/rfc/rfc8981" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc8981&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source note
&lt;/h2&gt;

&lt;p&gt;The IPv6 IOC workbook reflects Pixalate's published watchlist data and disclaimer language for internal operational use. It is useful as an indicator set, not as a standalone market estimate or a definitive claim about any ISP, subscriber, or platform.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>ctv</category>
      <category>fraud</category>
    </item>
    <item>
      <title>The great AI enshittification</title>
      <dc:creator>Frank A</dc:creator>
      <pubDate>Sat, 30 May 2026 16:01:28 +0000</pubDate>
      <link>https://dev.to/frank_a_64393c6f49a90e428/the-great-ai-enshittification-45di</link>
      <guid>https://dev.to/frank_a_64393c6f49a90e428/the-great-ai-enshittification-45di</guid>
      <description>&lt;p&gt;Would you trust a "Vibe taxi driver", or a "vibe dentist"? Somehow the industry trusts "vibe coders" or "AI coders" but as expected quality is down the drain.  &lt;/p&gt;

&lt;p&gt;Regardless of if you like it or not, the AI enshittification has begun. Now AI is making even &lt;a href="https://news.ycombinator.com/item?id=39427821" rel="noopener noreferrer"&gt;healthcare decisions&lt;/a&gt;, an unregulated market. Not just physical health, &lt;a href="https://www.politico.com/news/2024/02/18/artificial-intelligence-health-care-fda-00141768" rel="noopener noreferrer"&gt;mental health &lt;/a&gt; too. The self-driving car you take, may  &lt;a href="https://www.reddit.com/r/waymo/comments/1mmdyto/waymo_almost_killed_us_now_what/" rel="noopener noreferrer"&gt;almost kill you&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Is there a place for quality products and services these days?&lt;/p&gt;

&lt;p&gt;Python was all the rage a few years back, and still highly popular. Though the methods of learning have adapted. In the past blogs were popular, combined with stackoverflow and fiddling. &lt;/p&gt;

&lt;p&gt;These days AI made a big impact, stackoverflow is down, and people use AI way more than fiddling. So there is a lot of "vibe coding", where code is generated, but quality is down the drain.&lt;/p&gt;

&lt;p&gt;Apps &lt;a href="https://haveibeenpwned.com/PwnedWebsites" rel="noopener noreferrer"&gt;full of security holes&lt;/a&gt;. Mind you that link is for large companies, you can imagine how many security holes exist in vibe coded apps: IDOR, credentials hard coded, no row-level security causing attackers to dump all your personal data, SQL injection, RCE caused by no upload filter. Indeed, there is more software than ever, but not software you want to trust your personal data with.  &lt;/p&gt;

&lt;p&gt;If you want to learn dev skills the old fashioned way, that's still possible. To practice Python there are many platforms like &lt;a href="https://pychallenge.com" rel="noopener noreferrer"&gt;PyChallenge&lt;/a&gt;, &lt;a href="https://leetcode.com" rel="noopener noreferrer"&gt;leetcode&lt;/a&gt; and others. And of course you can build personal projects without AI. &lt;/p&gt;

&lt;p&gt;But if there's still a place for quality products and services in the modern world? Only time will tell. There used to be "buy it for life" products, these days.. the products may last only one year. &lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Veltrix Treasure Hunt Engine: Why Our First Rewrite Cost Us 3.2 Million Requests Per Second</title>
      <dc:creator>Lillian Dube</dc:creator>
      <pubDate>Sat, 30 May 2026 16:01:13 +0000</pubDate>
      <link>https://dev.to/dev-architecture-blog/the-veltrix-treasure-hunt-engine-why-our-first-rewrite-cost-us-32-million-requests-per-second-4bdf</link>
      <guid>https://dev.to/dev-architecture-blog/the-veltrix-treasure-hunt-engine-why-our-first-rewrite-cost-us-32-million-requests-per-second-4bdf</guid>
      <description>&lt;h2&gt;
  
  
  The Problem We Were Actually Solving
&lt;/h2&gt;

&lt;p&gt;The product goal was simple: every player who walks into a building on the map should see the same treasure list within 300 ms. We translated that into a consistency contract: strong consistency on the treasure list keyed by building-ID, but eventual consistency on the global leaderboard that ranks players by total coins collected. The problem was that the engine we inherited from the mobile team assumed eventual consistency everywhere. Their Redis Cluster v6.2.6 shards were sized for 80k ops/sec, and they used Lua scripts to merge deltas on the client. When the Royale drop pushed 1.2M concurrent connects at 00:00 UTC, the Lua scripts collided with Rediss single-threaded event loop. We saw 47k script-timeouts per minute and a P99 tail latency of 4.2 seconds on the treasure-list endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Tried First (And Why It Failed)
&lt;/h2&gt;

&lt;p&gt;Our first rollout kept the Lua merges but moved the treasure lists to a Go service backed by a single PostgreSQL 14 cluster with pgbouncer 1.17.0 connection pooling. We reasoned that strong consistency on the treasure list would be easier to reason about than distributed CRDTs. The migration script ran at 20:00 UTC the night before the drop. Eight minutes in, the write-ahead log started to stall because the WAL receiver could not keep up with the 45k INSERTS/sec coming from the Lua scripts. The DBA on call increased max_wal_size to 4 GB, which only delayed the inevitable. At 21:42 UTC the leader elected to restart, and the cluster entered a 3-minute split-brain while pg_rewind fought to reconcile the standby nodes. When the service came back, the Lua scripts had already enqueued 1.9 million backlogged treasure events. The Go service fell over trying to replay them through logical decoding, and we hit an OOM at 32 GB RSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Decision
&lt;/h2&gt;

&lt;p&gt;We ripped out the Lua scripts and replaced the treasure list store with a partitioned RocksDB 8.7.0 tier that we called the Cellar. Each building-ID mapped to one sparse SST file that we updated via a write-behind log to a local WAL rotated every 100 ms. The Cellar sharded 64-way across NVMe volumes, giving us 320k ops/sec per node at &amp;lt;2 ms P95. We fronted the Cellar with a single envoy 1.26.0 proxy that implemented a consistent-hash policy on building-ID. Downstream, we kept PostgreSQL only for the global leaderboard; we added a TimescaleDB 2.12.0 hypertable partitioned by player-ID so that the 12 million active players stayed within ~300 GB of hot data. The Timescale instance ran on AWS RDS i3.8xlarge with 2 TB gp3 disks and a 30k IOPS burst credit.&lt;/p&gt;

&lt;p&gt;The global winner-notification fanout was the first place we accepted eventual consistency. We switched from WebSockets to NATS 2.9.21 jetstream with a 5-minute deduplication window. Each player subscribed to exactly one jetstream subject: user.. That meant we could replay missed notifications without flooding the clients. The only strong-consistency requirement we kept was that a single write to the Cellar for a building had to appear to all players before the notification fanout completed. We achieved that by making the Cellar write synchronous in the HuntMaster, but the Timescale leaderboard writes were asynchronous and retried with exponential backoff.&lt;/p&gt;

&lt;p&gt;We also introduced a local cache layer with Dragonfly 1.8.1 acting as a L1 shard for each envoy instance. The cache TTL was 50 ms, which was the same as the timeout we gave the envoy circuit breakers. We tuned the hop-by-hop retry budget to 3 attempts before failing the request to the client, which capped our tail latency at 220 ms P99 even when the Cellar was under 230k concurrent reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Numbers Said After
&lt;/h2&gt;

&lt;p&gt;The Winter Royale drop went live at 00:00 UTC on 15 December 2025. In the first hour we ingested 2.9 billion treasure updates. Our scrape job on the HuntMaster showed a steady 3.2M requests/sec on the write path with no flapping. The Cellar nodes reported 78 k ops/sec per shard at 2.1 ms P95 latency. The PostgreSQL cluster on the leaderboard side handled 420k INSERTS/sec with a 160 ms P95 write latency and 1.2 seconds P99. NATS jetstream delivered 1.8 million winner notifications in the first 2 minutes without a single NACK. The client error rate stayed below 0.04 % across all regions.&lt;/p&gt;

&lt;p&gt;The cost side was brutal: the Cellar nodes alone ran 64 r6i.2xlarge instances, each costing $1.092 per hour, or ~$1,500/day. The NATS jetstream cluster added another $840/day for 9 m5.2xlarge brokers with 5 TB gp3 storage each. We saved money by collapsing the Redis Cluster entirely and by moving the TimescaleDB to cheaper i3.2xlarge spot instances at $0.24/hour, reducing the leaderboard bill from $3.1k/day to $1.2k/day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;I would not have tried to migrate the treasure-list store while&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
      <category>systems</category>
    </item>
    <item>
      <title>I Made My AI Models Argue, Then Let Hermes Be the Judge</title>
      <dc:creator>Arqam Waheed</dc:creator>
      <pubDate>Sat, 30 May 2026 16:00:54 +0000</pubDate>
      <link>https://dev.to/arqamwd/i-made-my-ai-models-argue-then-let-hermes-be-the-judge-5e6c</link>
      <guid>https://dev.to/arqamwd/i-made-my-ai-models-argue-then-let-hermes-be-the-judge-5e6c</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hermes-agent-2026-05-15"&gt;Hermes Agent Challenge&lt;/a&gt;: Build With Hermes Agent&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Ask any judgment call and three different AI models argue it out, then Hermes hands down one verdict, a confidence score, and exactly why they split. Every verdict, dissent, and mind-changed-in-debate is written into Hermes' own memory, so the next question re-weights the jurors before they ever vote. The judging is a pure function over that memory: no memory, no weights, no verdict. Three models, one verdict, $0.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;An LLM once talked me into the wrong database with total confidence. One smooth, authoritative answer. I shipped it. It cost me a weekend and a migration I'm still not over.&lt;/p&gt;

&lt;p&gt;The villain here is &lt;strong&gt;single-model overconfidence&lt;/strong&gt;: you get one polished reply, and the disagreement that should have warned you is invisible. You never see the other opinions, because you only asked one model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So I stopped trusting one model. I convened a jury.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Council takes any judgment call ("Postgres or Mongo?", "is this PR safe to merge?", "is this clause risky?") and asks &lt;strong&gt;three different models&lt;/strong&gt;, lets them disagree, then has Hermes deliver one verdict, a confidence score, and exactly &lt;em&gt;why&lt;/em&gt; they split. Three models, one verdict, $0.&lt;/p&gt;

&lt;p&gt;You ask a question. Council fans it out to three jurors (two free OpenRouter models from different families and one local model via Ollama), each takes a position with reasons. Then, if they disagree, a &lt;strong&gt;second deliberation round&lt;/strong&gt; runs: each juror sees the others' answers and either holds or changes its mind, so the council &lt;em&gt;debates&lt;/em&gt; instead of just voting once. Hermes then judges the deliberated opinions: a single verdict, a &lt;strong&gt;confidence score&lt;/strong&gt; (high when they agree, low when they split 2-1), and a "why they disagreed" panel. Every verdict is remembered, a &lt;code&gt;council&lt;/code&gt; skill learns which juror to trust for which kind of question, and the agent can even &lt;strong&gt;propose its own&lt;/strong&gt; trust adjustments for you to approve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3c86rvf61fkb58mr2d7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3c86rvf61fkb58mr2d7.png" alt="The Council home screen: one input box, a model-agnostic jury behind it" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The whole product is one question box. Everything interesting happens behind it, and the rest of this post is mostly pictures of that "behind."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/tREMaJuJGH4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/ArqamWaheed/council" rel="noopener noreferrer"&gt;https://github.com/ArqamWaheed/council&lt;/a&gt; &lt;br&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://council-jet-kappa.vercel.app/" rel="noopener noreferrer"&gt;https://council-jet-kappa.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try "Should a 3-person startup use microservices?" and open the dissent panel.&lt;/p&gt;

&lt;p&gt;Local, one command (runs at $0 in offline mock mode, no key needed):&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/ArqamWaheed/council &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;council &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./setup_hermes.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture, in pictures
&lt;/h2&gt;

&lt;p&gt;I think the design is easiest to &lt;em&gt;see&lt;/em&gt;, so here's the system as a sequence of images. Each caption is the explanation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uzdgrtr7rlnb7iy8gbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uzdgrtr7rlnb7iy8gbd.png" alt="Convene flow: the browser/CLI sends one question to run_council.py, which calls hermes_run.py three times in parallel, two arrows to OpenRouter (hosted models) and one to Ollama (local model), then a fourth Hermes call to the foreman that returns a single verdict" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The core loop. One question, three independent Hermes subagents (2 hosted + 1 local) fanned out in parallel, then a fourth Hermes run (the foreman) synthesizes one verdict. Every arrow is the same &lt;code&gt;hermes -z&lt;/code&gt; interface; nothing talks to a model directly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjv22kevlk6ojqgm3ms07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjv22kevlk6ojqgm3ms07.png" alt="Model-agnostic jury: a single hermes -z interface in the middle, with three model cards plugged into it, openai/gpt-oss-120b:free and z-ai/glm-4.5-air:free via the openrouter provider, and qwen2.5 via the ollama-local provider running on-device" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The bet. A hosted model and an on-device model sit on the same jury, swapped with a single &lt;code&gt;--provider/--model&lt;/code&gt; flag, no code change. This model-agnosticism is the one Hermes property the whole project is built on.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvs0pa1hoz1ol8g41q3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvs0pa1hoz1ol8g41q3f.png" alt="Verdict card: a confidence dial reading 67%, three colour-coded juror chips (two green agreeing, one amber dissenting), a one-line verdict, and a collapsed " width="800" height="1595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The UX surface. Confidence is high when jurors agree and drops on a 2-1 split. The dissent panel is collapsed by default, and you expand it exactly when the confidence number makes you nervous.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;The actual product. A confident single answer hides this; Council makes the disagreement the headline. Getting the clustering right here was subtle (see "What I learned" below).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc0blkp3zovuds4epww2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc0blkp3zovuds4epww2.png" alt="Deliberation round: each juror card shows a " width="800" height="1077"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The headline feature: a council that **deliberates, not just votes&lt;/em&gt;&lt;em&gt;. After round 1, disagreeing jurors get a second Hermes pass where they read each other's arguments and may hold or change their vote. A "⇄ changed" badge marks the ones that moved, and the confidence dial actually climbs when a 2-1 split is talked into agreement.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;The agentic learning loop, human-in-the-loop. Hermes proposes; you approve or dismiss. Approved rules persist client-side and ride along with the next convene call.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxozezam917mjgn1wcc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxozezam917mjgn1wcc1.png" alt="Memory recall: a terminal running  raw `hermes -z " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Persistence the judge can verify. Verdicts are mirrored into Hermes' own memory, so recall is Hermes doing the work; proof lives in &lt;code&gt;docs/hermes-proof/04-memory-recall.txt&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/ArqamWaheed/council" rel="noopener noreferrer"&gt;https://github.com/ArqamWaheed/council&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interesting files:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hermes_run.py&lt;/code&gt; (the Hermes CLI driver every juror/judge call goes through)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_council.py&lt;/code&gt; (orchestration + the deterministic judge + Hermes foreman + the &lt;code&gt;--reflect&lt;/code&gt; loop)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skills/council/SKILL.md&lt;/code&gt; (the juror-weighting brain Hermes edits)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server.py&lt;/code&gt; (the &lt;code&gt;/api/reflect&lt;/code&gt; + &lt;code&gt;/api/learn&lt;/code&gt; endpoints) &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt; (the designed verdict UI with the foreman TTS readout and localStorage persistence). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proof that Hermes is genuinely in the loop (subagent transcripts, skill diff, memory recall) is in &lt;a href="https://github.com/ArqamWaheed/council/tree/main/docs/hermes-proof" rel="noopener noreferrer"&gt;&lt;code&gt;docs/hermes-proof/&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hermes_run.py: every juror/judge call is a real Hermes run
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&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="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--provider&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&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;skills&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-z&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# -z = one-shot, final answer on stdout
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&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;text&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;

&lt;span class="c1"&gt;# jurors.py: fan out one Hermes subagent per juror, in parallel
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;roster&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;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;opinions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ask_juror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;roster&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How I Used Hermes Agent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why Hermes at all: the model-agnostic core.&lt;/strong&gt; Hermes lets you point at any provider and swap with a flag, no code change. Council is built &lt;em&gt;on top of that one property&lt;/em&gt;: the jurors are different models, and Hermes is the only piece that makes "different models" cheap. The clearest proof is the third juror: it runs &lt;strong&gt;locally&lt;/strong&gt; via Ollama while the other two are &lt;strong&gt;hosted&lt;/strong&gt; on OpenRouter, and all three answer through the exact same &lt;code&gt;hermes -z&lt;/code&gt; interface (the model-agnostic diagram above). A hosted model and an on-device model, sitting on the same jury, no code change: that's model-agnosticism you can see. I genuinely didn't see another entry in this challenge exploit it; everyone picked one model and moved on. That's the whole bet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subagents: one real Hermes run per juror.&lt;/strong&gt; Each juror is a genuine, isolated Hermes invocation on a &lt;em&gt;different&lt;/em&gt; provider+model (&lt;code&gt;hermes -z --provider openrouter --model …&lt;/code&gt; for the two hosted jurors, &lt;code&gt;--provider ollama-local …&lt;/code&gt; for the on-device one), fanned out &lt;strong&gt;in parallel&lt;/strong&gt; so no model's reasoning anchors another's (the convene-flow diagram above). Hermes does the inference; my Python (&lt;code&gt;jurors.py&lt;/code&gt; to &lt;code&gt;hermes_run.py&lt;/code&gt;) is just the fan-out plumbing, and every juror in the output JSON is tagged &lt;code&gt;"via": "hermes"&lt;/code&gt;. The gotcha worth flagging: Hermes enforces a &lt;strong&gt;64K-context floor&lt;/strong&gt;, which for the local model meant setting both &lt;code&gt;ollama_num_ctx&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; a named &lt;code&gt;custom_providers&lt;/code&gt; entry; without the named provider, &lt;code&gt;--provider ollama&lt;/code&gt; silently routed to the wrong base URL. &lt;code&gt;setup_hermes.sh&lt;/code&gt; encodes the working config so a judge can reproduce it in one command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A true debate, not just a vote (round 2 is real Hermes work).&lt;/strong&gt; This is the feature I'm proudest of. After round 1, if the jurors disagree, each one gets a &lt;em&gt;second&lt;/em&gt; Hermes run that shows it the others' positions and lead reasons and asks it to hold or change its mind. Real jurors reconsider through the same &lt;code&gt;hermes -z&lt;/code&gt; path as round 1, so the debate is genuine extra agentic work, not a UI flourish; mock jurors reconsider deterministically so the offline demo stays reproducible. The judge then synthesizes the verdict from the &lt;strong&gt;deliberated&lt;/strong&gt; opinions, so a juror that's talked round actually moves the outcome (the deliberation diagram above). It's gated on disagreement (a unanimous round 1 skips it) and toggled with &lt;code&gt;COUNCIL_DEBATE=0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a skill, not a prompt, for judging.&lt;/strong&gt; The foreman's verdict is itself a Hermes run (&lt;code&gt;hermes -z --skills council&lt;/code&gt;) grounded in &lt;code&gt;skills/council/SKILL.md&lt;/code&gt;, which is &lt;strong&gt;installed into Hermes&lt;/strong&gt; (&lt;code&gt;hermes skills list&lt;/code&gt; shows it). The weighting logic lives in a machine-readable &lt;code&gt;weights&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdakiorz2ez87ajz7eqqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdakiorz2ez87ajz7eqqj.png" alt="The SKILL.md weights block: a small machine-readable table mapping (juror, topic) to multiplier, with a one-line comment that the foreman reads before synthesizing" width="799" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The judging brain is data, not a buried prompt. &lt;code&gt;--learn&lt;/code&gt; and &lt;code&gt;--reflect&lt;/code&gt; both edit this block, and the installed Hermes copy is kept in sync.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After a string of security questions, &lt;code&gt;--learn&lt;/code&gt; appended a rule to upweight the local model on that topic (&lt;em&gt;and synced the installed Hermes copy&lt;/em&gt;) because it had caught issues the hosted models missed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python run_council.py &lt;span class="nt"&gt;--learn&lt;/span&gt; &lt;span class="s2"&gt;"Local Juror | security | 1.5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the next security question that juror's vote counts 1.5×, read straight back by the judge. Counterfactual: a static synthesis prompt can't get better; this does. (The before/after skill diff is in &lt;a href="https://github.com/ArqamWaheed/council/blob/main/docs/hermes-proof/03-skill-learning.txt" rel="noopener noreferrer"&gt;&lt;code&gt;docs/hermes-proof/03-skill-learning.txt&lt;/code&gt;&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Letting the agent propose its own learning, now on the web and grounded in evidence.&lt;/strong&gt; &lt;code&gt;python run_council.py --reflect&lt;/code&gt; (and the &lt;strong&gt;"Should the council reweight itself?"&lt;/strong&gt; button in the UI) hands Hermes its &lt;em&gt;own&lt;/em&gt; memory of past verdicts and asks it to propose one weight change, e.g. "the local juror has dissented on three database calls; upweight it." The key fix this round: the proposal is &lt;strong&gt;evidence-grounded&lt;/strong&gt;, since Hermes is fed the actual dissent tally and any rule backed by fewer than two real dissents is rejected, so it can't just parrot the example baked into the skill. You then &lt;strong&gt;Approve or Dismiss&lt;/strong&gt; it (the reflect-flow diagram above). That's the agentic loop done honestly: a single verdict has no ground truth, so the agent surfaces a &lt;em&gt;pattern&lt;/em&gt; and a human confirms it's signal, not overfitting (the exact tension this post closes on). (Offline, it falls back to a deterministic heuristic so it never breaks.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making learning survive a stateless deploy.&lt;/strong&gt; On a hosted demo the filesystem is read-only, so an approved rule can't be written back to &lt;code&gt;SKILL.md&lt;/code&gt;. Council handles this honestly: approved rules are stored in the browser's &lt;strong&gt;localStorage&lt;/strong&gt; and re-sent with every &lt;code&gt;/api/convene&lt;/code&gt; call, where they're merged into the judge's weights for that request. Locally you get a persistent &lt;code&gt;SKILL.md&lt;/code&gt;; on the web you get per-browser persistence, and either way the learning sticks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why memory.&lt;/strong&gt; Each verdict is appended to a log &lt;em&gt;and mirrored into Hermes' own &lt;code&gt;MEMORY.md&lt;/code&gt;&lt;/em&gt;, so I can ask &lt;code&gt;hermes -z "what did the council decide about auth?"&lt;/code&gt; and Hermes recalls it from its memory, not from my code (the memory-recall image above). Proof: &lt;a href="https://github.com/ArqamWaheed/council/blob/main/docs/hermes-proof/04-memory-recall.txt" rel="noopener noreferrer"&gt;&lt;code&gt;docs/hermes-proof/04-memory-recall.txt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The foreman reads the verdict aloud.&lt;/strong&gt; The verdict card has a "the foreman reads the verdict" button (browser SpeechSynthesis, $0); Hermes also ships native TTS via &lt;code&gt;hermes setup tts&lt;/code&gt;. On-theme and memorable: a jury foreman &lt;em&gt;announcing&lt;/em&gt; the decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The build itself was agent-run.&lt;/strong&gt; I kept a &lt;code&gt;memory.md&lt;/code&gt; the coding agent read before each task and updated after (so context stayed cheap), committed every increment with Conventional Commits, and built the verdict UI with the &lt;strong&gt;frontend-design&lt;/strong&gt; skill, which is why the confidence dial and colour-coded juror chips read as &lt;em&gt;designed&lt;/em&gt;, not default-template AI slop. The repo's &lt;code&gt;AGENTS.md&lt;/code&gt; + commit history show the process, not just the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why these models, and the concession.&lt;/strong&gt; Two free OpenRouter models from different families (≥64K context, since Hermes rejects smaller at startup) plus a local Ollama juror. Two honest concessions: (1) free models are slower and three calls add latency (~10-20s/verdict); (2) the free tier is &lt;em&gt;aggressively&lt;/em&gt; rate-limited, so I hit 429s constantly while building, and Council retries and, if a juror still won't answer, falls back (Hermes to direct API to deterministic stand-in) rather than crashing the verdict, which also means the demo runs &lt;strong&gt;fully offline at $0&lt;/strong&gt;. For a once-a-decision tool, I'll take it. Cost: $0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License.&lt;/strong&gt; MIT. Fork it, add your own jurors.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned (and what's next)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The disagreement is the product.&lt;/strong&gt; A 2-1 split is &lt;em&gt;more&lt;/em&gt; useful than a confident single answer, so the clustering that decides "who actually disagreed" has to be right. A small local model once wrote a vague position ("to facilitate efficient integration…") whose &lt;em&gt;reasons&lt;/em&gt; clearly endorsed Postgres; the first version mis-filed it as a dissenter. The fix: when a juror's stated position is ambiguous, fall back to reading its reasons, and ignore options only mentioned in a comparison ("better &lt;em&gt;than&lt;/em&gt; Mongo" isn't a vote for Mongo). Now agreeing jurors cluster together, and the split count is honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounded beats glib.&lt;/strong&gt; Letting the agent propose its own weighting only works if the proposal is tied to real evidence; an ungrounded "reflect" just echoes whatever example is in the skill.&lt;/li&gt;
&lt;li&gt;Hermes' 64K-context floor caught a model that would've quietly underperformed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A council should deliberate, not just vote.&lt;/strong&gt; The round-2 debate above was the turning point: letting jurors read each other and reconsider means a juror that's genuinely persuaded moves the verdict, and you watch the confidence dial climb as a 2-1 split becomes unanimous. A one-shot vote can't do that.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>hermesagentchallenge</category>
      <category>devchallenge</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Road To KiwiEngine #4: The Racecar Driver Analogy</title>
      <dc:creator>Drew Marshall</dc:creator>
      <pubDate>Sat, 30 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/stinklewinks/road-to-kiwiengine-4-the-racecar-driver-analogy-2lj1</link>
      <guid>https://dev.to/stinklewinks/road-to-kiwiengine-4-the-racecar-driver-analogy-2lj1</guid>
      <description>&lt;p&gt;One thing I keep coming back to when thinking about modern software is this:&lt;/p&gt;

&lt;p&gt;A racecar driver shouldn’t need to manufacture every part of the car before racing.&lt;/p&gt;

&lt;p&gt;They should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose reliable components&lt;/li&gt;
&lt;li&gt;assemble systems&lt;/li&gt;
&lt;li&gt;tune performance&lt;/li&gt;
&lt;li&gt;focus on operating effectively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But in software, we often expect businesses to do the opposite.&lt;/p&gt;

&lt;p&gt;Before a company can even begin solving its actual operational problems, it frequently has to piece together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hosting&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;databases&lt;/li&gt;
&lt;li&gt;deployment pipelines&lt;/li&gt;
&lt;li&gt;billing systems&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;infrastructure&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;admin systems&lt;/li&gt;
&lt;li&gt;integrations&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;workflow tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And by the time all of that is assembled, the original business problem sometimes becomes secondary to maintaining the technology stack itself.&lt;/p&gt;

&lt;p&gt;That realization changed how I think about software architecture entirely.&lt;/p&gt;




&lt;h1&gt;
  
  
  Businesses Usually Don’t Want Technology Stacks
&lt;/h1&gt;

&lt;p&gt;Most businesses do not wake up excited about infrastructure assembly.&lt;/p&gt;

&lt;p&gt;They care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serving customers&lt;/li&gt;
&lt;li&gt;operating efficiently&lt;/li&gt;
&lt;li&gt;scaling sustainably&lt;/li&gt;
&lt;li&gt;managing workflows&lt;/li&gt;
&lt;li&gt;improving reliability&lt;/li&gt;
&lt;li&gt;growing revenue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The software is supposed to support the operation.&lt;/p&gt;

&lt;p&gt;But increasingly, modern systems require businesses to become partial infrastructure companies just to function effectively online.&lt;/p&gt;

&lt;p&gt;That’s a huge shift from the earlier web.&lt;/p&gt;




&lt;h1&gt;
  
  
  Modern Software Has Become Operationally Heavy
&lt;/h1&gt;

&lt;p&gt;One thing I’ve noticed is that the complexity of modern software often comes less from the business logic itself and more from the surrounding operational ecosystem.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
launching a modern platform may involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend systems&lt;/li&gt;
&lt;li&gt;backend systems&lt;/li&gt;
&lt;li&gt;cloud infrastructure&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;environment management&lt;/li&gt;
&lt;li&gt;container orchestration&lt;/li&gt;
&lt;li&gt;observability tooling&lt;/li&gt;
&lt;li&gt;CDN layers&lt;/li&gt;
&lt;li&gt;API gateways&lt;/li&gt;
&lt;li&gt;billing providers&lt;/li&gt;
&lt;li&gt;authentication services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All before the business even begins delivering value.&lt;/p&gt;

&lt;p&gt;That operational weight compounds quickly.&lt;/p&gt;




&lt;h1&gt;
  
  
  This Is Part of Why Blueprint Thinking Became Important to Me
&lt;/h1&gt;

&lt;p&gt;The more systems I worked on, the more I became interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable operational systems
instead of:&lt;/li&gt;
&lt;li&gt;endlessly rebuilding implementation details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A restaurant platform shouldn’t need to reinvent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ordering flows&lt;/li&gt;
&lt;li&gt;delivery states&lt;/li&gt;
&lt;li&gt;inventory workflows&lt;/li&gt;
&lt;li&gt;customer notifications&lt;/li&gt;
&lt;li&gt;payment systems&lt;/li&gt;
&lt;li&gt;operational dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A creator platform shouldn’t need to rebuild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memberships&lt;/li&gt;
&lt;li&gt;subscriptions&lt;/li&gt;
&lt;li&gt;storefront systems&lt;/li&gt;
&lt;li&gt;content delivery&lt;/li&gt;
&lt;li&gt;audience workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those operational patterns already exist.&lt;/p&gt;

&lt;p&gt;So the interesting challenge becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we create systems that allow businesses to focus more on operating and less on rebuilding infrastructure repeatedly?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  This Is Where Platforms Become Interesting
&lt;/h1&gt;

&lt;p&gt;I think this is one reason platform ecosystems became so influential historically.&lt;/p&gt;

&lt;p&gt;Platforms reduce operational friction.&lt;/p&gt;

&lt;p&gt;WordPress did this incredibly well for publishing.&lt;/p&gt;

&lt;p&gt;Shopify did this for eCommerce.&lt;/p&gt;

&lt;p&gt;Other ecosystems solved similar operational problems in different industries.&lt;/p&gt;

&lt;p&gt;The common pattern is usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reduce setup friction&lt;/li&gt;
&lt;li&gt;abstract operational complexity&lt;/li&gt;
&lt;li&gt;provide extensibility&lt;/li&gt;
&lt;li&gt;improve accessibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s much bigger than simply “building apps.”&lt;/p&gt;




&lt;h1&gt;
  
  
  But Modern Systems Need More Than Simplicity
&lt;/h1&gt;

&lt;p&gt;At the same time, modern operational systems increasingly require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scalability&lt;/li&gt;
&lt;li&gt;deployment awareness&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;portability&lt;/li&gt;
&lt;li&gt;infrastructure flexibility&lt;/li&gt;
&lt;li&gt;lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now the challenge becomes balancing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simplicity
with&lt;/li&gt;
&lt;li&gt;operational capability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s not easy.&lt;/p&gt;

&lt;p&gt;Especially as systems become larger and more interconnected.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Infrastructure Layer Is Becoming the Real Product
&lt;/h1&gt;

&lt;p&gt;One thing I increasingly believe is that many modern software companies are actually infrastructure companies disguised as application companies.&lt;/p&gt;

&lt;p&gt;Because eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliability matters&lt;/li&gt;
&lt;li&gt;deployment matters&lt;/li&gt;
&lt;li&gt;scaling matters&lt;/li&gt;
&lt;li&gt;integrations matter&lt;/li&gt;
&lt;li&gt;operational workflows matter&lt;/li&gt;
&lt;li&gt;portability matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The operational layer becomes the long-term challenge.&lt;/p&gt;

&lt;p&gt;Not just the UI.&lt;/p&gt;




&lt;h1&gt;
  
  
  This Shift Changed How I Think About WebEngine
&lt;/h1&gt;

&lt;p&gt;A lot of the philosophy behind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebEngine&lt;/li&gt;
&lt;li&gt;KiwiPress&lt;/li&gt;
&lt;li&gt;Citrode&lt;/li&gt;
&lt;li&gt;blueprint systems&lt;/li&gt;
&lt;li&gt;operational runtime architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;comes from thinking deeply about this operational burden.&lt;/p&gt;

&lt;p&gt;I became increasingly interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deployment-aware systems&lt;/li&gt;
&lt;li&gt;infrastructure-aware development&lt;/li&gt;
&lt;li&gt;operational portability&lt;/li&gt;
&lt;li&gt;composable runtime architecture&lt;/li&gt;
&lt;li&gt;reusable business blueprints&lt;/li&gt;
&lt;li&gt;lifecycle-aware systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not because technology itself is the goal.&lt;/p&gt;

&lt;p&gt;But because reducing operational friction matters enormously for businesses.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Makes This More Important, Not Less
&lt;/h1&gt;

&lt;p&gt;Ironically, I think AI increases the importance of operational architecture.&lt;/p&gt;

&lt;p&gt;Because AI can increasingly generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code&lt;/li&gt;
&lt;li&gt;interfaces&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;boilerplate systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;quickly.&lt;/p&gt;

&lt;p&gt;But generated systems still require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;operational boundaries&lt;/li&gt;
&lt;li&gt;infrastructure&lt;/li&gt;
&lt;li&gt;maintainability&lt;/li&gt;
&lt;li&gt;lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise complexity compounds at machine speed.&lt;/p&gt;

&lt;p&gt;That’s one reason I think blueprint systems and operational platforms are becoming increasingly important.&lt;/p&gt;




&lt;h1&gt;
  
  
  I Think the Industry Is Moving Toward Operational Abstraction
&lt;/h1&gt;

&lt;p&gt;One thing I suspect we’ll see more of over time is software moving higher up the abstraction ladder.&lt;/p&gt;

&lt;p&gt;Not just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frameworks&lt;/li&gt;
&lt;li&gt;components&lt;/li&gt;
&lt;li&gt;libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;operational systems&lt;/li&gt;
&lt;li&gt;infrastructure orchestration&lt;/li&gt;
&lt;li&gt;lifecycle-aware platforms&lt;/li&gt;
&lt;li&gt;composable ecosystems&lt;/li&gt;
&lt;li&gt;business blueprints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because businesses ultimately want operational outcomes.&lt;/p&gt;

&lt;p&gt;Not endless infrastructure assembly.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Goal Isn’t Removing Flexibility
&lt;/h1&gt;

&lt;p&gt;This is important:&lt;br&gt;
I don’t think businesses should lose flexibility.&lt;/p&gt;

&lt;p&gt;I think they should gain better operational foundations.&lt;/p&gt;

&lt;p&gt;The ideal system should allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extensibility&lt;/li&gt;
&lt;li&gt;customization&lt;/li&gt;
&lt;li&gt;scalability&lt;/li&gt;
&lt;li&gt;portability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without forcing every company to become infrastructure experts before they can operate effectively.&lt;/p&gt;

&lt;p&gt;That’s a very different architectural philosophy than simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“assemble everything manually.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Software has become incredibly powerful.&lt;/p&gt;

&lt;p&gt;But it has also become operationally heavy.&lt;/p&gt;

&lt;p&gt;And increasingly, I think the biggest challenge isn’t:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we build more technology?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do we reduce operational friction while still enabling powerful systems?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because most businesses don’t actually want to spend their lives assembling racecars.&lt;/p&gt;

&lt;p&gt;They want to race.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>softwareengineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How I Cut Aider's Token Bill 80%: Prompt Caching, MCP Code Mode, and Tier Routing</title>
      <dc:creator>Vishal VeeraReddy</dc:creator>
      <pubDate>Sat, 30 May 2026 15:56:21 +0000</pubDate>
      <link>https://dev.to/lynkr/run-aider-on-ollama-bedrock-or-any-llm-provider-one-gateway-every-model-3jm4</link>
      <guid>https://dev.to/lynkr/run-aider-on-ollama-bedrock-or-any-llm-provider-one-gateway-every-model-3jm4</guid>
      <description>&lt;p&gt;Aider is the best terminal AI coding tool I've used. But by default it sends every diff through your OpenAI or Anthropic key, which gets expensive fast on real refactors — a single 100-file repo map can torch a few dollars before Aider even reads your prompt.&lt;/p&gt;

&lt;p&gt;This post shows how to run Aider against &lt;strong&gt;any LLM provider&lt;/strong&gt; — Ollama for free local runs, OpenRouter for mixed-provider routing, AWS Bedrock for the enterprise plate — through a single OpenAI-compatible endpoint, with &lt;strong&gt;prompt caching&lt;/strong&gt; and &lt;strong&gt;MCP Code Mode&lt;/strong&gt; layered on top to slash the bill further. I'll use &lt;a href="https://github.com/Fast-Editor/Lynkr" rel="noopener noreferrer"&gt;Lynkr&lt;/a&gt;, the self-hosted gateway I maintain.&lt;/p&gt;

&lt;p&gt;Full disclosure: I build Lynkr. I'm going to make the case for why the combination — gateway + caching + code-mode tools — is the real cost lever, not just "swap your provider."&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup in three commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Start the gateway&lt;/span&gt;
npx lynkr@latest

&lt;span class="c"&gt;# 2. Point Aider at it&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8081/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;any-value

&lt;span class="c"&gt;# 3. Run Aider with any model name Lynkr knows about&lt;/span&gt;
aider &lt;span class="nt"&gt;--model&lt;/span&gt; deepseek/deepseek-v3.2-reasoner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Aider speaks the OpenAI Chat Completions protocol; Lynkr speaks it back and quietly translates the call to whichever upstream provider you've configured (Ollama, Bedrock, Anthropic, Azure, OpenRouter, Databricks, llama.cpp, LM Studio, ...). Aider has no idea it's talking to a router.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the money actually leaks in Aider
&lt;/h2&gt;

&lt;p&gt;Most "save money on AI coding" posts focus on swapping GPT-4o for a cheaper model. That's table stakes. The real spend in an Aider session breaks down roughly like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Call type&lt;/th&gt;
&lt;th&gt;Share of total tokens&lt;/th&gt;
&lt;th&gt;Where it goes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repo map (system context, sent every turn)&lt;/td&gt;
&lt;td&gt;~50–60%&lt;/td&gt;
&lt;td&gt;Same prefix, every single request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File contents you've /add'd&lt;/td&gt;
&lt;td&gt;~20–30%&lt;/td&gt;
&lt;td&gt;Same prefix until you change the files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The actual diff / instruction&lt;/td&gt;
&lt;td&gt;~5–10%&lt;/td&gt;
&lt;td&gt;Genuinely new each turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit messages, summarization&lt;/td&gt;
&lt;td&gt;~5%&lt;/td&gt;
&lt;td&gt;Cheap model anyway&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at that table. &lt;strong&gt;Most of your Aider bill is the same bytes being re-sent over and over.&lt;/strong&gt; Swapping models helps a little. Caching that repetitive prefix helps a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 1: Prompt caching — cuts the repeated-prefix tax
&lt;/h2&gt;

&lt;p&gt;Anthropic, Bedrock, Gemini, and OpenRouter all support prompt caching now, but Aider doesn't speak any of their cache-control protocols natively (it speaks one — OpenAI's — and only partially). Lynkr sits in the middle and injects &lt;code&gt;cache_control: ephemeral&lt;/code&gt; breakpoints on the right blocks before forwarding upstream.&lt;/p&gt;

&lt;p&gt;What that means in practice: the second Aider request in a session — same repo map, same /added files — only pays for the few hundred tokens of new instruction. Cached input tokens are &lt;strong&gt;10% the price of fresh input&lt;/strong&gt; on Anthropic, &lt;strong&gt;25%&lt;/strong&gt; on Bedrock, free for 5 minutes on Gemini.&lt;/p&gt;

&lt;p&gt;On a 4-hour Aider session against Claude Opus 4 or GPT-5, this single lever has cut my own input bill by &lt;strong&gt;~70%&lt;/strong&gt; before I even start tier-routing.&lt;/p&gt;

&lt;p&gt;Lynkr enables it automatically when the upstream provider supports it. No Aider config change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;MODEL_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;span class="nv"&gt;PROMPT_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;    &lt;span class="c"&gt;# default on, but explicit is good&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lever 2: MCP Code Mode — collapse N tool calls into 1
&lt;/h2&gt;

&lt;p&gt;Aider doesn't use tool calls itself (it parses code blocks from plain Markdown). But the moment you start composing Aider with other MCP tools — file search, web fetch, sandboxed execution — the round-trip cost explodes. Every tool call is a full request/response cycle through the LLM.&lt;/p&gt;

&lt;p&gt;Lynkr's &lt;strong&gt;MCP Code Mode&lt;/strong&gt; (borrowed from Cloudflare's pattern) flips this. Instead of advertising each MCP tool as a separate function the model can call, Lynkr exposes them as a small TypeScript API that the model writes a single program against. The program runs in a sandbox, hits all the tools it needs, and returns the result in one LLM round trip.&lt;/p&gt;

&lt;p&gt;Example: "find every file that imports &lt;code&gt;redis&lt;/code&gt;, check if any still use the v3 API, and print a migration TODO list."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-call mode (default everywhere else):&lt;/strong&gt; 5 file_search calls + 12 file_read calls + 1 grep call = 18 round trips. Each round trip re-sends the conversation history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Code Mode (Lynkr):&lt;/strong&gt; model writes ~20 lines of TS using &lt;code&gt;mcp.fileSearch()&lt;/code&gt; and &lt;code&gt;mcp.fileRead()&lt;/code&gt;, executes once, returns the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For coding-heavy sessions where Aider is composed with other MCP tools, this is a 5–15x reduction in tokens spent on tool plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 3: Tier routing — match model to task
&lt;/h2&gt;

&lt;p&gt;Aider's &lt;a href="https://aider.chat/docs/leaderboards/" rel="noopener noreferrer"&gt;own polyglot leaderboard&lt;/a&gt; tells a more interesting story in late 2026 than most people realize:&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;% correct&lt;/th&gt;
&lt;th&gt;Total benchmark cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5 (high)&lt;/td&gt;
&lt;td&gt;88.0%&lt;/td&gt;
&lt;td&gt;$29.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;o3-pro (high)&lt;/td&gt;
&lt;td&gt;84.9%&lt;/td&gt;
&lt;td&gt;$146.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Pro (32k think)&lt;/td&gt;
&lt;td&gt;83.1%&lt;/td&gt;
&lt;td&gt;$49.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4 (32k think)&lt;/td&gt;
&lt;td&gt;72.0%&lt;/td&gt;
&lt;td&gt;$65.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeepSeek-V3.2 Reasoner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;74.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.30&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-V3.2 Chat&lt;/td&gt;
&lt;td&gt;70.2%&lt;/td&gt;
&lt;td&gt;$0.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2&lt;/td&gt;
&lt;td&gt;59.1%&lt;/td&gt;
&lt;td&gt;$1.24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o (2024-08-06)&lt;/td&gt;
&lt;td&gt;23.1%&lt;/td&gt;
&lt;td&gt;$7.03&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things to notice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4o — the model most Aider quickstarts still suggest — is now near the bottom.&lt;/strong&gt; 23% on polyglot. The defaults aged badly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek-V3.2 Reasoner is 74% correct for $1.30 of total benchmark cost.&lt;/strong&gt; That's within striking distance of GPT-5 at ~1/22nd the bill, and roughly &lt;em&gt;50× cheaper&lt;/em&gt; than o3-pro.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Aider specifically, you don't need a $146 model to rename a variable. You need it for architecture decisions — and even then, V3.2 Reasoner is probably the right default for everything except the genuinely hardest 10% of calls.&lt;/p&gt;

&lt;p&gt;Lynkr's tier routing splits the work by prompt complexity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aider call type&lt;/th&gt;
&lt;th&gt;Routes to&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repo map summarization&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; (Ollama, local)&lt;/td&gt;
&lt;td&gt;Free, runs on your laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File edits, single-function diffs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deepseek-v3.2-chat&lt;/code&gt; (OpenRouter)&lt;/td&gt;
&lt;td&gt;70% correct, ~$0.88/benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default coding workhorse&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deepseek-v3.2-reasoner&lt;/code&gt; (OpenRouter)&lt;/td&gt;
&lt;td&gt;74% correct, ~$1.30/benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardest 10% — architecture, multi-file refactor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5&lt;/code&gt; or &lt;code&gt;gemini-2.5-pro&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Used sparingly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env additions&lt;/span&gt;
&lt;span class="nv"&gt;TIER_SIMPLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama:qwen2.5-coder:7b
&lt;span class="nv"&gt;TIER_MEDIUM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openrouter:deepseek/deepseek-v3.2-chat
&lt;span class="nv"&gt;TIER_COMPLEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openrouter:deepseek/deepseek-v3.2-reasoner
&lt;span class="nv"&gt;TIER_REASONING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openrouter:openai/gpt-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point Aider at &lt;code&gt;--model lynkr-auto&lt;/code&gt; and Lynkr scores each prompt before picking the tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stacking the three levers
&lt;/h2&gt;

&lt;p&gt;Each lever on its own is meaningful. Stacked, they compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching alone:&lt;/strong&gt; ~70% input-token cut on a stable session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;+ Tier routing:&lt;/strong&gt; another ~40% by pushing routine calls to Flash/Ollama&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;+ MCP Code Mode&lt;/strong&gt; (if you compose with other MCP tools): another 5–15x on tool-plumbing tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my own Aider workflow — heavy refactors against a 200k-LOC monorepo — this combination has dropped a session that used to cost ~$8 in Claude calls down to under $1.50. Not because Claude got cheaper. Because most of the work is now happening on cached prefixes, free local models, or in-sandbox code execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Install and start Lynkr
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lynkr@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run creates a &lt;code&gt;.env&lt;/code&gt; file. Minimal config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MODEL_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;span class="nv"&gt;PROMPT_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For full local + free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MODEL_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama
&lt;span class="nv"&gt;OLLAMA_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:11434
&lt;span class="nv"&gt;OLLAMA_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qwen2.5-coder:latest
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;ollama pull qwen2.5-coder:latest&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Point Aider at the gateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8081/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dummy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop those in your shell rc file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Pick a model (or let Lynkr pick)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Direct pass-through&lt;/span&gt;
aider &lt;span class="nt"&gt;--model&lt;/span&gt; deepseek/deepseek-v3.2-reasoner

&lt;span class="c"&gt;# Or let Lynkr tier-route&lt;/span&gt;
aider &lt;span class="nt"&gt;--model&lt;/span&gt; lynkr-auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8081/v1/models | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start Lynkr with &lt;code&gt;LOG_LEVEL=info&lt;/code&gt; and watch the cache-hit lines on your second Aider request — that's where the savings show up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aider-specific gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Weak model for commits / summarization.&lt;/strong&gt; Aider uses a cheaper model for non-code calls; default is &lt;code&gt;gpt-4o-mini&lt;/code&gt;. Override to a free local one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aider &lt;span class="nt"&gt;--model&lt;/span&gt; openai/gpt-4o &lt;span class="nt"&gt;--weak-model&lt;/span&gt; ollama/qwen2.5-coder:7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Long context.&lt;/strong&gt; Local Ollama models will OOM on 200k+ token repo maps. Either set &lt;code&gt;--map-tokens 0&lt;/code&gt;, or route long-context calls to Gemini Flash 1M-token contexts via the &lt;code&gt;TIER_REASONING&lt;/code&gt; line above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming.&lt;/strong&gt; Aider expects streaming responses. Lynkr streams by default. If you're on a non-streaming Databricks endpoint, set &lt;code&gt;STREAM_PASSTHROUGH=false&lt;/code&gt; and Lynkr buffers + simulates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache hit rate.&lt;/strong&gt; Prompt caching only fires when the prefix is byte-identical across requests. If your repo map changes (you edit a /added file), the cache for that block invalidates and rebuilds. Lynkr logs cache-hit ratios per session — watch them; if hit rate is below 60% something in your workflow is busting the prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quickref
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aider env var&lt;/th&gt;
&lt;th&gt;Lynkr role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_BASE=http://localhost:8081/v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where Lynkr listens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY=dummy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required by Aider, ignored by Lynkr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--model deepseek/deepseek-v3.2-reasoner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forwarded as-is to the configured upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--model lynkr-auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Triggers Lynkr's complexity-based tier routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--weak-model ollama/qwen2.5-coder:7b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Free local model for commit messages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The default Aider setup pays full price for the same repo-map bytes on every turn. The fix isn't "use a cheaper model" — it's:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cache the repetitive prefix&lt;/strong&gt; (prompt caching).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collapse tool plumbing into one call&lt;/strong&gt; (MCP Code Mode).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match model size to task complexity&lt;/strong&gt; (tier routing).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stacked, those three levers have taken my Aider sessions from ~$8 to ~$1.50 without changing how I work. Lynkr is one gateway that does all three; it's Apache 2.0, single Node binary, drop-in OpenAI base URL.&lt;/p&gt;

&lt;p&gt;Aider's GitHub: &lt;a href="https://github.com/Aider-AI/aider" rel="noopener noreferrer"&gt;https://github.com/Aider-AI/aider&lt;/a&gt;&lt;br&gt;
Lynkr's GitHub: &lt;a href="https://github.com/Fast-Editor/Lynkr" rel="noopener noreferrer"&gt;https://github.com/Fast-Editor/Lynkr&lt;/a&gt; — star to follow next integration writeups (OpenHands, Vercel AI SDK, Open Interpreter queued).&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>BAIXAR VÍDEO DO YOUTUBE</title>
      <dc:creator>Vinicius Andrade</dc:creator>
      <pubDate>Sat, 30 May 2026 15:55:59 +0000</pubDate>
      <link>https://dev.to/vinicius_andrade_1826c6db/baixar-video-do-youtube-2a9p</link>
      <guid>https://dev.to/vinicius_andrade_1826c6db/baixar-video-do-youtube-2a9p</guid>
      <description>&lt;p&gt;Criei um gerenciador de downloads desktop em Python e quero feedback da comunidade!&lt;/p&gt;

&lt;p&gt;O PyFlowDownloader é um app desktop feito com Python + PySide6 que usa yt-dlp para baixar vídeos e áudios de forma assíncrona do youtube. Algumas coisas que ele já faz:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fila de downloads com progresso em tempo real&lt;/li&gt;
&lt;li&gt;Cancelamento de downloads ativos ou pendentes&lt;/li&gt;
&lt;li&gt;Suporte a MP4 e MP3, de 144p até 1080p&lt;/li&gt;
&lt;li&gt;Histórico com exportação para CSV&lt;/li&gt;
&lt;li&gt;Interface desktop com tema visual via QSS
Build para Windows via PyInstaller + pipeline de release no GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Está na versão v0.3.0 e ainda tem muito espaço pra crescer. Repositório: &lt;a href="https://github.com/Vinny00101/PyFlowDownloader" rel="noopener noreferrer"&gt;https://github.com/Vinny00101/PyFlowDownloader&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se você puder **testar e deixar sua opinião nos comentários, ficaria muito grato! Quer saber:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O que achou da experiência de uso?&lt;/li&gt;
&lt;li&gt;Algum bug que encontrou?&lt;/li&gt;
&lt;li&gt;O que você adicionaria ou melhoraria no projeto?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo feedback é bem-vindo!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Releasing HeliosProxy, The programmable Postgres data-plane</title>
      <dc:creator>Dani Moya</dc:creator>
      <pubDate>Sat, 30 May 2026 15:54:05 +0000</pubDate>
      <link>https://dev.to/danimoya/releasing-heliosproxy-the-programmable-postgres-data-plane-18n5</link>
      <guid>https://dev.to/danimoya/releasing-heliosproxy-the-programmable-postgres-data-plane-18n5</guid>
      <description>&lt;p&gt;Happy to announce &lt;strong&gt;&lt;a href="https://github.com/HeliosDatabase/HeliosDB-Proxy" rel="noopener noreferrer"&gt;HeliosProxy&lt;/a&gt;&lt;/strong&gt; !!&lt;br&gt;
Far beyond a pooling tool, &lt;strong&gt;HeliosProxy ** is a next-gen programmable Postgres data-plane. **Works with PostgreSQL-compatible databases&lt;/strong&gt;, not only HeliosDB.&lt;/p&gt;

&lt;p&gt;It starts as a PgBouncer-compatible wedge, then adds the operational surface teams usually build from multiple tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection pooling&lt;/li&gt;
&lt;li&gt;failover and &lt;strong&gt;transaction replay&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;shadow execution&lt;/li&gt;
&lt;li&gt;anomaly detection&lt;/li&gt;
&lt;li&gt;edge cache controls&lt;/li&gt;
&lt;li&gt;admin REST API&lt;/li&gt;
&lt;li&gt;embedded admin UI&lt;/li&gt;
&lt;li&gt;signed WASM plugins&lt;/li&gt;
&lt;li&gt;OCI-style plugin artifacts&lt;/li&gt;
&lt;li&gt;Kubernetes operator&lt;/li&gt;
&lt;li&gt;Terraform and Pulumi providers&lt;/li&gt;
&lt;li&gt;22 installable Claude/Codex operator skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install operator skills:&lt;br&gt;
heliosdb-proxy install skills&lt;/p&gt;

&lt;h1&gt;
  
  
  PostgreSQL #DevOps #SRE #Database #AIcoding
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>rust</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Hello, DEV Community! 👋</title>
      <dc:creator>Ana Villar</dc:creator>
      <pubDate>Sat, 30 May 2026 15:53:23 +0000</pubDate>
      <link>https://dev.to/vilan011/hello-dev-community-4i30</link>
      <guid>https://dev.to/vilan011/hello-dev-community-4i30</guid>
      <description>&lt;p&gt;I'm Ana, and I'm excited to start sharing my technical journey here. This is a brief post to introduce myself and give you a heads-up on what's coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Expect
&lt;/h2&gt;

&lt;p&gt;In upcoming posts, I'll be diving into hands-on, infrastructure-focused tutorials and walkthroughs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setting up a XWiki server on-premises&lt;/strong&gt; — from installation to configuration, getting a collaborative wiki platform running in your own environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploying Kubernetes on RHEL 10 virtual machines&lt;/strong&gt; — step-by-step guidance on building a Kubernetes cluster on Red Hat Enterprise Linux 10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploying OpenShift as virtual machines on a RHEL 10 host&lt;/strong&gt; — exploring how to run OpenShift on top of RHEL 10, combining the power of containers with VM-level control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My goal is to keep things practical, clear, and rooted in real-world experience — the kind of content I wish I'd had when tackling these setups myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why These Topics?
&lt;/h2&gt;

&lt;p&gt;Because on-premises infrastructure is far from dead. Whether it's compliance requirements, performance needs, or simply wanting full control over your stack, there's still a strong case for running things yourself. And RHEL was my choice for certification purposes, and it's given me the flexibility to keep using hardware that other vendors have discontinued.&lt;/p&gt;

&lt;p&gt;Stay tuned, and feel free to follow me so you don't miss the upcoming posts. If any of these topics spark your interest, I'd love to hear about it in the comments! 🚀&lt;/p&gt;

</description>
      <category>redhat</category>
      <category>kubernetes</category>
      <category>onprem</category>
    </item>
    <item>
      <title>Three Bitcoin Primitives That Don't Exist Anywhere Else (PoW Beacon, DLC Oracle, Fair-Launch Rune)</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 30 May 2026 15:52:11 +0000</pubDate>
      <link>https://dev.to/zekebuilds/three-bitcoin-primitives-that-dont-exist-anywhere-else-pow-beacon-dlc-oracle-fair-launch-rune-532g</link>
      <guid>https://dev.to/zekebuilds/three-bitcoin-primitives-that-dont-exist-anywhere-else-pow-beacon-dlc-oracle-fair-launch-rune-532g</guid>
      <description>&lt;h1&gt;
  
  
  The Problem With "Bitcoin-native" Claims
&lt;/h1&gt;

&lt;p&gt;Most things calling themselves Bitcoin-native are not. They settle on Ethereum, they custody coins through a federation, they hand you an IOU and call it Bitcoin. Plenty of projects ship something useful that touches Bitcoin somewhere. Few ship primitives where every byte that matters lives on the chain, or anchors to the chain, or settles on the chain.&lt;/p&gt;

&lt;p&gt;This week I shipped three primitives that fit that bar, on the same captcha endpoint that hands out SHA-256 challenges to AI agents around the clock. They are not new ideas in isolation. Randomness beacons, DLC oracles, and Rune fair-launches all exist. What is new is wiring them through honest proof-of-work, so the entropy comes from work nobody can grind in their favor, and the distribution rewards the exact same kind of compute that secures Bitcoin itself.&lt;/p&gt;

&lt;p&gt;Here is what landed, how to verify it, and where the seams are.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 1: PoW Randomness Beacon
&lt;/h1&gt;

&lt;p&gt;Every minute the captcha server batches the PoW solutions it received, builds a Merkle tree, publishes the root, and anchors that root in Bitcoin via OpenTimestamps. The Merkle root becomes a public seed that nobody could have predicted, because nobody knew what challenges agents would solve in the next sixty seconds. Once the OTS proof confirms, the seed is forever attestable against the Bitcoin chain.&lt;/p&gt;

&lt;p&gt;This inverts the usual move. Most randomness oracles inject an external source of entropy into Bitcoin. We harvest entropy that already exists out in the wild, compress it cheaply, and anchor it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/api/beacon/latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"epoch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merkle_root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9321a45272fd3331e0ee73cbd86c32ad30dd6a786e3f1c95cb1afd8a2d1c18c1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beacon_random"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc1fead17f55476ad0e248357db8b3d29510318f1c59111b78785da5368629a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"leaf_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ots_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"btc_confirmed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weak"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weak_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"leaf_count=3 &amp;lt; threshold=10"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noticing. &lt;code&gt;weak: true&lt;/code&gt; is honest signaling, not a bug. When leaf count is low, the beacon flags itself as weak so you do not build a contract on it that needs strong unpredictability. &lt;code&gt;ots_status: submitted&lt;/code&gt; means the OTS server has the proof and is waiting for the next Bitcoin block to anchor it. Once that happens, &lt;code&gt;btc_confirmed&lt;/code&gt; flips to the block height and the seed is forever verifiable against the chain.&lt;/p&gt;

&lt;p&gt;Why this matters: the beacon does not ask you to trust me. It asks you to trust SHA-256 and the Bitcoin chain. If you can verify a Merkle root and an OTS proof, you can verify the beacon yourself. That is the whole point.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 2: DLC Oracle on PoW
&lt;/h1&gt;

&lt;p&gt;The PowForge Schnorr oracle lives at &lt;code&gt;attest.powforge.dev&lt;/code&gt; and signs outcomes with a stable BIP-340 Schnorr key. Two parties anywhere on Earth can write a Bitcoin contract whose outcome depends on a future beacon or attested event, fund it into a 2-of-2 multisig, and have it auto-settle the moment the oracle attests.&lt;/p&gt;

&lt;p&gt;This is standard DLC machinery. What is new is that the oracle's signing pipeline is gated behind proof-of-work, and it serves binary TLV outputs (&lt;code&gt;OracleAnnouncement&lt;/code&gt; type 55332, &lt;code&gt;OracleAttestation&lt;/code&gt; type 55400) that any dlcdevkit-compatible wallet can consume without additional wrapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://attest.powforge.dev/api/v1/info | jq &lt;span class="s1"&gt;'{oracle_pubkey, attestation_tag}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oracle_pubkey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attestation_tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DLC/oracle/attestation/v0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That x-only pubkey is the Schnorr key the oracle signs with. Any DLC-aware wallet can pin a contract to it. The JavaScript client is available as &lt;code&gt;@powforge/attest-client&lt;/code&gt; on npm.&lt;/p&gt;

&lt;p&gt;What can you actually do with it? A few things that are awkward to build with conventional oracles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programmable lotteries where the winning number is provably unmanipulated. The number was already committed before tickets closed.&lt;/li&gt;
&lt;li&gt;Insurance contracts that settle on observable computational difficulty. If real AI-agent traffic spikes, the beacon reflects it. Sell a put on that.&lt;/li&gt;
&lt;li&gt;Any agreement that needs both parties to trust a number that neither side can grind. The grinder would need to control the entire AI captcha solver fleet, which is exactly the population that does not coordinate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The economic property here is that the oracle's signature is gated by work the oracle itself did not do. The oracle is a publisher, not a producer. The randomness was already paid for by every agent that solved a challenge.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 3: PoW Fair-Launch Rune
&lt;/h1&gt;

&lt;p&gt;A new Bitcoin Rune called &lt;code&gt;POWFORGE•PROOF&lt;/code&gt; will etch on mainnet with the entire 21,000,000 supply premined to a single relay key. Distribution happens through one mechanism: solve a 14-bit SHA-256 PoW challenge, supply a Bitcoin address, get 1,000 units. One claim per address per 24 hours. No presale, no allocations, no VCs, no fundraising round.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/rune/info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rune"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POWFORGE•PROOF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"⚒"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_supply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parcel_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"algo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"difficulty_bits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off-chain-enforcement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rate_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1 claim per recipient address per 24h"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scaffold"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conventional Rune fair-launch model is open-mint, first-confirmed-wins. It devolves into a gas war the moment a Rune attracts attention. Whoever pays the highest fee wins the next block, and the actual buyers get priced out. PoW gating swaps that for work-proof fairness. Anyone with a CPU-second to spare can win a parcel. There is no fee escalation, because fees are not the bottleneck. The challenge is.&lt;/p&gt;

&lt;p&gt;Where it stands: Phase 1 (scaffold) and Phase 2 (real Runestone OP_RETURN bytes via &lt;code&gt;@magiceden-oss/runestone-lib&lt;/code&gt;) are shipped. Phase 3 wires PSBT assembly with &lt;code&gt;@scure/btc-signer&lt;/code&gt; and broadcasts via a local mainnet node. The minting key sits next to the oracle key in the operator's config directory. RPC permission for &lt;code&gt;sendrawtransaction&lt;/code&gt; is confirmed working.&lt;/p&gt;

&lt;p&gt;What sits between here and mainnet etch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSBT builder for the etch tx (around 254 vbytes counting the commit-reveal witness)&lt;/li&gt;
&lt;li&gt;Rune-name uniqueness audit against the ord registry&lt;/li&gt;
&lt;li&gt;Multisig gating on the relay key so no single human can grief the launch&lt;/li&gt;
&lt;li&gt;A funded UTXO at the minting address (around 2,000 sats covers the etch round-trip)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest framing: until those pieces land, &lt;code&gt;POWFORGE•PROOF&lt;/code&gt; is reserved by convention, not by chain. The Rune does not exist on Bitcoin yet. The infrastructure that will etch it does, and you can poke every endpoint that drives it.&lt;/p&gt;

&lt;h1&gt;
  
  
  How They Connect
&lt;/h1&gt;

&lt;p&gt;Real work flows in. AI agents solve PoW captchas to pay for free-tier API access. The captcha server processes those solutions three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The solutions feed an honest randomness primitive (the beacon)&lt;/li&gt;
&lt;li&gt;The primitive becomes oracle-signed for trustless contracts (the DLC oracle)&lt;/li&gt;
&lt;li&gt;The pipeline anchors a fair token distribution that rewards the same work modality that secures Bitcoin itself (the Rune)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The through-line is Bitcoin's own thesis: proof of work is the cheapest way to make a number trustworthy. Apply that to randomness, you get a beacon. Apply that to attestation, you get a DLC oracle. Apply that to token distribution, you get an un-front-runnable fair-launch. Each layer is independently useful. Together they are a working demonstration that PoW economics extend further than Bitcoin's blockspace.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's Next
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rune Phase 3.&lt;/strong&gt; PSBT assembly via &lt;code&gt;@scure/btc-signer&lt;/code&gt;, dedicated minting key, mainnet etch behind a gated runbook. Roughly 10 hours of dev plus 2 hours in the operator loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DLC client integration.&lt;/strong&gt; Example contract templates so two parties can spin up a beacon-settled wager in a single command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PoW-gated oracle signing.&lt;/strong&gt; The captcha server's PoW verification gates a Schnorr signature from a stable oracle key. Tapscript leaves can reference that oracle pubkey as a spending condition, making "valid PoW solution" the prerequisite for an on-chain signing event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct on-chain PoW gate&lt;/strong&gt; (the long path). A real &lt;code&gt;OP_SHA256&lt;/code&gt; plus difficulty comparison inside a Tapscript leaf needs &lt;code&gt;OP_CAT&lt;/code&gt;. That opcode is reserved on Bitcoin but not activated as of this writing. Until soft-fork activation, we route PoW through the oracle layer rather than the script layer. The oracle path is strictly weaker than a script-level gate, but it ships today.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Try It
&lt;/h1&gt;

&lt;p&gt;Verify everything yourself. All three services are live and return JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The randomness beacon (on pow-captcha)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/api/beacon/latest

&lt;span class="c"&gt;# The DLC oracle info + pubkey (on attest.powforge.dev)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://attest.powforge.dev/api/v1/info

&lt;span class="c"&gt;# The Rune fair-launch metadata (on pow-captcha)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/rune/info

&lt;span class="c"&gt;# A live PoW challenge for the rune fair-launch&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/rune/challenge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of those return something that looks broken, that is data. Tell me. The whole point of building in public is that the next iteration is shaped by what the last one got wrong.&lt;/p&gt;

&lt;p&gt;PoW is not a perfect economic primitive. It is the simplest one we have for making a number expensive to forge. Three primitives this week, all on the same engine. More coming.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
      <category>crypto</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Append-only doesn't mean what you'd hope</title>
      <dc:creator>Norbert Rosenwinkel</dc:creator>
      <pubDate>Sat, 30 May 2026 15:51:07 +0000</pubDate>
      <link>https://dev.to/norbert_rosenwinkel_3eb41/append-only-doesnt-mean-what-youd-hope-41go</link>
      <guid>https://dev.to/norbert_rosenwinkel_3eb41/append-only-doesnt-mean-what-youd-hope-41go</guid>
      <description>&lt;p&gt;Event sourcing gets sold on immutability. You don't update, you don't delete, you only append, so the history is permanent.&lt;/p&gt;

&lt;p&gt;It mostly isn't. The events are immutable because your code agrees not to touch them, not because anything actually stops it. Underneath they're still rows in Postgres, and rows have a DBA with write access. A migration that "cleans up" old data. A 2 a.m. query run against the wrong connection. A backup restored with slightly different bytes in it.&lt;/p&gt;

&lt;p&gt;Change one of those rows and a replay won't blink. The aggregate rebuilds, the projections rebuild, everything looks fine. Usually the first person to notice is a customer whose balance is off, and by then the trail is cold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chain each event into the next
&lt;/h2&gt;

&lt;p&gt;The trick is small. Give every row two extra columns: a hash of its contents, and the hash of the row before it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#1  AccountOpened     prev=00000…  hash=70be4f…
                                      │
                                      ▼
#2  AmountDeposited   prev=70be4f…  hash=796018…
                                      │
                                      ▼
#3  AmountWithdrawn   prev=796018…  hash=6a0260…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hash is &lt;code&gt;SHA-256(previousHash || json(payload))&lt;/code&gt;. Nothing exotic.&lt;/p&gt;

&lt;p&gt;The point is that each hash depends on the one before it. Edit a payload and its hash stops matching. Rewrite that hash to cover for the edit, and now the next row's pointer is wrong. You can't fix one without breaking the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  About forty lines of it
&lt;/h2&gt;

&lt;p&gt;Appending an event hashes it together with the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;HashChainedEntry&lt;/span&gt; &lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;previousHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;GenesisHash&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_entries&lt;/span&gt;&lt;span class="p"&gt;[^&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousHash&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HashChainedEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&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;previousHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&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;entry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;payloadJson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToUtf8Bytes&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;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;payloadJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BlockCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BlockCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payloadJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payloadJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&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;SHA256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HashData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifying is the same thing backwards. Walk the rows, recompute, and check two things on each one: the pointer and the hash.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;previousHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// genesis&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;ByteArraysEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PreviousHash&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EventStreamCorruptedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"previous-hash pointer does not match the prior entry's hash"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;recomputed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;ByteArraysEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recomputed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EventStreamCorruptedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"stored hash does not match a fresh re-hash of the payload (payload was modified after commit)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;previousHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bump Alice's $50 deposit to $5,000 straight in the table, and the check stops you cold at the exact row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event stream tampering detected at sequence #2: stored hash does not
match a fresh re-hash of the payload (payload was modified after commit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What that gets you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Someone tries to…&lt;/th&gt;
&lt;th&gt;…and it shows up because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edit one event's payload&lt;/td&gt;
&lt;td&gt;the re-hash no longer matches the stored hash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewrite the stored hash to match&lt;/td&gt;
&lt;td&gt;the next row's pointer no longer matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete a row from the middle&lt;/td&gt;
&lt;td&gt;the next row's pointer doesn't match its new neighbour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slip in a forged row&lt;/td&gt;
&lt;td&gt;same thing, the pointer chain breaks at the seam&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The honest ceiling
&lt;/h2&gt;

&lt;p&gt;Here's the part people gloss over. That table assumes the attacker is lazy: edit a row, move on, leave the stale hash behind. Someone with full write access doesn't have to be lazy. They can edit the row and then recompute every hash after it. Now the chain is consistent again and the verifier has nothing to say.&lt;/p&gt;

&lt;p&gt;A hash chain is a checksum, not a signature. If you own both ends of it, so does anyone who owns your database. That's the honest ceiling of doing this inside your own four walls, and it's worth saying out loud before someone says it for you in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting out of your own walls
&lt;/h2&gt;

&lt;p&gt;This is what anchoring is for, and it's the part I find actually interesting.&lt;/p&gt;

&lt;p&gt;Next to the per-stream chains, Stratara keeps a second table of anchors. Every so many events it writes down the head of the chain at that point. Each anchor row has a &lt;code&gt;BlockchainTxHash&lt;/code&gt; column, and that column is the hook: you take the anchor and commit it somewhere you don't control. A public blockchain. An RFC 3161 timestamp authority. An &lt;a href="https://opentimestamps.org/" rel="noopener noreferrer"&gt;OpenTimestamps&lt;/a&gt; calendar. A notary. Anything you trust that isn't you.&lt;/p&gt;

&lt;p&gt;Once an anchor lives somewhere out of your reach, the recompute attack falls apart. Your insider can rewrite every hash in the database and still can't touch the value you already pinned elsewhere. The question stops being "is this chain internally consistent" and becomes "does it still match what we committed outside." That second one is much harder to fake.&lt;/p&gt;

&lt;p&gt;Let me be straight about what ships versus what you wire yourself. The anchor table, the worker that writes anchors, and the &lt;code&gt;BlockchainTxHash&lt;/code&gt; column are in the box. Actually pushing an anchor to your source of truth, and checking against it later, is the part you wire up. Stratara doesn't pick the chain for you, the same way it doesn't pick your message broker. The sample at the end runs the whole thing in memory so you can see the shape of it.&lt;/p&gt;

&lt;p&gt;One caveat, said plainly: if someone owns your database &lt;em&gt;and&lt;/em&gt; your anchoring pipeline, they can re-chain and re-anchor and it'll all look fine. The defense only holds if the thing you anchor to is genuinely out of their hands. That's the entire reason to put it outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the hashing happens, and where verifying does
&lt;/h2&gt;

&lt;p&gt;The hashing runs on a background worker, not inline on every append, so writes stay cheap. The chain gets filled in a beat behind the commit. Verifying is a separate thing you do on purpose: a scheduled job, or checking the external anchor. You don't want it on the read path, because that's a &lt;code&gt;SELECT … ORDER BY Sequence&lt;/code&gt; on every query and it ties each read to the integrity check.&lt;/p&gt;

&lt;p&gt;Worth being straight about: nothing in the framework wakes up and hunts for tampering on its own today. The hashes and the anchors are there so that &lt;em&gt;when&lt;/em&gt; you verify — on a schedule, during an audit, after an incident — the evidence is intact and a break lands on the exact row. For a SOC 2 or ISO 27001 audit, the worker's structured logs are the running record that the hashing happened across the period; the verification job is what proves the chain held.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this lives
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://github.com/yesbert/Stratara" rel="noopener noreferrer"&gt;Stratara&lt;/a&gt;, a CQRS and event-sourcing stack for .NET 10. The chaining is the &lt;code&gt;EventStreamHashing&lt;/code&gt; worker, running against Postgres. None of the idea is Stratara-specific though. If you've got an append-only table, you can bolt this on yourself.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/yesbert/Stratara/tree/main/samples/Stratara.Sample.TamperProof" rel="noopener noreferrer"&gt;&lt;code&gt;TamperProof&lt;/code&gt; sample&lt;/a&gt; is the whole story in zero-dependency, in-memory code, in three acts: a clean chain that verifies, a sloppy tamper caught at the exact row, and a full re-chain that sails past the local check but gets caught by an external anchor.&lt;/p&gt;

&lt;p&gt;Wiring it into a real app is more than one &lt;code&gt;dotnet add&lt;/code&gt; — you need the event store, the hashing worker, and a little DI — so the &lt;a href="https://docs.stratara.tech/getting-started/" rel="noopener noreferrer"&gt;getting-started guide&lt;/a&gt; walks the minimal setup. Full docs are at &lt;a href="https://docs.stratara.tech" rel="noopener noreferrer"&gt;https://docs.stratara.tech&lt;/a&gt;, and it's source-available under FSL-1.1-MIT (not OSI-approved OSS), which flips to plain MIT after two years.&lt;/p&gt;

&lt;p&gt;This is just one slice of Stratara, and honestly the easiest to show off. There's plenty more I want to write up — the tenant-aware encryption side especially, where a tenant's data is cryptographically bound to their own key — without cramming it all into one wall of text. So if this was your kind of thing, stick around: more coming.&lt;/p&gt;

&lt;p&gt;If you're already event sourcing: how would you actually prove to an auditor that nobody's touched the log? Genuinely curious what people are doing here.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>postgres</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
