<?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>Forem: João André Gomes Marques</title>
    <description>The latest articles on Forem by João André Gomes Marques (@jagmarques).</description>
    <link>https://forem.com/jagmarques</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3836862%2Fb366b35b-2375-486a-b867-2535919afb9f.png</url>
      <title>Forem: João André Gomes Marques</title>
      <link>https://forem.com/jagmarques</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jagmarques"/>
    <language>en</language>
    <item>
      <title>Sign the intent, the decision, and the execution</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Wed, 15 Apr 2026 17:36:20 +0000</pubDate>
      <link>https://forem.com/jagmarques/sign-the-intent-the-decision-and-the-execution-amc</link>
      <guid>https://forem.com/jagmarques/sign-the-intent-the-decision-and-the-execution-amc</guid>
      <description>&lt;p&gt;Most governance tools produce a single signed receipt after the fact. The action happened, here is the proof. That approach covers you in the simplest case, but it leaves two critical moments completely unrecorded.&lt;/p&gt;

&lt;p&gt;The first is intent. Before an agent runs anything, it declares what it wants to do. That declaration is worth signing on its own, because it tells you what the agent was trying to accomplish regardless of whether it was allowed to proceed. The second is the policy decision itself, the moment your rules evaluated the request and returned an approval or a denial. If you only sign the final action, you lose both of these, and you are left with no proof that governance actually ran before the agent acted.&lt;/p&gt;

&lt;p&gt;Three-phase signing treats each of these moments as a separate signed record in a chain. The intent gets signed first, capturing the agent, the action, and the context it provided. The decision gets signed next, recording whether the policy approved or denied the request and which rules were evaluated. If the request was approved, the execution gets signed last, confirming the action completed.&lt;/p&gt;

&lt;p&gt;The interesting case is when the policy blocks the action. You still end up with two signed records: the intent and the denial. That means you have a tamper-proof record showing that the agent tried to do something it was not allowed to do, and that your governance layer caught it. For compliance, this is just as valuable as a successful execution chain, because auditors want to see that controls are working, not just that actions happened.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orchestrator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign_with_phases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:delete:users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cleanup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# signed: agent wanted to delete users
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# policy evaluated and approved/denied
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# signed: action completed (only if approved)
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# True/False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each phase in the chain is individually verifiable. You can hand an auditor the intent signature alone and they can confirm it is authentic without needing the rest of the chain. Or you can export the full chain as a single bundle and let them walk through the entire lifecycle of the action from request to completion.&lt;/p&gt;

&lt;p&gt;This also changes how you think about denied actions. Instead of treating a policy denial as a silent non-event, it becomes a first-class record in your audit trail. Over time, the pattern of denied intents tells you as much about your agents as the approved ones do, because it shows you what they are consistently trying to do and being stopped from doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  CrewAI GuardrailProvider
&lt;/h2&gt;

&lt;p&gt;On a related note, we recently shipped a &lt;code&gt;GuardrailProvider&lt;/code&gt; integration for CrewAI that plugs three-phase signing directly into crew task execution. If you are building with CrewAI, the guardrail provider evaluates each agent action against your policies and produces the same signed chain automatically, so you get full intent, decision, and execution records without changing how your crews are structured.&lt;/p&gt;

&lt;p&gt;Source on &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If something breaks, open an issue.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Trace agent actions across workflows and kill everything in one call</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Wed, 15 Apr 2026 17:05:58 +0000</pubDate>
      <link>https://forem.com/jagmarques/trace-agent-actions-across-workflows-and-kill-everything-in-one-call-aa7</link>
      <guid>https://forem.com/jagmarques/trace-agent-actions-across-workflows-and-kill-everything-in-one-call-aa7</guid>
      <description>&lt;p&gt;Two problems kept coming up while I was building multi-step agent workflows.&lt;/p&gt;

&lt;p&gt;First, when an orchestrator delegates to sub-agents, the audit trail turns into a mess. You get a flat list of signed actions with no way to tell which ones belong to the same workflow. Was that &lt;code&gt;data:read&lt;/code&gt; from the reporting pipeline or the cleanup job? No idea.&lt;/p&gt;

&lt;p&gt;Second, when something goes sideways in production, you need to stop every agent in your organization immediately instead of figuring out which one is causing the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace correlation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;trace_id&lt;/code&gt; solves the first problem. Generate one ID for the workflow, then pass it to every &lt;code&gt;sign()&lt;/code&gt; call. Every action in that workflow shares the same trace, so you can reconstruct exactly what happened and in what order.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orchestrator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate a trace ID for the whole workflow
&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_trace_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Every action in this workflow shares the same trace
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task:start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task:delegate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sub-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sig_abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task:complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;parent_id&lt;/code&gt; parameter links delegated actions back to the signature that spawned them. So you get a tree, not a list. The orchestrator signed &lt;code&gt;task:delegate&lt;/code&gt;, and the sub-agent's actions reference that signature as their parent.&lt;/p&gt;

&lt;p&gt;This matters for compliance. When an auditor asks "show me everything this workflow did," you filter by &lt;code&gt;trace_id&lt;/code&gt; and get a complete, ordered chain of signed actions. No guessing, no log correlation hacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emergency halt
&lt;/h2&gt;

