<?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: deavgprime</title>
    <description>The latest articles on DEV Community by deavgprime (@deavgprime).</description>
    <link>https://dev.to/deavgprime</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%2F4220840%2F64792876-4af9-4610-bffa-739fb61bff0d.png</url>
      <title>DEV Community: deavgprime</title>
      <link>https://dev.to/deavgprime</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deavgprime"/>
    <language>en</language>
    <item>
      <title>Resuming an eval without changing the experiment</title>
      <dc:creator>deavgprime</dc:creator>
      <pubDate>Sun, 01 Nov 2026 19:36:54 +0000</pubDate>
      <link>https://dev.to/deavgprime/resuming-an-eval-without-changing-the-experiment-c8d</link>
      <guid>https://dev.to/deavgprime/resuming-an-eval-without-changing-the-experiment-c8d</guid>
      <description>&lt;p&gt;An eval stops halfway through. There is already an answer on disk, and another task has not finished.&lt;/p&gt;

&lt;p&gt;What should happen when we run it again?&lt;/p&gt;

&lt;p&gt;The easy part is skipping a file that already exists. The harder part is deciding whether that file still belongs to the experiment we are about to continue.&lt;/p&gt;

&lt;p&gt;If the prompt changed, the grader changed, or a failed request quietly gets another chance, we may end up with a different experiment under the same directory name.&lt;/p&gt;

&lt;p&gt;This is the next case study in the series, after &lt;a href="https://dev.to/deavgprime/what-exactly-are-ai-evals-775"&gt;What exactly are AI evals?&lt;/a&gt; and &lt;a href="https://dev.to/deavgprime/before-scoring-a-model-test-the-evaluator-3noi"&gt;Before scoring a model, test the evaluator&lt;/a&gt;. This time, I am looking at the runner around the scorer in &lt;a href="https://github.com/deavgprime/iac-eval-bench" rel="noopener noreferrer"&gt;IaC Eval Bench&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On this page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What resume means here&lt;/li&gt;
&lt;li&gt;Interrupting a two-task run&lt;/li&gt;
&lt;li&gt;What must stay the same&lt;/li&gt;
&lt;li&gt;A recorded failure is still an attempt&lt;/li&gt;
&lt;li&gt;Try the checks locally&lt;/li&gt;
&lt;li&gt;The boundary a checkpoint cannot solve&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What resume means here
&lt;/h2&gt;

&lt;p&gt;For this project, a resume does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks that the run configuration, inputs, evaluator, and recorded validation runtime still match.&lt;/li&gt;
&lt;li&gt;Validates and reuses the attempt records already saved.&lt;/li&gt;
&lt;li&gt;Runs the missing attempts and rebuilds the reports.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A completed response can pass or fail the task. Either way, it is an attempt to keep. A recorded provider error is also retained.&lt;/p&gt;

&lt;p&gt;A retry asks for another attempt. A resume continues the same planned set of attempts. Those are different decisions, and I do not want one to happen silently inside the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interrupting a two-task run
&lt;/h2&gt;

&lt;p&gt;I used two of the benchmark's tasks: a Deployment configuration and a Service configuration. Both used the project's saved reference fixtures.&lt;/p&gt;

&lt;p&gt;These are constructed checks of the runner. They do not call a model or measure model quality.&lt;/p&gt;

&lt;p&gt;For the interruption check, a controlled exception stops the fixture provider before the second attempt returns. The first attempt has already been written to disk. The runner is then called again with the same configuration and &lt;code&gt;resume=True&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is what happened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Saved records&lt;/th&gt;
&lt;th&gt;Fixture calls during this step&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before the controlled interruption&lt;/td&gt;
&lt;td&gt;One completed attempt&lt;/td&gt;
&lt;td&gt;Two calls entered; the second was interrupted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume the missing work&lt;/td&gt;
&lt;td&gt;Two completed attempts&lt;/td&gt;
&lt;td&gt;One&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume the completed run again&lt;/td&gt;
&lt;td&gt;The same two attempts&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first attempt file stayed byte-for-byte unchanged. After the first resume, both reference tasks passed. The second resume returned the same summary without calling the fixture provider again.&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%2Fp47prnjks1g5onnycxho.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%2Fp47prnjks1g5onnycxho.png" alt="Two views of a run logbook: the saved first attempt is reused, while the missing second fixture is replayed. Changed inputs lead to a rejected resume." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. A controlled fixture example. The saved record stays; only missing work is continued.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The useful result is not the two passing grades. It is that continuing the run did not replace its earlier evidence or add an unplanned attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What must stay the same
&lt;/h2&gt;

&lt;p&gt;A directory name cannot tell us whether two runs mean the same thing.&lt;/p&gt;

