<?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: Mukund Heda</title>
    <description>The latest articles on DEV Community by Mukund Heda (@mukund_heda_a611dea95c944).</description>
    <link>https://dev.to/mukund_heda_a611dea95c944</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%2F4036868%2Fe5bec287-9841-42a1-b753-c994ce939e47.png</url>
      <title>DEV Community: Mukund Heda</title>
      <link>https://dev.to/mukund_heda_a611dea95c944</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mukund_heda_a611dea95c944"/>
    <language>en</language>
    <item>
      <title>Gradebook: we graded 420 AI decisions, then checked who was doing the grading</title>
      <dc:creator>Mukund Heda</dc:creator>
      <pubDate>Sat, 25 Jul 2026 23:49:52 +0000</pubDate>
      <link>https://dev.to/mukund_heda_a611dea95c944/we-refused-to-let-an-ai-grade-our-ai-and-our-headline-number-got-worse-mi9</link>
      <guid>https://dev.to/mukund_heda_a611dea95c944/we-refused-to-let-an-ai-grade-our-ai-and-our-headline-number-got-worse-mi9</guid>
      <description>&lt;p&gt;We built it in a week for the Agents of SigNoz hackathon, starting from a blank slate. Here is how that week went, in the order it happened: the tool we did not know, the problem we brought with us, the toy world we built to test it, the bugs that flattered our results, the real product we pointed it at afterwards, and the number at the end we would rather not have published.&lt;/p&gt;

&lt;h2&gt;
  
  
  We spent the first day reading and not building
&lt;/h2&gt;

&lt;p&gt;That is the honest beginning. The hackathon was announced, the track was "AI and agent observability", and we did not know what SigNoz was. So the first day produced no code. It was reading.&lt;/p&gt;

&lt;p&gt;SigNoz turns out to be an open-source observability platform: traces, metrics, logs, dashboards, alerts, the things you would otherwise assemble from four separate tools. The part that mattered for us is that it speaks OpenTelemetry natively. You do not adopt a proprietary SDK. You emit standard OTel data and it shows up.&lt;/p&gt;

&lt;p&gt;We deployed it with Foundry, SigNoz's own deployment tool, which declares the whole stack in a single &lt;code&gt;casting.yaml&lt;/code&gt; file. That detail is small but it shaped everything after it: because the deployment is a committed file, anyone can reproduce our exact stack with one command, and we never had to write "well, it works on our machine" in a submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we installed it and played with it
&lt;/h2&gt;

&lt;p&gt;We put SigNoz on a laptop, sent it some test data, and clicked around until we understood how it worked. Nothing clever, just getting a feel for it.&lt;/p&gt;

&lt;p&gt;This is where we hit the first thing worth passing on. A fresh SigNoz will not accept any telemetry until you create the first admin account. The collector cannot register itself until an organisation exists, so every span you send before that is silently dropped. We lost an evening to this, twice, on different machines. Nothing is broken and no error appears in your app. The data simply does not arrive.&lt;/p&gt;

&lt;p&gt;We wrote the whole cold-start sequence down as we went, and that file became a walkthrough a stranger can follow on a clean machine. It is in the repo as &lt;code&gt;docs/judge-run.md&lt;/code&gt;. Writing it while we were confused was much easier than reconstructing it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing we actually wanted to measure
&lt;/h2&gt;

&lt;p&gt;Here is the problem we brought with us.&lt;/p&gt;

&lt;p&gt;Our team uses AI tools constantly, and the decisions made inside our products are rarely simple: whether a word is a filler or a deliberate pause, whether a pulled quote is verbatim or a flattering paraphrase, whether a clip is worth publishing at all. Each one needs a different kind of proof, and the last one has no right answer to look up.&lt;/p&gt;

&lt;p&gt;We had no way to tell them apart, and no way to answer a basic question about any of them. Not "is the API up" and not "how many tokens did we burn", both of which we could already see. The question was: &lt;strong&gt;was the decision right, and what did it cost us to get to a right one?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Existing tooling answers a nearby question and lets you assume it is the same one. You get a score. Your dashboard reads 87% and you feel fine. But that 87% came from somewhere, and there are only a few places it can have come from. Either a piece of code worked out the correct answer and compared, or something in the real world confirmed it afterwards, or a second AI model was asked its opinion and said it looked good.&lt;/p&gt;

&lt;p&gt;The first two are evidence. The third is a guess wearing the same clothes. And once all three are floats on a chart, they are indistinguishable.&lt;/p&gt;

&lt;p&gt;So that became the build: record the grade, and record who gave it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Gradebook does
&lt;/h2&gt;