&lt;p&gt;The second problem is simpler but scarier. An agent starts behaving unpredictably, or you detect a security incident, and you need a kill switch.&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;# Something goes wrong? Kill everything.
&lt;/span&gt;&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emergency_halt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security incident&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call. Every agent registered under your organization gets revoked immediately. Their signatures stop verifying, their scope tokens become invalid, and any in-flight actions get rejected. The halt reason gets recorded in the audit trail so you have a clear record of when and why you pulled the plug.&lt;/p&gt;

&lt;p&gt;You can also halt a specific agent or agent group instead of the whole org. But the point of &lt;code&gt;emergency_halt()&lt;/code&gt; with no target is that you do not need to figure out which agent is the problem when things are on fire. Stop everything, investigate after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Agent workflows are getting more complex. An orchestrator calls a planner, the planner calls three workers, each worker calls external APIs. Without trace correlation, you are debugging in the dark. Without an emergency halt, you are hoping nothing goes wrong.&lt;/p&gt;

&lt;p&gt;Both features ship in the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav &lt;span class="nt"&gt;--upgrade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full docs at &lt;a href="https://www.asqav.com/docs/" rel="noopener noreferrer"&gt;asqav.com/docs&lt;/a&gt;. Source on &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If something breaks, open an issue.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop replay attacks on AI agent tokens</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Wed, 15 Apr 2026 06:31:20 +0000</pubDate>
      <link>https://forem.com/jagmarques/stop-replay-attacks-on-ai-agent-tokens-38nj</link>
      <guid>https://forem.com/jagmarques/stop-replay-attacks-on-ai-agent-tokens-38nj</guid>
      <description>&lt;p&gt;Scope tokens let you prove what an AI agent is allowed to do. I wrote about portable scope tokens a few days ago. They travel with requests so the receiving service can verify permissions without calling back to your org.&lt;/p&gt;

&lt;p&gt;But there is a problem I did not cover. If someone intercepts a valid scope token, they can replay it. The token is signed, unexpired, and carries legitimate permissions. Nothing stops a second use.&lt;/p&gt;

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

&lt;p&gt;Say your agent creates a scope token with &lt;code&gt;data:read&lt;/code&gt; permission and a one-hour TTL. It sends a request to a partner API with the token attached. An attacker sitting on the network copies that token. They now have 59 minutes to make their own requests using the same token, with the same permissions.&lt;/p&gt;

&lt;p&gt;The signature still verifies. The TTL has not expired. The actions list matches. From the receiver's perspective, the replayed request looks identical to the original.&lt;/p&gt;

&lt;p&gt;Short TTLs reduce the window but do not close it. Even a 60-second TTL gives an attacker 60 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nonce-based replay protection
&lt;/h2&gt;

&lt;p&gt;The fix in v0.2.14 is straightforward. Every scope token now includes a unique nonce, a random string generated at creation time. The receiver tracks which nonces it has already seen. If a nonce shows up twice, the token gets rejected.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-caller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_scope_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&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;data:read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Each token has a unique nonce
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "a1b2c3d4..."
&lt;/span&gt;
&lt;span class="c1"&gt;# Receiver side
&lt;/span&gt;&lt;span class="n"&gt;seen_nonces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&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;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen_nonces&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Replay detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;seen_nonces&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;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is simple: generate, check, reject duplicates. The nonce set only needs to hold entries until the token's TTL expires. After that, the token is invalid anyway, so you can clean up old nonces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use shorter TTLs
&lt;/h2&gt;

&lt;p&gt;Shorter TTLs help. But they create a different problem. Your agent needs to mint new tokens more frequently, adding latency to every request. And even a 1-second TTL is vulnerable to automated replay within that window.&lt;/p&gt;

&lt;p&gt;Nonces and TTLs work together. The TTL limits how long the nonce set needs to persist. The nonce ensures each token is truly single-use regardless of TTL length.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;p&gt;For most setups, an in-memory set works fine. If you are running multiple receiver instances behind a load balancer, you need a shared store like Redis so all instances see the same nonce set.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;is_replay&lt;/code&gt; helper handles the check and cleanup for you. Pass in the nonce and your set. It returns &lt;code&gt;True&lt;/code&gt; if the nonce was already seen.&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;# Production setup with Redis
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_nonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nonce:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Replay detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install or upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav &lt;span class="nt"&gt;--upgrade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs at &lt;a href="https://www.asqav.com/docs/" rel="noopener noreferrer"&gt;asqav.com/docs&lt;/a&gt;. Source on &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If something breaks, open an issue.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Portable scope tokens: prove what your agent can do without calling home</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:40:52 +0000</pubDate>
      <link>https://forem.com/jagmarques/portable-scope-tokens-prove-what-your-agent-can-do-without-calling-home-27p6</link>
      <guid>https://forem.com/jagmarques/portable-scope-tokens-prove-what-your-agent-can-do-without-calling-home-27p6</guid>
      <description>&lt;p&gt;Your agent authenticates with an API key. The external service knows who is calling. But it has no idea what the agent is actually allowed to do.&lt;/p&gt;