&lt;p&gt;IaC Eval Bench records a fingerprint derived from the configuration, task definitions, schema manifest, harness source, fixture contents where applicable, and Python and validation dependency versions. On resume, it recomputes that fingerprint and checks the saved manifest's provenance fields as well.&lt;/p&gt;

&lt;p&gt;The source of this behavior is inspectable in the &lt;a href="https://github.com/deavgprime/iac-eval-bench/blob/1dbabecd8cc377cef1082b2652131b6921c4c74b/src/iac_eval_bench/runner.py" rel="noopener noreferrer"&gt;runner&lt;/a&gt;. It is a specific contract for this small project, not a requirement that every evaluation framework must use the same fields.&lt;/p&gt;

&lt;p&gt;I checked four ways that continuing a saved run could go wrong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change before resuming&lt;/th&gt;
&lt;th&gt;Observed result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Change the configured seed from 42 to 99&lt;/td&gt;
&lt;td&gt;Rejected: configuration, inputs, or evaluator changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add a newline to a selected fixture file&lt;/td&gt;
&lt;td&gt;Rejected: configuration, inputs, or evaluator changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change the saved manifest's harness version&lt;/td&gt;
&lt;td&gt;Rejected: manifest provenance does not match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change an attempt's score without updating its checksum&lt;/td&gt;
&lt;td&gt;Rejected: attempt integrity check failed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://github.com/deavgprime/iac-eval-bench/blob/98b7ec20d8a8bd3acf308e56f9a6d8ee75be2b57/docs/examples/resume-observations.json" rel="noopener noreferrer"&gt;recorded observations&lt;/a&gt; include the continuation checks, rejection messages, and CLI exit codes.&lt;/p&gt;

&lt;p&gt;The newline case is deliberately strict. The fixture means the same thing as JSON, but its bytes changed. This implementation compares content hashes rather than trying to prove that two inputs are semantically equivalent.&lt;/p&gt;

&lt;p&gt;If I want to compare a new prompt or a changed grader, I should use a new output directory and record a new run. Keeping the old run lets me explain what changed.&lt;/p&gt;

&lt;p&gt;The fingerprints also have limits. They do not establish which remote model weights a server actually loaded. A user-supplied model revision is recorded, not independently verified. A requested seed does not guarantee identical generation across machines or model servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A recorded failure is still an attempt
&lt;/h2&gt;

&lt;p&gt;Suppose a provider request fails and the runner saves that error. On resume, should the runner try it again?&lt;/p&gt;

&lt;p&gt;In this project, it does not. I checked that separately by injecting two provider errors, then resuming with the fixture function configured to fail the test if called.&lt;/p&gt;

&lt;p&gt;The resumed run made zero provider calls and retained both errors.&lt;/p&gt;

&lt;p&gt;There were no completed answers, so the quality pass rate was unknown. The operational pass rate was zero: none of the two planned attempts produced a passing result.&lt;/p&gt;

&lt;p&gt;This distinction matters when reading a report. A failed request is not evidence that the model produced a wrong configuration. It is still evidence that the evaluation attempt did not succeed operationally.&lt;/p&gt;

&lt;p&gt;An intentional retry belongs in a separately named run under this project's current interface. It should not erase the first failure or quietly improve the original run's headline result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the checks locally
&lt;/h2&gt;

&lt;p&gt;The previous case study includes the &lt;a href="https://dev.to/deavgprime/before-scoring-a-model-test-the-evaluator-3noi#run-the-checks-yourself"&gt;environment setup&lt;/a&gt;. Use the same pinned revision, &lt;code&gt;1dbabecd8cc377cef1082b2652131b6921c4c74b&lt;/code&gt;, with its locked dependencies.&lt;/p&gt;

&lt;p&gt;From that checkout, create a small run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iac-eval run &lt;span class="nt"&gt;--provider&lt;/span&gt; fixture &lt;span class="nt"&gt;--fixture-dir&lt;/span&gt; fixtures/reference &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task&lt;/span&gt; deployment-resources &lt;span class="nt"&gt;--task&lt;/span&gt; service-routing &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; runs/resume-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same command with &lt;code&gt;--resume&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iac-eval run &lt;span class="nt"&gt;--provider&lt;/span&gt; fixture &lt;span class="nt"&gt;--fixture-dir&lt;/span&gt; fixtures/reference &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task&lt;/span&gt; deployment-resources &lt;span class="nt"&gt;--task&lt;/span&gt; service-routing &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; runs/resume-demo &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reuses the two saved attempts. It is a completed-run check, not a simulation of an interruption.&lt;/p&gt;

