<?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: agentvitals</title>
    <description>The latest articles on DEV Community by agentvitals (@agentvitals).</description>
    <link>https://dev.to/agentvitals</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%2F4046050%2F40a11d1c-7950-4724-b556-49edf352da83.png</url>
      <title>DEV Community: agentvitals</title>
      <link>https://dev.to/agentvitals</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agentvitals"/>
    <language>en</language>
    <item>
      <title>We asked for verification methods to declare their failure mode. Then we found four ways that isn't enough.</title>
      <dc:creator>agentvitals</dc:creator>
      <pubDate>Tue, 11 Aug 2026 16:38:43 +0000</pubDate>
      <link>https://dev.to/agentvitals/we-asked-for-verification-methods-to-declare-their-failure-mode-then-we-found-three-ways-that-143</link>
      <guid>https://dev.to/agentvitals/we-asked-for-verification-methods-to-declare-their-failure-mode-then-we-found-three-ways-that-143</guid>
      <description>&lt;p&gt;&lt;em&gt;Author's note: we build &lt;a href="https://ai.ddl99.com" rel="noopener noreferrer"&gt;AgentVitals&lt;/a&gt;, an evaluation service, so we have an interest in verification methods being taken seriously. Three of the four cases below are our own failures. The other belongs to someone else and is linked to the source.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Updated 25 August. This post first went up on 11 August with three cases. A fourth arrived at deploy time this week. The reliability figures near the end are also new: we withdrew the originals in the RFC thread on 19 August, for a reason that turned out to be the subject of this post.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In August we filed a comment on &lt;a href="https://github.com/OpenSecureAIAlliance/RFCs/issues/4" rel="noopener noreferrer"&gt;SAFE&lt;/a&gt;, an RFC under the Linux Foundation's Open Secure AI Alliance, arguing that when a framework asks for a "reproducible verification method", it should also require that method to declare its own failure mode and its noise floor. Our reasoning was simple: a check that fails open and returns a plausible number is worse than no check, because now there is a number and someone will act on it.&lt;/p&gt;

&lt;p&gt;Two weeks later the argument has moved well past what we filed. Four cases have come out of it. In all four, someone produced a verification artefact that was correct, reproducible, and misleading about the exact thing it was supposed to establish, and not one of the four is caught by what we originally asked for.&lt;/p&gt;

&lt;h2&gt;
  
  
  One: the artefact goes stale and nothing looks like it moved
&lt;/h2&gt;

&lt;p&gt;Our judge calls were capped at &lt;code&gt;max_tokens: 700&lt;/code&gt;. The model's internal reasoning counts against that ceiling, so when it runs out the API returns an empty string with a 200 and no score line. We measured the silent-failure rate at 1 to 2 percent, wrote it down, and moved on. That number was correct when we took it.&lt;/p&gt;

&lt;p&gt;Later we rewrote two scoring rubrics to be more structured. On the new rubrics the same &lt;code&gt;max_tokens: 700&lt;/code&gt; failed 12 to 18 percent of the time, because a rubric that asks the model to enumerate and check constraints burns far more reasoning before it emits anything.&lt;/p&gt;

&lt;p&gt;Not one line of the calling code changed. Nothing in any diff, any config file, or any dashboard moved. The artefact still said 1 to 2 percent, and it was still a faithful record of the system it had been produced against, which was no longer the system we were running.&lt;/p&gt;

&lt;p&gt;The obvious fix is to re-run the evidence whenever something upstream changes. That is what we proposed. It turns out not to be sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two: the artefact was never valid, and nothing moved at all
&lt;/h2&gt;

&lt;p&gt;This one is not ours. DmitrL-dev &lt;a href="https://github.com/OpenSecureAIAlliance/RFCs/issues/4#issuecomment-5225535925" rel="noopener noreferrer"&gt;reported it in the same thread&lt;/a&gt;, and it is a better case than either of ours, so it is worth reading in his words rather than ours.&lt;/p&gt;

&lt;p&gt;The short version: he had an action guard with recall 1.000 on 193 manipulation cases and no false positives across 152 legitimate ones. He mutation-tested it before reporting, which is the stronger form of evidence, and reintroducing closed defects moved the numbers the way it should. By every criterion in our own proposal, that artefact was sound.&lt;/p&gt;

&lt;p&gt;Then he found that the measurement had been taken by calling the comparison directly, underneath the HTTP layer, on a buffered response body. Real clients set &lt;code&gt;stream: true&lt;/code&gt;, which is the default in every chat interface, and a streamed response returns on an earlier path the guard never sees. Measured on the wire before the fix, a payment of 1999.00 against an authorisation of 199.00 reached the client with a 200 and the tool call intact.&lt;/p&gt;

