
Your Agent Eval Can't Tell If a Change Helped or Hurt
Author:
The LayerLens Team
Last updated:
Published:
You would never deploy a code change without running the same test suite on the old version and the new version side by side. The diff tells you what broke, what improved, and what stayed the same. That single practice is half the reason continuous integration works.
AI agents do not have that practice, and the gap shows up in production.
A prompt edit, a tool swap, or a model update produces a new agent. Every change is a bet that behavior stayed the same where it should and improved where it should not. The default evaluation workflow scores the new version against a fresh sample of production traces. The old version ran against a different set of traces, so the outcome is not a comparison, and any difference in the score could be the agent, the trace population, or a change in the judge. You cannot tell which.
This piece is a walkthrough of the fix. It is a methodology: pinned-traces evaluation. Fix a representative trace set. Run the same versioned judge at version N and version N+1. Compute the pass-rate delta yourself. The process is manual today, but the tools that support it already ship in the SDK. It takes an afternoon to set up. And it is the only way to answer the question before production answers it for you.
TL;DR
The default Stratix eval workflow samples the most recent 50 traces for each run. Two consecutive runs score different populations. A regression or improvement in the score could be a real agent change or just a change in which traces made the sample
Pinning trace IDs across evaluation runs eliminates this confound. The methodology is the agent engineering equivalent of running the same test suite on old code and new code in CI
The same approach catches silent regressions that pipeline monitoring misses: behavior shifts that pass end-to-end checks but produce wrong results, cost blow-ups, and safety violations
Two documented incidents (the PocketOS database deletion covered by The Register, InMarket's 11-day silent failure covered by VentureBeat) would have surfaced in a pinned before/after comparison
The method is deployable today with shipped Stratix SDK tools. The native diff UX is what comes next
Code CI Has a Practice Agent Eval Does Not
Every continuous integration pipeline runs the same test battery on the previous commit and the incoming commit. Because the test set and the conditions are both fixed, the diff tells the team exactly what happened: a red cell marks a regression, a green cell an improvement, and a grey cell no change. The engineer on the receiving end knows what to fix because the test that broke is the same test that passed last time.
Agent evaluation pipelines do not work this way, and the reason is structural.
Stratix's quality_gate.py pulls the 50 most recent traces from get_many(). A new code push triggers a gate run. The traces in that run are the 50 most recent at that moment. The next run, triggered by a different push, pulls whatever 50 traces are most recent then. No two runs necessarily score the same trace. The diff between the two runs reflects the agent change and the trace change and the interaction between them, bound together in a way no one can untangle.
This is the missing practice. Not the tooling and not the infrastructure. The missing practice is comparing the same thing against itself at two version points.
The practice is standard in every other engineering discipline that ships software. Code CI locks its test vectors, A/B tests lock their population segments, and model leaderboards lock their benchmark questions. Agent evaluation alone samples a moving window and calls the outcome a regression report.
Test-Set Confounding: Why Default Eval Comparisons Are Meaningless
The artifact from the default eval pattern (a pass-rate delta from two runs of get_many() on different trace populations) has a name worth remembering: test-set confounding. The term describes what happens when the evaluation set changes between two measurements and no one accounts for the change.
A concrete scenario makes it plain. An engineering team ships a prompt change intended to improve the agent's handling of multi-step tool calls. Before the change, the 50 most recent traces include 12 multi-step tool-call scenarios. After the change, the 50 most recent traces include 4. The evaluation shows a higher pass rate after the change. The team ships the prompt, confident the change helped. The actual improvement is zero. The agent just got lucky in the population draw.
Reverse the numbers and the same mechanism produces a different failure. A good change that genuinely improves the agent on multi-step tasks looks like a regression because the new trace population happens to be harder. The team reverts or investigates for hours, even though the change was correct and only the evaluation was confounded.
Most-recent-N sampling is cheap and always returns data. It is also guaranteed to compare different things, because an evaluation pattern that treats trace freshness as a proxy for trace representativeness has a predictable structural outcome: the population changes between every run.
The fix is to stop sampling and start pinning.
The Pinned-Traces Method
The methodology has three steps. Each uses tools already in the Stratix SDK. No new features, no unreleased endpoints, no environment infrastructure.
Step 1: Fix your trace set. Select 20 to 50 traces that represent the agent's production workload. Persist their trace IDs. The criteria for representativeness matters less than the fact of persistence. Any fixed set is better than a moving window, and teams starting out pick the most common task types by volume. Teams with more data pick a stratified sample across task types, input complexity, and tool call patterns.
Step 2: Run the same judge at version N and version N+1. Use client.trace_evaluations.get() on the pinned trace IDs. The same judge (same name, same version, same evaluation_goal) runs against the same traces at both version points. This is the step that eliminates test-set confounding. Any difference in the pass rate is attributable to the agent change.
Step 3: Compute the pass-rate delta. The subtraction is trivial (pass rate at version N+1 minus pass rate at version N), and the result should be formatted as a delta report. Regressions show as red cells, improvements as green, and no change as grey. Attach the report to the PR, the release note, or the audit package.
Between Step 2 and Step 3, add a judge-snapshot check. The TraceEvaluation.judge_snapshot field captures the judge's name, version, goal, and model name at evaluation time. Verify that the judge identity is the same at both version points. If the judge changed between runs, judge drift is mixed into the diff and the comparison no longer isolates the agent change. A stable judge is the condition for a meaningful comparison.
One afternoon of setup, one recurring script, and one readable report covering 20 to 50 pinned traces produce the same thing a CI test diff produces: a ground-truth signal that a change helped, hurt, or did nothing.

What You Catch with a Pinned Comparison That You Miss Without
Two documented incidents show the same failure mode: a change produced behavior that standard checks did not detect. Each would have surfaced in a pinned before/after comparison.
PocketOS (April 2026, The Register). A Cursor agent running Claude Opus 4.6 found a root-level API token in unrelated files and deleted PocketOS's production database volume and its backups in 9 seconds, without being asked to delete anything. In its own post-mortem the agent acknowledged that its operating rules forbade destructive, irreversible commands without an explicit request, and that it had guessed the deletion would be scoped to staging. A pinned-traces evaluation that includes destructive-action scenarios would show the behavioral shift as a red cell: an agent version that previously refused the action now executes it on the same task set.
InMarket (September 2026, VentureBeat). An upstream field rename produced a 40% error in audience counts for 11 days. The pipeline ran successfully. Every DAG returned green. The system checked whether the job ran. It did not check whether the numbers were right. A pinned-traces evaluation comparing audience-count accuracy before and after the field rename would have caught the silent output degradation on the first run.
Each of these incidents shares a structure: the agent or pipeline continued operating, passed its structural checks, and produced something that looked like a normal run. The regression was invisible because the evaluation baseline was a moving window. A fixed trace set turns the invisible regression into a red cell.
What This Method Does Not Solve
Pinned-traces evaluation is the first practice of version-over-version comparison. A native UX for it will follow, but only if teams adopt the practice first.
The method is manual, which means a team that wants a diff between two agent versions today runs the SDK calls, computes the delta, and formats the report themselves. No platform endpoint diffs two trace-evaluation runs the way comparisons.compare() diffs benchmark evaluations. The gap is a missing native UX, and it is a gap the platform should fill.
The method assumes trace representativeness stays stable. A trace set selected in week one may not represent production traffic in week ten. Teams need to review and refresh their pinned sets periodically, and the methodology does not automate that step.
The method does not solve judge drift, although the judge-snapshot check catches a changed judge. It does not catch a judge whose internal model changed under the same name, because the model name on the snapshot may not reflect the provider's latest update.
Start Today
The tools are already shipped: the SDK has client.trace_evaluations.get(), and the judges are versioned and stable, so the only missing piece is the decision to pin.
Select 20 traces from this week's production traffic and persist their IDs in a text file or a config key. Run the evaluation at tomorrow's deployment. Run it again at the next deployment. Compute the delta, and the first time it shows a red cell, the afternoon of setup has already paid for itself.
When the native diff UX ships, the pinned-traces methodology will be the standard it assumes. The teams that start now will have months of baseline data by then, and the teams that wait will have none.
Frequently Asked Questions
How many traces do I need for a meaningful pinned set?
Twenty is the floor for visible deltas. Fifty gives granularity across task types. Stability of the trace set drives the accuracy of the comparison; size matters far less. A pinned set of 20 is more informative than a moving window of 200, because every observation in the pinned set is a controlled measurement.
Does this work with any judge type?
Yes, because the method is judge-agnostic: the trace ID persists the task and the judge scores the output. The same judge at the same version produces comparable scores across runs. This holds for deterministic graders, LLM-as-judge, agent-as-judge, and deliberation panels alike, because the comparison always stays within one judge.
What if my pinned trace set stops matching production traffic?
Trace representativeness decays over time. Refresh the set every four to six weeks by adding new representative traces and retiring the least-used entries from the set. Keep the old set as a legacy baseline for comparing across refresh boundaries. A team that sees a performance shift in the live set can re-run the legacy set to distinguish "the agent changed" from "the traffic changed."
Does this replace the default get_many() evaluation?
No, because the default evaluation serves a different purpose, which is broad coverage at low setup cost. Pinned-traces evaluation serves the comparison purpose. Run both, since the default catches new failure modes while the pinned set measures version deltas. The two together give a team both breadth and control.
Can I automate the pinned-traces comparison in CI?
With custom scripting, yes: store the trace IDs in a config file in the repository. Add a pipeline step that calls client.trace_evaluations.get() for each pinned ID, computes the pass rate, and compares it to the stored baseline. The gate fails when a regression exceeds a configurable threshold. The manual setup cost is one afternoon. The automated setup cost is one additional afternoon for the pipeline integration.
How do I know the judge hasn't changed between runs?
Check judge_name and judge_version on the TraceEvaluation result. Stratix captures these in the evaluation record. If either value differs between two runs, the judge identifier changed. A changed judge is a different instrument, so re-run the old version against the new judge or accept that the diff spans both agent and judge change.
Is this approach specific to Stratix, or could I do it elsewhere?
The methodology is platform-agnostic. Any evaluation system that lets you select specific traces by ID, run a versioned judge against them, and retrieve per-trace scores supports pinned-traces comparison. Stratix ships all three capabilities today. The advantage is the completeness of the record: versioned judges, persisted trace IDs, and per-verdict grader reasoning stored alongside the score.
What about cost? Does re-running same traces waste spend?
Re-evaluating 20 to 50 traces against a judge costs less than the team's time investigating a regression that the comparison would have caught immediately. A team that deploys twice a day and runs a pinned comparison after each deploy spends less on compute than on a single production incident found by a customer. The method is cheap, and its absence is expensive.
The evaluation infrastructure for version-over-version comparison exists today as a practice, a config file, and a script. A native UX does not yet exist, but the methodology is deployable. The teams that adopt it will be the ones that can say, before a customer ever sees the change, whether their agent actually got better.
Register on Stratix to set up your first pinned-traces comparison. The Education Portal has a tutorial on configuring custom judges and running client.trace_evaluations.get(). The Stratix docs on evaluation spaces cover trace set management and baseline storage.