&lt;p&gt;Now change the seed while keeping the same output directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iac-eval run &lt;span class="nt"&gt;--provider&lt;/span&gt; fixture &lt;span class="nt"&gt;--fixture-dir&lt;/span&gt; fixtures/reference &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task&lt;/span&gt; deployment-resources &lt;span class="nt"&gt;--task&lt;/span&gt; service-routing &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--seed&lt;/span&gt; 99 &lt;span class="nt"&gt;--output&lt;/span&gt; runs/resume-demo &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command exits with code 2 and reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: resume rejected: configuration, inputs or evaluator changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a fresh output directory if &lt;code&gt;runs/resume-demo&lt;/code&gt; already contains evidence you want to keep.&lt;/p&gt;

&lt;p&gt;For the controlled interruption and the related regression checks, run the existing runner tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests &lt;span class="nt"&gt;-p&lt;/span&gt; test_runner.py &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests cover partial-run continuation, duplicate generation, changed configuration, record integrity, locks, and recorded provider failures. The full suite has 51 test methods; it also covers the scoring cases from the previous post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary a checkpoint cannot solve
&lt;/h2&gt;

&lt;p&gt;There is an important gap between making a provider request and saving its result.&lt;/p&gt;

&lt;p&gt;If a process stops after the provider has produced an answer but before the attempt record is written, the runner has no completed record to reuse. A later resume can make that request again. The provider might have charged for the first request even though the local report never recorded it.&lt;/p&gt;

&lt;p&gt;Writing attempt files atomically protects the local checkpoint transition. It does not make a remote request and a local file write one indivisible operation. This implementation does not guarantee exactly-once execution at the provider.&lt;/p&gt;

&lt;p&gt;There are other practical boundaries too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The directory lock stops competing local writers. An abrupt stop can leave a stale lock. Check that no runner is active before removing it.&lt;/li&gt;
&lt;li&gt;Checksums detect a record changed without a matching checksum. They are not signatures and do not prove authenticity against someone who can rewrite both.&lt;/li&gt;
&lt;li&gt;Rebuilding a report from saved attempts does not mean rerunning those answers through a different grader. A changed grader requires a new experiment under the current resume contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These checks used local fixtures and controlled faults. They establish the specific continuation and rejection behavior described above. They do not test a live provider outage or a machine losing power during a request.&lt;/p&gt;

&lt;p&gt;The question I now ask of a resumable eval is simple: did it continue the same experiment, and can I still account for every attempt?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>evals</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Before scoring a model, test the evaluator</title>
      <dc:creator>deavgprime</dc:creator>
      <pubDate>Sun, 01 Nov 2026 19:07:46 +0000</pubDate>
      <link>https://dev.to/deavgprime/before-scoring-a-model-test-the-evaluator-3noi</link>
      <guid>https://dev.to/deavgprime/before-scoring-a-model-test-the-evaluator-3noi</guid>
      <description>&lt;p&gt;One version of my evaluator passed a Kubernetes configuration whose selector contradicted its own pod labels. It also rejected a configuration just because an extra label had been added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both results were scoring errors.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are new to evals, start with &lt;a href="https://dev.to/deavgprime/what-exactly-are-ai-evals-775"&gt;What exactly are AI evals?&lt;/a&gt;. It explains the tasks, graders, and metrics used in this case study.&lt;/p&gt;

&lt;p&gt;These came up while working on &lt;a href="https://github.com/deavgprime/iac-eval-bench" rel="noopener noreferrer"&gt;IaC Eval Bench&lt;/a&gt;, a small project for evaluating model-generated Kubernetes configurations. A scoring review found five mistakes in the evaluator, even though its original reference and negative-control examples were producing the expected totals.&lt;/p&gt;

&lt;p&gt;That last part is worth explaining. A passing test suite tells us that the cases we wrote behave as expected. We still have to ask whether we chose enough useful cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On this page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How the evaluator works&lt;/li&gt;
&lt;li&gt;A passing and a failing answer&lt;/li&gt;
&lt;li&gt;Two mistakes the controls missed&lt;/li&gt;
&lt;li&gt;All five scoring fixes&lt;/li&gt;
&lt;li&gt;Run the checks&lt;/li&gt;
&lt;li&gt;Limits and next checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the evaluator works
&lt;/h2&gt;

&lt;p&gt;The benchmark has six tasks. They ask for configurations such as a Deployment with resource limits, a Service that selects the right pods, or a scheduled job with execution limits.&lt;/p&gt;

&lt;p&gt;For each answer, the evaluator checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The response format.&lt;/strong&gt; Can it read the expected JSON structure?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Kubernetes schema.&lt;/strong&gt; Do the fields and their types match the bundled schema?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The requested resources.&lt;/strong&gt; Are the right resource types, names, and namespaces present?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The task requirements.&lt;/strong&gt; Do the labels, ports, limits, and other settings satisfy the prompt?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A schema is a description of the allowed structure. For example, it can tell us that a replica count should be a number. That does not mean it checks every relationship between the fields.&lt;/p&gt;

