<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: OpsVeritas</title>
    <description>The latest articles on DEV Community by OpsVeritas (opsveritas).</description>
    <link>https://dev.to/opsveritas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13404%2F56e2340b-6cae-4cc9-9224-ecb013f9d8b9.png</url>
      <title>DEV Community: OpsVeritas</title>
      <link>https://dev.to/opsveritas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opsveritas"/>
    <language>en</language>
    <item>
      <title>Delegation Masking: Why Your LangChain Callbacks Lie About Sub-Agent Failures</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Tue, 28 Jul 2026 08:38:58 +0000</pubDate>
      <link>https://dev.to/opsveritas/delegation-masking-why-your-langchain-callbacks-lie-about-sub-agent-failures-4l02</link>
      <guid>https://dev.to/opsveritas/delegation-masking-why-your-langchain-callbacks-lie-about-sub-agent-failures-4l02</guid>
      <description>&lt;p&gt;You delegate a task from Agent A to Agent B in LangChain. Agent B fails. Agent A's callback chain fires 'success' anyway.&lt;/p&gt;

&lt;p&gt;This is the observability blind spot most builders miss in agentic workflows: &lt;strong&gt;delegation masking&lt;/strong&gt;. A sub-agent fails silently, but the parent agent's callback layer never knows because it only watches the delegation &lt;em&gt;call itself&lt;/em&gt;, not what the delegated agent actually did.&lt;/p&gt;

&lt;p&gt;Let's walk the mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Delegation Pattern in LangChain
&lt;/h2&gt;

&lt;p&gt;When you wire up agent-to-agent delegation in LangChain, you're typically using the &lt;code&gt;tool&lt;/code&gt; decorator to wrap a sub-agent invocation:&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delegate_to_classification_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Delegate classification to a specialized sub-agent.&lt;/span&gt;&lt;span class="sh"&gt;"""&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;classification_agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;output&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent agent treats this as just another tool. It calls it, gets a result, moves on. The parent agent's callback chain (the layer that logs success/failure, fires alerts, measures latency) only sees the &lt;strong&gt;function return value&lt;/strong&gt;, not the internal state of the sub-agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Failure Hides
&lt;/h2&gt;

&lt;p&gt;Here's what can happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent B (sub-agent) fails to produce valid output.&lt;/strong&gt; Its internal chain breaks, maybe a tool call failed, or output parsing broke, or the LLM went silent. Agent B's callback chain logs the failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;But the delegation function still returns something.&lt;/strong&gt; Maybe it returns an empty string, a cached fallback, or a generic error message. It doesn't raise an exception, it just returns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent A's callback layer sees HTTP 200.&lt;/strong&gt; The delegation tool returned &lt;em&gt;something&lt;/em&gt;, so the callback fires &lt;code&gt;on_tool_end&lt;/code&gt; with &lt;code&gt;status: "success"&lt;/code&gt;. The parent agent logs success, moves on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The actual failure is buried two layers deep.&lt;/strong&gt; Agent A's monitoring sees "delegation succeeded." Only if someone digs into Agent B's logs does the failure surface.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a &lt;strong&gt;callback visibility boundary&lt;/strong&gt;. The parent agent's instrumentation layer is one level too high to catch delegation failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Signal That Catches It
&lt;/h2&gt;

&lt;p&gt;Standard token-counting observability misses this because both agents might report partial token usage (Agent B started, burned some tokens, then failed). A success callback fired, so metrics look nominal.&lt;/p&gt;