&lt;p&gt;This is the gap between authentication and authorization in agent-to-service communication. Auth tokens prove identity. They do not prove permission. When agent A calls external service B, B can verify that A is a legitimate caller, but it cannot verify that A has been authorized to read customer data, trigger a payment, or access a specific endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with calling home
&lt;/h2&gt;

&lt;p&gt;The traditional fix is for service B to call back to A's organization and ask "is this agent allowed to do this?" That works when both services are inside the same network. It falls apart when agents interact with third-party APIs, partner services, or any system outside the org boundary. The callback adds latency, creates a runtime dependency, and requires the external service to know how to reach your authorization system in the first place.&lt;/p&gt;

&lt;p&gt;Most teams skip this entirely. They give agents broad API keys and hope the agent only does what it should. That is not a security model. That is wishful thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope tokens
&lt;/h2&gt;

&lt;p&gt;A scope token is a signed, portable proof of what an agent is authorized to do. You create it before the agent makes an outgoing request. It lists the specific actions the agent is permitted to perform, has a time-to-live, and is signed by &lt;a href="https://www.asqav.com" rel="noopener noreferrer"&gt;Asqav&lt;/a&gt; as an independent third party.&lt;/p&gt;

&lt;p&gt;The token travels with the request. The receiving service verifies the signature using Asqav's public key, checks the action list and expiry, and makes its access decision. No callback to your org. No shared secret. No runtime dependency.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-caller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create scope token before calling external API
&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_scope_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;actions&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;data:read:customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api:call:external&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Attach to outgoing request
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://partner-api.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_header&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token gets attached as an &lt;code&gt;X-Agent-Scope&lt;/code&gt; header. On the partner side, verification is one call.&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;# Partner side - verify without calling your org
&lt;/span&gt;&lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_scope_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Agent-Scope&lt;/span&gt;&lt;span class="sh"&gt;"&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;received&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:read:customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Verified: agent is authorized for this scope
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What makes this different from OAuth scopes
&lt;/h2&gt;

&lt;p&gt;OAuth scopes are declared during the authorization flow and embedded in the access token. The resource server trusts the authorization server that issued the token. Scope tokens work differently. They are issued per-request (or per-session), signed by an independent third party, and verifiable by anyone with the public key. The receiving service does not need a pre-existing trust relationship with your authorization server.&lt;/p&gt;

&lt;p&gt;This matters for agent-to-agent communication. When your agent calls a partner's agent, there is no shared OAuth provider. Scope tokens give both sides a common verification mechanism without requiring either to adopt the other's auth infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical constraints
&lt;/h2&gt;

&lt;p&gt;Scope tokens are intentionally short-lived. The &lt;code&gt;ttl&lt;/code&gt; parameter sets the expiry in seconds. A token created with &lt;code&gt;ttl=3600&lt;/code&gt; is valid for one hour. After that, the receiving service rejects it. This limits the blast radius if a token leaks.&lt;/p&gt;

&lt;p&gt;Actions follow a hierarchical format: &lt;code&gt;resource:operation:target&lt;/code&gt;. The format is flexible enough to model most permission structures but specific enough to verify programmatically.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full docs at &lt;a href="https://www.asqav.com/docs/" rel="noopener noreferrer"&gt;asqav.com/docs&lt;/a&gt;. Source on &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If something does not work the way you expect, open an issue.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Replay what your AI agent did, step by step</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:12:47 +0000</pubDate>
      <link>https://forem.com/jagmarques/replay-what-your-ai-agent-did-step-by-step-53j7</link>
      <guid>https://forem.com/jagmarques/replay-what-your-ai-agent-did-step-by-step-53j7</guid>
      <description>&lt;p&gt;If you're running AI agents in production, you probably have some form of audit trail already. Maybe signed receipts, maybe logs, maybe just hope. But here's the thing, when something goes wrong or an auditor shows up, nobody wants to read raw JSON.&lt;/p&gt;

&lt;p&gt;They want to walk through what the agent did. Step by step. In order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with raw receipts
&lt;/h2&gt;

&lt;p&gt;I've been building &lt;a href="https://github.com/asqav/asqav" rel="noopener noreferrer"&gt;asqav&lt;/a&gt; for a while now, and the signed action receipts work great as cryptographic proof. Each action gets signed, each signature is chained to the previous one, and you get tamper-evident records of everything your agent did.&lt;/p&gt;

&lt;p&gt;But proof and readability are different things. When you're debugging an incident at 2am or preparing for a compliance review, you need a timeline you can actually follow. Not a folder of JSON blobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  asqav.replay()
&lt;/h2&gt;

&lt;p&gt;The new replay feature takes your signed receipts and turns them into a walkable timeline. It fetches all actions for a given session, orders them chronologically, verifies the hash chain is intact, and hands you something you can actually read.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;timeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agt_abc123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sess_xyz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Human-readable summary of what the agent did
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Each step in order
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Verify nothing was tampered with
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chain_integrity&lt;/span&gt;  &lt;span class="c1"&gt;# True if hash chain is valid
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chain integrity check is the key part. It doesn't just replay events. It re-verifies every link in the hash chain. If someone deleted a step, modified an action, or inserted something after the fact, it catches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline compliance bundles
&lt;/h2&gt;