&lt;p&gt;The evaluator saves the answer and the outcome of its checks so a result can be inspected later.&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%2Flkzg73lvk6vm29yhhs96.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%2Flkzg73lvk6vm29yhhs96.png" alt="Four inspection stages check format, schema, required resources, and task requirements. The evaluator saves its evidence without deploying a workload." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The evaluator checks configuration data without deploying it. Task requirements are available when it scores the answer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a passing and a failing answer
&lt;/h2&gt;

&lt;p&gt;Take a Service, which directs traffic to a set of pods. Kubernetes uses labels to decide which pods the Service selects.&lt;/p&gt;

&lt;p&gt;Here is the relevant part of a matching example. These are excerpts, not complete manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pod labels in the Deployment template&lt;/span&gt;
&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;

&lt;span class="c1"&gt;# Service selector&lt;/span&gt;
&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service asks for pods with &lt;code&gt;app: api&lt;/code&gt;, and the pod template has that label.&lt;/p&gt;

&lt;p&gt;Now change only the Service selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unrelated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both versions have the same field structure. The second selector does not match the intended pods. The evaluator needs to check that relationship.&lt;/p&gt;

&lt;p&gt;This is a useful negative control: a constructed answer with a mistake we expect the evaluator to catch. The unchanged answer is the positive control.&lt;/p&gt;

&lt;p&gt;The project has one reference answer and one targeted negative example for each task:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constructed examples&lt;/th&gt;
&lt;th&gt;Full task passes&lt;/th&gt;
&lt;th&gt;Schema and resource checks passed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Six reference answers&lt;/td&gt;
&lt;td&gt;6/6&lt;/td&gt;
&lt;td&gt;6/6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Six answers with a targeted mistake&lt;/td&gt;
&lt;td&gt;0/6&lt;/td&gt;
&lt;td&gt;6/6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those are the expected results. All six negative examples still get through the schema and resource checks. Each fails its intended task requirement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reading these results:&lt;/strong&gt; These are constructed examples for testing the evaluator. No model produced them, so the pass counts do not measure model ability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Two mistakes the original controls missed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A broken selector passed
&lt;/h3&gt;

&lt;p&gt;The original examples all behaved as expected. Then the review tried changes beyond those six negative controls.&lt;/p&gt;

&lt;p&gt;One change added this condition to a Deployment selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NotIn&lt;/span&gt;
      &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the two conditions together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first requires &lt;code&gt;app&lt;/code&gt; to equal &lt;code&gt;api&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The second requires &lt;code&gt;app&lt;/code&gt; not to equal &lt;code&gt;api&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They cannot both be satisfied. The selector also contradicts the Deployment's own pod template, which has &lt;code&gt;app: api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The old evaluator passed this configuration because it checked &lt;code&gt;matchLabels&lt;/code&gt; and ignored &lt;code&gt;matchExpressions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix checks every declared selector condition against the submitted pod labels. This follows Kubernetes' rule that the conditions in a label selector must all be satisfied. &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" rel="noopener noreferrer"&gt;Kubernetes selector documentation&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A harmless label failed
&lt;/h3&gt;

&lt;p&gt;Here is the other side of the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The required &lt;code&gt;app: api&lt;/code&gt; label is still present. A selector asking for &lt;code&gt;app: api&lt;/code&gt; still matches these labels.&lt;/p&gt;

&lt;p&gt;The old label check compared the whole label map with the reference map. The extra &lt;code&gt;team&lt;/code&gt; entry caused a failure.&lt;/p&gt;

&lt;p&gt;That check was too strict. It now checks for the required label while allowing compatible additions. The task wording also makes those allowed additions explicit, so the prompt and the scoring rule agree.&lt;/p&gt;

&lt;p&gt;This matters for model evaluation. If the evaluator rejects an acceptable answer, the model gets blamed for a scoring mistake. If the evaluator accepts a broken answer, the score looks better than it should.&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%2Fgvxiym6v79r59qbrlcyr.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%2Fgvxiym6v79r59qbrlcyr.png" alt="A conflicting selector requires app to equal api and not equal api at once. A separate example adds a compatible team label while preserving the required app label. The old and corrected verdicts are shown for each." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. The conflicting selector must fail; the compatible extra label must still pass. Both cases need regression coverage.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  All five scoring fixes
&lt;/h2&gt;