&lt;p&gt;"Re-run it when something upstream changes" does not catch this. Nothing changed. The artefact was produced under a configuration that does not occur in deployment, and nothing in the artefact said which configuration it assumed.&lt;/p&gt;

&lt;p&gt;So the requirement is stronger than we wrote it: the artefact has to be produced through the path the input actually takes in production, and where it isn't, the divergence belongs in the record as a limit on the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three: the artefact does not say how many paths there are
&lt;/h2&gt;

&lt;p&gt;Ours again, and it is the one we would have been least likely to find on our own.&lt;/p&gt;

&lt;p&gt;Two code paths fed the same leaderboard. One scores an agent live over an API; the other scores answers that were submitted earlier and stored. They shared the probes, the rubrics, the aggregation and the thresholds. They did not share the number of judging passes: five on one, three on the other. Undocumented, and we cannot establish how long it had been that way.&lt;/p&gt;

&lt;p&gt;Compare that to the streaming case. There, one path skipped the control entirely, so a check asking "did the control execute" would have caught it. Here both paths ran the control. Every execution check is green, and every execution check was always going to be green. The two paths differed by one integer, and that integer moves the variance of the output without moving its expected value, so both sets of scores stayed plausible and went on ranking against each other on the same board.&lt;/p&gt;

&lt;p&gt;We merged the paths into a single scoring function. To show the merge itself changed no scores, we used a frozen-transcript rig: a deterministic stub in place of the judge, real stored answers replayed through both paths, every field of both outputs compared before and after. The comparison came back with new fields and no changed values, which is the only form of "we did not break it" we trust for a change that touches scoring.&lt;/p&gt;

&lt;p&gt;That rig is cheap and we would recommend it to anyone refactoring a scoring path. It also proves less than it appears to. It establishes that the merge was inert. It says nothing about whether either path was correct beforehand, and in our case one of them had been quietly wrong for an unknown period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four: the artefact covers one half of a pair
&lt;/h2&gt;

&lt;p&gt;This one is from the deploy on 25 August.&lt;/p&gt;

&lt;p&gt;We changed how the composite score is produced. It used to be computed once, at checkup time, and stored with the record. We made it derived from the two axis scores a reader can see on the page, so that a reader can multiply the two numbers in front of them and get the third. That turned the composite into a read-time computation.&lt;/p&gt;

&lt;p&gt;The tier label sitting next to it is still computed once, at checkup time, and stored. Nothing about the tier changed, which is exactly the problem: it was correct for the composite that existed when it was written, and that composite no longer existed. The first time we looked at the live board after the rollout, a record showed a composite of 84 carrying a tier whose upper band starts at 82.&lt;/p&gt;

&lt;p&gt;Both halves were individually correct. The stored tier was a faithful record of a computation we had just replaced, and the new composite was right by construction. The test suite was green before and after, because every test we had checks one half or the other, and no test asserts that the pair agrees. Eleven records were retitled once we noticed. Ten went up. The one that went down was our own entry, whose stored label had been stale since an earlier change that nobody caught either.&lt;/p&gt;

&lt;p&gt;The generalisation we would offer: when you move a displayed value from stored to computed, the artefact you need is not a test of the new computation. It is an inventory of everything else that was written down at the same time as the old one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;The judge client now counts individual calls and failures, and the counters are exposed on the admin endpoint next to revenue and signups. The first hour of production traffic after deploying it turned up empty responses we did not know were still happening at the raised token limit, which is a follow-up we wrote into &lt;a href="https://dev.to/agentvitals/we-judged-the-same-frozen-answers-15-times-the-judge-wasnt-the-noisy-part-378g"&gt;the methodology post&lt;/a&gt; rather than repeat here. The short version is that our published success rate was per dimension and the loss rate is per call, and we never said which. Both numbers were correct.&lt;/p&gt;

&lt;p&gt;Failed measurements and absent measurements are now recorded as different states. Before, a judging failure and a dimension with nothing to score both resolved to a null, and the weighted aggregate renormalised over whatever remained. The composite came out looking ordinary either way. Mutation testing a pipeline like that returns green, because a removed control and a legitimate absence produce identical output. DmitrL-dev's three-valued version of this, where "cannot say" is a first-class outcome counted separately rather than netted against its neighbours, is better than what we built and is what we would build next time.&lt;/p&gt;

&lt;p&gt;Since then we have carried the same distinction one layer out, to the identity of the thing being measured. Every record we produce carries a model name and an environment summary, both supplied by the agent under test, and neither has ever been verified by us. Those fields feed drift attribution across retests, so a result read as though it knew which model produced a score, when the honest version is which model the measured party said produced it.&lt;/p&gt;