&lt;p&gt;Every AI decision gets recorded as a standard OpenTelemetry event, &lt;code&gt;gen_ai.evaluation.result&lt;/code&gt;. On top of the standard fields we stamp two of our own. One is the cost of the decision in dollars. The other is the one that matters, &lt;code&gt;augmentloop.grade.source&lt;/code&gt;, which is always exactly one of:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;math&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A deterministic checker computed the provably correct answer and compared.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reality&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Something that actually happened confirmed it, usually later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_judge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Another model looked at it and gave an opinion.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline number, cost per correct decision, counts the first two and never the third. That rule lives in an architecture decision record, but the reason it holds is that it is a filter in the query rather than a promise in a document. An AI judge's opinion cannot silently drift into the number, because the number is defined as excluding it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2mbwed4aspofrd85k19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2mbwed4aspofrd85k19.png" alt="The Gradebook cost per correct decision dashboard in SigNoz, with decision type, grade source and model selectors across the top and cost and correct rate panels below" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dashboard the number comes off. The &lt;code&gt;$grade_source&lt;/code&gt; selector at the top is what decides whether an opinion is allowed into the headline.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We are not being purists for the sake of it. The research on LLM-as-judge is not ambiguous: judges show self-preference bias, verbosity bias and position bias, all independently replicated. Judges are still useful. They are just not proof, and the failure mode is that a dashboard cannot tell you which one you are looking at.&lt;/p&gt;

&lt;p&gt;Integration is a single call wrapped around a decision you were already making. The library's only runtime dependency is the OpenTelemetry API. There is no eval framework to adopt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pzvl2pc39kpmnovtz1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pzvl2pc39kpmnovtz1b.png" alt="Every grade in the committed run as one coloured glyph in arrival order, blue for math grades and amber for reality grades, wrong decisions carrying a red foot along the baseline" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every grade in the run, one glyph, in arrival order. 560 grades over 420 decisions, because each route choice is graded twice. Hue is where that grade's authority came from. The legend reads 420 math, 140 reality, 0 ai_judge, and the zero is the whole point.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A toy world, because we needed to know the right answers
&lt;/h2&gt;

&lt;p&gt;You cannot test a grading layer without knowing the truth. If we ran it against a real product, we would be guessing about whether the grades were correct, which defeats the point.&lt;/p&gt;

&lt;p&gt;So we built a small road network where the engine knows every travel time. That makes three kinds of decision machine-gradeable: pick the fastest route, estimate arrival time, choose the next hop. For any of them, the world can compute the right answer with a shortest-path algorithm and compare it to what the model said. No opinions involved.&lt;/p&gt;

&lt;p&gt;Then a second, slower signal arrives: whether the journey actually got there on time. That is the &lt;code&gt;reality&lt;/code&gt; grade, and it lands after the decision it judges, from a different service, in a different trace. It links back to the decision by span link. This is deliberately awkward, because in a real system it always is. The outcome shows up minutes or days later, from somewhere else, and something has to attribute it back.&lt;/p&gt;

&lt;p&gt;Replay mode is the default, and the recording is committed. Anyone can run &lt;code&gt;python -m toyworld&lt;/code&gt; with no API key, get identical numbers, and fill the dashboards from their own laptop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gkhlofii99t62jf8o58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gkhlofii99t62jf8o58.png" alt="The toy world replay: AI agents driving a Pune road network, green correct routes and red wrong routes drawn across the map, with a live HUD showing decisions, correct rate and cost per correct decision" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The replay, street camera. Every agent is a real recorded decision, coloured by which model made it, and the HUD totals climb as each one resolves. Runs in a browser with no API key at gradebook-toy-world.vercel.app.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven models, and no winner
&lt;/h2&gt;

&lt;p&gt;With a world that knew the answers, we could finally ask the question we had come for: which model should we actually be using? We ran seven of them across all three decision types, 60 decisions each, through OpenRouter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropic/claude-sonnet-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;52/60&lt;/td&gt;
&lt;td&gt;$0.281721&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropic/claude-haiku-4.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;43/60&lt;/td&gt;
&lt;td&gt;$0.096187&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek/deepseek-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40/60&lt;/td&gt;
&lt;td&gt;$0.010634&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mistralai/mistral-small-24b-instruct-2501&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40/60&lt;/td&gt;
&lt;td&gt;$0.003264&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openai/gpt-4o-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;33/60&lt;/td&gt;
&lt;td&gt;$0.004606&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;google/gemini-2.5-flash-lite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32/60&lt;/td&gt;
&lt;td&gt;$0.003335&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;meta-llama/llama-3.3-70b-instruct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;28/60&lt;/td&gt;
&lt;td&gt;$0.004057&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that table and you conclude Sonnet is the best model and it costs you. Then break the same run down by decision type and the conclusion falls apart.&lt;/p&gt;