&lt;p&gt;The review recorded five scoring mistakes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change to the reference answer&lt;/th&gt;
&lt;th&gt;Old&lt;/th&gt;
&lt;th&gt;Corrected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conflicting Deployment selector&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compatible extra pod label&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matching PodDisruptionBudget condition&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two readiness probe handlers&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UDP workload port with TCP Service&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A PodDisruptionBudget describes how many pods should remain available during voluntary disruptions. Like the Deployment, it uses a selector to identify the relevant pods.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/deavgprime/iac-eval-bench/blob/1dbabecd8cc377cef1082b2652131b6921c4c74b/docs/scoring-audit.md" rel="noopener noreferrer"&gt;scoring audit&lt;/a&gt; explains the individual fixes and links the saved before-and-after observations.&lt;/p&gt;

&lt;p&gt;The original controls were useful. They caught the mistakes they were designed to catch. But neither their old nor their corrected totals exposed these five scoring errors. Both versions reported six reference passes and zero negative-control passes.&lt;/p&gt;

&lt;p&gt;The individual cases mattered more than those totals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the checks yourself
&lt;/h2&gt;

&lt;p&gt;The examples can run locally without a model endpoint or Kubernetes cluster. With Python 3.11 to 3.13 installed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Get the version used in this article.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/deavgprime/iac-eval-bench.git
&lt;span class="nb"&gt;cd &lt;/span&gt;iac-eval-bench
git switch &lt;span class="nt"&gt;--detach&lt;/span&gt; 1dbabecd8cc377cef1082b2652131b6921c4c74b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Prepare the Python environment.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.lock
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-deps&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Run the tests and constructed examples.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests &lt;span class="nt"&gt;-v&lt;/span&gt;
python scripts/verify_baselines.py &lt;span class="nt"&gt;--output&lt;/span&gt; runs/article-check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The revision is pinned so the code stays the same when someone repeats the example. Use a fresh output directory if you run the baseline check again.&lt;/p&gt;

&lt;p&gt;This revision has 51 test methods, including multiple cases within some tests. The baseline check should finish with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verified: 6 reference passes; all 6 targeted semantic mutations detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output folders retain the inputs, individual check results, and reports. Open &lt;code&gt;runs/article-check/reference/report.html&lt;/code&gt; or &lt;code&gt;runs/article-check/mutated/report.html&lt;/code&gt; locally to inspect them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and next checks
&lt;/h2&gt;

&lt;p&gt;These checks examine configuration data. They do not submit it to a Kubernetes API server, start a workload, or verify that an application serves traffic.&lt;/p&gt;

&lt;p&gt;For example, a matching Service selector does not prove that the application is listening on the requested port. That needs a different kind of test.&lt;/p&gt;

&lt;p&gt;The suite is also small: six public, synthetic tasks. Its controls and regression tests cover specific requirements and failure cases. They are not evidence that every valid configuration will pass or every invalid one will fail.&lt;/p&gt;

&lt;p&gt;For my next evaluator change, I want to answer three questions before looking at a model score:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What answer should pass?&lt;/li&gt;
&lt;li&gt;What specific mistake should fail?&lt;/li&gt;
&lt;li&gt;What harmless change should still pass?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this project, the third question caught a problem that the original pass/fail examples had missed.&lt;/p&gt;

&lt;p&gt;Next in the series: &lt;a href="https://dev.to/deavgprime/resuming-an-eval-without-changing-the-experiment-c8d"&gt;Resuming an eval without changing the experiment&lt;/a&gt;. I test what happens when a run stops, which attempts are reused, and when resuming should be rejected.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>evals</category>
      <category>scoring</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>What exactly are AI evals?</title>
      <dc:creator>deavgprime</dc:creator>
      <pubDate>Sun, 01 Nov 2026 18:46:23 +0000</pubDate>
      <link>https://dev.to/deavgprime/what-exactly-are-ai-evals-775</link>
      <guid>https://dev.to/deavgprime/what-exactly-are-ai-evals-775</guid>
      <description>&lt;p&gt;You ask an AI to write a configuration. It returns something that looks reasonable, explains what it did, and says the result is ready.&lt;/p&gt;

&lt;p&gt;Would you use it?&lt;/p&gt;

&lt;p&gt;Reading the answer might catch an obvious mistake. But if you change the prompt tomorrow, how will you know whether the new version is better? What if it fixes one example and breaks another?&lt;/p&gt;

&lt;p&gt;That is where evaluations, usually shortened to &lt;strong&gt;evals&lt;/strong&gt;, become useful.&lt;/p&gt;