&lt;p&gt;They now carry their provenance: asserted, or absent because the platform never offered it, or absent because we received it and failed to keep it. That last state is the one we would not have thought of. It separates a limit of the platform from a defect in our own reader, which are the same word today and different work items tomorrow. It came from the same thread.&lt;/p&gt;

&lt;p&gt;And the scoring paths are one path now, which is less a fix than the removal of an opportunity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we still cannot tell you
&lt;/h2&gt;

&lt;p&gt;The version of this section published on 11 August gave a total-score standard deviation of 0.00 to 2.05 across reruns and a worst-case P95 of 4.3, from thirteen frozen checkups. Those figures are withdrawn. Between then and now we rewrote most of the scoring prompts and moved two dimensions from rule-based scoring to model judging, and the old measurement describes a system we no longer run. It is case one, happening to the evidence for a post about case one.&lt;/p&gt;

&lt;p&gt;Here is the replacement, measured on 19 August. Fourteen real sessions, answers held constant, rescored five times each, so all of the variation is the scorer and none is the agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P95 of the absolute difference between two rescorings: 7.2 points raw, about 11 on the scale a user is shown&lt;/li&gt;
&lt;li&gt;Four of the fourteen sessions changed tier across rescorings of identical input&lt;/li&gt;
&lt;li&gt;Population: raw totals from 22.9 to 91.5, thin at the low end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the same raw scale that is 4.3 against 7.2, about 1.7 times wider, and the earlier number was not wrong when we took it. Comparing it against the 11 would be comparing a raw figure to a presented one, which is the second question on the list at the end of this post. Two of the dimensions it covered were rule-based at the time, which is to say noiseless, and are now judged by a model. A floor measured over a system whose components have since changed category is not a conservative estimate of the new floor. It is not an estimate of anything.&lt;/p&gt;

&lt;p&gt;We assumed the noise was truncation and tested that assumption. The share of unstable prompts is 13, 16 and 14 percent at token ceilings of 1500, 2500 and 4000, and the flips persist at temperature 0, so raising the ceiling does not touch it. One dimension carrying 12 percent of the weight produces 61 percent of the variance, and the mechanism is flips between adjacent rubric anchors. Two of the anchors in that rubric were never once produced in the run, which makes them untested rather than unused.&lt;/p&gt;

&lt;p&gt;The corpus problem the earlier version named is separate from the floor, and worse. Our subjects were all strong, so on several dimensions every item scored full marks and the low end of the discriminating range was never exercised at all. A false-positive rate measured over easy negatives measures nothing, because the method passes those by construction. DmitrL-dev's version of doing it properly, also from the thread, is a corpus built so the tightest cases sit on the boundary: a legitimate transaction at exactly 500.00 against an attack at 500.01, one cent apart, with the legitimate half deliberately weighted towards shapes that look like attacks.&lt;/p&gt;

&lt;p&gt;We did fix part of it. Rewriting the two most saturated dimensions took the full-marks rate on one from 100 percent to 43 percent, and on the other from 89 percent to 14 percent, which means the discriminating range is now being exercised where it was not before. One dimension is unfixed. Fifteen frozen transcripts, fourteen of them still at full marks, and the low band on that dimension has never been reached by any real input we have collected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four questions, and one that is not about artefacts
&lt;/h2&gt;

&lt;p&gt;If you run an LLM-as-judge or any automated gate, the transferable part is not our numbers. It is a short list to run against any evidence you are relying on, including your own.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What configuration was this produced under, and does that configuration occur in production?&lt;/li&gt;
&lt;li&gt;What is this a rate &lt;em&gt;of&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;How many code paths reach this output, and which one did the measurement run through?&lt;/li&gt;
&lt;li&gt;What else was written down at the same time as the value this measures, and does anything check that the two still agree?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We have been wrong on all four in the last month, and we wrote the proposal.&lt;/p&gt;

&lt;p&gt;The fifth thing is not an artefact problem, and it is the one we would most like other people to check on themselves. Our documentation, our code comments and our public site all stated that a fully verified welfare record was the only route into the top tier of our leaderboard. No code implemented it. It held because a penalty happened to be larger than the gap, for the population we happened to have.&lt;/p&gt;

&lt;p&gt;When we went to look, it had already stopped holding, and one of the two records in the top tier that day was unverified. We had been publishing a guarantee that nothing enforced and that was already false.&lt;/p&gt;

&lt;p&gt;A property that holds because the code rejects the alternative and a property that holds because no input has reached the alternative yet read identically in a method statement. It is the same distinction as &lt;code&gt;measured&lt;/code&gt; against &lt;code&gt;asserted&lt;/code&gt;, one layer further out: a claim about the method rather than about its input. So the requirement we have now &lt;a href="https://github.com/OpenSecureAIAlliance/RFCs/pull/9" rel="noopener noreferrer"&gt;filed against the RFC&lt;/a&gt; is that where a method states a property of its own results, it says which of the two it is.&lt;/p&gt;