&lt;p&gt;Not every auditor wants to hit your API. Some want a self-contained package they can review independently. That's what export bundles are for.&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;# Export a compliance bundle with all signatures
&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu_ai_act_art12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Anyone can replay from the bundle - no API access needed
&lt;/span&gt;&lt;span class="n"&gt;timeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replay_from_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundle includes all the signed actions, the public keys needed to verify them, and metadata about which compliance framework it targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;The EU AI Act Article 12 requires that high-risk AI systems maintain logs that enable "the reconstruction of the system's activity." Signed receipts are the proof that your agent did what it says it did. Replay is how you present that proof to someone who needs to understand it.&lt;/p&gt;

&lt;p&gt;This isn't theoretical compliance. If you're deploying agents in the EU (or selling to companies that operate there), you'll need reconstructable audit trails. The regulation is already in force for some categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;asqav is open source and the SDK is on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The replay feature ships in the next release. If you want to get started with signed audit trails today, the &lt;a href="https://asqav.com/docs" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; takes about five minutes.&lt;/p&gt;

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

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>One-click compliance bundles for AI agent audits</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Mon, 13 Apr 2026 15:28:51 +0000</pubDate>
      <link>https://forem.com/jagmarques/one-click-compliance-bundles-for-ai-agent-audits-4o3p</link>
      <guid>https://forem.com/jagmarques/one-click-compliance-bundles-for-ai-agent-audits-4o3p</guid>
      <description>&lt;p&gt;An auditor walks in and asks for evidence that your AI agents are governed. You have signing data scattered across API responses, CSVs from different time ranges, and a vague explanation of how your Merkle verification works. Good luck putting that together under time pressure.&lt;/p&gt;

&lt;p&gt;This is the compliance evidence problem. You have the data, but packaging it into something an auditor can actually verify takes hours of manual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance bundles in asqav 0.2.11
&lt;/h2&gt;

&lt;p&gt;The new &lt;code&gt;export_bundle&lt;/code&gt; function takes a list of signatures and a compliance framework identifier, then returns a self-contained JSON document with a Merkle root. One file. Everything an auditor needs.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compliance-demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Collect signatures from your agent pipeline
&lt;/span&gt;&lt;span class="n"&gt;signatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql-read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http-external&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api.openai.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file-write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reports/q1.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c1"&gt;# Package into a compliance bundle
&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu_ai_act_art12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audit-q1-2026.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output JSON contains the framework metadata, every signature receipt, individual receipt hashes, and a Merkle root computed over all of them. An auditor can independently verify the root by re-hashing the receipts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four supported frameworks
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;framework&lt;/code&gt; parameter accepts four values out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eu_ai_act_art12&lt;/code&gt; - EU AI Act Article 12 record-keeping requirements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eu_ai_act_art14&lt;/code&gt; - EU AI Act Article 14 human oversight requirements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dora_ict&lt;/code&gt; - Digital Operational Resilience Act (DORA) for financial services&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;soc2&lt;/code&gt; - SOC 2 Type II audit evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each framework maps to specific metadata that gets embedded in the bundle, so the auditor knows exactly which standard the evidence targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in the bundle
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ComplianceBundle&lt;/code&gt; dataclass gives you several ways to work with the data:&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;# Inspect before saving
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# SHA-256 Merkle root
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receipt_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Number of signatures included
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# "eu_ai_act_art12"
&lt;/span&gt;
&lt;span class="c1"&gt;# Export options
&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audit.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Write to disk
&lt;/span&gt;&lt;span class="n"&gt;json_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# Get JSON string
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# Get plain dict
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verification section of the JSON includes the Merkle root, the hash algorithm (SHA-256), and individual receipt hashes so anyone can rebuild the tree and confirm nothing was tampered with.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits into a real workflow
&lt;/h2&gt;

&lt;p&gt;You probably already have signatures from your agent pipeline. The typical flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your agents run and sign actions through asqav (LangChain, CrewAI, whatever framework you use)&lt;/li&gt;
&lt;li&gt;At the end of an audit period, pull signatures via &lt;code&gt;asqav.export_audit_json()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Feed them into &lt;code&gt;asqav.export_bundle(signatures, "eu_ai_act_art12")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hand the auditor one JSON file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No more exporting CSVs. No more explaining your signing pipeline. The bundle is self-describing and independently verifiable.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;asqav&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.2.11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compliance module works entirely client-side. Merkle root computation happens locally using SHA-256, so no extra API calls are needed beyond the signatures you already have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://asqav.com" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Test AI agent governance without touching production</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Mon, 13 Apr 2026 15:24:18 +0000</pubDate>
      <link>https://forem.com/jagmarques/test-ai-agent-governance-without-touching-production-19ie</link>
      <guid>https://forem.com/jagmarques/test-ai-agent-governance-without-touching-production-19ie</guid>
      <description>&lt;p&gt;You built an AI agent pipeline. It works. Users depend on it. Now someone asks you to add governance: audit trails, policy checks, cryptographic signing. Reasonable request. But the thought of plugging a new library into a production pipeline makes your stomach turn.&lt;/p&gt;