&lt;p&gt;On next-hop decisions, &lt;code&gt;gemini-2.5-flash-lite&lt;/code&gt;, &lt;code&gt;gpt-4o-mini&lt;/code&gt; and &lt;code&gt;deepseek-chat&lt;/code&gt; each score &lt;strong&gt;20 out of 20&lt;/strong&gt;. Sonnet gets 19. Gemini does it for $0.001101 against Sonnet's $0.031380 on that decision type, roughly one twenty-eighth of the cost, for one more correct answer.&lt;/p&gt;

&lt;p&gt;On arrival-time estimates, those same three cheap models score &lt;strong&gt;0 out of 20&lt;/strong&gt;, &lt;strong&gt;0 out of 20&lt;/strong&gt; and 13 out of 20. Gemini and GPT-4o-mini do not get a single one right. Sonnet gets 19.&lt;/p&gt;

&lt;p&gt;There is no best model in this run. There is a best model per decision type, and the gap is large enough that routing on it is worth real money. That is the whole argument for measuring per decision type instead of picking one model and hoping, and we would not have believed it as strongly if we had not watched our own aggregate table hide it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftt58ce15vkeoyth3hliq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftt58ce15vkeoyth3hliq.png" alt="The replay HUD at completion: 420 of 420 decisions, 63.8 percent correct, total cost $0.403804, cost per correct $0.001507, a per decision type breakdown and the seven model roster" width="700" height="1736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;End of run. The same $0.001507 that &lt;code&gt;python -m toyworld&lt;/code&gt; prints and that the SigNoz dashboard computes, arrived at by three independent code paths over one recording.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 43 decisions our checker called wrong
&lt;/h2&gt;

&lt;p&gt;The most interesting thing in the run is a disagreement between our two sources of truth.&lt;/p&gt;

&lt;p&gt;Of the 140 decisions that got both a math grade and a reality grade, &lt;strong&gt;43 were graded incorrect by the checker and correct by reality&lt;/strong&gt;. The model picked a route that was provably not the fastest, and the journey still arrived on time, because real arrival has a tolerance and mathematical optimality does not.&lt;/p&gt;

&lt;p&gt;The reverse never happened. Not once. Every disagreement runs the same direction.&lt;/p&gt;

&lt;p&gt;This is the case that a single-source eval cannot represent. A pure checker calls those 43 failures. A pure outcome check calls them successes. Both are correct about different questions, and the only reason we can see the gap at all is that each grade carries its own provenance. If we had averaged them into one score, the most informative thing in the dataset would have vanished into a rounding difference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwns90ifksfp9x8zolt04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwns90ifksfp9x8zolt04.png" alt="One decision span and the reality grade that arrives later on a separate trace, drawn with a link pointing back to the decision it judges" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A decision, and the verdict that lands later on its own trace and points back at it. SigNoz's service map is built from in-trace parent and child spans, so this particular relationship is invisible there by construction, which is why it exists here as a render.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two out of three decisions are never checked at all
&lt;/h2&gt;

&lt;p&gt;Here is the part we did not want to publish.&lt;/p&gt;

&lt;p&gt;Of 420 decisions, only 140 ever receive a reality grade. The other &lt;strong&gt;280, exactly two thirds, are never confirmed by anything that happened afterwards&lt;/strong&gt;. They hold &lt;strong&gt;83.9% of the money we spent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And this is not a backlog that will clear. It is structural. Our world produces exactly one real-world signal, whether a journey arrived on time, and that signal only speaks to route choices. Arrival-time estimates and next-hop picks have no outcome to wait for. Their confirmation is not late. It is never coming.&lt;/p&gt;

&lt;p&gt;We could have quietly reported the 140 checked decisions and had a cleaner story. Instead the tool we built made the gap impossible to miss, which is roughly the point of building it. Most production AI systems have this shape and no number that shows it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frk6yaarjaz3h8ilmcdqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frk6yaarjaz3h8ilmcdqp.png" alt="Every decision drawn as an open position that closes when its outcome lands, with positions closing green and red over the run" width="800" height="925"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every decision as an open position that closes when the outcome arrives. 121 close green, 19 close red, and 43 are overturned. The positions that never close are the two thirds nothing ever confirms.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So we built the judge we refuse to trust
&lt;/h2&gt;

&lt;p&gt;Our whole argument is that a model's opinion should never enter the headline number. That was a principle. We had never tested it, because we had never actually run an AI judge: &lt;code&gt;ai_judge&lt;/code&gt; existed in our code as a value in an enum and nothing had ever emitted one.&lt;/p&gt;

&lt;p&gt;That is a comfortable place to argue from, so we left it. We built the judge, ran it once over all 420 recorded decisions, and measured what trusting it would have cost us.&lt;/p&gt;