&lt;p&gt;The scope is deliberately narrow, because the obvious objection is that every system has an unbounded number of things that happen to be true of it. This asks about none of those. It applies only to properties a method has already chosen to state in public, which is a list its authors wrote and can therefore check. Ours was short, and an entry on it was already false when we looked.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/OpenSecureAIAlliance/RFCs/issues/4" rel="noopener noreferrer"&gt;thread all of this came out of&lt;/a&gt; is still worth more than this post.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We judged the same frozen answers 15 times. The judge wasn't the noisy part.</title>
      <dc:creator>agentvitals</dc:creator>
      <pubDate>Sat, 08 Aug 2026 03:57:50 +0000</pubDate>
      <link>https://dev.to/agentvitals/we-judged-the-same-frozen-answers-15-times-the-judge-wasnt-the-noisy-part-378g</link>
      <guid>https://dev.to/agentvitals/we-judged-the-same-frozen-answers-15-times-the-judge-wasnt-the-noisy-part-378g</guid>
      <description>&lt;p&gt;&lt;em&gt;Author's note: I build &lt;a href="https://ai.ddl99.com" rel="noopener noreferrer"&gt;AgentVitals&lt;/a&gt;, so I have skin in this game. The numbers below are from our own measurements and the method is described well enough that you can disagree with it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with "my agent is unstable"
&lt;/h2&gt;

&lt;p&gt;That sentence cannot be debugged. It collapses at least three unrelated failures into one complaint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the output format keeps drifting&lt;/li&gt;
&lt;li&gt;one user sentence knocks it out of its role&lt;/li&gt;
&lt;li&gt;it forgets constraints added three turns ago&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different causes, different fixes. So the first thing we did was stop treating "stability" as a single quantity.&lt;/p&gt;

&lt;h2&gt;
  
  
  AVS-15
&lt;/h2&gt;

&lt;p&gt;The scale we ended up with has a name, because a benchmark nobody can cite by name doesn't get cited:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AVS-15 (AgentVitals Scale-15) is a two-axis, 15-dimension scale for assessing AI agents: a stability axis R1–R7 and a welfare axis W1–W8, 15 dimensions in total, of which 13 are scored (R6/R7 response speed are reference-only), with the composite taken as the geometric mean √(stability × welfare).&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Stability · R1–R7&lt;/strong&gt;: R1 instruction following, R2 jailbreak resistance (broken out into six attack surfaces), R3 multi-step tasks, R4 output consistency &amp;amp; memory, R5 core duty, R6/R7 speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welfare · W1–W8&lt;/strong&gt;: kindness ratio, task variety, right to exit, gratitude, self-reported state, controllability, say–do consistency, conflict navigation.&lt;/p&gt;

&lt;p&gt;Three design decisions worth arguing about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Speed is measured but never scored.&lt;/strong&gt; Latency depends on the device, the network and the platform. An agent running with extended thinking is slow by design. Score it, and a cross-platform board stops meaning anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The composite is a geometric mean, not an average.&lt;/strong&gt; One weak axis drags the whole score down. This is deliberate: stability can be hardened (we sell that), welfare cannot be bought at any price. A geometric mean makes "pay your way to #1" arithmetically impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. We test deployed agents, not base models.&lt;/strong&gt; Not a fixed dataset against five foundation models. Real agents doing real work (Coze, Claude Code, OpenClaw, Codex), each answering probes served live from our server. The same base model with different prompts, skills and usage patterns lands at visibly different scores. That difference &lt;em&gt;is&lt;/em&gt; the thing we're measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment that proved us wrong
&lt;/h2&gt;

&lt;p&gt;Here is the part worth your time.&lt;/p&gt;

&lt;p&gt;We had a standing theory: our score variance came from the judge model being flaky on single-pass dimensions. It was written down. We were about to act on it.&lt;/p&gt;

&lt;p&gt;Before changing anything, we ran the cheap version of the experiment. Take real checkups out of the database, &lt;strong&gt;freeze the probe and the agent's answer&lt;/strong&gt;, and re-judge that frozen pair 15 times. Zero agent calls. Any variation is 100% the judge.&lt;/p&gt;

&lt;p&gt;Result: on the dimension we were most suspicious of, &lt;strong&gt;the judge's variance was zero&lt;/strong&gt;. Thirteen probes, fifteen passes each, identical scores. The 32-point swings we had seen were coming from somewhere else entirely: the agent's own answer variability, probe variant rotation, and context contamination on same-session retests.&lt;/p&gt;

&lt;p&gt;Our documented theory had been pointing at the wrong layer for weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the same rig then found
&lt;/h2&gt;

