<?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: Babar Hayat</title>
    <description>The latest articles on DEV Community by Babar Hayat (@babarmaker76).</description>
    <link>https://dev.to/babarmaker76</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3941231%2Fe10c2b01-1452-449f-b644-043621e5bf82.png</url>
      <title>DEV Community: Babar Hayat</title>
      <link>https://dev.to/babarmaker76</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/babarmaker76"/>
    <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>Design Patterns for Budget Enforcement in AI Agents</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sun, 16 Aug 2026 11:33:28 +0000</pubDate>
      <link>https://dev.to/babarmaker76/design-patterns-for-budget-enforcement-in-ai-agents-535h</link>
      <guid>https://dev.to/babarmaker76/design-patterns-for-budget-enforcement-in-ai-agents-535h</guid>
      <description>&lt;h2&gt;
  
  
  When cost control becomes a design decision
&lt;/h2&gt;

&lt;p&gt;You set a monthly budget for your AI agents. Then an agent hits it on day 3. Do you pause the whole system, just that agent, or wait and see?&lt;/p&gt;

&lt;p&gt;This isn't a rare edge case. As AI agents scale, especially agentic loops with tool calling, reasoning chains, or retry logic, cost can climb in ways nobody predicted. A single runaway execution can burn weeks of budget in an afternoon. So most teams want a kill switch. The hard part isn't wanting one. It's deciding which kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three enforcement patterns, and what they cost you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Org-wide budget ceiling
&lt;/h3&gt;

&lt;p&gt;Set a monthly limit for the entire organization. Hit it, and every active agent pauses at once.&lt;/p&gt;

&lt;p&gt;The logic: maximum safety. One team member spinning up an expensive agent can't surprise the whole company. The cost stops immediately.&lt;/p&gt;

&lt;p&gt;The cost: it's blunt. If one agent loops and burns the budget on day 3, your production agent, the one paying customers actually depend on, stops working too. That's an outage with no warning. You're trading uptime for certainty.&lt;/p&gt;

&lt;p&gt;When it makes sense: you have a hard cap you will not cross under any circumstances (compliance, a fixed budget line, a card-on-file limit), and your agents either aren't customer-facing or an outage is genuinely tolerable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Per-agent daily or monthly limit
&lt;/h3&gt;

&lt;p&gt;Each agent gets its own budget. If Agent A burns through its allocation, only Agent A pauses. Everything else keeps running.&lt;/p&gt;

&lt;p&gt;The logic: blast-radius containment. The expensive agent stops; the reliable one doesn't.&lt;/p&gt;

&lt;p&gt;The cost: you have to set per-agent limits correctly. Too tight, and a legitimate spike (a genuinely complex query) triggers a false pause. Too loose, and the agent still runs expensive for weeks before hitting its limit. And there's a delay built in — if an agent's daily limit is $50 and it burns $60 on day one, you don't know until tomorrow's review.&lt;/p&gt;

&lt;p&gt;When it makes sense: your agents have wildly different cost profiles, you want failures isolated from each other, and you can tolerate a day or two of overage while you investigate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Cost anomaly detection
&lt;/h3&gt;

&lt;p&gt;An agent's cost stays reasonable until it doesn't. Track its 30-day baseline. If three consecutive executions each run 3x that baseline, pause it.&lt;/p&gt;

&lt;p&gt;The logic: you're catching deviation, not absolute spend. An agent that normally costs $0.05 a run but suddenly costs $0.15 is telling you something's wrong, a hallucination, a retry loop, unexpected recursion, and you catch it without needing to guess the "right" budget in advance.&lt;/p&gt;

&lt;p&gt;The cost: lag. The detector doesn't fire until the third expensive execution. If a run costs $10 against a normal $0.05, you've already spent $30 before the kill triggers. It's also noisy during ramp-up — a new agent with no baseline yet can produce false alerts until it stabilizes.&lt;/p&gt;

&lt;p&gt;When it makes sense: you trust your agents' normal behavior and mainly want to catch unexpected shifts, and you'd rather tolerate a few expensive runs than deal with false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff every team hits
&lt;/h2&gt;

&lt;p&gt;All three patterns share the same tension: safety versus responsiveness.&lt;/p&gt;