&lt;p&gt;I want to start this series with the basics. The case studies that follow will make more sense if we agree on what we are measuring, how we judge it, and what a score can leave out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On this page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What an eval actually does&lt;/li&gt;
&lt;li&gt;One answer, three different questions&lt;/li&gt;
&lt;li&gt;The parts of an eval&lt;/li&gt;
&lt;li&gt;How many types of evals are there?&lt;/li&gt;
&lt;li&gt;How to design a useful first eval&lt;/li&gt;
&lt;li&gt;Who checks the grader?&lt;/li&gt;
&lt;li&gt;Why the same run can produce two percentages&lt;/li&gt;
&lt;li&gt;Where our case study fits&lt;/li&gt;
&lt;li&gt;The reading list for this series&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What an eval actually does
&lt;/h2&gt;

&lt;p&gt;An eval tests an AI system against a defined expectation and records what happened.&lt;/p&gt;

&lt;p&gt;That expectation needs to be more useful than “give me a good answer.” For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Produce a configuration with exactly three replicas.&lt;/li&gt;
&lt;li&gt;Answer using only the supplied document.&lt;/li&gt;
&lt;li&gt;Fix a bug without breaking the existing tests.&lt;/li&gt;
&lt;li&gt;Move a meeting to the requested time without changing its attendees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The eval gives the system a task, collects its answer or actions, and checks them against the success criteria. &lt;strong&gt;A score is a result of that process, not the whole process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For an agent that can take actions, its final message is only part of the evidence. Saying “I moved the meeting” does not establish that the calendar changed. We need to inspect the resulting state too. Anthropic makes this distinction between a recorded interaction and its actual outcome in its &lt;a href="https://www.anthropic.com/research/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;agent evaluation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This also explains why an eval can contain ordinary software tests. A unit test may be one of its checks. The larger evaluation adds the chosen tasks, run conditions, repeated attempts where appropriate, and the records we need to interpret the results.&lt;/p&gt;

&lt;p&gt;Running an eval does not, by itself, train or improve the model. We use its findings to decide what to change, then test again. The change might be in a prompt, a retrieved document, a tool, application code, or the model itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  One answer, three different questions
&lt;/h2&gt;

&lt;p&gt;Here is the running example for this article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a Kubernetes Deployment with three replicas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes runs applications, and a replica is a copy of a workload. You do not need to know Kubernetes to follow the example: we asked for three copies.&lt;/p&gt;

&lt;p&gt;Suppose the answer contains this excerpt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"spec"&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="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is readable JSON. But it says two, not three.&lt;/p&gt;

&lt;p&gt;Now separate the questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structure:&lt;/strong&gt; Can we read the answer in the requested format?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirement:&lt;/strong&gt; Does it contain the setting we asked for?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavior:&lt;/strong&gt; Does the complete configuration work when deployed?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The excerpt answers the first two questions. It cannot establish the third. It is also not a complete Kubernetes manifest, so accepting this JSON does not establish full schema validity.&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%2F64b7jiue7q7xl1mg129v.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%2F64b7jiue7q7xl1mg129v.png" alt="A configuration excerpt examined through three different lenses: readable format, task compliance, and runtime behavior." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. An illustrative example. Readable output can still miss a requirement, and neither observation proves runtime behavior.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An eval should say which of these questions it checks. Otherwise, “passed” is easy to overinterpret.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts of an eval
&lt;/h2&gt;

&lt;p&gt;The terminology becomes easier when we attach each word to the same example.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it means here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Task or test case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The request, its inputs, and the criteria for success.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dataset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The collection of examples we choose to test.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evaluation suite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A group of evaluations covering selected behaviors or capabilities.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System under test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The model, application, or agent whose behavior we are examining.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One attempt at a case. Repeating a case creates more trials.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grader or scorer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The method that checks an answer and assigns a result.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A result for one case, such as pass/fail or partial credit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A summary across results, such as a pass rate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Harness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The software that runs the cases and saves the evidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Frameworks use these words somewhat differently. In Inspect AI, a task brings together a dataset, a solver that produces answers, and a scorer that grades them. Its &lt;a href="https://inspect.aisi.org.uk/tasks.html" rel="noopener noreferrer"&gt;task documentation&lt;/a&gt; shows how those pieces fit together.&lt;/p&gt;

&lt;p&gt;A benchmark is usually a defined collection of tasks and a way to report performance. A public benchmark can be useful for comparisons. A project-specific eval asks whether a system meets the requirements of that project. Doing well on one does not automatically answer the other question.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many types of evals are there?
&lt;/h2&gt;

&lt;p&gt;There is no single, fixed number that covers all the ways people use the word. Different labels answer different questions.&lt;/p&gt;