&lt;p&gt;What if it adds latency? What if a signing failure kills a chain run? What if a policy misconfiguration blocks legitimate actions?&lt;/p&gt;

&lt;p&gt;This is the observe mode problem. You need to see what governance would do before you let it do anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: observe mode
&lt;/h2&gt;

&lt;p&gt;asqav 0.2.11 ships an &lt;code&gt;observe&lt;/code&gt; flag on every framework adapter. When enabled, the handler logs every action it would sign but makes zero API calls. Your pipeline runs exactly as before. You just get visibility into what governance would look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;asqav.extras.langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsqavCallbackHandler&lt;/span&gt;

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# observe=True: logs actions, never calls the signing API
&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsqavCallbackHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observe&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="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&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;callbacks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="c1"&gt;# Check your logs for lines like:
# OBSERVE: would sign chain:start with context {...}
# OBSERVE: would sign llm:start with context {...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler hooks into every chain start, tool call, and LLM interaction. With &lt;code&gt;observe=True&lt;/code&gt;, each event gets logged with the exact action type and context that would have been signed. No network calls. No latency. No risk.&lt;/p&gt;

&lt;p&gt;Once you are comfortable with what you see in the logs, flip &lt;code&gt;observe=False&lt;/code&gt; and governance goes live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic patterns instead of glob strings
&lt;/h2&gt;

&lt;p&gt;Another friction point: action type strings. In older versions you had to remember namespaces like &lt;code&gt;data:delete:*&lt;/code&gt; or &lt;code&gt;api:call:external:*&lt;/code&gt;. Now you can use semantic labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Before: agent.sign("data:delete:*", {"table": "users"})
# Now:
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql-destructive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK resolves &lt;code&gt;sql-destructive&lt;/code&gt; to &lt;code&gt;data:delete:*&lt;/code&gt; internally. Other built-in patterns include &lt;code&gt;sql-read&lt;/code&gt;, &lt;code&gt;file-write&lt;/code&gt;, &lt;code&gt;http-external&lt;/code&gt;, &lt;code&gt;shell-execute&lt;/code&gt;, &lt;code&gt;pii-access&lt;/code&gt;, and more. You can still use raw glob strings if you prefer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preflight with plain-English explanations
&lt;/h2&gt;

&lt;p&gt;Before 0.2.11, &lt;code&gt;agent.preflight()&lt;/code&gt; returned a boolean and a list of reason codes. Now it includes a human-readable explanation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql-destructive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;explanation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# "Blocked: action not permitted by current policy rules"
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# ["blocked by policy: no-delete-production"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for debugging policy configurations and for showing end users why an agent action was denied.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;asqav&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.2.11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK is open source, MIT licensed, and works with LangChain, CrewAI, LlamaIndex, OpenAI Agents, smolagents, DSPy, LiteLLM, and Haystack. Start with observe mode, review the logs, then go live when you are ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://asqav.com" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Layer 1 is identity, Layer 2 is attestation</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:01:12 +0000</pubDate>
      <link>https://forem.com/jagmarques/layer-1-is-identity-layer-2-is-attestation-353b</link>
      <guid>https://forem.com/jagmarques/layer-1-is-identity-layer-2-is-attestation-353b</guid>
      <description>&lt;p&gt;AI agents are getting identity systems. DIDs, Ed25519 signatures, certificate-based auth. The tooling is growing fast. Microsoft shipped Entra agent identity. AgentNexus brought decentralized identifiers to autonomous agents. This is good work.&lt;/p&gt;

&lt;p&gt;But identity only answers one question: who is this agent?&lt;/p&gt;

&lt;p&gt;It does not answer the harder question: what did this agent actually do, and can it prove it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers, two problems
&lt;/h2&gt;

&lt;p&gt;You can think of agent governance as a stack with two layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 is identity.&lt;/strong&gt; The agent holds a signing key. It can prove who it is to other systems. DID documents, Ed25519 keypairs, X.509 certificates. These all live here. The agent controls the key and uses it to authenticate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 is attestation.&lt;/strong&gt; A separate system, one the agent does not control, certifies what the agent did. Server-side signatures, certifying proxies, neutral third-party receipts. The signing key never touches the agent.&lt;/p&gt;

&lt;p&gt;The distinction matters more than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The student grading their own exam
&lt;/h2&gt;

&lt;p&gt;Layer 1 alone has a structural problem. If the agent controls the signing key, a compromised agent can forge its own identity proofs. It can sign falsified logs. It can claim it did things it never did, or hide things it actually did.&lt;/p&gt;

&lt;p&gt;Self-attestation is a student grading their own exam. It works when trust is high and stakes are low. For anything serious (compliance, audit trails, legal evidence) you need a proctor.&lt;/p&gt;

&lt;p&gt;Layer 2 gives you the proctor. The attestation comes from something the agent cannot tamper with. A server-side signer. A certifying proxy sitting between the agent and the outside world. A receipt generated by infrastructure the agent never touches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why compliance needs both
&lt;/h2&gt;