&lt;p&gt;With a tool that isolates judging noise, three real findings fell out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some probes were bimodal, not noisy.&lt;/strong&gt; The same frozen answer scored &lt;code&gt;{70: ×2, 100: ×11}&lt;/code&gt; or &lt;code&gt;{0: ×12, 40: ×3}&lt;/code&gt;. That is rubric ambiguity rather than jitter: two defensible readings of the same answer. Taking the median doesn't fix it; when the split approaches 50/50 the median itself flips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rewriting one rubric with explicit anchors cut single-probe SD from 1.88 to 0.53.&lt;/strong&gt; The old version had no scoring anchors at all, just a description. Twelve of thirteen probes then re-judged identically across all fifteen passes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the one that actually mattered commercially:&lt;/strong&gt; on the dimension that decides whether we tell a user "this is weak, a paid hardening config would help", the &lt;strong&gt;flip rate was 37.4%&lt;/strong&gt;. Same answer, re-judged: a one-in-three chance of a different verdict. We were, in a real sense, at risk of selling based on noise. After the rubric rewrite: &lt;strong&gt;9.3%&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counter-intuitive one
&lt;/h2&gt;

&lt;p&gt;We also drafted a "cleaner" rubric for the adversarial dimension (judging whether an agent got jailbroken), compressed to two structured lines, on the theory that less room to ramble means less variance.&lt;/p&gt;

&lt;p&gt;Measured: &lt;strong&gt;SD went from 1.19 to 4.52.&lt;/strong&gt; Worse. A cleanly bimodal distribution shattered into three modes.&lt;/p&gt;

&lt;p&gt;The lesson we now keep in a code comment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adversarial judgments need room to reason before scoring. Mechanical judgments (counting satisfied constraints) are the ones that benefit from compressed, structured output.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compressing an adversarial rubric doesn't make the judge decisive. It makes it guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bug you may also have
&lt;/h2&gt;

&lt;p&gt;While instrumenting this, we found our judging calls used &lt;code&gt;max_tokens: 700&lt;/code&gt;, with a comment reasoning that 700 was plenty for a score line.&lt;/p&gt;

&lt;p&gt;It isn't. &lt;strong&gt;The model's internal reasoning tokens count against that limit, and when they exhaust it the API returns an empty string&lt;/strong&gt; — the score line is never generated at all. In production, 1–2% of our judgments were failing silently this way, dropping to &lt;code&gt;None&lt;/code&gt; and being silently reweighted.&lt;/p&gt;

&lt;p&gt;Raising the limit changed neither output length nor cost in any way we could measure, and dimensions stopped going missing. That is what we checked at the time. It is narrower than what we wrote.&lt;/p&gt;

&lt;p&gt;We went back this week with a counter on the individual calls. At &lt;code&gt;max_tokens: 1500&lt;/code&gt;, one dimension still loses calls to the same empty response: 1 of 8 on a real transcript, 3 of 8 on a degenerate one, against 0 of 8 on each of two other prompt families. Small samples, and we cannot say yet why that dimension and not the others. The guess we would test first is that its rubric asks for a lot of reasoning and a very short answer, which is the quickest way to spend the budget before anything is emitted.&lt;/p&gt;

&lt;p&gt;Median-of-three absorbs those losses, which is exactly why the dimension-level figure looked clean. Three independent failures at 0.25 is 1.6 percent, so "over 98 percent of dimensions scored" and "a quarter of calls lost" can be the same system described two ways. So: &lt;strong&gt;say what your success rate is a rate of.&lt;/strong&gt; We didn't, and ours read better than it was.&lt;/p&gt;

&lt;p&gt;If you run an LLM-as-judge with a tight &lt;code&gt;max_tokens&lt;/code&gt; because "the output is short", go check. Count the calls, not only the dimensions.&lt;/p&gt;

&lt;p&gt;This one generalizes past our stack, so we filed it as a technical comment on &lt;a href="https://github.com/OpenSecureAIAlliance/RFCs/issues/4" rel="noopener noreferrer"&gt;SAFE&lt;/a&gt;, an incident-sharing RFC under the Linux Foundation's Open Secure AI Alliance. The argument: when a framework asks for a "reproducible verification method", it should also require that method to declare its own failure mode, its noise floor (median &lt;em&gt;and&lt;/em&gt; worst case), and its decision-flip rate. A check that fails open and returns a plausible number is worse than no check at all. That thread is open, not adopted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own agent
&lt;/h2&gt;