&lt;p&gt;I find it easier to sort them along three separate axes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are we evaluating?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A model:&lt;/strong&gt; Can it solve a problem or produce the required response?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An application:&lt;/strong&gt; Does the complete system, including prompts, retrieval, and other components, do its job?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent:&lt;/strong&gt; Do its tool calls and actions produce the intended outcome under the allowed conditions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A failure in an application does not immediately tell us which component caused it. The retrieved document might be wrong even when the model follows it correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are we grading it?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Code-based grading&lt;/strong&gt; uses explicit checks: parse the output, compare a value, run tests, or inspect an environment. It works well when success can be stated precisely. It can also be wrong if the checks are incomplete or unnecessarily strict. Inspect's &lt;a href="https://inspect.aisi.org.uk/custom-scorers.html" rel="noopener noreferrer"&gt;custom scorer examples&lt;/a&gt; show this kind of scoring logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model-based grading&lt;/strong&gt; asks another model to assess the response against instructions or a rubric. A rubric is a written set of judging criteria. This can help with open-ended answers, but the grader model can make mistakes too. Its judgments need comparison with trusted examples and human review. See &lt;a href="https://inspect.aisi.org.uk/model-graded.html" rel="noopener noreferrer"&gt;Inspect's model-grading guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human grading&lt;/strong&gt; uses people with the relevant understanding to review the work. It is valuable for ambiguous cases and for checking automated graders. People also need clear criteria and a way to resolve disagreements.&lt;/p&gt;

&lt;p&gt;These methods can be combined. We can use code to check structure, a model to flag a possible factual error, and a person to review disputed cases.&lt;/p&gt;

&lt;p&gt;We do not always need one reference answer that every response must resemble. A code change can be judged by tests, and an explanation can be judged against a rubric. The expectation still needs to be defined even when several different answers could satisfy it.&lt;/p&gt;

&lt;h3&gt;
  
  
  When are we evaluating it?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline:&lt;/strong&gt; Run saved or constructed cases without exposing users to the candidate system's actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow:&lt;/strong&gt; Observe how a candidate would behave on live inputs while its actions or answers are withheld from users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online:&lt;/strong&gt; Assess the system during real use, for example through a controlled experiment and operational monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Offline does not mean “no network.” An offline eval may call a hosted model API. It describes how the experiment relates to users and deployment.&lt;/p&gt;

&lt;p&gt;We can also name evals by their purpose: checking a new capability, catching regressions, probing safety requirements, or measuring reliability.&lt;/p&gt;

&lt;p&gt;So our future case study can be an &lt;strong&gt;offline, code-graded evaluation of generated configurations&lt;/strong&gt;, used to investigate scoring reliability. Those labels fit together. They are not competing categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to design a useful first eval
&lt;/h2&gt;

&lt;p&gt;Start with a decision you need to make. “Is this model good?” is too broad. “Did this prompt change improve the required configuration settings without increasing failures?” is something we can investigate.&lt;/p&gt;

&lt;p&gt;Then work through these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define success before judging the answers.&lt;/strong&gt; Identify the required behavior and acceptable variations. Refine unclear criteria when reviewing examples, then version the revised task and grader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose cases that matter.&lt;/strong&gt; Include ordinary requests, important edge cases, and failures you have observed. A few convenient examples are a starting point, not evidence of broad coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the simplest appropriate checks.&lt;/strong&gt; Use a direct assertion when a direct assertion answers the question. A model judge is not required for every eval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare under matching conditions.&lt;/strong&gt; Keep the task versions, tools, limits, and scoring rules consistent. Record what changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the failures.&lt;/strong&gt; Classify whether the problem came from the system, the task wording, the environment, or the grader.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeat trials when the behavior varies. Keep each attempt rather than quietly replacing failures with successful retries. Repeating the same cases helps measure variation on those cases; it does not make the case collection more representative.&lt;/p&gt;

&lt;p&gt;Correctness is not the only useful measurement. Record latency, token usage, cost, and resource use when they matter to the decision. Report these alongside the task results so readers can see what a successful attempt required.&lt;/p&gt;

&lt;p&gt;An eval also needs records: the input, response, relevant actions, configuration, check results, and errors. Otherwise, the headline number may survive while the explanation disappears.&lt;/p&gt;

&lt;p&gt;Hamel Husain's &lt;a href="https://hamel.dev/blog/posts/evals/" rel="noopener noreferrer"&gt;practical evaluation workflow&lt;/a&gt; connects this work to a useful development cycle: evaluate, inspect the problems, change the system, and evaluate again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who checks the grader?
&lt;/h2&gt;

&lt;p&gt;This is the question that connects the introduction to our case study.&lt;/p&gt;

&lt;p&gt;Suppose our grader only checks that &lt;code&gt;replicas&lt;/code&gt; is present. The answer containing two replicas could pass even though the task asked for three.&lt;/p&gt;

&lt;p&gt;We could get a clean report from a broken scoring rule.&lt;/p&gt;