&lt;p&gt;Under the EU AI Act, Article 12 requires automatic logging for high-risk AI systems. But self-generated logs from the agent itself are weak evidence. If the agent produced the log and signed it with its own key, what stops a compromised agent from producing a clean log?&lt;/p&gt;

&lt;p&gt;Tamper-evident records need to come from outside the agent. That is what Layer 2 provides.&lt;/p&gt;

&lt;p&gt;You need Layer 1 to know who the agent is. You need Layer 2 to know what it did and that it cannot deny it. Identity plus attestation. Both layers, working together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The current gap
&lt;/h2&gt;

&lt;p&gt;Most teams building agents today are focused on Layer 1. Identity is well-understood. We have decades of PKI experience to draw from. The patterns are familiar.&lt;/p&gt;

&lt;p&gt;Layer 2 is less explored. Tools like ArkForge are building certifying proxies. asqav takes a server-side ML-DSA-65 approach where the signing key stays on the server. These are early but they point in the right direction.&lt;/p&gt;

&lt;p&gt;The gap is real. Teams are shipping agents with strong identity and no independent attestation. That is like having a passport with no customs stamps. You can prove who you are but not where you have been.&lt;/p&gt;

&lt;p&gt;If you are building agent infrastructure, Layer 2 deserves as much attention as Layer 1. It is harder to get right. It is also where compliance actually lives.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add governance to DSPy pipelines</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Sun, 12 Apr 2026 07:21:57 +0000</pubDate>
      <link>https://forem.com/jagmarques/add-governance-to-dspy-pipelines-2jgb</link>
      <guid>https://forem.com/jagmarques/add-governance-to-dspy-pipelines-2jgb</guid>
      <description>&lt;p&gt;DSPy pipelines are interesting because they optimize themselves. You write modules, the optimizer rewrites your prompts, and the final program runs in loops making LLM calls and tool invocations without you watching each step. That's the whole point.&lt;/p&gt;

&lt;p&gt;But it also means you can lose track of what happened. A pipeline that ran overnight. Which LLM calls did it make? What tools did it invoke? If it produced a weird output at 4am, good luck reconstructing the chain of events from logs alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two lines after your imports
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav[dspy]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dspy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;asqav.extras.dspy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsqavDSPyCallback&lt;/span&gt;

&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dspy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AsqavDSPyCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire setup. Once that callback is registered, every operation in your DSPy pipeline gets signed automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets signed
&lt;/h2&gt;

&lt;p&gt;The callback hooks into DSPy's event system and signs six event types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dspy.module.start&lt;/code&gt; / &lt;code&gt;dspy.module.end&lt;/code&gt; - when a module begins and finishes execution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dspy.lm.start&lt;/code&gt; / &lt;code&gt;dspy.lm.end&lt;/code&gt; - every LLM call, with the prompt going in and completion coming out&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dspy.tool.start&lt;/code&gt; / &lt;code&gt;dspy.tool.end&lt;/code&gt; - any tool invocations the pipeline makes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each event gets an ML-DSA-65 signature (FIPS 204). The signing happens server-side so the SDK is just a thin API client. You end up with a cryptographic audit trail of every operation your pipeline performed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this fits DSPy well
&lt;/h2&gt;

&lt;p&gt;DSPy programs are compiled. After optimization, the prompts and routing can look nothing like what you originally wrote. That's great for performance but terrible for observability. You need to know what the optimized program actually did, not what you think it should have done.&lt;/p&gt;

&lt;p&gt;The callback sits at the execution layer, so it captures what really happens at runtime. Not the source code, not the original prompts, but the actual calls the compiled program makes.&lt;/p&gt;

&lt;p&gt;Also, DSPy pipelines tend to run in loops during optimization. That means lots of LLM calls, lots of tool invocations. Having a signed record of each one lets you audit the optimization process itself, not just the final output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-open design
&lt;/h2&gt;

&lt;p&gt;Same as the other asqav integrations. If the signing service has an issue, the callback logs a warning and your pipeline keeps running. Governance doesn't become a bottleneck or a failure point. Your DSPy program doesn't care whether the signature succeeded or not.&lt;/p&gt;

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

&lt;p&gt;The SDK is MIT-licensed: &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;github.com/jagmarques/asqav-sdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grab an API key at &lt;a href="https://asqav.com/dashboard" rel="noopener noreferrer"&gt;asqav.com/dashboard&lt;/a&gt;, add those two lines after your imports, and your next pipeline run will have a complete signed audit trail. No changes to your modules, no changes to your tools.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why your AI agent needs a .well-known discovery endpoint</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Sun, 12 Apr 2026 07:21:01 +0000</pubDate>
      <link>https://forem.com/jagmarques/why-your-ai-agent-needs-a-well-known-discovery-endpoint-27of</link>
      <guid>https://forem.com/jagmarques/why-your-ai-agent-needs-a-well-known-discovery-endpoint-27of</guid>
      <description>&lt;p&gt;You know how websites publish &lt;code&gt;/.well-known/security.txt&lt;/code&gt; so security researchers can find the right contact? RFC 9116 made that a standard. Simple file, fixed location, machine-readable. It works because everyone agrees where to look.&lt;/p&gt;