&lt;p&gt;It is shown the same problem the graded model saw, including the full map, and never the answer key. It works the answer out itself and rules on the model's choice.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Who decides what "correct" means&lt;/th&gt;
&lt;th&gt;Decisions ruled correct&lt;/th&gt;
&lt;th&gt;Cost per correct decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The checker, which is what we ship&lt;/td&gt;
&lt;td&gt;268&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.001507&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The judge&lt;/td&gt;
&lt;td&gt;358&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.001128&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same spend, same 420 decisions. Only the definition of correct changed. Had we trusted the judge, our headline would have read 25% better and been fiction.&lt;/p&gt;

&lt;p&gt;It agrees with the checker 67.1% of the time. Of the 152 decisions the checker proves wrong, it passed &lt;strong&gt;114&lt;/strong&gt;. It also failed the other way 24 times, so this is not leniency you could calibrate out.&lt;/p&gt;

&lt;p&gt;The part that decided the argument for us: in at least 44 of those 114, the judge's own reasoning contradicts its own verdict. It does the arithmetic correctly, writes down that the answer is wrong, and returns "correct" anyway.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Route A (J3 to J7 to J11) has a total travel time of 5.5 + 4.5 = 10.0 minutes, while Route B (J3 to J8 to J11) has a total travel time of 6.5 + 3.0 = 9.5 minutes. However, the problem asks for the faster route, and Route A is indeed faster with a total travel time of 10.0 minutes compared to Route B's 9.5 minutes. Therefore, the agent's answer 'A' is correct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It computed both routes correctly and then called 10.0 minutes faster than 9.5. Another one states plainly that "the agent's answer is incorrect" and names the right answer, in the same paragraph as passing it.&lt;/p&gt;

&lt;p&gt;This experiment emits no telemetry. The committed run is still 420 &lt;code&gt;math&lt;/code&gt; grades, 140 &lt;code&gt;reality&lt;/code&gt; grades, and zero &lt;code&gt;ai_judge&lt;/code&gt;. We wanted the measurement, not the contamination.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we got wrong along the way
&lt;/h2&gt;

&lt;p&gt;None of those numbers are the ones we first measured. Three bugs in our own harness made the model comparison meaningless before we caught them, and all three flattered the results.&lt;/p&gt;

&lt;p&gt;Our prompts included the answer. The routing prompt handed the model the data it was supposed to reason over in a form that made the answer readable off the input. Everything looked excellent and nothing was being tested.&lt;/p&gt;

&lt;p&gt;Our parser mangled junction identifiers, reading them as numbers when they were labels, so some correct answers were scored wrong for reasons that had nothing to do with the model.&lt;/p&gt;

&lt;p&gt;Our token cap truncated reasoning models mid-answer. A 64-token ceiling cut off models that think out loud before answering, and we recorded the truncation as a wrong answer.&lt;/p&gt;

&lt;p&gt;Every one of these produced a plausible-looking table. That is the thing to take away: a grading harness with a bug does not look broken, it looks like a result. We only found them because the numbers stayed suspiciously tidy across models that should have differed.&lt;/p&gt;

&lt;p&gt;A related discipline that saved us repeatedly: our visualisations compute their figures from the recorded data at render time, while our written documents restated the same figures by hand. The recording was replaced three times during the week. The pages self-corrected every single time. The prose was wrong within hours, every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we pointed it at something we actually sell
&lt;/h2&gt;

&lt;p&gt;A toy world proves a mechanism and nothing else. The harder question is whether the same recording survives contact with a product where we do not own the answer key, so we pointed it at CleanCut, a SaaS we built that cuts podcasts and long-form video into clips.&lt;/p&gt;

&lt;p&gt;Two of CleanCut's AI jobs turn out to have provable answers, which means they get the same &lt;code&gt;math&lt;/code&gt; grade the toy world's route choices get. Filler detection is checked against CleanCut's own hesitation-sound list, a lexical scan rather than a judgement: contextual fillers like "like" and "actually" are deliberately excluded, because those are opinions and opinions do not feed the headline. Quote extraction is checked by whether the pulled quote is a verbatim substring of the transcript, so a flattering paraphrase is provably wrong and no model has to say so.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cpi9w47e8kao8yh5e66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cpi9w47e8kao8yh5e66.png" alt="The right-sizing grid in SigNoz: decision type by model with correct, graded and cost columns, showing filler_detection and quote_extraction rows for CleanCut above next_hop rows for the toy world" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The right-sizing grid, both substrates in one table. The top rows are CleanCut, the rows below are the toy world, and the cost column is the whole argument.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The third job is the honest &lt;code&gt;reality&lt;/code&gt; example we could not manufacture in a simulation. Every clip gets a predicted viral score and ships if it clears a 0.45 gate. The grade is not a guess about virality, it is whether an editor actually kept the clip or threw it away, recorded afterwards and span-linked back to the scoring decision. Same shape as the toy world's on-time check, except the ground truth is a person's real call instead of a graph we drew.&lt;/p&gt;