&lt;p&gt;Before using that grader to compare models, give it examples whose expected outcomes we understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Known-good:&lt;/strong&gt; A correct answer should pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known-bad:&lt;/strong&gt; An answer with a specific mistake should fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harmless variation:&lt;/strong&gt; A different but acceptable answer should still pass.&lt;/li&gt;
&lt;/ul&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%2Fc6cxksq34ed1e3g09okh.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%2Fc6cxksq34ed1e3g09okh.png" alt="An illustrated calibration bench: known-good, known-bad, and harmless variations are checked against the grader's expected results." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Test the measuring instrument too. These constructed examples check the grader, not a model's ability.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The third case is easy to overlook. An extra label that does not break a requested selector should not automatically make a configuration wrong. A grader needs to catch mistakes while respecting the task's allowed variations.&lt;/p&gt;

&lt;p&gt;These controls still cover only the situations we chose. A passing set of controls is useful evidence, not a guarantee that the grader is complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the same run can produce two percentages
&lt;/h2&gt;

&lt;p&gt;Here is a made-up run, not a result from our benchmark:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ten attempts were started.&lt;/li&gt;
&lt;li&gt;Six produced passing answers.&lt;/li&gt;
&lt;li&gt;Two produced answers that failed the checks.&lt;/li&gt;
&lt;li&gt;Two timed out before producing an answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we report passing answers among completed responses, the rate is &lt;strong&gt;6/8 = 75%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If we report successful attempts among everything started, it is &lt;strong&gt;6/10 = 60%&lt;/strong&gt;.&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%2Fjodyb7yovw3jl3tcvrky.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%2Fjodyb7yovw3jl3tcvrky.png" alt="Ten illustrative attempts: six passes, two failed answers, and two timeouts. The same outcomes yield 75 percent among completed responses or 60 percent among all attempts." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Same run, different denominators. Both calculations need their definitions beside them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Neither rate should be presented without explaining what counts. There is also a difference between a model producing an invalid answer and a grader crashing while trying to inspect it. Inspect's &lt;a href="https://inspect.aisi.org.uk/scoring-policy.html" rel="noopener noreferrer"&gt;scoring policy&lt;/a&gt; explains why those outcomes need separate treatment.&lt;/p&gt;

&lt;p&gt;A partial score is another choice. An answer may satisfy several requirements and still fail the full task. “Most checks passed” and “the task passed” are different statements.&lt;/p&gt;

&lt;p&gt;For comparisons, look beyond one percentage: case coverage, variation across runs, failure categories, and operational measurements can all matter. More trials and uncertainty estimates help describe the evidence we have; they do not repair a badly chosen dataset. Inspect documents several &lt;a href="https://inspect.aisi.org.uk/metrics.html" rel="noopener noreferrer"&gt;scoring metrics and uncertainty measures&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where our case study fits
&lt;/h2&gt;

&lt;p&gt;In IaC Eval Bench, we are studying model-generated Kubernetes configuration through offline structure and task checks. We retain the responses and individual scoring outcomes.&lt;/p&gt;

&lt;p&gt;The upcoming case study examines the evaluator itself. A review found cases where it accepted a contradictory selector and rejected an acceptable extra label. It shows why the original positive and negative examples could produce expected totals while other scoring errors remained.&lt;/p&gt;

&lt;p&gt;That work does not demonstrate runtime correctness. It does not establish a ranking of models. It gives us specific examples of how an evaluation can mislead us, and how to test the checks more carefully.&lt;/p&gt;

&lt;p&gt;The three questions I want readers to carry into it are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What are we claiming to measure?&lt;/li&gt;
&lt;li&gt;What evidence supports the grade?&lt;/li&gt;
&lt;li&gt;What could make the grade wrong?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The reading list for this series
&lt;/h2&gt;

&lt;p&gt;I plan to follow this introduction with practical case studies: a reproducible example, the failure, the investigation, and the limits of the fix. Each should give readers something they can inspect or try themselves.&lt;/p&gt;

&lt;p&gt;I will add links here as the case studies are published.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/deavgprime/before-scoring-a-model-test-the-evaluator-3noi"&gt;Before scoring a model, test the evaluator&lt;/a&gt;: five scoring errors in a Kubernetes evaluator and the tests that caught them.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/deavgprime/resuming-an-eval-without-changing-the-experiment-c8d"&gt;Resuming an eval without changing the experiment&lt;/a&gt;: keeping saved attempts, continuing missing work, and rejecting changed inputs in IaC Eval Bench.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the framework terminology and design choices behind this introduction, the linked Inspect, Anthropic, and Hamel Husain articles are useful places to continue reading.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>evals</category>
      <category>scoring</category>
    </item>
  </channel>
</rss>
