<?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>Cost Governance for AI Agents: From Detection to Enforcement</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Tue, 18 Aug 2026 19:23:56 +0000</pubDate>
      <link>https://dev.to/opsveritas/cost-governance-for-ai-agents-from-detection-to-enforcement-42mp</link>
      <guid>https://dev.to/opsveritas/cost-governance-for-ai-agents-from-detection-to-enforcement-42mp</guid>
      <description>&lt;p&gt;Your AI agent is burning money. It hasn't crashed, it's running fine, and the only way you find out is when the bill shows up.&lt;/p&gt;

&lt;p&gt;Here's the pattern: an agent gets confused and calls the same tool over and over. Or a model upgrade quietly triples the cost per token. Or one bad execution fires a hundred times in parallel. From the SDK's point of view, every one of those is a success. Status 200, output returned, nothing to flag.&lt;/p&gt;

&lt;p&gt;We think about cost governance as three layers, and they build on each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection
&lt;/h2&gt;

&lt;p&gt;You need per-execution visibility, not a monthly total. Token count, cost in dollars, latency, per run. That's how you find out which agent burned $500 yesterday instead of which month burned $10k.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosis
&lt;/h2&gt;

&lt;p&gt;An agent that costs $5 one day and $500 the next isn't random. Either the cost per call spiked (something's asking for way more tokens than usual) or the call count spiked (it's looping). Knowing which one happened tells you what to actually fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcement
&lt;/h2&gt;

&lt;p&gt;This is the part most teams skip. Once you know an agent's normal cost profile, you can pause it automatically if it breaks that pattern hard enough, or if it crosses a budget you set. That check runs before the next API call goes out, so it stops the spend rather than just reporting it after the fact. It fails open too, so a network hiccup on the check itself never breaks your agent's normal operation. And restarting it once you've found the root cause takes seconds.&lt;/p&gt;

&lt;p&gt;Detection and diagnosis should always be on. Enforcement is opt-in, because the policy is yours to set. One team wants a hard stop. Another wants an alert and a human in the loop.&lt;/p&gt;

&lt;p&gt;None of this works without seeing every execution, though. Sampled metrics will miss the loop that cost you a thousand dollars in five minutes.&lt;/p&gt;

&lt;p&gt;If you're running agents in production, the cost story shouldn't be a surprise invoice weeks later. It should be a dial you can turn today.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>We Built Monitoring Into Our Own AI Agents. Here's What We Learned.</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:30:42 +0000</pubDate>
      <link>https://dev.to/opsveritas/we-built-monitoring-into-our-own-ai-agents-heres-what-we-learned-enm</link>
      <guid>https://dev.to/opsveritas/we-built-monitoring-into-our-own-ai-agents-heres-what-we-learned-enm</guid>
      <description>&lt;p&gt;We run marketing workflows on an AI agent. When we tried to monitor it with existing tools, we found ourselves flying blind in ways we didn't expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem We Didn't Know We Had
&lt;/h2&gt;

&lt;p&gt;Three months ago, our marketing agent was supposed to draft social posts every morning. One Tuesday, it hadn't. We checked the logs. No errors. The LLM call succeeded. HTTP 200. The response came back. And yet: no draft. Just a blank.&lt;/p&gt;

&lt;p&gt;This is the failure mode that most observability tooling doesn't catch. The agent executed. The infrastructure said "success." But the agent produced nothing — zero output tokens, an empty response, a request that returned 200 OK but accomplished zero.&lt;/p&gt;

&lt;p&gt;We had to manually check the execution history to find it. By then, someone else had noticed the absence.&lt;/p&gt;

&lt;p&gt;That's when we realized: we couldn't see what was actually happening in our own agents in real time. We needed to build visibility from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting from First Principles
&lt;/h2&gt;

&lt;p&gt;We asked: what do we actually need to know about an AI agent execution?&lt;/p&gt;

&lt;p&gt;Not just "did it error?" — because errors aren't the only way an agent fails. We needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it run at all? (HTTP status, latency)&lt;/li&gt;
&lt;li&gt;Did it consume what we expected? (tokens, cost)&lt;/li&gt;
&lt;li&gt;Did it produce anything? (output length, not just "success" status)&lt;/li&gt;
&lt;li&gt;Did it do the right thing? (the harder one — we'll come back to this)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most APM tools watch infrastructure: request latency, error rates, dependencies. None of those answer "was the output empty?" They watch the wrapper, not what happened inside.&lt;/p&gt;

&lt;p&gt;So we wrote an SDK wrapper for our agents. Simple design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercept the LLM client calls (OpenAI, Anthropic, etc.)&lt;/li&gt;
&lt;li&gt;Capture the telemetry: tokens in/out, cost, latency, and — crucially — the output itself&lt;/li&gt;
&lt;li&gt;Send it somewhere we could query and alert on it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The SDK runs inside our own environment, not between us and the LLM provider. Your API keys stay in your process. We only ever see the telemetry.&lt;/p&gt;

&lt;p&gt;That design choice matters: it means we can capture what the model actually returned, but we can't see your proprietary prompt or system instructions. Read-only observability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built (And Why)
&lt;/h2&gt;

&lt;p&gt;The first version just captured raw metrics. We logged tokens consumed (input/output), cost calculated from per-model pricing, latency, status (success/failure/timeout), model name (so we could auto-calculate cost from a built-in rate table), and a summary of the output.&lt;/p&gt;

&lt;p&gt;We pushed it to a dashboard and set up basic alerts: if cost spiked, flag it. If latency crossed a threshold, flag it.&lt;/p&gt;

&lt;p&gt;This caught infrastructure problems. It didn't catch silent failures.&lt;/p&gt;

&lt;p&gt;So we added silent-failure detection: if status is "success" but output_tokens equals 0, that's an alert. The agent ran. It returned 200. It produced nothing.&lt;/p&gt;

&lt;p&gt;We caught three more of them within a week using this rule alone.&lt;/p&gt;

&lt;p&gt;Then came the harder question: what if the output looks fine but is actually wrong?&lt;/p&gt;

&lt;p&gt;An agent returns a well-formed response. Tokens flow normally. Cost is where we'd expect. But the output is garbage — a recommendation that doesn't match the input, a calculation that's off, a response that's just not what was asked for.&lt;/p&gt;

&lt;p&gt;No metric catches that. No baseline can. You need a human (or another model) to grade it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Correctness Problem
&lt;/h2&gt;

&lt;p&gt;We built a "correctness check" feature: you write a rubric in plain English describing what a correct output should look like ("The response must include a specific name and a recommendation. The recommendation must be actionable."). Then we run every execution's output past an AI grader against that rubric.&lt;/p&gt;

&lt;p&gt;The grader isn't perfect. That's the whole point of making it reviewable. On every alert, we let you give a thumbs-up or thumbs-down: "did the judge get this right?" Over time, that feedback becomes a record of accuracy.&lt;/p&gt;

&lt;p&gt;And if the judge is systematically wrong about something, you can flag a disagreement and add it as an example the judge learns from. The correction is always manual — we don't auto-add examples from feedback — so a bad rubric can't teach itself to fail.&lt;/p&gt;

&lt;p&gt;This catches the "200 OK but completely wrong" case. It doesn't solve the problem of an imperfect judge. It just makes the imperfection visible and reviewable, rather than hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Learned
&lt;/h2&gt;

&lt;p&gt;The infrastructure lies to you. HTTP 200 is not a guarantee of work done. Latency is not a guarantee of usefulness. Error rates don't tell you about silent failures. You need eyes on the actual output.&lt;/p&gt;

&lt;p&gt;Secrets stay secrets. You can have observability without sharing your API keys or your prompt logic. An SDK that runs in your own environment sees everything your code sees, but only sends us aggregates and summaries. That's the design that lets us help you without breaking your security model.&lt;/p&gt;

&lt;p&gt;The easiest failures to miss are the ones that don't error. An agent that times out, an API that 500s — those are obvious. An agent that returns 200 with no response? That's what sits in your queue unnoticed until a customer tells you.&lt;/p&gt;

&lt;p&gt;Correctness is a human problem with AI help. You can't write a metric that "correctness" is. You can write a rubric and ask another model to grade it, and you can let humans correct the grader. The system becomes useful when the correction loop is tight and transparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gaps We Hit
&lt;/h2&gt;

&lt;p&gt;When we were building this, we looked at what existed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application performance monitoring tools watch latency, errors, and infrastructure — not AI-specific problems like silent failures or cost anomalies.&lt;/li&gt;
&lt;li&gt;LLM observability tools (the newer ones) watch tokens and cost, which is closer — but most don't distinguish between "agent returned success with no output" and "agent returned success with output." The metrics look the same.&lt;/li&gt;
&lt;li&gt;Logging frameworks let you log whatever you want, but they don't automatically flag patterns. You're reading through logs manually or writing custom rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed something that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Auto-detected which model you were using (so cost was automatic, no config)&lt;/li&gt;
&lt;li&gt;Flagged silent failures specifically (not just errors)&lt;/li&gt;
&lt;li&gt;Didn't require you to share your API keys with us&lt;/li&gt;
&lt;li&gt;Let you define what "correct" means and alert when it's violated&lt;/li&gt;
&lt;li&gt;Gave you real-time visibility without slowing down your agent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's what we built. And we're eating our own dogfood: this very marketing system runs on an AI agent that we monitor with our own product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;We're learning as we use it. The silent-failure detection is solid — it catches what the infrastructure can't see. The cost anomaly detection helps catch runaway loops. The correctness check is useful but imperfect by design: we don't claim to know what "correct" means for your use case, only to help you define and enforce it.&lt;/p&gt;

&lt;p&gt;The thing we're still figuring out: how to make the correctness feedback loop even tighter. Right now, if the judge disagrees with you, you can promote that disagreement into a training example. But how many examples does the judge actually need to improve? When does a rubric become accurate enough to trust? How do you know when you've taught it enough?&lt;/p&gt;

&lt;p&gt;Those are open questions. We're answering them live, with real agents, real failures, and real feedback from the builders using the system.&lt;/p&gt;

&lt;p&gt;If you're building AI agents and you're wondering whether your monitoring is good enough, ask yourself: could you spot a silent failure in your own agent right now? Not an error — just an execution that returned success but produced nothing?&lt;/p&gt;

&lt;p&gt;If the answer is "not without checking the logs manually," you've found the gap we're trying to close.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>The Hidden Math: Why "Cheapest Model" Doesn't Mean Cheapest Execution</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Wed, 12 Aug 2026 10:20:37 +0000</pubDate>
      <link>https://dev.to/opsveritas/the-hidden-math-why-cheapest-model-doesnt-mean-cheapest-execution-562b</link>
      <guid>https://dev.to/opsveritas/the-hidden-math-why-cheapest-model-doesnt-mean-cheapest-execution-562b</guid>
      <description>&lt;h1&gt;
  
  
  The Hidden Math: Why "Cheapest Model" Doesn't Mean Cheapest Execution
&lt;/h1&gt;

&lt;p&gt;You're building an AI agent. Smart cost strategy: route to GPT-4o when you need reasoning, Haiku for simple classification, Groq when it's available and fast. In your head, the math is simple: pick the cheapest model per task.&lt;/p&gt;

&lt;p&gt;In practice, you ship code and never really know if it worked.&lt;/p&gt;

&lt;p&gt;Here's the gap: &lt;strong&gt;cheapest model ≠ cheapest execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A single agent run might span multiple model calls. One task uses Anthropic's Sonnet ($3 / 1M input, $15 / 1M output). Another uses OpenAI's GPT-4o mini ($0.15 / 1M input, $0.60 / 1M output). A third falls back to Groq Llama 3.1 when latency spikes. By the time the execution finishes, you have no idea which model was actually called, how many tokens each burned, or whether that "cheap" fallback actually saved money — or whether a long output summary from one call inflated the token count beyond what you budgeted.&lt;/p&gt;

&lt;p&gt;The assumption breaks immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the math matters (and why it's invisible)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Token costs compound differently by model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Take a real scenario: your agent needs to summarize a customer support transcript (2,000 input tokens). You budgeted for Haiku: 2,000 tokens × $0.80 / 1M = $0.0016 input cost. Cheap.&lt;/p&gt;

&lt;p&gt;But Haiku hits a rate limit. Code routes to Sonnet as fallback. Same 2,000 input tokens, now $0.006 — nearly 4× more. Multiply that across 100 daily runs, and you've overrun your mental budget without knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output lengths are invisible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Worse: you don't even know the output token count. An agent that "completes" a task could've produced 500 output tokens (brief response) or 5,000 (verbose reasoning). With Sonnet outputting at $15 / 1M, that's the difference between $0.0075 and $0.075 per run. Neither is huge. But if your agent runs 1,000 times a month and half your executions unexpectedly verbose?&lt;/p&gt;

&lt;p&gt;You're now $37 over budget without seeing why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-model routing compounds the blind spot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you route across three providers with six fallback models, you lose the ability to reason about cost at a glance. Did GPT-4o actually run, or did it fail and drop to Sonnet? How many times did Groq get chosen? Which model produced the longest outputs? You shipped it all, but you're flying blind on the arithmetic that actually happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unsexy solution: measure and account
&lt;/h2&gt;

&lt;p&gt;Here's what works (and it's not flashy):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Log every model call with input + output token counts.
&lt;/h3&gt;

&lt;p&gt;Before you route, log which model was selected. After the call returns, log the actual token counts from the response metadata. Not a guess — the real numbers.&lt;/p&gt;

&lt;p&gt;Why: your mental model of "mostly cheap, sometimes fallback" gets replaced with actual data. You can group by model, see fallback rates, and measure whether your routing actually favors the cheap path.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compute cost inline using per-model rates.
&lt;/h3&gt;

&lt;p&gt;Every model has a published price per 1M tokens (input and output separate). Compute cost at execution time using actual tokens.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Input: 2,000 tokens&lt;/li&gt;
&lt;li&gt;Output: 1,500 tokens&lt;/li&gt;
&lt;li&gt;Model: Anthropic Sonnet ($3 / 1M input, $15 / 1M output)&lt;/li&gt;
&lt;li&gt;Cost: (2,000 / 1M × $3) + (1,500 / 1M × $15) = $0.006 + $0.0225 = &lt;strong&gt;$0.0285 per run&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sixty runs a day? That's $1.71 / day, $51 / month. You now know the actual stake.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bucket by model + routing path.
&lt;/h3&gt;

&lt;p&gt;At the end of each day (or run batch), group executions by which model actually ran and whether it was your first choice or a fallback. Count, sum costs, measure fallback frequency.&lt;/p&gt;

&lt;p&gt;If Groq was supposed to be your primary but it failed 20% of the time, you now have evidence to either increase fallback tolerance, switch providers, or accept that Sonnet is your real cost center.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Alert on outliers.
&lt;/h3&gt;

&lt;p&gt;A single execution shouldn't surprise you. If one run's cost is 3σ above the 30-day average for that agent, you want to know why: did it produce an unexpectedly long output? Did it hit a fallback you didn't expect? Was there a retry loop?&lt;/p&gt;

&lt;p&gt;The math stops being invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this buys you
&lt;/h2&gt;

&lt;p&gt;You move from "I think this is cheap" to "I know what this costs, per execution, per model, per routing path."&lt;/p&gt;

&lt;p&gt;That's the difference between budgeting in hope and budgeting in fact.&lt;/p&gt;

&lt;p&gt;The math isn't exciting. But it's urgent, because runaway cost is usually a routing or retry problem hiding in production, and it stays hidden until the bill lands.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>llm</category>
      <category>observability</category>
    </item>
    <item>
      <title>The Silent Failure Detection Framework: Catching Agents That "Succeed" and Do Nothing</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 10 Aug 2026 14:01:59 +0000</pubDate>
      <link>https://dev.to/opsveritas/the-silent-failure-detection-framework-catching-agents-that-succeed-and-do-nothing-ak4</link>
      <guid>https://dev.to/opsveritas/the-silent-failure-detection-framework-catching-agents-that-succeed-and-do-nothing-ak4</guid>
      <description>&lt;p&gt;Your LLM agent returned a response. No error, no exception. But did it actually do what you asked?&lt;/p&gt;

&lt;p&gt;That's the silent failure problem. The system behaves normally — HTTP 200, status success — but the output is empty or nonsensical. Nothing alerts you. The customer complains first. To catch these, you need to know what makes a silent failure detectable in the first place. Here's the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're actually hunting
&lt;/h2&gt;

&lt;p&gt;A silent failure is execution that reports success but produces zero (or useless) output. The agent didn't crash — it just did nothing. The most common pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent calls the model.&lt;/li&gt;
&lt;li&gt;Model responds with ~0 output tokens, or a blank response.&lt;/li&gt;
&lt;li&gt;No error is raised.&lt;/li&gt;
&lt;li&gt;The calling code proceeds, unaware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two other variants show up often: an agent looping indefinitely without progress (tool calls that never advance state), and output that's structurally valid but semantically empty — "I don't know" when it should have searched.&lt;/p&gt;

&lt;p&gt;Standard error monitoring misses all three, because there is no error. You're checking the wrong signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three detection levers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Token counting — the baseline signal
&lt;/h3&gt;

&lt;p&gt;Every LLM call produces input tokens (your prompt) and output tokens (the model's response). Measurable, deterministic, available in the response metadata from every provider.&lt;/p&gt;

&lt;p&gt;The insight: a healthy agent call has a predictable token range, per agent. A ticket-summarizer might consume ~100-200 input tokens and produce ~50-150 output tokens, run after run. If an execution comes back with 100 input tokens and 0 output tokens, that's a signal worth flagging — not proof of failure on its own, but worth alerting on.&lt;/p&gt;

&lt;p&gt;How to implement it: log input/output tokens for every execution, compute the median and standard deviation of output tokens over the past 30 days, flag anything below the 10th percentile or exactly zero.&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;baseline_median_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;past_30_days_output_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;baseline_std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;past_30_days_output_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;is_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;today_output_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;baseline_median&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;baseline_std&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;OR &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_output_tokens&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Cost anomaly — a derived signal
&lt;/h3&gt;

&lt;p&gt;Cost = input_tokens × input_rate + output_tokens × output_rate. When token consumption drops, cost drops with it. Cost is often easier to track than raw tokens because it's vendor-independent — you can aggregate across models.&lt;/p&gt;

&lt;p&gt;If your agent normally costs $0.02-$0.05 per run and today it's $0.0001, something's very wrong. Same approach: baseline median/std over 30 days, alert below the 5th percentile. Cost lags token anomalies slightly since it's derived, but it's the easier number to reason about in business terms.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Output examination — the confirmatory signal
&lt;/h3&gt;

&lt;p&gt;If tokens suggest an anomaly, the next question is what the model actually said. Store a short excerpt of the output (not the whole response, that's usually too large), and you can scan for empty strings, repetitive/looping output, or run a secondary AI judge against a correctness rubric.&lt;/p&gt;

&lt;p&gt;Capture the first 500 characters, flag anything empty or suspiciously short against your baseline. Rubric-based judging is optional but valuable for high-stakes agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every execution logs input_tokens, output_tokens, cost_usd, output_summary, executed_at.&lt;/li&gt;
&lt;li&gt;On a schedule (nightly, or hourly at volume), compute per-agent baselines and flag anything below threshold.&lt;/li&gt;
&lt;li&gt;On a flag: alert the engineer with the output summary attached so triage is immediate, and optionally auto-pause the agent if the cost anomaly is severe.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this works, and where it doesn't
&lt;/h2&gt;

&lt;p&gt;Strengths: no false positives from error logs, since you're measuring execution quality directly rather than exceptions. Early signal — tokens are real-time, so you catch problems in minutes. Framework-agnostic — LangChain, CrewAI, raw SDK, every model call produces tokens. Low friction — logging tokens is a few lines of code.&lt;/p&gt;

&lt;p&gt;Limitations: you need baseline history (a brand-new agent with five executions has no reliable range yet), the technique is context-dependent (agents with legitimately wide output variance need a wider baseline), and detection isn't prevention — flagging a failure doesn't stop the next expensive call, that needs an actual pause or rate limit on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical next step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Log tokens — add input_tokens, output_tokens, cost_usd to every execution record.&lt;/li&gt;
&lt;li&gt;Compute a baseline once you have a week or two of history.&lt;/li&gt;
&lt;li&gt;Set an alert on the 10th percentile of output tokens, or output_tokens == 0.&lt;/li&gt;
&lt;li&gt;Triage once. When it fires, you'll either see a genuine empty response (actionable) or a legitimate edge case you exclude next round.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You won't catch every silent failure this way. You'll catch the common ones — the ones that hurt most because they're invisible until someone notices.&lt;/p&gt;

&lt;p&gt;If you'd rather not build and maintain this plumbing yourself, that's the layer we built into the AI Agents Control Tower — token tracking, baselining, and anomaly alerting, so you define thresholds instead of infrastructure. Either way the principle holds: measure the tokens, establish the baseline, alert on the gap. Silent failures are only silent until you start listening.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>observability</category>
      <category>devops</category>
    </item>
    <item>
      <title>Token budget guardrails: enforcement, not just visibility</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:39:57 +0000</pubDate>
      <link>https://dev.to/opsveritas/token-budget-guardrails-enforcement-not-just-visibility-3k89</link>
      <guid>https://dev.to/opsveritas/token-budget-guardrails-enforcement-not-just-visibility-3k89</guid>
      <description>&lt;p&gt;You've been monitoring your agent's token spend for weeks. Then one run goes wild — 50x the usual token count — and by the time you notice, half your budget is gone.&lt;/p&gt;

&lt;p&gt;Visibility is the first step. Enforcement is the second, and it's the one that actually stops the bleed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: seeing the spike after the damage
&lt;/h2&gt;

&lt;p&gt;Most monitoring setups give you a dashboard. Your agent runs, consumes tokens, and at month end you see the bill. Even with a live dashboard, the response chain is slow: monitor, alert, human, decision, pause. That gap costs real money.&lt;/p&gt;

&lt;p&gt;What you actually need is a guardrail that stops the agent from running the moment it crosses a ceiling you defined in advance. Not "alert the team" — "reject the call before it happens."&lt;/p&gt;

&lt;p&gt;This matters because of how token explosions actually happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry loops — an agent keeps calling the same tool because it misreads the response. Each retry burns tokens. A single run can loop 10-50 times before anyone notices.&lt;/li&gt;
&lt;li&gt;Context bloat — the agent accumulates conversation history or debug logs in its context window. By run 100, input tokens are 3x baseline.&lt;/li&gt;
&lt;li&gt;Hallucinated retries — the model thinks a tool call failed when it actually succeeded, so it tries again. Logs look clean. Cost doesn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case, the agent still succeeds — HTTP 200. The token spend doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set per-agent token budgets
&lt;/h2&gt;

&lt;p&gt;Start with a baseline. Run your agent 20-50 times under normal conditions and record the token count per execution. This is your reference point — not average monthly spend, but typical spend per single run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example: a document-retrieval agent
- Run 1: 3,200 input + 450 output = 3,650 total
- Run 2: 3,100 input + 480 output = 3,580 total
- Run 3: 3,400 input + 520 output = 3,920 total
Average per run: ~3,700 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that baseline, set two thresholds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-run ceiling.&lt;/strong&gt; Set it at 2-3x typical spend. Anomalies exist — a genuinely complex query might need more — but 3x is usually where something's actually wrong. If baseline is 3,700 tokens, ceiling lands around 10,000-11,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monthly budget ceiling.&lt;/strong&gt; Divide your monthly LLM budget by expected run count, then apply a safety margin. Budget $100/month, expect 100 runs — that's $1/run. At roughly $0.002 per 1K tokens for cheaper models, that's about 500 tokens/run. Set the monthly ceiling around $80 (a 20% margin) and enforce it across all agents.&lt;/p&gt;

&lt;p&gt;The math is simple. The enforcement is what actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: the kill-switch pattern
&lt;/h2&gt;

&lt;p&gt;Once you've defined ceilings, the agent needs to check them before each call, not after.&lt;/p&gt;

&lt;p&gt;If you're using the AI Agents Control Tower, this is built in. The kill switch is an opt-in feature that pauses your agents the moment your org crosses its monthly budget:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the kill switch in org settings and define your monthly token/cost limit.&lt;/li&gt;
&lt;li&gt;Wrap your LLM client with the SDK's enforcement layer (&lt;code&gt;wrap_langchain&lt;/code&gt; for LangChain models) — adds a lightweight status check (~3ms, cached) before each inference call.&lt;/li&gt;
&lt;li&gt;If the limit's crossed, the SDK raises &lt;code&gt;OpsVeritasKilledError&lt;/code&gt; instead of calling the model. The agent stops. No token burn, no surprise bill.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The kill switch fails open — if the status check itself fails (a network blip), the call proceeds normally. You're protected against silent failures, not against your own infrastructure breaking.&lt;/p&gt;

&lt;p&gt;In 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;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_langchain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chat_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Wrap the model for telemetry (which agent ran, how many tokens)
&lt;/span&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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add enforcement: blocks the call if budget is exceeded
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&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;# model.invoke() now raises OpsVeritasKilledError
# if the org's monthly limit is breached
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For non-LangChain setups, use the Universal Webhook to POST your agent's token telemetry to the control tower after each run. Include &lt;code&gt;cost_usd&lt;/code&gt; in the payload and the system tracks spend in real time across all agents. The kill switch still fires at the org level, but you own the decision of whether to actually call the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-agent ceilings: a second layer
&lt;/h2&gt;

&lt;p&gt;The org-wide kill switch is insurance. For finer control, add per-agent ceilings inside your agent's own execution loop:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;baseline_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3700&lt;/span&gt;  &lt;span class="c1"&gt;# your per-run baseline
&lt;/span&gt;    &lt;span class="n"&gt;ceiling_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11000&lt;/span&gt;  &lt;span class="c1"&gt;# 3x baseline
&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;model&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="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ceiling_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent exceeded token ceiling: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ceiling_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# retry with a simpler query, alert the user, or flag for review
&lt;/span&gt;        &lt;span class="k"&gt;return&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;budget_exceeded&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;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&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;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This layer catches anomalies per execution, not per month. If one run gets expensive, you know immediately, and you can retry, degrade gracefully, or fail loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring what matters
&lt;/h2&gt;

&lt;p&gt;Once guardrails are in place, track three metrics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tokens per run (p50, p95) — tells you if the baseline is drifting. A climbing trend means the agent is degrading.&lt;/li&gt;
&lt;li&gt;Cost per run — token count alone doesn't account for model differences; a gpt-4o run costs differently than gpt-4o-mini.&lt;/li&gt;
&lt;li&gt;% of runs hitting the ceiling — if more than 1-2% of runs exceed the threshold, either the ceiling's wrong or the agent has a real problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most monitoring tools show total spend. What you need is per-execution visibility — the individual run that went wild, not just the aggregate bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson
&lt;/h2&gt;

&lt;p&gt;Visibility without enforcement is a dashboard you check after something breaks. Enforcement without visibility is a kill switch that fires mysteriously. You need both.&lt;/p&gt;

&lt;p&gt;Set baselines from real data. Define ceilings at 2-3x normal. Enforce before the call, not after. Track per-run metrics, not just aggregate spend. When an anomaly hits, you'll catch it in milliseconds, not at month end.&lt;/p&gt;

&lt;p&gt;Cost governance is reliability. Silent token loops are a failure mode just as real as crashes — they just hide longer.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>observability</category>
    </item>
    <item>
      <title>The eval illusion: why passing tests doesn't mean safe in production</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Fri, 07 Aug 2026 06:04:18 +0000</pubDate>
      <link>https://dev.to/opsveritas/the-eval-illusion-why-passing-tests-doesnt-mean-safe-in-production-11h1</link>
      <guid>https://dev.to/opsveritas/the-eval-illusion-why-passing-tests-doesnt-mean-safe-in-production-11h1</guid>
      <description>&lt;p&gt;You built an AI agent. The reasoning holds up — your eval suite checks that it picks the right tool for each task, chains them logically, recovers from a bad step. Scores are high. You ship it.&lt;/p&gt;

&lt;p&gt;Three days later, a customer says the agent returned a successful response and did nothing. No errors in the logs. Your monitoring flags something odd though: output tokens dropped to zero while input tokens looked totally normal. A silent failure your evals never had a chance to catch.&lt;/p&gt;

&lt;p&gt;That's not a hole in your eval strategy. It's a different category of problem entirely.&lt;/p&gt;

&lt;p&gt;Evals test reasoning in a controlled environment. Does it pick the right tool? Does it combine tools correctly? Does it recover when something looks wrong? Yes, mostly — because it's running with fresh context, clean input, and nothing else competing for its attention.&lt;/p&gt;

&lt;p&gt;The reliability stack tests what happens once that same agent is loose in the real world. Did it actually execute what it decided to do? Did the output match what the model claimed? Did it quietly loop when a tool failed? These aren't reasoning questions. They're runtime questions, and evals were never built to answer them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What evals miss
&lt;/h2&gt;

&lt;p&gt;Evals run under close to ideal conditions — fresh context, known inputs, tools that behave the way you mocked them, one clean execution path.&lt;/p&gt;

&lt;p&gt;Production runs under none of that. APIs time out at random. Rate limits show up mid-run. Real users send things your training data never saw. Context balloons across hundreds of runs. And sometimes the agent just quietly retries a failing tool call, over and over, with nothing logged as an error.&lt;/p&gt;

&lt;p&gt;Here's a concrete version of that: your eval tests an agent that fetches user data from an API and summarizes it. It passes clean. In production, that API is slow one day. The timeout fires, the model reads that as a failed call, and decides to retry. Then retries again. Thirty attempts and 120,000 tokens later, it gives up and returns nothing — but logs "success" the whole way through, because technically nothing errored.&lt;/p&gt;

&lt;p&gt;Your evals never saw a slow API. They never saw a third retry. They definitely never logged what the agent does when it finally gives up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reliability stack actually watches
&lt;/h2&gt;

&lt;p&gt;A few signals fill that gap, and none of them come from an eval suite:&lt;/p&gt;

&lt;p&gt;Silent failures show up as a mismatch between input and output tokens — normal input, near-zero output means the agent processed the request and produced nothing.&lt;/p&gt;

&lt;p&gt;Latency anomalies show up when success stays green but timing spikes. That's almost always a loop or a retry burning time in the background.&lt;/p&gt;

&lt;p&gt;Token drift shows up when the same task starts costing more tokens over time on the same agent — usually prompt bloat or state quietly accumulating.&lt;/p&gt;

&lt;p&gt;Cost spikes are the most obvious canary. A 10x jump in cost per run is rarely a coincidence.&lt;/p&gt;

&lt;p&gt;And execution paths — which tools actually got called, in what order, did they all succeed — tell you whether the agent's recovery logic works under real failure, not just the happy path you tested.&lt;/p&gt;

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

&lt;p&gt;The gap between shipped and safe isn't a code quality problem. It's an observability problem.&lt;/p&gt;

&lt;p&gt;A well-reasoned agent with a clean eval score can still loop forever on a timeout, return silent success while doing nothing, or spike cost 100x from one bad retry cascade. None of those are logic errors — they only show up once the agent meets a messy, real execution environment.&lt;/p&gt;

&lt;p&gt;Your evals proved it can reason. Something else has to prove it can run safely.&lt;/p&gt;

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

&lt;p&gt;You don't need to rebuild the agent or the eval suite. You need visibility into execution itself: wrap the LLM client so every run logs tokens, latency, cost, and output length. Run it long enough to know your baselines. Alert when something drifts from them. Log which tools actually got called and what happened when one failed.&lt;/p&gt;

&lt;p&gt;Evals and the reliability stack aren't competing with each other. One proves the agent can think. The other proves that thinking survives contact with production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>observability</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Silent Loop: Why Latency and Token Count Together Catch What Status Codes Miss</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Thu, 06 Aug 2026 06:33:17 +0000</pubDate>
      <link>https://dev.to/opsveritas/the-silent-loop-why-latency-and-token-count-together-catch-what-status-codes-miss-dai</link>
      <guid>https://dev.to/opsveritas/the-silent-loop-why-latency-and-token-count-together-catch-what-status-codes-miss-dai</guid>
      <description>&lt;p&gt;You deploy an AI agent that calls a flaky API. The API fails 10% of the time. Your agent is wired to retry on failure, good practice, right?&lt;/p&gt;

&lt;p&gt;Then something goes wrong. The API stays broken for an hour. Your agent keeps retrying. Each retry consumes tokens. The loop never errors out, it just cycles. Success status, because the agent finished (it gave up after N retries). But the cost? Climbing in a straight line while no work got done.&lt;/p&gt;

&lt;p&gt;This is the infinite retry loop, not a crash, not a timeout, just silent token bleed. Your logs look healthy. Your error rate is zero. Your bill is bleeding.&lt;/p&gt;

&lt;p&gt;The reason most monitoring misses this: error rates and request counts don't catch it. The agent isn't erroring, it's succeeding at retrying. What does catch it is a pattern you can measure right now: latency and token count moving together, in a way that doesn't match your normal execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math of the Pattern
&lt;/h2&gt;

&lt;p&gt;When a healthy agent runs, there's a relationship between how long it takes and how many tokens it uses. Call this the latency-to-token ratio.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A normal run: 2 seconds, 500 tokens. Ratio: 250 tokens/sec.&lt;/li&gt;
&lt;li&gt;Another normal run: 3 seconds, 800 tokens. Ratio: ~267 tokens/sec.&lt;/li&gt;
&lt;li&gt;Your baseline range: say, 200-300 tokens/sec.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the loop starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First 30 seconds: 15,000 tokens. Ratio: 500 tokens/sec. Way above baseline.&lt;/li&gt;
&lt;li&gt;Next 30 seconds: another 15,000 tokens. Still climbing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The signal is clear: latency is spiking, and token consumption is accelerating disproportionately. The agent is doing a lot of token work in a short time, characteristic of retry loops where the model is being re-invoked repeatedly against the same or similar inputs.&lt;/p&gt;

&lt;p&gt;By contrast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A normal, slow run (agent thinking hard): latency high, tokens high, but the ratio stays in your normal band. The model had a complex problem; it thought longer and used more tokens. Proportional.&lt;/li&gt;
&lt;li&gt;A loop: latency high, tokens extremely high (disproportionate spike), ratio breaks baseline. The model is being called over and over in the same second window, burning tokens on retry cycles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the Heuristic
&lt;/h2&gt;

&lt;p&gt;Here's the threshold pattern builders should use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Establish your baseline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run 20 normal agent executions. For each, compute:&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;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record the 50th and 95th percentile ratios. Call these p50_ratio and p95_ratio.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50: 250 tokens/sec&lt;/li&gt;
&lt;li&gt;p95: 400 tokens/sec (accounting for "thinking hard" runs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Set alert thresholds&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caution zone: ratio &amp;gt; p95_ratio * 1.5 (e.g., &amp;gt; 600 tokens/sec)&lt;/li&gt;
&lt;li&gt;Alert zone: ratio &amp;gt; p95_ratio * 2.5 (e.g., &amp;gt; 1000 tokens/sec)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Combine with duration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If latency is also above your normal range (e.g., &amp;gt; 2 standard deviations from mean), weight the alert higher. A 45-second run with 20,000 tokens (444 tokens/sec) might be fine. A 45-second run with 50,000 tokens (1111 tokens/sec) is almost certainly looping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Watch the trend, not the single run&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A single run can be an outlier. Watch for the pattern across 3+ consecutive runs in a 5-minute window:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If 2 or more runs breach the alert threshold, fire the alert.&lt;/li&gt;
&lt;li&gt;If the ratio stays elevated, the loop is ongoing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Catches Loops Before Cost Explodes
&lt;/h2&gt;

&lt;p&gt;Consider the numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normal agent: 500 tokens/run, $0.00075 per run (at typical pricing).&lt;/li&gt;
&lt;li&gt;Loop for 1 hour: 150 retries, ~5000 tokens per cycle, = 750,000 tokens, ~$1.13 per hour.&lt;/li&gt;
&lt;li&gt;Loop for 4 hours undetected: $4.50. Small. Still bad.&lt;/li&gt;
&lt;li&gt;Loop for a full day: $27. Big enough to be noticed in a weekly bill review. Too late.&lt;/li&gt;
&lt;li&gt;Loop caught at 30 minutes via latency+token spike: ~$0.56. Caught, stopped, learning drawn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latency-token ratio detects the loop in the first 2-3 runs, not after hours or days. You catch it while the cost is still negligible.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Instrument This (No Vendor Lock-in)
&lt;/h2&gt;

&lt;p&gt;You don't need a specialized tool to start. Just log three numbers per execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"agent_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-4o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compute the ratio in your log aggregator (Datadog, New Relic, CloudWatch, or a simple script):&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;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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;ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# or your alert threshold
&lt;/span&gt;&lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High token-per-second ratio: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're wrapping an LLM SDK (OpenAI, Anthropic, etc.), you already have access to output_tokens and request duration. Logging that pair takes three lines.&lt;/p&gt;

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

&lt;p&gt;This heuristic works because infinite loops have a structural signature: they burn tokens fast relative to the time they're supposed to be thinking. A model pondering a hard question runs long and uses tokens proportionally. A retry loop cycles the model through shallow re-attempts, burning tokens in bursts.&lt;/p&gt;

&lt;p&gt;By watching both dimensions together, you're detecting the imbalance that characterizes the loop without needing to parse logs, understand retry logic, or set up complex alerting rules.&lt;/p&gt;

&lt;p&gt;It's not perfect, a pathological case might fool it. But it catches the 99% case: the silent loop that's invisible when you're watching latency alone, and eats budget fast when you're not watching at all.&lt;/p&gt;

&lt;p&gt;Start logging latency + output tokens today. Compute the ratio. Set the thresholds. You'll catch the next loop before your bill does.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're already running agents in production, pull 20 recent runs and compute your p50 and p95 right now. That baseline is your first line of defense against cost creep.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>devops</category>
    </item>
    <item>
      <title>You Shipped the Agent. Now Build the Monitoring Layer.</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:54:18 +0000</pubDate>
      <link>https://dev.to/opsveritas/you-shipped-the-agent-now-build-the-monitoring-layer-9jn</link>
      <guid>https://dev.to/opsveritas/you-shipped-the-agent-now-build-the-monitoring-layer-9jn</guid>
      <description>&lt;p&gt;You've shipped your first AI agent to production. The dashboard shows it running. But you have no real idea what it costs per request, or whether it's quietly failing in ways your error logs would never catch.&lt;/p&gt;

&lt;p&gt;This is the gap most builders find out about the hard way: deployment isn't observability. An agent can return HTTP 200, log success, and produce zero useful output — all without a single error anywhere in the chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why standard monitoring misses AI agents
&lt;/h2&gt;

&lt;p&gt;Typical app observability — latency, error rates, uptime — was built for request/response workflows. It catches crashes, timeouts, explicit errors. AI agents fail differently. They fail silently.&lt;/p&gt;

&lt;p&gt;Three ways this shows up in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 200, logs show success, zero output tokens generated. The agent ran, the API call succeeded, the response came back empty. No error. Just nothing.&lt;/li&gt;
&lt;li&gt;A hundred requests, ninety blank outputs. Your logs say 90% success. Your users see broken functionality.&lt;/li&gt;
&lt;li&gt;Cost per request 10x what it was last week — the agent's looping, retrying tool calls, burning tokens, and the logs still say "success."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are crashes. Nothing you're currently watching will flag them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually need to see
&lt;/h2&gt;

&lt;p&gt;Four signals, once the agent's live:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tokens per execution&lt;/strong&gt; (input + output). Your canary — zero output tokens on a "successful" run means something's broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost per request&lt;/strong&gt;, computed from tokens × model pricing. Aggregates hide loops; per-execution cost reveals them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did it actually produce output&lt;/strong&gt; — separate from whether the HTTP call succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency and retry patterns&lt;/strong&gt; — a 2-second call suddenly taking 30 usually means it's looping on a tool call.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Three ways to wire it in
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SDK instrumentation
&lt;/h3&gt;

&lt;p&gt;The easiest path if you're on OpenAI, Anthropic, or an OpenAI-compatible provider:&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;opsveritas&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;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;&amp;lt;your OpsVeritas SDK key&amp;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;# from your OpsVeritas org, not OpenAI/Anthropic
&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&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines, and every call gets tracked — tokens, cost, output status, latency, model — without your API keys ever leaving your process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook
&lt;/h3&gt;

&lt;p&gt;For frameworks without SDK support, or when you want full control over what gets sent. The &lt;code&gt;x-agents-key&lt;/code&gt; here is a key OpsVeritas issues to your org specifically — it's separate from your OpenAI/Anthropic credentials, which never touch OpsVeritas in this flow:&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://ai-agents-control-tower.onrender.com/webhooks/agent-execution &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-agents-key: &amp;lt;your OpsVeritas webhook key&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&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": "customer-support-bot",
    "status": "success",
    "input_tokens": 245,
    "output_tokens": 0,
    "cost_usd": 0.0042,
    "duration_ms": 1240,
    "executed_at": "2026-08-04T14:22:15Z"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Just log it locally
&lt;/h3&gt;

&lt;p&gt;If you're not ready for a platform yet — capture tokens, cost, and whether there was output on every run, print it as structured JSON, and grep for &lt;code&gt;output_tokens: 0&lt;/code&gt; in your own logs. Fifteen minutes, no external dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the thresholds
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cost per request under 2x your baseline — above that, something's looping.&lt;/li&gt;
&lt;li&gt;Output tokens should never be zero on a run marked successful.&lt;/li&gt;
&lt;li&gt;Latency spikes usually mean retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams skip this because it feels like overhead — right up until an agent burns a day's budget in an hour, or fails silently for your highest-value customer. Start with one agent, capture tokens and cost, watch for zeros. That's most of the observability you actually need, and it's a few minutes of work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>llm</category>
    </item>
    <item>
      <title>Your Agent Returned 200 and Did Nothing: Detecting Silent Failures with Token Counts</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:02:00 +0000</pubDate>
      <link>https://dev.to/opsveritas/your-agent-returned-200-and-did-nothing-detecting-silent-failures-with-token-counts-1nc2</link>
      <guid>https://dev.to/opsveritas/your-agent-returned-200-and-did-nothing-detecting-silent-failures-with-token-counts-1nc2</guid>
      <description>&lt;p&gt;Your agent returned HTTP 200, zero errors in the logs, and then did nothing. The failure wasn't loud — it was silent.&lt;/p&gt;

&lt;p&gt;This happens more than you'd think. An agent invokes a model, gets back a response, processes it, returns success. But somewhere in that flow the actual output — the thing that was supposed to happen — vanished. No exception. No timeout. Just an empty response where a decision or action should have been.&lt;/p&gt;

&lt;p&gt;Standard monitoring catches loud failures: connection timeouts, rate limits, exceptions. It misses the silent ones. Here's why, and how to spot them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three signals most monitors ignore
&lt;/h2&gt;

&lt;p&gt;When an LLM agent runs, three things happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The HTTP call completes (status 200 or 400+)&lt;/li&gt;
&lt;li&gt;Tokens are consumed (input, then output)&lt;/li&gt;
&lt;li&gt;Actual work occurs — a response is generated, a decision is made, text comes back&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most observability tools watch #1. Some logging catches parts of #2. Almost nobody watches #3 in a way that actually matters.&lt;/p&gt;

&lt;p&gt;The mechanical truth: HTTP 200 and a nonzero token count do not guarantee real output.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model refuses silently
&lt;/h3&gt;

&lt;p&gt;An agent asks Claude (or GPT, or any model) to do something that brushes up against a boundary — political, violent, ambiguous. The refusal policy triggers. It comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;HTTP 200
input_tokens: 450
output_tokens: 127
response: "" // or a short refusal like "[No response]"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call succeeded. Tokens were spent. Nothing usable came out. If your downstream logic checks &lt;code&gt;if response: do_something()&lt;/code&gt;, you've got a silent failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model generates emptiness
&lt;/h3&gt;

&lt;p&gt;Less common, but real: the model returns a completion that's all whitespace or filler with no actual content. Output tokens go up. Meaning doesn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 200
input_tokens: 300
output_tokens: 89
response: " \n\n " // whitespace, no content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tokens spent, call succeeded, nothing happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool use fails quietly
&lt;/h3&gt;

&lt;p&gt;An agent calls a tool — database lookup, API call, calculation. The tool errors or returns nothing. The model, correctly, generates a response explaining it couldn't do the thing. Except your downstream code expected a value, not an apology.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;HTTP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;input_tokens:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;520&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;output_tokens:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;response:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I attempted to fetch the user but the database returned no match."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;expected:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user_id:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent succeeded at speaking. It failed at acting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why token count is the canary
&lt;/h2&gt;

&lt;p&gt;Output tokens measure how much the model actually generated, independent of quality. A genuinely empty response will have an output token count near zero. A response that merely looks empty — but has some content, even noise — will burn tokens.&lt;/p&gt;

&lt;p&gt;That's why token count matters more than HTTP status:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 200 tells you the infrastructure worked.&lt;/li&gt;
&lt;li&gt;Output tokens &amp;gt; 0 tells you the model generated something.&lt;/li&gt;
&lt;li&gt;Actual content length tells you if there's signal or just noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rough heuristic: if output tokens are suspiciously low for the task, or nonzero but the response is empty/whitespace, you're looking at a silent failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple detector
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_silent_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;http_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;tokens_spent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;has_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;min_threshold&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. 10 chars
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;http_ok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tokens_spent&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;has_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SILENT_FAILURE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;http_ok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tokens_spent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;expected_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNDERSHOOTING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is comparing actual output against token count. If a model spent 150 output tokens and your response is empty, something broke between generation and delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's worth catching
&lt;/h2&gt;

&lt;p&gt;A crash tells you something broke — you debug it. A "success" that produced nothing is worse: it quietly corrupts pipelines, skips records, or leaves a user waiting on a response that's never coming. HTTP status and token count both look fine, so plain logging won't catch it. You need something that checks whether the response actually matches what was supposed to happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;If you're running agents and not instrumenting for this, you're flying blind. Three things help:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log output token count alongside every response. Put it in your metrics, not just your logs.&lt;/li&gt;
&lt;li&gt;Flag runs where tokens are spent but response length is suspiciously small.&lt;/li&gt;
&lt;li&gt;Trace downstream failures back to the source — did the agent really produce output, or did it just look like it did?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The green light doesn't mean it's working. The token count tells you if anyone's actually home.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>llmops</category>
    </item>
    <item>
      <title>6 Failure Modes to Instrument Before You Ship an AI Agent</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:44:24 +0000</pubDate>
      <link>https://dev.to/opsveritas/6-failure-modes-to-instrument-before-you-ship-an-ai-agent-3aoj</link>
      <guid>https://dev.to/opsveritas/6-failure-modes-to-instrument-before-you-ship-an-ai-agent-3aoj</guid>
      <description>&lt;p&gt;Before you ship an AI agent to production, you need to know what happens when it breaks silently, and most teams don't find out until a customer complains.&lt;/p&gt;

&lt;p&gt;The reason is structural: your agent can return HTTP 200, your logs can show success, your tests can pass, and the agent still did nothing. No tokens consumed, no output, no action taken. You asked it for a summary and it gave you an empty string. You asked it to classify an email and it returned null. The call succeeded. The agent failed.&lt;/p&gt;

&lt;p&gt;This is different from errors. Errors are loud, your monitoring catches them, your alerting screams, you fix them. Silent failures are quieter, and they're more expensive because they hide.&lt;/p&gt;

&lt;p&gt;What you need is a checklist, a set of things to instrument before production that would catch the failures that actually matter. Not every metric, just the ones that predict a broken agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6 failure modes to watch for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Zero output tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An LLM call that consumed input tokens but produced zero output tokens is a red flag. The model was invoked, it ran, and it produced nothing. Maybe it hit a length limit, maybe it looped and timed out mid-generation, maybe it rejected the prompt entirely.&lt;/p&gt;

&lt;p&gt;The check is simple: output_tokens equals 0, input_tokens is greater than 0, and status is success. If this happens once, it's probably noise. If it happens twice in a row, something is wrong.&lt;/p&gt;

&lt;p&gt;Track this per agent, per model, per day. If your agent's zero-output rate climbs above 5% on a Wednesday, you have a problem before your customer does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Output summary is blank or obviously wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your agent returns text, but the text is empty, or it's a hallucinated placeholder like "[No response]" or "ERROR: could not generate output." The agent didn't fail to run, it ran and produced garbage.&lt;/p&gt;

&lt;p&gt;Capture a short summary of the output, the first 50 to 100 characters, or a key field if your agent returns structured data. Then set an alert: if more than 2 consecutive runs produce blank or obviously-empty summaries, page someone.&lt;/p&gt;

&lt;p&gt;This is a heuristic, not a law. But it's the difference between finding out at 3am because your customer's support queue is stuck versus finding out during your afternoon standup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Latency spikes (model is timing out or looping)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent call that takes 60 seconds when it normally takes 2 seconds is a symptom. Usually it means one of two things: the model is in a retry loop, or it's stuck generating tokens and hit your timeout.&lt;/p&gt;

&lt;p&gt;Track p50 and p95 latency per agent. If p95 suddenly jumps 10x, it's not noise. Something changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Token consumption is wildly higher than expected&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You ship an agent that normally uses 500 tokens per run. On Tuesday, the same agent is consuming 15,000 tokens per run, same input, same queries, same model. But the token use is exploding.&lt;/p&gt;

&lt;p&gt;This usually means the agent got stuck in a loop, it's calling the model multiple times in a single run, or it's feeding its own output back into itself, or the prompt got injected with adversarial content that forces token-heavy generation.&lt;/p&gt;

&lt;p&gt;Track input and output tokens per run. Set a ceiling: if a single run exceeds 10x the 90th percentile of that agent's normal token use, that's a signal to investigate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Success rate drops below your baseline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You know what your agent's success rate is most of the time. Maybe it's 97%, maybe it's 87%. Whatever it is, that's your baseline. If it drops to 70%, you have a problem.&lt;/p&gt;

&lt;p&gt;Most monitoring tools measure errors, exceptions, 500s, timeouts. But an agent can have a very high "success" rate (all calls return 200) while having a low "useful" rate (half the calls produce empty output). Track both.&lt;/p&gt;

&lt;p&gt;Success rate should be defined as calls that returned a non-empty, non-hallucinated result divided by total calls. If it drops 10 points or more day-over-day, alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Cost per run is spiking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your agent cost $0.02 per run last week. This week it's $0.50 per run, same input distribution, same model. Something is bloating the token use.&lt;/p&gt;

&lt;p&gt;Usually this is the loop problem again, the agent calling the model multiple times per run, using a more expensive model, or the prompt got bloated. But you won't know until you have the number.&lt;/p&gt;

&lt;p&gt;Compute cost per run from input tokens plus output tokens times model pricing. If the per-run cost jumps 5x, that's your smoke signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to instrument for these checks
&lt;/h2&gt;

&lt;p&gt;You don't need a fancy platform to catch these. You need:&lt;/p&gt;

&lt;p&gt;Capture these fields per run: status (success, error, timeout), input_tokens and output_tokens, latency_ms, a short summary of the output (first 100 characters, or null if empty), cost_usd (computed from tokens times model pricing), and timestamp.&lt;/p&gt;

&lt;p&gt;Store them anywhere, a database, a log aggregate, a monitoring tool. Even a CSV file updated every 5 minutes will work to start.&lt;/p&gt;

&lt;p&gt;Set four alerts: zero-output rate above 5% in the last hour, two consecutive runs with blank summaries, p95 latency above 10x baseline, and per-run cost above 5x the 90th percentile.&lt;/p&gt;

&lt;p&gt;Check them daily, or set up a dashboard to watch them during launch week.&lt;/p&gt;

&lt;p&gt;That's it. You don't need ML, you don't need anomaly detection, you don't need sophisticated alerting. You need a checklist and the discipline to look at it before your customer's error budget is exhausted.&lt;/p&gt;

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

&lt;p&gt;Silent failures are cheap to fix early and expensive to fix late. Catch a zero-output loop before production and you tweak the prompt and redeploy. Catch it at 2am because your customer can't process their data and you're in firefighting mode.&lt;/p&gt;

&lt;p&gt;The difference is observability. Not just knowing that the agent ran, but knowing what it did when it ran.&lt;/p&gt;

&lt;p&gt;Run through this checklist before you ship. Instrument these six signals. Check them the day you launch. You'll find problems you didn't know you had, and you'll sleep better knowing you'll find the next ones before they become incidents.&lt;/p&gt;

&lt;p&gt;The agent that fails silently is not the one you instrumented, it's the one you shipped without looking.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>bestpractices</category>
    </item>
    <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>
  </channel>
</rss>