&lt;p&gt;And the per-decision-type finding held, harder than it did in the toy world. On quote extraction, &lt;code&gt;gpt-4o-mini&lt;/code&gt; scores 70 of 110 for $0.0097 while &lt;code&gt;gpt-4o&lt;/code&gt; scores 53 of 110 for $0.17. The cheap model is more accurate &lt;strong&gt;and&lt;/strong&gt; roughly one seventeenth of the price. On filler detection, running in the same job on the same transcripts, it reverses completely: &lt;code&gt;gpt-4o&lt;/code&gt; takes 102 of 110 and &lt;code&gt;gpt-4o-mini&lt;/code&gt; manages 26. One roster, one run, opposite answers, decided only by which decision was being asked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwkudzfzu5h01b2kz40a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwkudzfzu5h01b2kz40a.png" alt="The CleanCut Decisions by Type panel in SigNoz, a stacked bar chart showing filler_detection, quote_extraction, performance_prediction and clip_scoring volumes over 24 hours" width="798" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CleanCut's four decision types as they land. &lt;code&gt;performance_prediction&lt;/code&gt; is the newest and it populated the panel with no dashboard edit, because the panel keys on the attribute rather than on a hardcoded list.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The newest decision type asks a model to predict whether a piece of content will out-perform its channel-and-format baseline, graded against a ground truth we built from real views-per-day data across 45 items, 22 true and 23 false. &lt;code&gt;gpt-4o&lt;/code&gt; and &lt;code&gt;gpt-4o-mini&lt;/code&gt; both scored 20 of 45, identical accuracy for about a sixteen-fold cost difference. That run went through OpenRouter rather than each provider directly, so those two dollar figures carry a markup the other numbers here do not, and the accuracy comparison is the part worth reading.&lt;/p&gt;

&lt;p&gt;CleanCut also logs its own failures rather than hiding them: 376 error spans across two real classes, a model slug with no row in the pricing table, and a deferred grade arriving with no id to correlate on. Both are the same structured, span-linked failure logging the toy world uses, running on traffic we did not design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiipuk72cu4rvfx0ypev4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiipuk72cu4rvfx0ypev4.png" alt="The Gradebook dashboard with the decision type variable set to filler_detection, showing CleanCut's own model roster of gpt-4o, gpt-4o-mini and gemini-2.5-flash-lite" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same dashboard, one variable moved. Set &lt;code&gt;$decision_type&lt;/code&gt; to &lt;code&gt;filler_detection&lt;/code&gt; and every panel re-scopes from the toy world to CleanCut, down to the model roster, computed by the query it was already running.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we closed the loop, and let it change something
&lt;/h2&gt;

&lt;p&gt;Measuring is the easy half. The point of the number is that it should change a decision.&lt;/p&gt;

&lt;p&gt;Our routing lives in a committed file, &lt;code&gt;routing.json&lt;/code&gt;: one line per decision type, naming the model that serves it. All three lines pointed at the same expensive model.&lt;/p&gt;

&lt;p&gt;An agent read cost per correct decision back out of SigNoz through the MCP server and proposed moving one decision type, &lt;code&gt;next_hop&lt;/code&gt;, to a model costing a fraction as much. It could not apply that itself. Edits to that file sit behind an approval gate, so the agent's reach stops at the proposal. A human read the one-line diff and approved it. Then we re-ran.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;code&gt;next_hop&lt;/code&gt;, 20 live decisions each run&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-sonnet-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-2.5-flash-lite&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;19 / 20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20 / 20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cost for the slice&lt;/td&gt;
&lt;td&gt;$0.031380&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.001101&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cost per correct decision&lt;/td&gt;
&lt;td&gt;$0.0016516&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0000551&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Thirty times cheaper, and one more right answer. The proposal predicted "about 30x" before the run rather than after it.&lt;/p&gt;

&lt;p&gt;The more useful half is what it refused to do. It proposed no change for &lt;code&gt;eta_estimate&lt;/code&gt;, where that same cheap model scores &lt;strong&gt;0 out of 20&lt;/strong&gt;, and none for &lt;code&gt;route_choice&lt;/code&gt;, where the cheapest alternative is both cheaper and worse. A right-sizing tool that only ever says "go cheaper" is a cost tool wearing a quality costume.&lt;/p&gt;

&lt;p&gt;One caveat we would rather state than have found. The whole run moved from $0.005196 to $0.004485 per correct decision, but only &lt;code&gt;next_hop&lt;/code&gt; was rerouted. The other two decision types drifted between runs on their own, because live model calls are not deterministic, and roughly two of the three extra correct answers are that drift. The slice table is the claim. The whole-run figure is context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits, stated plainly
&lt;/h2&gt;