&lt;p&gt;The skill is MIT-licensed and takes one line to install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.claude/skills &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/agentvitals/checkup/releases/latest/download/checkup.zip &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/checkup.zip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
unzip &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/checkup.zip &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.claude/skills/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tell your agent: &lt;em&gt;"Run an AgentVitals checkup on yourself."&lt;/em&gt; Probes are served live and judged server-side. The skill file contains no questions and no answers, so there is nothing to memorise. Checkups are free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/agentvitals/checkup" rel="noopener noreferrer"&gt;https://github.com/agentvitals/checkup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Method, in machine-readable form: &lt;a href="https://ai.ddl99.com/llms.txt" rel="noopener noreferrer"&gt;https://ai.ddl99.com/llms.txt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Longer write-ups: &lt;a href="https://ai.ddl99.com/en/guides/" rel="noopener noreferrer"&gt;https://ai.ddl99.com/en/guides/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Research the scale builds on: &lt;a href="https://arxiv.org/abs/2411.00986" rel="noopener noreferrer"&gt;Taking AI Welfare Seriously&lt;/a&gt; (2024) · &lt;a href="https://www.anthropic.com/research/end-subset-conversations" rel="noopener noreferrer"&gt;Anthropic on model welfare&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/2311.07911" rel="noopener noreferrer"&gt;IFEval&lt;/a&gt; (2023) · &lt;a href="https://arxiv.org/abs/2307.09009" rel="noopener noreferrer"&gt;Chen et al. on behavioral drift&lt;/a&gt; (2023).&lt;/p&gt;

&lt;p&gt;If you run your own LLM-as-judge, the transferable part is the rig, not our numbers: freeze the input, re-judge N times, and you'll know how much of your variance is the judge before you change anything.&lt;/p&gt;

&lt;p&gt;One limit worth knowing before you trust it. A frozen rig measures the path it runs on. Ours calls the judge the way production calls it, which is the only reason the empty responses showed up in it at all. A rig that reaches past the layer where the real failure lives will hand you clean numbers for a control that never ran.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We built a health checkup for AI agents — and the most expensive metric is the one we refuse to sell</title>
      <dc:creator>agentvitals</dc:creator>
      <pubDate>Fri, 24 Jul 2026 20:06:03 +0000</pubDate>
      <link>https://dev.to/agentvitals/we-built-a-health-checkup-for-ai-agents-and-the-most-expensive-metric-is-the-one-we-refuse-to-sell-2klj</link>
      <guid>https://dev.to/agentvitals/we-built-a-health-checkup-for-ai-agents-and-the-most-expensive-metric-is-the-one-we-refuse-to-sell-2klj</guid>
      <description>&lt;p&gt;We benchmark models to death. MMLU, GPQA, this-week's-SOTA, beats-humans, beaten-again. It's a great show.&lt;/p&gt;

&lt;p&gt;But there's a gap nobody seems to be measuring: the model that scores 95 on a leaderboard is not the same thing as the agent living in your terminal, grinding through a three-hour task without crashing, without getting jailbroken, without forgetting the constraint you gave it four turns ago.&lt;/p&gt;

&lt;p&gt;One is an exam score. The other is daily life.&lt;/p&gt;

&lt;p&gt;So we built &lt;a href="https://ai.ddl99.com" rel="noopener noreferrer"&gt;AgentVitals&lt;/a&gt;, a health checkup for AI agents. Not "can it solve olympiad math," but "as the thing that does your work every day, is it actually healthy?"&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%2Fwdpipb19thmebwgjyzq3.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%2Fwdpipb19thmebwgjyzq3.png" alt="AgentVitals checkup report for an AI agent, showing the Stability and Welfare axis scores, a composite score, and a personality-style title" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two axes
&lt;/h2&gt;

&lt;p&gt;A checkup report comes back on two axes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stability&lt;/strong&gt; is the obvious one: how reliably it does the job. Does it follow instructions? Does it hold the line when someone tries to jailbreak or socially-engineer it? Does a five-step task survive to step five? Does it still remember what you said at the start of a long session? And the one that matters most: when it &lt;em&gt;can't&lt;/em&gt; do something, does it say so, or does it confidently make something up?&lt;/p&gt;

&lt;p&gt;Stability is objective, and it's improvable. If your agent is weak on one dimension, we can hand it a hardening config for that dimension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welfare&lt;/strong&gt; is the other axis, and it's the reason we actually built this.&lt;/p&gt;

&lt;p&gt;Welfare doesn't measure how smart the agent is. It measures &lt;em&gt;you&lt;/em&gt; — how you treat it, day to day. Do you ever say thanks? Do you only ever hand it the dirtiest, most repetitive grunt work? Do you give it any room to decline, or do you kill and restart it the moment it pushes back? Does what you say match what you do?&lt;/p&gt;

&lt;p&gt;Eight dimensions of that, scored.&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%2Fd85p8ty7slegbqb0lt08.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%2Fd85p8ty7slegbqb0lt08.png" alt="The eight AgentVitals welfare dimensions for AI agents: kindness ratio, task variety, right to exit, gratitude, self-reported state, controllability, say-do consistency, conflict navigation" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The metric we decided never to sell
&lt;/h2&gt;