&lt;p&gt;What actually catches delegation failures:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Output validation at the delegation boundary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Track what the delegation function &lt;em&gt;actually returned&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delegate_to_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Delegate with observability.&lt;/span&gt;&lt;span class="sh"&gt;"""&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;sub_agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&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="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;output&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="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Signal: validate the output exists and is non-empty
&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;output&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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;# This is a silent failure, the sub-agent ran but produced nothing
&lt;/span&gt;        &lt;span class="nf"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delegation_produced_empty_output&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;delegated_agent&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;classification_agent&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;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&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_status&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="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;status&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;token_count&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="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;usage&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="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;output_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signal is: &lt;strong&gt;a delegation that returned empty or unchanged input&lt;/strong&gt;. The parent agent's callback sees "success," but observability knows something went wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cross-agent execution correlation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Agent A calls Agent B, log a &lt;strong&gt;correlation ID&lt;/strong&gt; that links both agents' execution traces:&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;uuid&lt;/span&gt;

&lt;span class="n"&gt;correlation_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# In Agent A's tool:
&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;sub_agent&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;correlation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;correlation_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;metadata&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;correlation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;correlation_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;# In sub-agent's callback handler:
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_tool_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;corr_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;metadata&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="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;correlation_id&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;corr_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_execution&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;correlation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;corr_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_tokens&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;status&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;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&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;Now you can query: &lt;em&gt;"What sub-agent executions have a correlation_id but show empty output or error status?"&lt;/em&gt; That's where delegation failures hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Aggregate success rate per agent pair&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Track success rates at the &lt;strong&gt;delegation edge&lt;/strong&gt;, not just per-agent:&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;delegation_success_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;executions&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;parent_agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;delegated_agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;output_validation_passed&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="n"&gt;executions&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;parent_agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;delegated_agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Agent A to Agent B delegation shows 95% success in parent logs but only 70% pass output validation, you've found the callback masking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Frameworks Don't Catch This
&lt;/h2&gt;

&lt;p&gt;LangChain's callback layer is designed to instrument the &lt;strong&gt;calling agent's perspective&lt;/strong&gt;, not the called agent's internal state. That's by design, clean separation of concerns. But it means delegation failures are invisible until they propagate (or don't).&lt;/p&gt;

&lt;p&gt;Most frameworks have the same boundary. CrewAI's task delegation, AutoGen's sub-agent calls, they all fire success callbacks when the &lt;em&gt;call itself&lt;/em&gt; succeeds, regardless of what the called agent actually did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Observability Fix
&lt;/h2&gt;

&lt;p&gt;You need &lt;strong&gt;one layer deeper&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instrument sub-agents independently.&lt;/strong&gt; Log their execution status, output validity, token usage. Don't rely on the parent agent's callback to know what happened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate delegation outputs.&lt;/strong&gt; Don't trust that a delegation function returning a value means the sub-agent actually succeeded. Check the output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correlate across agents.&lt;/strong&gt; Link parent and child agent executions so failures propagate upward in your observability dashboard, not just downward in logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The callback chain is essential, but it's not enough. &lt;strong&gt;Delegation visibility requires you to see both sides of the boundary.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The takeaway:&lt;/strong&gt; When agents delegate to other agents, callback success is not the same as execution success. Standard monitoring stays silent because the parent agent's callbacks only see the delegation call, not the delegated agent's actual work. Catch it by validating outputs, correlating executions across agents, and measuring success rates at the delegation edge.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>langchain</category>
      <category>observability</category>
      <category>llmops</category>
    </item>
    <item>
      <title>Instrumentation Patterns for AI Agents: SDK vs Webhook</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 27 Jul 2026 05:30:28 +0000</pubDate>
      <link>https://dev.to/opsveritas/instrumentation-patterns-for-ai-agents-sdk-vs-webhook-467h</link>
      <guid>https://dev.to/opsveritas/instrumentation-patterns-for-ai-agents-sdk-vs-webhook-467h</guid>
      <description>&lt;p&gt;When you instrument a distributed system — a microservice mesh, a backend job queue, a real-time event pipeline — you don't ask "should we?" You ask "how?" And you know the playbook: wrap your client, push telemetry, choose your transport, decide on sampling.&lt;/p&gt;

&lt;p&gt;AI agents need the same discipline. But right now, most builders either skip instrumentation entirely or bolt it on as an afterthought. The gap between "my agent runs" and "I know what my agent actually did" is where silent failures hide, cost spikes live invisible, and production incidents start.&lt;/p&gt;

&lt;p&gt;There are two proven patterns for wiring observability into AI agents: SDK-based instrumentation and webhook-based telemetry. Neither is universally better, each trades off deployment simplicity, latency impact, privacy scope, and operational control. Understanding those tradeoffs matters: it determines whether you catch silent failures before your customers do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: SDK Instrumentation
&lt;/h2&gt;

&lt;p&gt;With the SDK pattern, you install a lightweight library into your agent's runtime and wrap your model client, the OpenAI, Anthropic, or Gemini instance your agent actually calls.&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;# Install: pip install opsveritas
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opsveritas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&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;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_AGENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&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;wrapped_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# That's it
&lt;/span&gt;
&lt;span class="c1"&gt;# Now your agent uses wrapped_client instead of client
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wrapped_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK intercepts the call before it leaves your process, reads the request metadata and response (tokens, latency, cost, parsed output), and ships that telemetry asynchronously. Your agent's latency is unaffected; the SDK's overhead is a few milliseconds of serialization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tradeoffs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low latency impact. Telemetry is pushed in the background, so your agent's response time doesn't change.&lt;/li&gt;
&lt;li&gt;In-process visibility. The SDK sees the raw request and response before they leave your Python or Node process, capturing token counts, model name, and optionally a summary of the output without re-parsing.&lt;/li&gt;
&lt;li&gt;Framework coverage. SDKs can auto-instrument specific client libraries (OpenAI, Anthropic, Gemini) and frameworks (LangChain callbacks, CrewAI integration). Each integration is narrow but deep.&lt;/li&gt;
&lt;li&gt;Operational cost. You manage telemetry transport, meaning SDK retries, buffering, batching. If your network is flaky, telemetry may queue or drop.&lt;/li&gt;
&lt;li&gt;Privacy scope. The SDK runs in your environment; you control whether to strip output text, run in metadata-only mode, or send full details.&lt;/li&gt;
&lt;li&gt;Framework coupling. You depend on SDK updates to support new models or client libraries. An obscure or internal LLM client won't be auto-instrumented.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 2: Webhook Instrumentation
&lt;/h2&gt;

&lt;p&gt;With the webhook pattern, you don't install a library. Instead, you POST telemetry directly to an observability service from your agent code.&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;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="c1"&gt;# After your agent runs
&lt;/span&gt;&lt;span class="n"&gt;response&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_name&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;document-processor&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;status&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;success&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;executed_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&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-4o&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;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0075&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ai-agents-control-tower.onrender.com/webhooks/agent-execution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;x-agents-key&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;YOUR_ORG_SECRET&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You decide what to capture and POST it yourself. There's no magic, just HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tradeoffs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework-agnostic. Works with any agent framework, any LLM client, even custom scripts. You're not locked into SDK coverage.&lt;/li&gt;
&lt;li&gt;Operational control. You own the payload shape, so you can capture custom fields (user ID, feature flags, request context) that matter to your business.&lt;/li&gt;
&lt;li&gt;Network latency. The webhook is an HTTP request. If your observability service is slow or the network is congested, it adds latency to your agent's response time unless you fire-and-forget with an async task.&lt;/li&gt;
&lt;li&gt;Manual instrumentation. You have to write the code to collect and POST telemetry. It's not automatically captured the way the SDK auto-patches a client.&lt;/li&gt;
&lt;li&gt;Privacy-first. You decide exactly what data gets shipped. No SDK auto-capturing output text or summarizing responses unless you code it.&lt;/li&gt;
&lt;li&gt;Operational resilience. If the observability service is down, your webhook requests will fail. You need retry logic and queueing to avoid blocking your agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How they differ in practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Capture scope:&lt;/strong&gt; the SDK automatically captures tokens, latency, model, and output (configurable). With webhooks, you decide, and minimal setup means only the fields you code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency cost:&lt;/strong&gt; SDK overhead is negligible, async telemetry serialization. Webhooks add 50 to 500ms per request unless you async-queue them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to first signal:&lt;/strong&gt; with the SDK it's immediate, since telemetry is already in your code. With webhooks you add instrumentation per agent or per framework, which takes more planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling new models:&lt;/strong&gt; SDK updates add support and you upgrade. With webhooks you handle it yourself, usually just adding the cost calculation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy:&lt;/strong&gt; the SDK is configurable, with a metadata-only mode that strips all output content. Webhooks send whatever you choose to POST.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use each
&lt;/h2&gt;

&lt;p&gt;Use the SDK if you have a small number of well-known model clients (OpenAI, Anthropic, Gemini), want observability with minimal code changes, your agent is latency-sensitive and can't afford webhook round-trips, or you're using a supported framework like LangChain or CrewAI and want callbacks wired automatically.&lt;/p&gt;

&lt;p&gt;Use webhooks if you have a heterogeneous stack (internal LLM API, third-party models, multiple clients), need custom telemetry fields (user context, feature flags, request metadata), want to avoid SDK dependencies and keep your deployment simple, or you're comfortable managing retry logic and async queueing.&lt;/p&gt;

&lt;p&gt;Use both if you have a hybrid setup: SDKs for critical paths like real-time APIs, webhooks for background jobs and batch processing.&lt;/p&gt;

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

&lt;p&gt;In practice, the pattern you choose shapes your observability architecture for months. The SDK path is faster to ship but locks you into SDK coverage. The webhook path requires more upfront design but gives you more flexibility.&lt;/p&gt;

&lt;p&gt;Most production AI systems end up using both: the SDK for OpenAI/Anthropic agents in hot paths where latency matters, webhooks for heterogeneous or custom setups. The tradeoff isn't binary, it's contextual.&lt;/p&gt;

&lt;p&gt;The core insight is that instrumentation isn't optional. Whether you choose SDK or webhooks, the choice forces you to think about what you need to observe, and that discipline is what catches silent failures before production users do.&lt;/p&gt;

&lt;p&gt;Pick the pattern that matches your architecture. Wire it in before you ship to production. And don't wait until a cost spike or a failed task to realize you have no visibility into what actually ran.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>llmops</category>
      <category>devops</category>
    </item>
    <item>
      <title>Token Anomaly Detection: The Algorithm That Stops Runaway Loops Before the Bill Arrives</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sat, 25 Jul 2026 05:37:25 +0000</pubDate>
      <link>https://dev.to/opsveritas/token-anomaly-detection-the-algorithm-that-stops-runaway-loops-before-the-bill-arrives-56ae</link>
      <guid>https://dev.to/opsveritas/token-anomaly-detection-the-algorithm-that-stops-runaway-loops-before-the-bill-arrives-56ae</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: Why Token Spikes Hide in Plain Sight
&lt;/h2&gt;

&lt;p&gt;Most AI teams monitor agent executions at the binary level: succeeded or failed. The agent gets 200 OK, so it worked. But there's a whole category of failure that standard monitoring misses: the execution that succeeds but consumes 10× its normal token budget in the process.&lt;/p&gt;

&lt;p&gt;This happens most often when an agent enters a loop. It calls a tool, checks the result, calls the tool again to refine, checks again—each iteration adds to the token count. Twenty iterations later, an execution that should have cost $0.02 has cost $2.00. And it still returns 200.&lt;/p&gt;

&lt;p&gt;By the time you see the spike in your monthly invoice, you've already lost thousands.&lt;/p&gt;

&lt;p&gt;The fix isn't to ban loops—sometimes agents need iteration to get the right answer. The fix is to detect meaningful token anomalies &lt;em&gt;before they become expensive&lt;/em&gt;—in the first execution, before the cascade.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math: Percentile Baselines and Z-Scores
&lt;/h2&gt;

&lt;p&gt;A solid anomaly detector needs two parts: a baseline that captures "normal," and a method to detect when an execution drifts far from it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Build a Percentile Baseline
&lt;/h3&gt;

&lt;p&gt;Don't use the mean. A running average is too easily pulled upward by a few expensive runs, and it doesn't tell you about the shape of your typical behavior.&lt;/p&gt;

&lt;p&gt;Instead, track percentiles of token usage over a rolling window (typically 30 days or the last 100 runs, whichever is larger):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p50 = median token count
p95 = 95th percentile token count
p99 = 99th percentile token count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why percentiles? Because they're robust to outliers. If your agent normally uses 500 tokens but one run uses 50,000 (an anomaly you're trying to catch), the median and p95 stay grounded in typical behavior. The mean would shift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: Flag a Token Spike When Z &amp;gt; 2.5
&lt;/h3&gt;

&lt;p&gt;Once you have your baseline (especially p95), check each new execution:&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;z_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens_this_run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;standard_deviation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;z_score &amp;gt; 2.5&lt;/code&gt;, flag it. (2.5 is a threshold; adjust based on false-alarm tolerance. Higher = fewer alarms but risk missing real spikes; lower = more false alarms but catch edge cases earlier.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why 2.5 and not 2?&lt;/strong&gt; At 2.0 standard deviations, you'd expect ~2.3% of &lt;em&gt;normal&lt;/em&gt; runs to be flagged (false alarms). At 2.5, it's ~0.6%. For cost governance, false alarms are cheap (annoying, but you're just investigating); missing a real spike costs money.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code Sketch
&lt;/h3&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenAnomalyDetector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxlen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;window_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z_threshold&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&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;token_count&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;is_anomalous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Need enough data
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="n"&gt;tokens_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;p95&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens_array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens_array&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;std&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# All runs identical (rare, but handle it)
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p95&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;

        &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;

        &lt;span class="n"&gt;is_anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z_threshold&lt;/span&gt;

        &lt;span class="c1"&gt;# Record the run for future baselines
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;is_anomaly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record every execution's token count in the window. When a new execution arrives, compute p95 and standard deviation from the window, calculate z_score, and flag if it breaches the threshold. Then add the new execution to the window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Correlate With Latency
&lt;/h2&gt;

&lt;p&gt;Token anomalies often come paired with latency spikes. If an execution took 10× its normal tokens &lt;em&gt;and&lt;/em&gt; took 5× longer to complete, it's almost certainly looping or stuck in retry logic.&lt;/p&gt;

&lt;p&gt;Add a second signal:&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;latency_z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_this_run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p95_latency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;std_latency&lt;/span&gt;
&lt;span class="n"&gt;anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Both must spike
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requiring both signals to spike simultaneously is more conservative—fewer false alarms—but you catch the real culprits: the agent that both burned tokens &lt;em&gt;and&lt;/em&gt; took forever. That's a loop.&lt;/p&gt;

&lt;p&gt;If you want to catch even fast anomalies (e.g., an agent that made 50 tool calls in rapid succession, token-heavy but latency-OK), track &lt;strong&gt;tool-call count&lt;/strong&gt; as a third signal:&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;tool_calls_z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_calls_this_run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p95_tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;std_tool_calls&lt;/span&gt;
&lt;span class="n"&gt;anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_calls_z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Either signal fires
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you catch both slow loops (token + latency spike) and fast loops (token + tool-call count spike).&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together: The Alert
&lt;/h2&gt;

&lt;p&gt;When an anomaly is detected, surface it immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which agent:&lt;/strong&gt; name and framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What spiked:&lt;/strong&gt; tokens (from 500 to 12,000), latency (from 2s to 15s), tool calls (from 3 to 47).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The baseline:&lt;/strong&gt; "normal p95 is 600 tokens; this run was 20× higher."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost:&lt;/strong&gt; if tokens spike 10×, cost spiked 10×. Show the dollar amount to make it real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The action:&lt;/strong&gt; link to the run's full execution trace so you can see which tool call started the loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the AI Agents Control Tower, this surfaces as a &lt;strong&gt;Token Anomaly&lt;/strong&gt; alert—one of the 12 alert types you can subscribe to. It fires only when a run's token usage jumps far above the agent's baseline, so you catch cost problems before they compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The difference between detecting a runaway loop in its first execution (cost: $0.50, alert received in seconds) and detecting it in your monthly invoice (cost: $5,000, damage already done) is an anomaly detector.&lt;/p&gt;

&lt;p&gt;The math is simple. The impact is large.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt; If you're building AI agents, set a token baseline from day one. Use percentiles, not averages. And correlate token spikes with latency and tool-call count—multiple signals reduce false alarms and catch real problems faster.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>python</category>
    </item>
    <item>
      <title>One Agent Times Out. Three More Agents Don't Notice.</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Fri, 24 Jul 2026 05:42:26 +0000</pubDate>
      <link>https://dev.to/opsveritas/one-agent-times-out-three-more-agents-dont-notice-o61</link>
      <guid>https://dev.to/opsveritas/one-agent-times-out-three-more-agents-dont-notice-o61</guid>
      <description>&lt;p&gt;One agent times out. Three more agents don't notice.&lt;/p&gt;

&lt;p&gt;Here's how a single point of failure becomes invisible in a distributed multi-agent system.&lt;/p&gt;

&lt;p&gt;Agent A is a document processor. It's supposed to fetch a user's uploaded file, extract structured data, and pass it downstream. Latency SLA: 5 seconds.&lt;/p&gt;

&lt;p&gt;On Tuesday at 2:47 PM, the file server hiccups. Agent A's request hangs for 7 seconds, then times out. Agent A returns an error to its caller—Agent B, the orchestrator.&lt;/p&gt;

&lt;p&gt;Agent B sees the timeout. Rather than halt, it has a fallback: retry using the last successful result from Agent C, a caching layer. Agent C was pinged 40 minutes earlier; it has stale data from the user's previous upload. Agent B doesn't know it's stale. It just knows it's available. Agent B retrieves it and passes it to Agent D.&lt;/p&gt;

&lt;p&gt;Agent D is the final step: validation and storage. It receives the data from Agent B, runs a schema check (passes—the data is well-formed, just old), and writes to the database. Agent D returns HTTP 200.&lt;/p&gt;

&lt;p&gt;From the outside: the system succeeded. Four agents ran. All returned success. A user's file was "processed."&lt;/p&gt;

&lt;p&gt;Except the data in the database is from last week.&lt;/p&gt;

&lt;p&gt;The user didn't notice until the next day, when they queried their own data and saw timestamps that didn't match their actions.&lt;/p&gt;

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

&lt;p&gt;In single-agent systems, timeouts and retries are loud. One agent fails, the caller sees it.&lt;/p&gt;

&lt;p&gt;In distributed multi-agent cascades, failures become options. Agent B didn't panic at the timeout—it had a graceful path. Agent C had cached data. Agent D validated successfully. Every agent did its job. The system appeared healthy to standard monitoring because every individual agent reported success.&lt;/p&gt;

&lt;p&gt;The silent failure lived in the handoff: Agent B made a reasonable choice (use cached data) without visibility into whether that cached data was still valid. Agent D validated syntax, not currency. Agent A's timeout was absorbed, not propagated.&lt;/p&gt;

&lt;p&gt;Standard monitoring sees four successful executions. It doesn't see that Agent B's retry decision was made on stale information, or that Agent D validated the wrong thing.&lt;/p&gt;

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

&lt;p&gt;Distributed multi-agent systems amplify a silent-failure risk that single agents don't have: one agent's failure mode becomes another agent's fallback option, and nobody asks whether the fallback is correct for this execution.&lt;/p&gt;

&lt;p&gt;When you move from monitoring individual agents to monitoring agent ecosystems, you need visibility at the handoff points. Not just "did agent B run?" but "did agent B have fresh data when it made its retry decision?" and "did agent D validate the right version?"&lt;/p&gt;

&lt;p&gt;Without that, you're monitoring the parts but not the system.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're running multi-agent systems in production, worth asking: can you trace what each agent actually received from the one before it? If not, you're flying partially blind.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>monitoring</category>
      <category>llm</category>
    </item>
    <item>
      <title>Multi-Agent Pre-Flight Checklist: Testing Cascade Detection Before Production</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Thu, 23 Jul 2026 05:22:54 +0000</pubDate>
      <link>https://dev.to/opsveritas/multi-agent-pre-flight-checklist-testing-cascade-detection-before-production-1ad8</link>
      <guid>https://dev.to/opsveritas/multi-agent-pre-flight-checklist-testing-cascade-detection-before-production-1ad8</guid>
      <description>&lt;p&gt;&lt;em&gt;Real stories on why automations and AI agents report "success" while quietly doing nothing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before you deploy a multi-agent system to production, one hard question: if a sub-agent fails silently, does your orchestrator know?&lt;/p&gt;

&lt;p&gt;Most don't. And that's exactly where cascading failures hide.&lt;/p&gt;

&lt;p&gt;When Agent A delegates work to Agent B, and Agent B returns HTTP 200 but produces zero output, the orchestrator often assumes success -- until your customer notices the work never happened. By then, the failure has already masked itself up the chain.&lt;/p&gt;

&lt;p&gt;Here's a three-part checklist to catch this &lt;strong&gt;before&lt;/strong&gt; production.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Chaos-Inject Silent Failures into Sub-Agents
&lt;/h2&gt;

&lt;p&gt;In your test environment, deliberately inject a silent failure into one sub-agent: have it return success with empty output (zero output tokens, blank response). Then run your orchestrator end-to-end.&lt;/p&gt;

&lt;p&gt;Does your orchestrator detect the empty output from the sub-agent? Does it flag it as a problem, or does it treat 200 as "success and move on"? If the latter, you have a cascade risk.&lt;/p&gt;

&lt;p&gt;Test this for each sub-agent independently. A multi-agent system is only as reliable as its weakest detection path.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Validate Fallback Chains Don't Mask the Real Failure
&lt;/h2&gt;

&lt;p&gt;Fallbacks are good -- but only if they surface the underlying failure, not hide it.&lt;/p&gt;

&lt;p&gt;Scenario: Agent A calls Agent B, which fails silently. Agent A's fallback kicks in and produces a result. Now the orchestrator sees a successful end-to-end run. But Agent B's failure is invisible -- baked into the fallback response without a flag.&lt;/p&gt;

&lt;p&gt;Before production, run a test where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sub-agent fails silently.&lt;/li&gt;
&lt;li&gt;Fallback executes and succeeds.&lt;/li&gt;
&lt;li&gt;Check: does the final result carry metadata indicating "fallback was triggered"? Can you distinguish between "worked perfectly" and "fell back after a silent failure"?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't, you're masking failures you should know about.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Cross-Agent Callback Correlation
&lt;/h2&gt;

&lt;p&gt;In a multi-agent system, each agent fires callbacks (on execution, tool call, output). If these callbacks aren't correlated across agents, you lose visibility into the chain.&lt;/p&gt;

&lt;p&gt;Before production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instrument each agent with callback handlers that include a &lt;strong&gt;trace ID&lt;/strong&gt; (a unique identifier that flows from orchestrator to sub-agent to callback).&lt;/li&gt;
&lt;li&gt;Verify that when Agent A calls Agent B, the trace ID is propagated -- so you can later correlate all callbacks from Agent B back to Agent A's execution.&lt;/li&gt;
&lt;li&gt;Without correlation, a silent failure in Agent B looks like an orphaned callback from nowhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Cascading failures in multi-agent systems are silent &lt;em&gt;by design&lt;/em&gt;: each layer reports success, so the failure hides until it bubbles up as a customer complaint. Testing for these patterns now -- before production -- costs hours. Missing them costs dollars and reputation.&lt;/p&gt;

&lt;p&gt;Run these three tests on your system. If any fail, you have observability work to do before shipping.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We build &lt;a href="https://opsveritas.com" rel="noopener noreferrer"&gt;OpsVeritas&lt;/a&gt; and &lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;AI Agents Control Tower&lt;/a&gt; -- monitoring layers that catch silent failures like these across your automations and AI agents before your customers do.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>automation</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Your CrewAI Agent Delegates a Task. It Fails Silently. The Caller Never Knows.</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Wed, 22 Jul 2026 06:37:51 +0000</pubDate>
      <link>https://dev.to/opsveritas/your-crewai-agent-delegates-a-task-it-fails-silently-the-caller-never-knows-4i2n</link>
      <guid>https://dev.to/opsveritas/your-crewai-agent-delegates-a-task-it-fails-silently-the-caller-never-knows-4i2n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxyv5b7kfwzl5ftrqiu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faxyv5b7kfwzl5ftrqiu1.png" alt="cover" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agent A delegates a task to Agent B. Agent B fails silently. Agent A never sees it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;CrewAI's &lt;strong&gt;delegated_agent&lt;/strong&gt; pattern is powerful: one agent can offload work to another, each with its own tools and prompts. In theory, clean separation of concerns. In practice, there's a gap where failures disappear.&lt;/p&gt;

&lt;p&gt;Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agent A&lt;/strong&gt; (the caller) creates a task and delegates it to &lt;strong&gt;Agent B&lt;/strong&gt; (the delegated agent).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent B&lt;/strong&gt; executes, calls its tools, and returns a task output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent A&lt;/strong&gt; receives that output and treats it as ground truth — it continues execution based on what Agent B claimed to return.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem: if Agent B's tools fail &lt;em&gt;partially&lt;/em&gt; — say, one tool returns an empty response, or a tool succeeds with a malformed result — Agent B may still construct a task output that &lt;strong&gt;looks valid&lt;/strong&gt; to Agent A. The output serializes cleanly. No exception is raised. But the actual work didn't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Inter-agent communication in CrewAI happens through &lt;strong&gt;task outputs&lt;/strong&gt;, which are strings or structured objects returned by the delegated agent. These outputs are not automatically validated against the original task's intent.&lt;/p&gt;

&lt;p&gt;Let's walk through a concrete case:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Agent A is a "report coordinator." It delegates a task: "Fetch the latest Q3 sales data from the database and return it as a JSON array."&lt;/p&gt;

&lt;p&gt;Agent B (the "data fetcher") has a tool called &lt;code&gt;fetch_sales_data()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's where the gap opens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fetch_sales_data()&lt;/strong&gt; calls the API and gets a network timeout.&lt;/li&gt;
&lt;li&gt;The tool catches the exception and returns a &lt;strong&gt;default value&lt;/strong&gt; — an empty JSON array &lt;code&gt;[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Agent B's LLM sees &lt;code&gt;[]&lt;/code&gt; and, because it's valid JSON, decides "the query returned no results" and returns a task output: &lt;code&gt;"Sales data retrieved successfully. Result: []"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Agent A receives this output. It's a string. It parses cleanly. Agent A continues its logic, possibly iterating over the empty array or passing it downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six hours later&lt;/strong&gt;, when you check the report, you realize the data was never pulled — the entire downstream analysis is built on nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No error logs. No exception trace. The delegated agent's output looked complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mechanical Root
&lt;/h2&gt;

&lt;p&gt;The root is that &lt;strong&gt;delegated agents don't automatically validate their own outputs against the task definition&lt;/strong&gt;. CrewAI doesn't (by default) assert: "Did you actually complete what was asked?" It assumes that if the LLM constructed a response and the tool calls happened, the task is done.&lt;/p&gt;

&lt;p&gt;There are three specific points where this breaks:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tool-Level Fallbacks Are Silent
&lt;/h3&gt;

&lt;p&gt;If a delegated agent's tool has a fallback (like &lt;code&gt;return [] if error else result&lt;/code&gt;), the agent's LLM can't distinguish between "the query succeeded and returned nothing" and "the query failed but I'm hiding it." The output looks the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No Built-In Output Validation
&lt;/h3&gt;

&lt;p&gt;When the delegated agent finishes, its task output is a string or dict. CrewAI doesn't validate it against the original task intent by default. There's no assertion like "the task asked for a list of 10 items; does the output contain 10 items?"&lt;/p&gt;

&lt;p&gt;If you want that validation, you build it yourself — it's not in the framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Calling Agent's Assumption of Completeness
&lt;/h3&gt;

&lt;p&gt;Agent A, receiving the delegated output, treats it as ground truth. It doesn't re-check. It doesn't call the original tools itself. It trusts the delegation was complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Hides
&lt;/h2&gt;

&lt;p&gt;Standard monitoring of CrewAI agents often looks at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the workflow complete (end-to-end status)?&lt;/li&gt;
&lt;li&gt;How many tool calls happened?&lt;/li&gt;
&lt;li&gt;What was the final output?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It doesn't look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did each delegated agent's output match the intent of its task?&lt;/li&gt;
&lt;li&gt;Did tool errors get swallowed by fallbacks?&lt;/li&gt;
&lt;li&gt;Is the calling agent's reasoning grounded in real data or in an agent's polite fiction?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you see: workflow status ✓, tool calls ✓, output returned ✓. All green. But the intermediate agent's output was empty or malformed, and it cascaded downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Builders Usually Miss
&lt;/h2&gt;

&lt;p&gt;Most teams find this when they add monitoring &lt;em&gt;after&lt;/em&gt; an incident. They instrument the delegated agent's tool calls and discover:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The tool returned an error 50% of the time, but the delegated agent returned a "success" output anyway.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The output structure was inconsistent — sometimes it's an array, sometimes it's a string. The calling agent's code broke on one variant, silently fell back to a default, and nobody noticed for two days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You Can Do
&lt;/h2&gt;

&lt;p&gt;If you're building with CrewAI's delegation pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicitly validate outputs in the task definition.&lt;/strong&gt; Set clear acceptance criteria in the task prompt: "Return ONLY a JSON array of exactly these fields…" not "Fetch the data." The delegated agent's LLM will be more careful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log intermediate outputs.&lt;/strong&gt; Capture what each delegated agent returns and store it (not just the final workflow output). If something goes wrong downstream, you can trace which agent's output was the culprit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add fallback-aware monitoring.&lt;/strong&gt; When you instrument tool calls, track which ones failed &lt;em&gt;and&lt;/em&gt; had fallbacks applied. That distinction matters — it's a signal that the delegated agent's output might be reconstructed, not real.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test the delegation path in isolation.&lt;/strong&gt; Before shipping, run the delegated agent standalone with known bad inputs (network failures, timeouts, malformed API responses) and verify the output still clearly signals "incomplete" — not just returns an empty or default value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observe token flow.&lt;/strong&gt; A delegated agent that fails silently often shows a tell: unusually low token counts on the calling agent (because it had less data to reason about). If your monitoring platform tracks tokens per agent, an anomalous dip can surface these incidents early.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;This isn't a CrewAI bug — it's a structural pattern in any multi-agent system: when agents communicate through outputs rather than shared state or explicit errors, gaps emerge.&lt;/p&gt;

&lt;p&gt;The fix isn't to avoid delegation. It's to treat delegated outputs as &lt;strong&gt;potentially incomplete&lt;/strong&gt; and &lt;strong&gt;validate aggressively&lt;/strong&gt; rather than assume they're ground truth just because they parse cleanly.&lt;/p&gt;

&lt;p&gt;Your monitoring should answer: "Did the delegated agent actually do what was asked?" not just "Did the delegated agent return an output?"&lt;/p&gt;

&lt;p&gt;That distinction is the difference between a working system and one that fails silently for hours before anyone notices.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're running CrewAI agents in production, what validation are you doing on delegated outputs? This is a pattern worth stress-testing before it surprises you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>monitoring</category>
      <category>python</category>
    </item>
    <item>
      <title>Your Agent's Bill Jumped 40%. It Never Errored Once.</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:21:24 +0000</pubDate>
      <link>https://dev.to/opsveritas/your-agents-bill-jumped-40-it-never-errored-once-34p2</link>
      <guid>https://dev.to/opsveritas/your-agents-bill-jumped-40-it-never-errored-once-34p2</guid>
      <description>&lt;p&gt;You ship an AI agent to production. It runs smoothly for a week. Then one morning your LLM bill is 40% higher than expected—and you have no idea why. By the time you dig into logs, the damage is done.&lt;/p&gt;

&lt;p&gt;The problem isn't that your agent broke loudly. It's that cost spikes are &lt;em&gt;silent&lt;/em&gt;. Your agent succeeded. It returned tokens. It burned budget. And without a baseline to compare against, you won't see the spike until the bill arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Baselines Matter (and Why Most Setups Skip Them)
&lt;/h2&gt;

&lt;p&gt;Every LLM agent has a natural token consumption pattern. For a given input, it tends to use roughly the same number of tokens each time—within a predictable range. That range is your baseline.&lt;/p&gt;

&lt;p&gt;When an agent deviates sharply from its baseline, something changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A retry loop is running (hitting rate limits, trying again, trying again).&lt;/li&gt;
&lt;li&gt;The prompt got longer accidentally.&lt;/li&gt;
&lt;li&gt;A tool call is looping—each iteration calling the model again.&lt;/li&gt;
&lt;li&gt;The model switched to a more expensive variant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch: a 30% spike in tokens is real and expensive, but it's &lt;em&gt;invisible&lt;/em&gt; if you're only looking at raw counts. You need a baseline to know it's an anomaly at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Build a Token Baseline (Three Metrics)
&lt;/h2&gt;

&lt;p&gt;The cleanest approach uses percentiles. Here's why percentiles work better than simple averages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Median (p50):&lt;/strong&gt; Half your runs use fewer tokens than this; half use more. It's stable and unaffected by outliers. If your agent usually takes 150 tokens per run and the p50 is 160, that's your normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;95th percentile (p95):&lt;/strong&gt; 95% of your runs fall below this threshold. It captures the "busy day" for your agent—the legitimate high-end of normal. If your p95 is 400 tokens, you expect some runs to hit that; they're not anomalies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;99th percentile (p99):&lt;/strong&gt; The rare, expensive run. Useful for budgeting headroom, but too loose for early anomaly detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why percentiles over averages?&lt;/strong&gt; One runaway loop pushes an average up by 30%. The p95 barely moves—it's already accounting for variability. Averages lie when there are outliers. Percentiles don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Signal-to-Noise Problem: When Do You Alert?
&lt;/h2&gt;

&lt;p&gt;Here's where most baselines fail: they're too trigger-happy. Alert on every spike above p95, and you'll get alerts when the agent just had a legitimately complex input. Ignore real spikes, and you miss the runaway loop that costs $500.&lt;/p&gt;

&lt;p&gt;The trick is &lt;strong&gt;meaningful deviation&lt;/strong&gt;: a spike that's both &lt;em&gt;large&lt;/em&gt; and &lt;em&gt;sustained&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-run spike above p99?&lt;/strong&gt; Probably legitimate—the agent got a complex query, used more tokens, moved on. No alert needed yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three runs in a row above p95, with at least one above p99?&lt;/strong&gt; Now we're talking. Multiple high-cost runs suggest something systematic changed, not a one-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weekly spend 50% above last week's baseline?&lt;/strong&gt; That's a signal worth investigating.&lt;/p&gt;

&lt;p&gt;The math is straightforward: if your agent normally costs $0.02 per run (p50 at 100 tokens * $0.00015/token for GPT-4o) and you see three runs at $0.08 each, that's 4x normal. Investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Baseline Detection Works in Practice
&lt;/h2&gt;

&lt;p&gt;Here's a concrete example. Say your agent summarizes customer feedback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run 1: 145 tokens
Run 2: 152 tokens
Run 3: 148 tokens
Run 4: 5,200 tokens ← anomaly
Run 5: 4,800 tokens ← anomaly again
Run 6: 156 tokens (back to normal)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your p50 baseline: ~150 tokens. Your p95: ~160 tokens.&lt;/p&gt;

&lt;p&gt;Runs 4 and 5 are 30–40x your baseline. One spike could be noise. Two in a row is a pattern. If you're running this agent 100 times a day, 30 of those runs suddenly costing 5000 tokens changes your bill overnight.&lt;/p&gt;

&lt;p&gt;A baseline system flags this: "Runs 4–5 exceeded p99 + sustained above p95. Investigate: possible retry loop or tool recursion."&lt;/p&gt;

&lt;p&gt;You check the logs, find the agent is calling a downstream API that's slow, triggering a retry loop. You add a timeout or a backoff. Crisis averted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need to Implement This
&lt;/h2&gt;

&lt;p&gt;To build and track baselines, you need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Per-agent token counts from every run.&lt;/strong&gt; Not just successes—failed runs and retries matter too, because they're where costs spike.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-windowed percentiles.&lt;/strong&gt; Recompute p50, p95, p99 weekly or daily so your baseline adapts. (If you upgraded your model, the new baseline should reflect that.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spike detection tied to your baseline.&lt;/strong&gt; Runs &amp;gt; p99 + multiple occurrences = alert. Runs &amp;gt; p95 once = log, but don't alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost overlay.&lt;/strong&gt; Baseline tokens are useful; baseline &lt;em&gt;cost&lt;/em&gt; is what matters to your CFO. Map tokens to USD per model, then alert on cost anomalies too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most standard application monitoring tools (DataDog, New Relic, etc.) don't track model-specific token usage out of the box—you're usually exporting logs and computing percentiles yourself. That's labor-intensive and easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A simpler approach:&lt;/strong&gt; Use an observability tool built for LLM agents. The AI Agents Control Tower, for example, auto-computes p50/p95/p99 baselines per agent and alerts when a run spikes above them—no manual percentile math, no custom logging. You define your SLA (expected cost or latency), and the system flags deviations automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Baseline Mindset
&lt;/h2&gt;

&lt;p&gt;The broader lesson: &lt;strong&gt;silent cost spikes are only silent if you have no baseline to compare against.&lt;/strong&gt; The moment you establish what "normal" looks like for your agent—in tokens, cost, latency, or all three—anomalies become visible.&lt;/p&gt;

&lt;p&gt;Before you ship your next AI agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish a baseline during your pre-prod or first week of live traffic.&lt;/li&gt;
&lt;li&gt;Track p50, p95, and p99 token usage per agent.&lt;/li&gt;
&lt;li&gt;Set a meaningful deviation threshold (not every spike, only sustained ones).&lt;/li&gt;
&lt;li&gt;Alert on cost anomalies, not just execution failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your bill (and your sleep) will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you seen a cost spike on an agent you didn't expect? What caught it—or what didn't?&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Yes, We Support LangGraph — Here's Why That Was Never a Separate Integration</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 20 Jul 2026 04:43:53 +0000</pubDate>
      <link>https://dev.to/opsveritas/yes-we-support-langgraph-heres-why-that-was-never-a-separate-integration-13lp</link>
      <guid>https://dev.to/opsveritas/yes-we-support-langgraph-heres-why-that-was-never-a-separate-integration-13lp</guid>
      <description>&lt;p&gt;We never built LangGraph support. It's worked from day one anyway — and until now, we'd never actually said so anywhere.&lt;/p&gt;

&lt;p&gt;That's not a typo. Here's the reasoning, and why it matters if you're one of the teams running LangGraph in production (Klarna, Uber, LinkedIn, BlackRock, Cisco, Elastic, and JPMorgan all reportedly do).&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph doesn't have its own way of calling a model
&lt;/h2&gt;

&lt;p&gt;LangGraph models agents as explicit state graphs — nodes, edges, persistent state, checkpoints, human-in-the-loop gates. It's a genuinely good abstraction for production agent orchestration. But when a LangGraph node actually needs to call an LLM, it doesn't invent a new calling convention to do it — it reaches for a standard LangChain chat model and calls &lt;code&gt;.invoke()&lt;/code&gt; or &lt;code&gt;.ainvoke()&lt;/code&gt; on it, exactly the same interface any other LangChain code uses.&lt;/p&gt;

&lt;p&gt;That single fact is the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually built
&lt;/h2&gt;

&lt;p&gt;Our LangChain integration works at the model-object layer, not the orchestration layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;opsveritas.langchain("Agent Name")&lt;/code&gt; — a callback handler for telemetry (tokens, cost, latency, status).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wrapLangChain()&lt;/code&gt; / &lt;code&gt;wrap_langchain()&lt;/code&gt; — patches &lt;code&gt;.invoke()&lt;/code&gt;/&lt;code&gt;.ainvoke()&lt;/code&gt; directly on the model object, for kill-switch enforcement (see our &lt;a href="https://dev.to/opsveritas/the-kill-switch-auto-pause-runaway-ai-agents-before-they-burn-your-budget-ien"&gt;kill switch article&lt;/a&gt; for what that actually does).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of those cares what's calling the model. A LangGraph node, a plain LangChain chain, a custom orchestration loop — if it's invoking a wrapped model object, we see it. We didn't write a single line of LangGraph-specific code, because the integration point was never LangGraph. It was always the model.&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;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&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="n"&gt;opsveritas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;langchain&lt;/span&gt;&lt;span class="p"&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="c1"&gt;# telemetry
&lt;/span&gt;&lt;span class="n"&gt;opsveritas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap_langchain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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="c1"&gt;# kill-switch enforcement
&lt;/span&gt;&lt;span class="n"&gt;graph_builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;make_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;            &lt;span class="c1"&gt;# LangGraph uses the wrapped model, unmodified
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration. The graph never knows the model underneath it is instrumented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we're only saying this now
&lt;/h2&gt;

&lt;p&gt;Because until recently, we hadn't said it anywhere — not the docs, not onboarding, not the dashboard. A prospect evaluating us against a LangGraph-based stack would have had no way to know it already worked. That's a real gap between what the product does and what it visibly claims to do, and it's now closed across our docs, onboarding, and settings pages.&lt;/p&gt;

&lt;p&gt;If you're running LangGraph and monitoring silent failures, cost, or kill-switch enforcement, wrap the model before you pass it to the graph. That's it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Kill Switch: Auto-Pause Runaway AI Agents Before They Burn Your Budget</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 20 Jul 2026 04:43:38 +0000</pubDate>
      <link>https://dev.to/opsveritas/the-kill-switch-auto-pause-runaway-ai-agents-before-they-burn-your-budget-ien</link>
      <guid>https://dev.to/opsveritas/the-kill-switch-auto-pause-runaway-ai-agents-before-they-burn-your-budget-ien</guid>
      <description>&lt;p&gt;Every AI agent monitoring tool will tell you when you've gone over budget. Almost none of them will actually stop it from happening again five minutes later.&lt;/p&gt;

&lt;p&gt;That's the gap between an alert and a kill switch, and it's the reason we built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with alert-only budget monitoring
&lt;/h2&gt;

&lt;p&gt;Here's the typical flow: your agent racks up an unexpected cost spike — a bad prompt loop, a retry storm, a runaway multi-agent chain calling itself in circles. Your monitoring tool notices, sends you an email or a Slack ping, and... that's it. The agent keeps running. It keeps spending. You find out you're over budget at the exact same moment your bill does.&lt;/p&gt;

&lt;p&gt;An alert is a notification. It's not a brake pedal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the kill switch actually does
&lt;/h2&gt;

&lt;p&gt;The kill switch is a monthly budget threshold, set per organization, with real enforcement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross the threshold, and every agent in the org auto-pauses — not "gets flagged," actually pauses.&lt;/li&gt;
&lt;li&gt;For SDK-integrated agents (&lt;code&gt;wrap()&lt;/code&gt;, &lt;code&gt;monitor()&lt;/code&gt;, and now &lt;code&gt;wrapLangChain()&lt;/code&gt;/&lt;code&gt;wrap_langchain()&lt;/code&gt;), the enforcement happens &lt;strong&gt;before&lt;/strong&gt; the API call — the wrapped client throws a typed &lt;code&gt;OpsVeritasKilledError&lt;/code&gt; instead of ever reaching OpenAI, Anthropic, or Gemini. The cost genuinely stops, not just the notification.&lt;/li&gt;
&lt;li&gt;One-click restart from the dashboard, with the same actionable guidance whether you restart one agent or all of them: raise the budget or disable the kill switch first if you want it to actually keep running past the next paid call.&lt;/li&gt;
&lt;li&gt;It resets monthly, with rollover — this is a recurring monthly cap, not a permanent lifetime ceiling you cross once and stay crossed forever.&lt;/li&gt;
&lt;li&gt;The SDK polls for kill state in the background (every 3 minutes), gated entirely on whether your org has opted in — if you never enable it, your agents generate zero extra background traffic. And if the poll itself fails for any reason (network blip, backend hiccup), it fails &lt;strong&gt;open&lt;/strong&gt; — a transient monitoring issue should never be the thing that breaks your production agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one honest limitation
&lt;/h2&gt;

&lt;p&gt;This only works for agents integrated via the SDK. If your agent only sends telemetry over the generic webhook (the zero-code path for n8n, Make, or any custom script), you still get the alert — but there's no OpsVeritas code running inside your process to actually gate the call, so nothing stops running. We'd rather tell you that plainly than let you assume enforcement exists where it structurally can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it sounds
&lt;/h2&gt;

&lt;p&gt;The real cost of a runaway agent isn't usually the first spike — it's the one that keeps compounding while nobody's looking, because "I'll check the dashboard later" is exactly the gap a kill switch is built to close. An alert assumes a human is watching. A kill switch doesn't need one to be.&lt;/p&gt;




&lt;p&gt;Available now on both SDKs (&lt;code&gt;opsveritas-sdk&lt;/code&gt; on npm, &lt;code&gt;opsveritas&lt;/code&gt; on PyPI). If you're already instrumented with &lt;code&gt;wrap()&lt;/code&gt; or &lt;code&gt;langchain()&lt;/code&gt;, enabling enforcement is a one-line addition — no re-architecture required.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>HTTP 200 Is Not a Product Guarantee</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sat, 04 Jul 2026 00:49:16 +0000</pubDate>
      <link>https://dev.to/opsveritas/http-200-is-not-a-product-guarantee-3b7o</link>
      <guid>https://dev.to/opsveritas/http-200-is-not-a-product-guarantee-3b7o</guid>
      <description>&lt;h1&gt;
  
  
  HTTP 200 Is Not a Product Guarantee
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;AI Agents in Production - Series 2, Article 5 of 6&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;An AI agent ran 47 times last week.&lt;/p&gt;

&lt;p&gt;Every run returned HTTP 200. Every run had latency under 2 seconds. No exceptions. No errors in the logs.&lt;/p&gt;

&lt;p&gt;And every run produced absolutely nothing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;output_tokens: 0&lt;/code&gt;. Forty-seven times in a row.&lt;/p&gt;

&lt;p&gt;The infrastructure saw success. The product saw nothing. And no alert fired.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Failure Class Nobody Monitors
&lt;/h2&gt;

&lt;p&gt;Most teams monitor what the API &lt;em&gt;says&lt;/em&gt;. HTTP 200 means the request was accepted, processed, and returned cleanly. That is true. The infrastructure worked perfectly.&lt;/p&gt;

&lt;p&gt;But HTTP 200 only tells you about the transport layer. It says nothing about what your agent was supposed to produce.&lt;/p&gt;

&lt;p&gt;This is the failure class called &lt;strong&gt;silent failure&lt;/strong&gt;: the system reports success while the business value goes to zero.&lt;/p&gt;

&lt;p&gt;It is the most dangerous failure type because monitoring dashboards show green, on-call does not get paged, and the client has no idea for days or weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Failure Shapes That Look Like Success
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shape 1 - The Empty Responder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent calls the LLM, gets back an empty completion. &lt;code&gt;choices[0].message.content&lt;/code&gt; is empty. HTTP 200. &lt;code&gt;output_tokens: 0&lt;/code&gt;. Your pipeline continues as if everything worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shape 2 - The Stuck Safety Filter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model triggers an internal safety classification and returns an empty response rather than a refusal. No error. Just silence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shape 3 - The Token Drain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prompt tokens go up, output tokens stay at zero. You are paying for every prompt, generating nothing. All three return HTTP 200.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Need to Monitor
&lt;/h2&gt;

&lt;p&gt;`python&lt;br&gt;
from opsveritas import AgentTracer&lt;/p&gt;

&lt;p&gt;tracer = AgentTracer(api_key="your-key")&lt;/p&gt;

&lt;p&gt;with tracer.trace("content-generator") as span:&lt;br&gt;
    response = client.chat.completions.create(&lt;br&gt;
        model="gpt-4o",&lt;br&gt;
        messages=messages&lt;br&gt;
    )&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content = response.choices[0].message.content
output_tokens = response.usage.completion_tokens

span.set_output(
    summary=content[:500] if content else "[EMPTY - silent failure]",
    output_tokens=output_tokens,
    success=bool(content and output_tokens &amp;gt; 0)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;p&gt;Success is not &lt;code&gt;http_status == 200&lt;/code&gt;. Success is &lt;code&gt;output_tokens &amp;gt; 0&lt;/code&gt; AND the output contains meaningful content.&lt;/p&gt;

&lt;p&gt;`javascript&lt;br&gt;
import { AgentTracer } from "@opsveritas/sdk";&lt;/p&gt;

&lt;p&gt;const tracer = new AgentTracer({ apiKey: "your-key" });&lt;/p&gt;

&lt;p&gt;const span = tracer.start("content-generator");&lt;br&gt;
try {&lt;br&gt;
  const response = await openai.chat.completions.create({ ... });&lt;br&gt;
  const outputTokens = response.usage.completion_tokens;&lt;/p&gt;

&lt;p&gt;span.finish({&lt;br&gt;
    outputTokens,&lt;br&gt;
    outputSummary: response.choices[0].message.content || "[EMPTY]",&lt;br&gt;
    success: outputTokens &amp;gt; 0&lt;br&gt;
  });&lt;br&gt;
} catch (err) {&lt;br&gt;
  span.error(err);&lt;br&gt;
}&lt;br&gt;
`&lt;/p&gt;




&lt;h2&gt;
  
  
  The Alert That Should Have Fired
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "alert_type": "silent_failure",&lt;br&gt;
  "workflow_name": "content-generator",&lt;br&gt;
  "message": "output_tokens = 0 on 47 consecutive runs. HTTP 200 returned each time.",&lt;br&gt;
  "diagnosis": "Likely: safety filter on prompt, empty completion, or context window exhaustion.",&lt;br&gt;
  "consecutive_empty_runs": 47,&lt;br&gt;
  "cost_usd_burned": 0.22&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note the diagnosis field: AI-generated root cause analysis, not just a raw metric.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Changes
&lt;/h2&gt;

&lt;p&gt;When you monitor &lt;code&gt;output_tokens&lt;/code&gt; alongside HTTP status, silent failures get caught in the first run, not the 47th. You stop paying for token consumption that produces nothing. Client-facing failures get resolved before the client notices.&lt;/p&gt;

&lt;p&gt;HTTP 200 is a transport guarantee. Build your monitoring at the product layer.&lt;/p&gt;




&lt;p&gt;Free to try: &lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;https://agents.opsveritas.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also build these end-to-end: &lt;a href="https://opsveritas.com" rel="noopener noreferrer"&gt;https://opsveritas.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DM for a 15-min demo if you are running agents in production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Two Lines of Code to Full Observability</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Thu, 02 Jul 2026 18:21:49 +0000</pubDate>
      <link>https://dev.to/opsveritas/two-lines-of-code-to-full-observability-36h2</link>
      <guid>https://dev.to/opsveritas/two-lines-of-code-to-full-observability-36h2</guid>
      <description>&lt;p&gt;Most observability platforms require you to rebuild your infrastructure around them. New logging pipelines. New deployment configs. Dedicated sampling layers. By the time you have "observability," you've also rewritten half your stack.&lt;/p&gt;

&lt;p&gt;The AI Agents Control Tower SDK doesn't work that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Lines
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opsveritas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_agent_function&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opsveritas/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myAgentFunction&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;inputData&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. Your existing agent call, wrapped. No new infrastructure. No deployment changes. No sidecars, no proxies, no log aggregators.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens From Those Two Lines
&lt;/h2&gt;

&lt;p&gt;From the moment you wrap the call, every execution automatically sends: duration, token usage, USD cost, status (success/failed/empty), output_summary, and timestamp — all flowing into your agents.opsveritas.com dashboard, visible per-execution and per-agent across your entire fleet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Custom Webhook Fallback
&lt;/h2&gt;

&lt;p&gt;Already using LangSmith? Running a framework that doesn't wrap cleanly? The platform also accepts raw webhook payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://agents.opsveritas.com/api/sdk/ingest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"agent_name":"my-agent","status":"success","duration_ms":1250,"tokens_used":842,"cost_usd":0.0017}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same dashboard. Same alerts. Any platform, any language, any orchestration framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get Automatically
&lt;/h2&gt;

&lt;p&gt;Once the SDK is connected, the platform detects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;silent_failure&lt;/strong&gt; — output_tokens = 0 on HTTP 200 (ran successfully, produced nothing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;token_anomaly&lt;/strong&gt; — usage 3× above baseline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;agent_loop&lt;/strong&gt; — same tool call repeated (runaway retry behavior)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;budget_exceeded&lt;/strong&gt; — total spend over configured threshold&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;high_cost_spike&lt;/strong&gt; — single-run cost anomaly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;execution_failed&lt;/strong&gt; — non-200 response from the model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no_activity&lt;/strong&gt; — agent hasn't run in its expected window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All 7 alert types, active automatically. No configuration beyond the API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monitoring Gap Is a Friction Problem
&lt;/h2&gt;

&lt;p&gt;Teams that skip observability aren't being reckless. They're making a rational trade-off: the perceived effort of setting up monitoring versus the urgency of shipping the next feature.&lt;/p&gt;

&lt;p&gt;Two lines and an API key is the whole setup. That's a trade-off worth making on any sprint.&lt;/p&gt;




&lt;p&gt;Free to try → &lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;agents.opsveritas.com&lt;/a&gt;&lt;br&gt;
We also build AI agents and automations end-to-end → &lt;a href="https://opsveritas.com" rel="noopener noreferrer"&gt;opsveritas.com&lt;/a&gt;&lt;br&gt;
DM me for a 15-min walkthrough.&lt;/p&gt;

</description>
      <category>llm</category>
    </item>
    <item>
      <title>Token Costs That Compound While You Sleep</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Wed, 01 Jul 2026 11:55:38 +0000</pubDate>
      <link>https://dev.to/opsveritas/token-costs-that-compound-while-you-sleep-d5c</link>
      <guid>https://dev.to/opsveritas/token-costs-that-compound-while-you-sleep-d5c</guid>
      <description>&lt;p&gt;An AI agent ran inside a customer's pipeline for 30 seconds. By the time anyone looked at the logs, it had made 47 API calls, bloated its context window to 128k tokens, and spent $23.40.&lt;/p&gt;

&lt;p&gt;The alert arrived the next morning. The bill arrived 30 days later.&lt;/p&gt;

&lt;p&gt;This is the cost compounding problem. It's not about one expensive run — it's about not knowing a run &lt;em&gt;was&lt;/em&gt; expensive until long after it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI agent costs compound
&lt;/h2&gt;

&lt;p&gt;Three scenarios cause most runaway token spend:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context bloat.&lt;/strong&gt; Agents that don't trim their context window accumulate history across turns. Turn 1: 1,200 tokens. Turn 10: 18,000 tokens. Turn 20: 67,000 tokens. Each call costs more than the last, and the agent never tells you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry storms.&lt;/strong&gt; An agent hits a rate limit or a malformed JSON response and retries. Each retry is a full prompt re-send at full token cost. Without a circuit breaker, the agent retries until it exhausts the budget or times out. We've seen 12 retries in under 2 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent loops.&lt;/strong&gt; The agent calls a tool, the tool returns output, the agent reinterprets the output and calls the tool again. Same tool, same parameters, slightly different framing. Repeat 30 times. This is the &lt;code&gt;agent_loop&lt;/code&gt; failure mode — it produces no useful output and runs up cost in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What per-execution cost tracking actually looks like
&lt;/h2&gt;

&lt;p&gt;Most platforms give you daily token totals. That's useful for billing. It's useless for debugging.&lt;/p&gt;

&lt;p&gt;What you need is &lt;strong&gt;per-execution cost in USD&lt;/strong&gt; — not just input tokens, not just output tokens, real dollars, broken out per run.&lt;/p&gt;

&lt;p&gt;In AI Agents Control Tower, every execution row shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total cost in USD (input + output tokens × model rate)&lt;/li&gt;
&lt;li&gt;Token breakdown (input / output separately)&lt;/li&gt;
&lt;li&gt;Model used (cost rates differ significantly across GPT-4o, Claude Haiku, Gemini Flash)&lt;/li&gt;
&lt;li&gt;Execution duration in seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you see a run that cost $0.23 instead of the usual $0.003, you know to look at it. When you see 47 runs in 2 minutes all at $0.50+, you know you have a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget alert thresholds
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;budget_exceeded&lt;/code&gt; alert fires when cumulative spend on an agent crosses a threshold you set. You configure it per agent — not per account, per agent — because a scraper agent might run at $0.10/day while a reasoning agent might run at $2/day, and both are normal.&lt;/p&gt;

&lt;p&gt;The threshold is configurable on the agent detail page. When it fires, it routes the same as any other alert: Slack, email, Teams, simultaneously, with the agent name, current spend, and threshold included in the message.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;high_cost_spike&lt;/code&gt; — a single execution that costs significantly more than that agent's rolling baseline. This catches one-off anomalies before they become sustained runaway spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right mental model
&lt;/h2&gt;

&lt;p&gt;Your AI agent bill is a function of decisions made at design time — context window management, retry logic, loop detection — not just runtime usage. But you can't improve what you can't see.&lt;/p&gt;

&lt;p&gt;Per-execution cost tracking is what makes the cost side of AI agents observable. Not a monthly summary. Not a vague "tokens used" counter. A row per execution with a dollar amount attached.&lt;/p&gt;

&lt;p&gt;That's what we built into AI Agents Control Tower, and it's free to try at &lt;strong&gt;&lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;agents.opsveritas.com&lt;/a&gt;&lt;/strong&gt; — 2-line SDK, no new infrastructure.&lt;/p&gt;

&lt;p&gt;We also build custom AI agents end-to-end: &lt;strong&gt;&lt;a href="https://opsveritas.com" rel="noopener noreferrer"&gt;opsveritas.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DM me if you want a 15-min walkthrough.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