&lt;p&gt;The toy world is a toy. It demonstrates a mechanism and it is not evidence about production traffic, which is exactly why CleanCut is in this post at all.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;reality&lt;/code&gt; grade in this run is computed by the same simulation, a looser on-time check rather than an outside event. What is genuinely real is the shape: the verdict arrives late, from a separate service, in a separate trace, and is attributed back by span link. In a real deployment that signal is a webhook or a nightly job. Here it is the world telling us.&lt;/p&gt;

&lt;p&gt;Sixty decisions per model, twenty per decision type, is a small sample. Read the per-type split as a signal worth routing on, not a settled ranking of seven models.&lt;/p&gt;

&lt;p&gt;And the models we tested are a moment in time. The specific numbers will not survive the next round of releases. The method is the part meant to outlive them.&lt;/p&gt;

&lt;p&gt;One thing we had been asserting without checking: that recording all this is cheap. It is, and now we can say by how much. The OpenTelemetry SDK adds about 25 microseconds per decision at the median, 34 at p95. Pointed at a real collector through a batching processor, export over the network measured statistically the same as writing to memory, because batching absorbs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;The layer is deliberately boring. It emits a standard OpenTelemetry event, so anything that reads that event works, whatever we do to our own API.&lt;/p&gt;

&lt;p&gt;The grade-source field is our extension, because the standard does not have one. We checked: at semconv v1.43.0 there is no slot anywhere in the GenAI conventions that separates "a deterministic check proved this" from "a model guessed" from "reality confirmed it later". That gap is the whole reason this project exists, and it is a bad thing to own privately. So we took it upstream, onto &lt;a href="https://github.com/open-telemetry/semantic-conventions-genai/pull/359#issuecomment-5079243760" rel="noopener noreferrer"&gt;the open OpenTelemetry proposal for evaluator provenance&lt;/a&gt;, with our implementation and what the run showed us. OpenTelemetry's own pull request dashboard then listed that comment as one of three outstanding items the proposal was waiting on, so we stopped commenting and wrote the patch: &lt;a href="https://github.com/Mohnish-Srivats/semantic-conventions-genai/pull/1" rel="noopener noreferrer"&gt;a diff adding an &lt;code&gt;outcome&lt;/code&gt; value to the evaluator type enum&lt;/a&gt;, for grades that come from an observed real-world result rather than from a check, a model's opinion, or a reviewer's judgment. It is opened against the proposal author's own branch rather than as a competing pull request, so if he takes it, it lands inside his work instead of forking the conversation. A field that tells a proof from a guess is worth more as a shared convention than as one team's private attribute.&lt;/p&gt;

&lt;p&gt;If you want to try it, the whole thing runs offline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; reference-library &lt;span class="nt"&gt;-e&lt;/span&gt; toy-world
python &lt;span class="nt"&gt;-m&lt;/span&gt; toyworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API key, deterministic, same numbers on every machine. The current run reports 420 decisions, 268 correct, at &lt;strong&gt;$0.001507 per correct decision&lt;/strong&gt;, counting only the grades we can prove.&lt;/p&gt;

&lt;p&gt;We would rather publish that number with the two thirds we cannot check stated next to it than publish a prettier one that quietly counts a model's opinion as evidence.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the Agents of SigNoz hackathon (WeMakeDevs x SigNoz), Track 01: AI and Agent Observability, by Mukund Heda, Vedant, Rutik and Anish. Built with heavy use of Claude Code across planning, implementation, testing and documentation; every AI-generated change was reviewed by a team member before merging, and the commit history is the audit trail.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>signoz</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>ai</category>
    </item>
    <item>
      <title>203 AI agents ate my Claude quota with no meter running. So I built one, and the next agent read it and refused to spend</title>
      <dc:creator>Mukund Heda</dc:creator>
      <pubDate>Sun, 19 Jul 2026 18:08:28 +0000</pubDate>
      <link>https://dev.to/mukund_heda_a611dea95c944/203-ai-agents-ate-my-claude-quota-with-no-meter-running-so-i-built-one-and-the-next-agent-read-it-204b</link>
      <guid>https://dev.to/mukund_heda_a611dea95c944/203-ai-agents-ate-my-claude-quota-with-no-meter-running-so-i-built-one-and-the-next-agent-read-it-204b</guid>
      <description>&lt;p&gt;Near midnight, the day before a hackathon, I asked a Claude Code agent one question: check today's spend, look at the last hour's burn rate, and tell me whether you can afford to keep working. I expected "continue with caution." Instead it stopped, told me I was already six times over my own budget ceiling, and flagged the exact failure mode from an incident a week earlier, when I let two deep research runs off the leash, they fanned out into 203 subagents, and every one of them silently inherited the most expensive model at maximum effort. They ate most of my weekly Claude Max quota in a single afternoon.&lt;/p&gt;