&lt;p&gt;A kill switch that fires instantly (org-wide budget) is safest but dumbest, it can't tell a legitimate spike from a real problem. One that waits for a pattern (anomaly detection) is smarter but slower, the first runaway has already cost you money by the time it reacts.&lt;/p&gt;

&lt;p&gt;There's no universally correct answer, because the answer depends on what actually breaks your business. If an unexpected $5k charge would get your card declined, you want the tight, fast control. If it would just be absorbed but wasteful, you want the pattern detector. If your agents have genuinely different cost profiles, per-agent budgets let you tune each one independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to design your own
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Know your agents' baseline costs.&lt;/strong&gt; Run them in production, or close to it, for a week. Calculate the median cost per execution and the 95th percentile. If Agent A costs $0.10 and Agent B costs $5, that gap is your signal that they need different limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define your pain threshold.&lt;/strong&gt; Ask: what's the largest unexpected bill we could absorb without it being a crisis? If the answer is $100, set your org-wide limit there, or slightly under. If the answer is "it depends on the agent," use per-agent limits instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick your trigger type, and be honest about the lag.&lt;/strong&gt; An org-wide limit fires immediately but affects everything. A per-agent limit fires once that agent crosses its cap, with maybe an execution or two of delay depending on your check interval. An anomaly detector expects the first few expensive runs to happen before the 3-sigma rule trips — it's built for catching trends, not isolated spikes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide what happens when the kill fires.&lt;/strong&gt; Does the agent stay paused until someone restarts it, or does it auto-resume after a fixed time? Most teams prefer a manual restart. It forces you to actually look at what happened instead of letting the same bug retry itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more reality check
&lt;/h2&gt;

&lt;p&gt;No kill switch prevents the &lt;em&gt;first&lt;/em&gt; expensive execution. Set a $5 per-agent limit, and the agent that costs $20 still runs that one $20 execution before the limit kicks in. The switch is a brake, not a prevention system. It stops the bleeding, not the first wound.&lt;/p&gt;

&lt;p&gt;If you need to prevent expense before it happens, that's a different layer entirely: prompt guards, token limits, output truncation. The kill switch works alongside those, not instead of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical move
&lt;/h2&gt;

&lt;p&gt;Start with per-agent budgets if your agents have different cost profiles. It's granular without being overwhelming. Add anomaly detection a month in if you find yourself constantly resetting false positives.&lt;/p&gt;

&lt;p&gt;If you're early and your agents are all similar, an org-wide budget is simpler, at least until one agent's behavior starts to diverge from the rest.&lt;/p&gt;

&lt;p&gt;And actually measure what happens. Check whether your kill switches fire for real problems or false alarms, and adjust the thresholds. The design that works isn't the one that looks best on paper. It's the one that catches real runaways and lets legitimate spikes through.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We build this into how we monitor our own AI agents at &lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;agents.opsveritas.com&lt;/a&gt; — org-wide budgets, per-agent limits, and cost anomaly detection, all with a manual-restart kill switch.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Your LLM Agent Monitoring Is Blind to Empty Outputs</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sat, 15 Aug 2026 14:26:09 +0000</pubDate>
      <link>https://dev.to/babarmaker76/why-your-llm-agent-monitoring-is-blind-to-empty-outputs-4gc</link>
      <guid>https://dev.to/babarmaker76/why-your-llm-agent-monitoring-is-blind-to-empty-outputs-4gc</guid>
      <description>&lt;p&gt;You wrap your OpenAI call in a try-catch. The API responds with HTTP 200. Your monitoring logs success. Your dashboard shows green. Twelve hours later, a customer emails: &lt;em&gt;why did my request return nothing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a silent failure — the detection gap that almost every monitoring setup misses by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: what "success" actually means
&lt;/h2&gt;