&lt;p&gt;AI agents need something similar. Not for security contacts, for governance discovery.&lt;/p&gt;

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

&lt;p&gt;Say you're integrating with an AI service. You want to know: does this agent sign its actions? What algorithm? Where do I verify a signature? What capabilities does the governance layer support?&lt;/p&gt;

&lt;p&gt;Right now you'd dig through docs, maybe find an API reference, maybe email someone. There's no standard place to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;/.well-known/governance.json&lt;/code&gt; file at a predictable URL. Any client or auditor can fetch it and immediately understand what governance is available.&lt;/p&gt;

&lt;p&gt;Here's what goes in it:&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;"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;"your-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&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"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endpoints"&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;"sign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/v1/agents/{agent_id}/sign"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/v1/verify/{signature_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"agents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/v1/agents"&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;"algorithms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ml-dsa-44"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ml-dsa-65"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ml-dsa-87"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&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="s2"&gt;"sign"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"delegation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"policy_enforcement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"audit_export_csv"&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;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"X-API-Key"&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;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&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 key sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;endpoints&lt;/strong&gt; - where to sign actions, verify signatures, manage agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;algorithms&lt;/strong&gt; - which cryptographic algorithms are supported (ML-DSA is the FIPS 204 post-quantum family)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;capabilities&lt;/strong&gt; - what the governance layer can actually do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;auth&lt;/strong&gt; - how to authenticate&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters for interoperability
&lt;/h2&gt;

&lt;p&gt;If every AI governance provider publishes this file at the same path, tooling can auto-discover it. CI/CD pipelines can check if governance is configured. Auditors can programmatically verify that an agent's governance claims match reality.&lt;/p&gt;

&lt;p&gt;It's the same reason &lt;code&gt;robots.txt&lt;/code&gt; works. Convention over configuration. You don't need a registry or a discovery service. Just a file at a known path.&lt;/p&gt;

&lt;h2&gt;
  
  
  A live example
&lt;/h2&gt;

&lt;p&gt;We publish ours at &lt;a href="https://asqav.com/.well-known/governance.json" rel="noopener noreferrer"&gt;asqav.com/.well-known/governance.json&lt;/a&gt;. It includes endpoints, supported algorithms, integration list, and links to docs. Anyone can fetch it right now.&lt;/p&gt;

&lt;p&gt;But this isn't an asqav-specific idea. Any project doing AI governance could publish the same file. The schema is straightforward. Adapt it to whatever your service provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopting this yourself
&lt;/h2&gt;

&lt;p&gt;If you run an AI governance service, or even just an agent platform with audit capabilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;/.well-known/governance.json&lt;/code&gt; on your domain&lt;/li&gt;
&lt;li&gt;List your endpoints, algorithms, and capabilities&lt;/li&gt;
&lt;li&gt;Keep it updated when your API changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No spec to implement. No committee to join. Just a JSON file at a URL that makes sense.&lt;/p&gt;

&lt;p&gt;The more projects that do this, the easier it gets to build tooling around AI governance discovery. And that's the whole point, making governance something you can verify programmatically instead of taking someone's word for it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Add governance to Hugging Face smolagents in 4 lines</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Sun, 12 Apr 2026 07:20:11 +0000</pubDate>
      <link>https://forem.com/jagmarques/add-governance-to-hugging-face-smolagents-in-4-lines-192h</link>
      <guid>https://forem.com/jagmarques/add-governance-to-hugging-face-smolagents-in-4-lines-192h</guid>
      <description>&lt;p&gt;I was building an agent with Hugging Face's smolagents last month and hit a problem that kept bugging me. The agent ran tools, made decisions, called APIs, and I had zero record of what happened. If something went wrong, I'd be guessing.&lt;/p&gt;

&lt;p&gt;smolagents gives you a clean way to build tool-calling agents. But once a tool runs, there's no audit trail. No proof of what was called, when, or what it returned. For hobby projects, fine. For anything touching production data, that's a gap.&lt;/p&gt;

&lt;p&gt;Here's how I plugged it with asqav in 4 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asqav[smolagents]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;asqav.extras.smolagents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsqavSmolagentsHook&lt;/span&gt;

&lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsqavSmolagentsHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-smolagent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signed_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;wrap_tool&lt;/code&gt; takes your existing tool and returns a wrapped version that signs three events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tool:start&lt;/code&gt; - when the tool is called, with its input parameters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool:end&lt;/code&gt; - when it completes, with the output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool:error&lt;/code&gt; - if it throws, with the exception details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each event gets a cryptographic signature using ML-DSA-65 (that's FIPS 204, the post-quantum standard). Signatures happen server-side so the SDK stays thin. You get a tamper-evident log of every tool execution your agent performed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about the design
&lt;/h2&gt;

&lt;p&gt;It's fail-open. If the signing service is down or something goes wrong with the signature, it logs a warning and moves on. Your agent keeps working. Governance shouldn't be a single point of failure for your pipeline.&lt;/p&gt;

&lt;p&gt;You also don't need to change your tool's code. The hook wraps the tool at the boundary. Your tool function stays exactly the same. Swap it in, swap it out, your logic doesn't care.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this matters
&lt;/h2&gt;

&lt;p&gt;If you're running smolagents that touch external APIs, databases, or anything with side effects, you probably want to know what happened after the fact. Debugging is one thing. But if you need to show an auditor or a compliance team what your agent did last Tuesday at 3am, "I think it worked fine" doesn't cut it.&lt;/p&gt;

&lt;p&gt;The signed events give you a verifiable chain. Each signature can be independently verified, and the log can't be quietly edited after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The SDK is open source: &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;github.com/jagmarques/asqav-sdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grab an API key from &lt;a href="https://asqav.com/dashboard" rel="noopener noreferrer"&gt;asqav.com/dashboard&lt;/a&gt; and wrap your first tool. Four lines, and you've got a governance layer that doesn't get in the way.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>SDK v0.2.9: Output Verification, Attestations, Preflight and Budgets</title>
      <dc:creator>João André Gomes Marques</dc:creator>
      <pubDate>Fri, 10 Apr 2026 18:24:31 +0000</pubDate>
      <link>https://forem.com/jagmarques/sdk-v029-output-verification-attestations-preflight-and-budgets-17a9</link>
      <guid>https://forem.com/jagmarques/sdk-v029-output-verification-attestations-preflight-and-budgets-17a9</guid>
      <description>&lt;p&gt;v0.2.9 is out on PyPI. Four new things, all driven by what people asked for after shipping agents to production: prove the output wasn't swapped, hand a customer a document they can verify themselves, check everything before you run, and stop agents burning through a budget.&lt;/p&gt;

&lt;p&gt;Install or upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; asqav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Output verification
&lt;/h2&gt;

&lt;p&gt;Signing that an action happened is one thing. Proving the output you see now is the same output the agent produced is another. &lt;code&gt;sign_output&lt;/code&gt; binds a hash of the result to a hash of the input, then &lt;code&gt;verify_output&lt;/code&gt; checks both later.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latest NIST PQC guidance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;result&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;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FIPS 203, 204, 205 finalized in 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool:search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;input_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_hash_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Later, or on another machine:
&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signature_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# signature valid AND output matches
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anyone changes a character in &lt;code&gt;result&lt;/code&gt; before verification, &lt;code&gt;output_matches&lt;/code&gt; comes back false. If the signature itself was tampered with, &lt;code&gt;signature_valid&lt;/code&gt; comes back false. You get both signals separately so you can tell what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Portable attestations
&lt;/h2&gt;

&lt;p&gt;You often need to show someone outside your team that an agent did what it was supposed to, without giving them access to your Asqav account. &lt;code&gt;generate_attestation&lt;/code&gt; builds a self-contained document with the agent's public key, a session summary, and a signature over the whole thing. &lt;code&gt;verify_attestation&lt;/code&gt; checks it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contract-reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... run the agent, sign actions ...
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_attestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Hand doc.json to an auditor. They run:
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_attestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signatures_checked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attestation carries its own hash and signature, plus every signature ID from the session. The auditor doesn't need your keys. They just need the SDK and the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-flight checks
&lt;/h2&gt;

&lt;p&gt;Before v0.2.9 you had to call three things to know if an agent was cleared: status, policy list, and sometimes certificate. &lt;code&gt;preflight&lt;/code&gt; does it in one call and tells you why if it says no.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agt_abc123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api:transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocked:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;

&lt;span class="c1"&gt;# Agent is active and policy allows the action. Proceed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;cleared&lt;/code&gt;, &lt;code&gt;agent_active&lt;/code&gt;, &lt;code&gt;policy_allowed&lt;/code&gt;, and a list of reasons. If a sub-check errors, the reason is noted but the agent isn't blocked on infrastructure hiccups. Run it at the top of any sensitive action and you catch revoked agents and policy blocks before you waste an LLM call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget tracking
&lt;/h2&gt;

&lt;p&gt;Agents that call paid APIs need a ceiling. &lt;code&gt;BudgetTracker&lt;/code&gt; enforces the limit client-side and signs every spend entry so the trail is tamper-evident.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data-enricher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asqav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BudgetTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "budget_exhausted"
&lt;/span&gt;
&lt;span class="c1"&gt;# ... call OpenAI, measure actual cost ...
&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api:openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&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;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# {'limit': 10.0, 'currency': 'USD', 'spend': 0.23, 'remaining': 9.77, 'records': 1}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;record&lt;/code&gt; call writes a signed entry through the agent's key. You can replay the trail against the verification endpoint at any point and prove exactly where the money went. Negative, NaN, and infinite costs all get rejected. The check fails closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these four
&lt;/h2&gt;

&lt;p&gt;These are the gaps people kept hitting. Signing the call isn't enough if the output can be swapped. Signatures aren't useful if you can't hand one to an external reviewer. Status and policy checks want to be one call. Budget caps need to be real, not a comment in a README.&lt;/p&gt;

&lt;p&gt;Full changelog and source on &lt;a href="https://github.com/jagmarques/asqav-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Docs at &lt;a href="https://www.asqav.com/docs/" rel="noopener noreferrer"&gt;asqav.com/docs&lt;/a&gt;. If something doesn't work the way you expect, open an issue.&lt;/p&gt;

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