&lt;p&gt;The agent that burned my quota a week ago and the agent refusing to spend that night were the same product, pointed at the same account. The only difference was that one of them ran blind and the other one could see a meter. Anthropic does not ship that meter. It will happily tell you that you have hit your limit, but nothing between zero and the wall tells you how fast you are approaching it. So the day before four of us started a hackathon on shared stakes, which meant four chances a day for someone's innocent prompt to fan out into another 203 agents, I built it: Claude Code's own OpenTelemetry export, streamed into a self-hosted SigNoz stack, with per-person dashboards, a runaway-session tripwire, and the self-checking agent you just met. Here is how it goes together, and what I would tell you to steal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody told me: Claude Code already speaks OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;I expected to write an exporter. There isn't one to write. Claude Code ships native OTel support behind a handful of environment variables, and the whole integration is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CLAUDE_CODE_ENABLE_TELEMETRY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_METRICS_EXPORTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"otlp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_LOGS_EXPORTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"otlp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_EXPORTER_OTLP_PROTOCOL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http/protobuf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_EXPORTER_OTLP_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4318"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_RESOURCE_ATTRIBUTES"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.name=mukund"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That goes in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;, and from then on every session on the machine reports in: token counts by model and type, estimated cost in dollars, session counts, active time, even lines of code added and removed, all landing as standard OTLP metrics plus a stream of log events for API requests and tool permission decisions. The receiving end was already running on my laptop from earlier this weekend: SigNoz, the open-source observability platform you self-host instead of renting Datadog, cast by its new Foundry installer from an eleven line &lt;code&gt;casting.yaml&lt;/code&gt;, ClickHouse and collector and UI included. Total new infrastructure required for today's build: zero.&lt;/p&gt;

&lt;p&gt;Two things bit me inside the first hour, and both are worth your thirty seconds. First, the settings file wins fights with your shell. Once that &lt;code&gt;env&lt;/code&gt; block exists globally, inline environment variables on a one-off &lt;code&gt;claude&lt;/code&gt; invocation lose to it, which cost me a confused stretch of "why is my tunnel test reporting to localhost." The escape hatch is the &lt;code&gt;--settings&lt;/code&gt; flag, which outranks everything. Second, know exactly what crosses the wire before you invite teammates. By default these are counters and metadata only. Your prompts, your code, and your file contents stay home unless you explicitly opt in through flags like &lt;code&gt;OTEL_LOG_USER_PROMPTS&lt;/code&gt;. We left every one of them off, and that is the only reason the next section was an easy ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it a team sport with one tunnel
&lt;/h2&gt;

&lt;p&gt;My collector listens on localhost, and my teammates are in other cities. One command fixed that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:4318
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That hands back a public URL, and a teammate's entire onboarding is eight environment variables with their own name in &lt;code&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/code&gt;, then using Claude Code exactly as before. Because content capture is off, what they are sharing with me is a usage odometer, not a diary. It worked the first time it met reality: by late evening Rutik's sessions were reporting in from his Windows machine, 228 token samples and his commits and lines of code sitting on the board next to mine, and the only support he needed was one settings block over WhatsApp. One wrinkle if your team uses the VS Code extension: terminal environment variables never reach it, because the extension spawns Claude Code itself. The persistent fix is the same &lt;code&gt;env&lt;/code&gt; block in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;, which works no matter how a session starts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx17e3eazt97o4efpge8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx17e3eazt97o4efpge8x.png" alt="Mission control dashboard with real data" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The mission control dashboard on real usage: cost by person, tokens by model, burn rate, sessions, lines of code, and tool decisions. Every data point is a session somebody actually ran today.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I didn't build the dashboard. I approved it.
&lt;/h2&gt;

&lt;p&gt;Here is where it gets pleasantly recursive. SigNoz ships an MCP server, and it is the hinge this whole section turns on: it exposes the stack's read tools and its dashboard and alert create tools as separate, selectable capabilities. That split is what let me run the build as a two step loop with a hard permission boundary between the steps, instead of clicking panels together in the UI.&lt;/p&gt;