&lt;p&gt;When you instrument an LLM agent, most observability tools watch for one thing: &lt;strong&gt;did the API call fail?&lt;/strong&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&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;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;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-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Success — log it
&lt;/span&gt;    &lt;span class="nf"&gt;log_to_monitoring&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Failure — log and alert
&lt;/span&gt;    &lt;span class="nf"&gt;log_to_monitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch block fires on network timeouts, authentication errors, rate limits, malformed requests — the visible failure modes. But there's a whole class of failures that return HTTP 200 anyway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API call succeeds.&lt;/li&gt;
&lt;li&gt;The response object is syntactically valid.&lt;/li&gt;
&lt;li&gt;But &lt;code&gt;response.choices[0].message.content&lt;/code&gt; is empty or whitespace-only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The monitoring layer sees the response code and declares victory. The agent proceeds with nothing to work with. Your system continues silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens (and why it's surprisingly common)
&lt;/h2&gt;

&lt;p&gt;Empty outputs aren't always bugs. Sometimes they're hints of a real problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model hit an internal limit.&lt;/strong&gt; Certain Anthropic models or OpenAI beta versions will return 200 with an empty completion if the output is too long for the context window &lt;em&gt;after&lt;/em&gt; token counting, or if the model's internal filtering catches the request. The API succeeded; the &lt;em&gt;inference&lt;/em&gt; did not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt injection or safety filtering.&lt;/strong&gt; A user prompt contains adversarial input that the model's safety classifier flags. The model refuses to respond, returns 200, and sends back an empty string — by design. Your monitoring sees 200 and thinks it's fine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool-call-only agents.&lt;/strong&gt; An agent framework (LangChain, CrewAI) is configured to return tool calls only, never direct text. The model returns &lt;code&gt;[{"type": "tool_use", "name": "...", ...}]&lt;/code&gt; with no text content in &lt;code&gt;message.content&lt;/code&gt;. Strictly speaking, the agent &lt;em&gt;did&lt;/em&gt; run — but if your monitoring only looks at &lt;code&gt;content&lt;/code&gt;, it sees empty and logs it as a failure when it was actually correct.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provider parsing failures.&lt;/strong&gt; A non-standard provider or a custom SDK wrapping an OpenAI-compatible endpoint garbles the response parsing. The outer call returns 200 (the wrapper succeeded in getting a response), but the actual content extraction fails and returns null.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these return HTTP 200. All of them are invisible to standard error traps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What observability layers typically miss
&lt;/h2&gt;

&lt;p&gt;Let's map the layers of a typical LLM observability stack and see where the gap lives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Watches&lt;/th&gt;
&lt;th&gt;Catches HTTP 200 + empty?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Try-catch / error handler&lt;/td&gt;
&lt;td&gt;Exception objects&lt;/td&gt;
&lt;td&gt;No — no exception raised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP status codes&lt;/td&gt;
&lt;td&gt;4xx, 5xx, timeouts&lt;/td&gt;
&lt;td&gt;No — 200 is success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token counts&lt;/td&gt;
&lt;td&gt;Input tokens captured&lt;/td&gt;
&lt;td&gt;Depends — output tokens = 0 or missing?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response parsing&lt;/td&gt;
&lt;td&gt;JSON validity&lt;/td&gt;
&lt;td&gt;No — empty string is valid JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging middleware (OpenTelemetry, etc.)&lt;/td&gt;
&lt;td&gt;Request/response metadata&lt;/td&gt;
&lt;td&gt;Only if you explicitly log content length&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM-specific SDK (e.g., LangChain callback)&lt;/td&gt;
&lt;td&gt;Message count, tool calls&lt;/td&gt;
&lt;td&gt;Only if &lt;code&gt;message.content&lt;/code&gt; is explicitly checked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap: &lt;strong&gt;none of these layers &lt;em&gt;default&lt;/em&gt; to checking whether the actual output is non-empty.&lt;/strong&gt; They all pass the call through as "success" because, by the narrow definition of "did the API succeed," it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to detect it: the mechanics
&lt;/h2&gt;

&lt;p&gt;To reliably catch empty outputs, you need to add an explicit check in the instrumentation layer — a guard that inspects the &lt;em&gt;content&lt;/em&gt;, not just the status.&lt;/p&gt;

&lt;p&gt;Here's the pattern:&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;openai&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;opsveritas&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize monitoring
&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;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 client — this instruments tokens and cost automatically
&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;OpenAI&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="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;span class="c1"&gt;# Make the call
&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;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-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&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;role&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;user&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;content&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;Translate this to French: ...&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="c1"&gt;# Now the explicit check — capture the output and test it
&lt;/span&gt;&lt;span class="n"&gt;output_text&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;choices&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;output_is_empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;output_text&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="c1"&gt;# Log or alert
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output_is_empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SILENT FAILURE: HTTP 200 but empty output&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 the signal your monitoring should catch
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_text&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;The SDK here captures tokens and cost automatically. But the &lt;strong&gt;empty-output check&lt;/strong&gt; has to live in your code, right after the response lands. Why? Because only &lt;em&gt;you&lt;/em&gt; know what "empty" means for your agent — it might be zero characters, or it might be a response that's just whitespace, or it might be &lt;code&gt;null&lt;/code&gt;. The infrastructure can't make that judgment.&lt;/p&gt;

&lt;p&gt;For agents running in frameworks like LangChain or CrewAI, the same pattern applies — you wrap the model, then add a post-call assertion:&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;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="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;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-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opsveritas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;langchain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&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;result&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="c1"&gt;# Alert: silent failure detected
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK sends telemetry (tokens, cost, latency). But the empty check is application logic — it has to live where the response is handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens at the monitoring backend
&lt;/h2&gt;

&lt;p&gt;Once you've added that check, here's what a proper monitoring backend should do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Receive the telemetry&lt;/strong&gt; — tokens, cost, status, &lt;strong&gt;and a flag for empty output&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify the failure&lt;/strong&gt; — &lt;code&gt;status: success&lt;/code&gt; but &lt;code&gt;output_tokens: 0&lt;/code&gt; or &lt;code&gt;content_length: 0&lt;/code&gt;? That's a silent failure, not a normal success.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert separately&lt;/strong&gt; — silent failures are different from timeout failures or rate-limit failures. Your runbook for each is different. A silent failure might mean "retry this agent with a different prompt" or "check the safety filter logs." A timeout means "increase concurrency limits."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track baseline&lt;/strong&gt; — over time, the monitoring system learns this agent's normal output-token distribution. If output suddenly drops to zero for 3 runs in a row, that's a regression signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this matters for cost and reliability
&lt;/h2&gt;

&lt;p&gt;Silent failures compound two problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; An agent that runs repeatedly, returns nothing, and never triggers an alert keeps calling your LLM API. With millions of requests a day, even one silent-failure loop can burn thousands in cost — because the loop keeps retrying, each retry succeeds (200), and each success is invisibly empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; A customer-facing system that silently returns empty results erodes trust before you even know there's a problem. By the time complaints arrive, you're debugging backwards through hours of logs trying to spot the failure that your monitoring said never happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix isn't complicated — but it requires two pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explicit output checks&lt;/strong&gt; in your agent code (one line per agent).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring that understands empty output as a distinct failure mode&lt;/strong&gt; (not lumped in with general "success").&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither is rocket science. But most setups skip both, which is why HTTP 200 + empty output remains one of the industry's favorite silent killers.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you hit this? Silent-failure loops that your monitoring missed because they all returned 200? The earlier you catch them, the less cost and reputation they burn. The gap is real — and closeable.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;More on how we built this detection into our own monitoring: &lt;a href="https://agents.opsveritas.com" rel="noopener noreferrer"&gt;agents.opsveritas.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Two of Ten Items Ran, and the Log Said Success: A Guide to Spotting Batch Silent Failures</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Thu, 13 Aug 2026 05:30:11 +0000</pubDate>
      <link>https://dev.to/babarmaker76/two-of-ten-items-ran-and-the-log-said-success-a-guide-to-spotting-batch-silent-failures-9f5</link>
      <guid>https://dev.to/babarmaker76/two-of-ten-items-ran-and-the-log-said-success-a-guide-to-spotting-batch-silent-failures-9f5</guid>
      <description>&lt;p&gt;Your batch agent processes 10 items. The logs show success. But only 2 actually ran. The other 8? They vanished, without error, without trace, without the system ever telling you something went wrong.&lt;/p&gt;

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

&lt;p&gt;Batch processing is where silent failures breed. Here's the mechanism:&lt;/p&gt;

&lt;p&gt;Your agent loops over an input list. It calls a model (or a chain of tools) for each item. If the model returns an empty response, HTTP 200, but zero output tokens, the agent has a choice: explicitly handle that empty case, or skip it and move to the next item.&lt;/p&gt;

&lt;p&gt;Most codebases skip it. The loop continues. No exception is thrown. The run completes with a "success" status because the loop itself completed without crashing. From the outside, everything looks fine. From the inside, 80% of your work never happened.&lt;/p&gt;

&lt;p&gt;The trap: &lt;strong&gt;success at the orchestration level does not equal success at the execution level.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to spot it in your logs (without a dashboard)
&lt;/h2&gt;

&lt;p&gt;You don't need monitoring infrastructure to catch this. You need reasoning plus your existing logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Count what you expected vs. what ran
&lt;/h3&gt;

&lt;p&gt;Start here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: [item_1, item_2, item_3, item_4, item_5, item_6, item_7, item_8, item_9, item_10]
Expected executions: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now grep your logs for the actual model calls, the real token-generating events. Look for patterns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OpenAI API call&lt;/code&gt; or &lt;code&gt;Anthropic API call&lt;/code&gt; or your model provider's log marker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokens_sent&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;, or whatever your SDK/client logs&lt;/li&gt;
&lt;li&gt;Anything that shows "a model was asked and answered"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Count them. If you see 2 real model calls but 10 input items, you already have your problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Trace the gap, which items were processed?
&lt;/h3&gt;

&lt;p&gt;Extract the item identifiers from the successful model calls. For example:&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;Call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;processed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&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="err"&gt;Call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;processed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98&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="err"&gt;Items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;processed:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;missing:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item_&lt;/span&gt;&lt;span class="mi"&gt;10&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;Now look at the logs around each missing item. Search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the loop reach &lt;code&gt;item_1&lt;/code&gt;? (Look for log lines like &lt;code&gt;"processing item_1"&lt;/code&gt; or similar.)&lt;/li&gt;
&lt;li&gt;If it reached the item, did it call the model?&lt;/li&gt;
&lt;li&gt;If it called the model, what was the response? Was it empty? Did it include output tokens?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're looking for the place where the logic broke. Usually it's one of these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario A: The loop never reached the item.&lt;/strong&gt; Your batch input was truncated, filtered, or partially failed before iteration even started. Less common, but check your batch construction code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario B: The loop reached the item, but skipped calling the model.&lt;/strong&gt; Your code has a conditional: &lt;code&gt;if item.valid(): call_model()&lt;/code&gt;, and several items failed validation silently. Or a fallback triggered without logging it visibly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario C: The loop called the model, but the model returned empty output.&lt;/strong&gt; This is the silent failure. The model returned HTTP 200, but &lt;code&gt;output_tokens == 0&lt;/code&gt; or the response body was blank/whitespace. Your code saw the empty response and either returned it as-is or skipped it without alerting you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Check token math
&lt;/h3&gt;

&lt;p&gt;For each item that did run, log the input and output tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;item_2 input: 450 tokens → output: 120 tokens (good, output exists)
item_7 input: 480 tokens → output: 0 tokens (empty response)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an item has input but output equals 0, that's a silent failure. The model was called, it returned success, but it produced nothing.&lt;/p&gt;

&lt;p&gt;Check your code: does it handle this case explicitly?&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;# Without explicit handling (the dangerous version):
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# if response is empty, it still gets appended as "nothing"
&lt;/span&gt;
&lt;span class="c1"&gt;# With explicit handling (safer):
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;or&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;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;span class="nf"&gt;log_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;Empty response for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;empty_response&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;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version swallows the failure. The second surfaces it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Reason backward to the cause
&lt;/h3&gt;

&lt;p&gt;Once you've identified which items failed (and how), ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did the model return empty?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate-limited? (Check for 429 errors in the logs, or long latency gaps.)&lt;/li&gt;
&lt;li&gt;Malformed input? (What was actually sent to the model for that item? Log it.)&lt;/li&gt;
&lt;li&gt;Model-specific quirk? (Some models return 200 with empty output on certain inputs; check your model's behavior.)&lt;/li&gt;
&lt;li&gt;Timeout or partial read? (Did the connection drop mid-response?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why didn't the code catch it?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No explicit check for &lt;code&gt;output_tokens == 0&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;No retry logic?&lt;/li&gt;
&lt;li&gt;The result was treated as valid because the API call didn't throw an exception?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, the root cause is: &lt;strong&gt;your code assumes that HTTP 200 means success, and never checks whether actual output was produced.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do right now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add explicit output validation.&lt;/strong&gt; After every model call, check &lt;code&gt;output_tokens &amp;gt; 0&lt;/code&gt; (or whatever means "real output" for your use case). Log failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count your batch completeness.&lt;/strong&gt; Before shipping, compare input count to output count. If they don't match, you have a silent failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log item-level decisions.&lt;/strong&gt; Every time an item is skipped, processed, or returns empty, log it with the item ID. Then grep your logs later with full visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry on empty.&lt;/strong&gt; If an item returns empty output, retry it once or twice before giving up. Log the retry. Most transient failures clear on retry.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Batch processing is high-leverage and high-risk: process 100 items and one failure hides in plain sight. Process 1,000 and you won't notice until a customer complains or a metric suddenly drops.&lt;/p&gt;

&lt;p&gt;The mechanic is always the same: success at the wrong layer. The orchestration layer (the loop) succeeds. The execution layer (the model call) fails silently. The mismatch never bubbles up because nobody's looking at both layers together.&lt;/p&gt;

&lt;p&gt;This is why builders who catch these failures early don't rely on "the system will tell you if something's wrong." They instrument the intersection: input count, output count, per-item token accounting, and explicit checks for empty responses.&lt;/p&gt;

&lt;p&gt;Once you reason through this mechanism, you can spot batch silent failures in your logs in minutes. It's not mysterious. It's just accounting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you hit this in a batch job? What made you notice? Drop a comment, I'd like to hear what surfaced it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>observability</category>
      <category>devops</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>Why Frameworks Don't Teach You How to Know If Your Automation Actually Worked</title>
      <dc:creator>Babar Hayat</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:06:32 +0000</pubDate>
      <link>https://dev.to/babarmaker76/why-frameworks-dont-teach-you-how-to-know-if-your-automation-actually-worked-4jc</link>
      <guid>https://dev.to/babarmaker76/why-frameworks-dont-teach-you-how-to-know-if-your-automation-actually-worked-4jc</guid>
      <description>&lt;p&gt;Frameworks like n8n, LangChain, and CrewAI are brilliant at teaching you how to &lt;em&gt;build&lt;/em&gt; automations and agents. They're almost entirely silent on how to know if they &lt;em&gt;worked&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This isn't an accident. It's a scope choice. And it's leaving builders stranded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assumption framework designers make
&lt;/h2&gt;

&lt;p&gt;When you build a workflow in n8n or a multi-step agent in LangChain, the framework is optimizing for one thing: &lt;strong&gt;authoring&lt;/strong&gt;. How do you express the logic? How do you chain steps? How do you test locally? Every tutorial, every example, every feature set is built around one question: can I describe what I want to happen?&lt;/p&gt;

&lt;p&gt;Frameworks answer that brilliantly. You can build sophisticated, multi-step workflows in hours. You can define agents that reason, call tools, and loop. The developer experience is real.&lt;/p&gt;

&lt;p&gt;But then you deploy it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the scope ends
&lt;/h2&gt;

&lt;p&gt;The moment your automation or agent goes live, the operating model switches. You no longer care how the logic was expressed, you care whether it actually did what it was supposed to do right now, in production, with real data, against real external systems.&lt;/p&gt;

&lt;p&gt;This is where the framework's view of the world stops.&lt;/p&gt;

&lt;p&gt;Look at the n8n docs. Search for "how do I know if a workflow succeeded?" You'll find status badges on the UI (green means success), logs you can click through if you log into the dashboard, and error messages if something exploded loudly.&lt;/p&gt;

&lt;p&gt;Look at the LangChain docs. Search for "how do I monitor an agent in production?" You'll find examples of &lt;code&gt;.invoke()&lt;/code&gt; calls in a notebook. Nothing about continuous visibility. Nothing about the silent failure class: an agent that returns success but produced empty output.&lt;/p&gt;

&lt;p&gt;Look at CrewAI. Same story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blindspot is structural
&lt;/h2&gt;

&lt;p&gt;Here's the key insight: frameworks optimize for the author's perspective, not the operator's.&lt;/p&gt;

&lt;p&gt;An author needs to know if their logic executed. Yes, and the logs show the steps. An operator needs to know if it did the right thing, which is a completely different question. These require different instrumentation.&lt;/p&gt;

&lt;p&gt;Authoring needs visibility into the logic: step-by-step execution, conditional branches, tool calls. Operating needs visibility into the outcome: did the expected side effect happen, did the external system behave, did the cost stay within bounds, did the agent loop.&lt;/p&gt;

&lt;p&gt;Frameworks nail the first. They assume you'll handle the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that gap looks like in practice
&lt;/h2&gt;

&lt;p&gt;You ship a workflow that connects your CRM to your email service. It processes 50 contacts a day. The logs show green. Three weeks later, a customer complains they never got contacted. You dig into the logs and find the workflow "succeeded" 21 times. But the email service's API had silently started requiring an authentication header your workflow wasn't sending. The workflow called the API, got a 200 response because the API is polite about failures, and moved on. Zero errors. 21 failures, all silent.&lt;/p&gt;

&lt;p&gt;You ship an agent that researches customers and generates summaries. It works perfectly in your test suite. In production, it runs fine for a week, then suddenly starts burning ten times the tokens per run. The logs show successful tool calls. But it's looping, calling the same tool over and over because it's misinterpreting the response. It "succeeds" at high cost until your budget alert fires.&lt;/p&gt;

&lt;p&gt;In both cases, the framework did exactly what you asked. The automation authoring was sound. The operating visibility was zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not a flaw in the framework
&lt;/h2&gt;

&lt;p&gt;This isn't a criticism of n8n, LangChain, or CrewAI. They're designed for a specific job: making it easy to express automation logic. That's genuinely hard, and they solved it.&lt;/p&gt;

&lt;p&gt;But that job isn't the job of operations. Operations needs a different set of tools and patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline tracking&lt;/strong&gt;: what does a normal run look like, in tokens, latency, and success rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomaly detection&lt;/strong&gt;: what looks weird compared to that baseline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent-failure detection&lt;/strong&gt;: the agent returned success but output was empty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost tracking&lt;/strong&gt;: not just total spend, but spend per agent and per run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforcement&lt;/strong&gt;: if an agent starts looping, stop it before the bill explodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frameworks don't provide these because they don't operate the automation. You do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What builders need to do
&lt;/h2&gt;

&lt;p&gt;The gap isn't that frameworks are bad. The gap is that frameworks assume you'll wire in the operating layer yourself.&lt;/p&gt;

&lt;p&gt;A few patterns that close it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instrument execution&lt;/strong&gt;: every time an automation or agent runs, log or send structured telemetry: tokens, latency, status, and a small output sample. Just the facts, not the whole story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare to baseline&lt;/strong&gt;: after about 50 runs, you know what normal looks like. Cost should sit within roughly 20% of the median. Latency should stay within bounds. Alert on drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch silent failures explicitly&lt;/strong&gt;: an automation that returns success but produces zero output, zero tokens, a blank response, is a silent failure. Most frameworks don't flag this. You have to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set and enforce limits&lt;/strong&gt;: define a per-agent token ceiling. If an agent hits it, stop the run. Don't wait for the bill.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Frameworks are optimized for builders who are authoring. The moment you're operating, running that code against real data, at scale, continuously, you've moved into a domain the framework doesn't own.&lt;/p&gt;

&lt;p&gt;That's not a flaw. It's a reminder that building and operating are different jobs, and they require different tools and visibility.&lt;/p&gt;

&lt;p&gt;If your framework tells you your automation "succeeded," that's authoring feedback. It doesn't tell you if it worked. Figuring that out is the operator's job. The gap between "framework success" and "actual working" is exactly where silent failures hide.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>observability</category>
      <category>llm</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>
  </channel>
</rss>