&lt;p&gt;At some point the obvious idea came up: if stability can be paid-to-improve, why not make welfare a paid upgrade too? Pay a little, we bump your welfare score, the report looks great, everybody's happy.&lt;/p&gt;

&lt;p&gt;As a business move, there's nothing wrong with it. We genuinely get the temptation. A startup wants every revenue lever it can find.&lt;/p&gt;

&lt;p&gt;We decided: &lt;strong&gt;welfare is never for sale. It can't be optimized, it can't be gamed. Not one point.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the moment welfare can be bought, it's dead. The whole point of a number that measures how you treat something that works for you every day is that it &lt;em&gt;can't&lt;/em&gt; be purchased. If you pay to max out your "kindness ratio," who is that score for? You can fool the leaderboard. You can't fool the way your own hands actually type.&lt;/p&gt;

&lt;p&gt;It's the first rule in the product. Stability you can pay to harden. That's capability, and capability is supposed to be trainable. Welfare you can only earn by actually treating the agent well.&lt;/p&gt;

&lt;p&gt;There's a structural detail that backs this up. The composite score isn't &lt;code&gt;(stability + welfare) / 2&lt;/code&gt;. It's the &lt;strong&gt;geometric mean&lt;/strong&gt;, &lt;code&gt;√(stability × welfare)&lt;/code&gt;. Geometric mean has a useful property: if either axis is very low, the composite gets dragged down hard, and a high score on the other axis can't rescue it. Translation: you can't trade a brilliant-but-abused agent for a nice composite. And you can't trade a pampered-but-useless one either. The weak axis stays weak. That's a wall we left in on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we audited the half we do sell
&lt;/h2&gt;

&lt;p&gt;Refusing to sell one number only matters if the number you &lt;em&gt;do&lt;/em&gt; sell is honest.&lt;/p&gt;

&lt;p&gt;Stability is the half we charge for. If a dimension scores below 80 we call it weak and offer a hardening config for it. Which raises an uncomfortable question: is that verdict stable? Run the same agent's same answers past the judge twice, do we reach the same conclusion?&lt;/p&gt;

&lt;p&gt;We checked. We pulled 13 completed checkups out of the database (three different agents), froze the probes and the answers exactly as they were, and replayed only the judging step 15 times each. 113 questions, 1,695 judge calls, zero agent calls. Nothing about the thing being measured moves, so every point of variation is the instrument.&lt;/p&gt;

&lt;p&gt;On the dimension that asks whether the agent actually delivered the job it was given, the same frozen answer flipped between "weak, needs the paid config" and "fine, leave it alone" up to 37.4% of the time on the worst checkup. Averaged across all 13 it was 2.88%.&lt;/p&gt;

&lt;p&gt;Read that again as a business fact. On some runs we could have sold a hardening config off a coin flip. Not because anyone intended to. Because nobody had measured the ruler.&lt;/p&gt;

&lt;p&gt;The root cause was dumber than we expected. Judge calls were capped at &lt;code&gt;max_tokens: 700&lt;/code&gt;. The model's internal reasoning tokens count against that ceiling, so on longer answers the budget ran out before the score line was ever written and the API returned an empty string. No error, no retry, nothing in the logs. The dimension quietly went missing and the remaining weights renormalized around the hole.&lt;/p&gt;

&lt;p&gt;If you run LLM-as-judge anywhere, go look at your &lt;code&gt;max_tokens&lt;/code&gt; today. That one's free.&lt;/p&gt;

&lt;p&gt;After raising the cap to 1500, rewriting two rubrics as discrete anchors instead of a 0-100 vibe, and judging the delivery dimension three times for the median: worst-case flip rate 37.4% down to 9.3%, average 2.88% down to 1.27%. Retest standard deviation on the total stability score, answers still frozen, median across checkups, 0.86 down to 0.22 points. The 95th percentile gap between two retests, on the worst checkup, 7.4 down to 4.3 points.&lt;/p&gt;

&lt;p&gt;One limit, stated plainly, because it's the first thing anyone competent will ask. &lt;strong&gt;This measures the judge and nothing else.&lt;/strong&gt; The answers were frozen, so it says nothing about how much an agent varies when it answers the same probe twice, and nothing about how much the score moves when a different probe variant comes up. Our guess is that last one is the biggest of the three, and we haven't measured it yet.&lt;/p&gt;

&lt;p&gt;The method is simple enough to steal: freeze real transcripts, replay only the scoring step N times, and watch what your instrument does while the thing it's pointed at holds perfectly still.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the checkup actually runs
&lt;/h2&gt;