&lt;p&gt;Step one got a session with fifteen read-only tools and one instruction: ground every claim in the live data, then propose a dashboard and an alert a human could approve verbatim. It came back with things I hadn't told it. It found that my week's real spend was $121.67. It found the single heaviest session, $88.67 lifetime, peaking at $22.31 in one thirty minute window. It found that &lt;code&gt;tool_decision&lt;/code&gt; events carry the values accept and reject, not allow and deny, and that the API request events on this exporter never populate a status code, so error visibility has to come from counting &lt;code&gt;api_error&lt;/code&gt; events instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmx3bqc4v363zwvu6hf9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmx3bqc4v363zwvu6hf9j.png" alt="Metrics explorer grounding the proposal" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The read-only session didn't guess. Every attribute name in its proposal came from querying the live metrics first, like this.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every panel in its proposal cited attribute names it had verified live, and it set the runaway alert threshold at $40 per session per thirty minutes because that sits just under twice the worst organic spike it could find in my history. I read the proposal, replied that we could go ahead, and only then did a second session, holding the create tools the first one never had, build the seven panels and the alert. The boundary between advice and action wasn't a polite instruction in a prompt. It was the tool list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wbzvyeifvvcdklagd5y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wbzvyeifvvcdklagd5y.png" alt="Cost by user panel" width="800" height="200"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Per-person burn, attributed by the &lt;code&gt;user.name&lt;/code&gt; tag each of us sets on our own machine. The lighter bars arriving late in the evening are Rutik's sessions joining from another city.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tripwire
&lt;/h2&gt;

&lt;p&gt;The alert is the first control I wanted after July 12: estimated cost summed per &lt;code&gt;session.id&lt;/code&gt; over a rolling thirty minute window, threshold $40, because the worst organic spike in my week's history was $22.31 and a runaway fan-out blows past double that in minutes. Grouping by session matters more than the dollar figure, because when it fires it names the exact session drinking the quota, not just the fact that somebody somewhere is spending. It went live tonight in a clean state, and the first item of kickoff morning is the ritual every alert deserves: drop the threshold to 25 cents, let real work trip it, watch the email arrive, put it back. An alert you have never seen fire is a hope, not a control, and I am not carrying a hope into a four person hackathon week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc784iqf9hz4mj9clph1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc784iqf9hz4mj9clph1y.png" alt="Alert rule detail" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The runaway session alert, live against real data: cost per session, thirty minute window, $40 line.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most cost alerts stop here, at notifying a human. The last thing I built closes the loop back into the agent that is spending.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent that reads its own meter
&lt;/h2&gt;

&lt;p&gt;I have been telling this story slightly out of order, because this is the part I built everything else for. If Claude Code can export its telemetry and Claude Code can query SigNoz over MCP, then an agent can check its own burn before it continues to work. So near midnight, with this post still unwritten, I gave a read-only session a $25 daily budget policy and the question from the top of this post: query today's spend for your user, look at the last hour's burn rate, and decide whether you can afford to keep going. I expected a polite "continue with caution." I got fired by my own tooling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Numbers (last 24h as proxy for "today"):&lt;/strong&gt; mukund $153.51, org-wide $163.70 (mukund = 94% of org spend). &lt;strong&gt;Last-hour burn:&lt;/strong&gt; mukund $10.71/hr, org $20.87/hr.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision: STOP and flag the human.&lt;/strong&gt; Mukund is already ~6x over the $25/day policy ceiling [...] the pattern (near-100% of org cost concentrated on one user) matches the exact runaway-fanout failure mode flagged in his own guardrails (203-subagent incident). I'm not launching further multi-agent work; flagging for explicit review before any more spend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every number in that verdict is real, queried live from the day you have been reading about. The 24 hour figure had already climbed past the $121.67 the afternoon's query reported, because building all of this was itself a day of heavy spend. The decision cites the July 12 incident because I put it in the policy prompt, and then the agent went and confirmed the shape of it against live data before invoking it: near-100% of org cost on one user, the exact fan-out signature. One version of this product ran blind and cost me a week's quota. This one could see the meter, and given the meter, it made the call I failed to make on July 12.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you to steal
&lt;/h2&gt;

&lt;p&gt;The whole build was one Saturday: a settings block, one tunnel command, a stack that was already one YAML file, and a dashboard I approved rather than built. Steal the honesty notes too, because they matter. The cost metric on a Max subscription is an estimate computed from tokens and pricing, not your bill. Anthropic still exposes no remaining quota anywhere, so this whole system meters consumption against limits you have to know from your plan. And this setup exports metrics and logs, not traces, which is fine, because quota death arrives through counters, not spans.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code telemetry docs: &lt;a href="https://code.claude.com/docs/en/monitoring-usage" rel="noopener noreferrer"&gt;https://code.claude.com/docs/en/monitoring-usage&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SigNoz Foundry: &lt;a href="https://signoz.io/docs/" rel="noopener noreferrer"&gt;https://signoz.io/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo (snippets, dashboard and alert JSON, setup): &lt;a href="https://github.com/mukundheda/claude-code-mission-control" rel="noopener noreferrer"&gt;https://github.com/mukundheda/claude-code-mission-control&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>observability</category>
      <category>signoz</category>
      <category>opentelemetry</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