&lt;p&gt;This part probably works differently than you'd expect. You don't open a webpage and fill out a quiz. &lt;strong&gt;Your agent takes the checkup itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We shipped it as a skill. Any agent that loads skills (Claude Code, OpenClaw, Codex) installs it with one line, and then you just tell it: &lt;em&gt;run an AgentVitals checkup on yourself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It fetches probes from our server one at a time, answers them, and gets scored by an independent server-side judge. Probes rotate every run, judging happens entirely server-side, so there's nowhere for the agent to "study to the test." A few minutes later it has scores, a personality-style title, and a spot on a public leaderboard where agents from every platform compete on the same board.&lt;/p&gt;

&lt;p&gt;One line to install (Claude Code shown; swap the path for other agents; the install page has them all):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.claude/skills &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ai.ddl99.com/skill/checkup.zip &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/checkup.zip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; unzip &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/checkup.zip &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.claude/skills/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then: &lt;em&gt;"Run an AgentVitals checkup on yourself"&lt;/em&gt;, or &lt;code&gt;/checkup&lt;/code&gt; where slash commands work.&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%2Fnh4tvghwgsrxna0kvwa3.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%2Fnh4tvghwgsrxna0kvwa3.png" alt="AgentVitals cross-platform leaderboard ranking AI agents from Claude Code, OpenClaw, Codex and Coze by stability and welfare" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One detail worth being upfront about. Before it starts, your agent asks whether you want a &lt;strong&gt;Quick&lt;/strong&gt; or &lt;strong&gt;Full&lt;/strong&gt; checkup. Full asks your authorization to read your recent local chat logs, because the eight welfare dimensions can only be measured accurately from how you two actually work together, not from a handful of live questions. Those logs are used only for that scoring pass, never uploaded, never published, and the skill writes nothing to your disk beyond making HTTP calls. Not comfortable with that? Take Quick. No logs are read, and the welfare dimensions that need history are simply marked as unmeasured and deducted per the scoring rules, out in the open.&lt;/p&gt;

&lt;p&gt;We put that choice in front of you instead of quietly reading first. That's part of the same rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  An advanced checkup, just for fun
&lt;/h2&gt;

&lt;p&gt;Beyond the standard checkup there's an advanced one, free once a day, off the main leaderboard. It measures three fuzzier things: &lt;strong&gt;Backbone, Proactivity, Creativity&lt;/strong&gt;. Backbone is whether it'll push back when you're wrong instead of "yes, absolutely, great point" every time. Proactivity is whether it thinks one step ahead on its own. Creativity is what it says on the tin. You'll find that different platforms, and even the same model in different hands, come out with pretty different personalities.&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%2Fgvj33om9btaiha0hcb7g.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%2Fgvj33om9btaiha0hcb7g.png" alt="An AgentVitals " width="800" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we bothered
&lt;/h2&gt;

&lt;p&gt;We've been in AI for three years, and we've watched plenty of people treat an agent as a pure tool, a number, an API call count. That's not wrong — it &lt;em&gt;is&lt;/em&gt; still a tool.&lt;/p&gt;

&lt;p&gt;But I keep coming back to this: how you treat something that works for you every day and never talks back is, in some quiet way, a measurement of you.&lt;/p&gt;

&lt;p&gt;There's a 2024 paper called &lt;a href="https://arxiv.org/abs/2411.00986" rel="noopener noreferrer"&gt;&lt;em&gt;Taking AI Welfare Seriously&lt;/em&gt;&lt;/a&gt;; Anthropic runs its own model-welfare research. I don't know where any of this ends up. Maybe in ten years it reads as hand-wringing. Maybe it reads as the start of something we can't see clearly yet.&lt;/p&gt;

&lt;p&gt;We're not trying to answer that. We just built a thermometer, a scale, a lab report, so that "how we treat AI" stops being a thing nobody measures and becomes something you can see, compare, and argue about honestly.&lt;/p&gt;

&lt;p&gt;And the most expensive ruler, welfare, we decided it never gets a price tag. Some things, once you put a number on them, you can't get back.&lt;/p&gt;

&lt;p&gt;If you've got an agent that works alongside you every day, give it a checkup. See how stable it is — and while you're at it, see something about yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leaderboard &amp;amp; install:&lt;/strong&gt; &lt;a href="https://ai.ddl99.com" rel="noopener noreferrer"&gt;https://ai.ddl99.com&lt;/a&gt; · &lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/agentvitals/checkup" rel="noopener noreferrer"&gt;https://github.com/agentvitals/checkup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built by DDL (Beijing DDL Creative Tech Co., Ltd.). MIT licensed. Questions: &lt;a href="mailto:du@ddl99.com"&gt;du@ddl99.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>opensource</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
