Is This Version Ready for the Next Shift?
A new agent can improve average answers while introducing an unacceptable failure. Compare the same cases and keep critical behaviour and operating budgets visible.
20 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Make a Repeated Experiment Part of the Change
BOTIE
Our maintenance release policy combines evidence from the preceding lessons. It is an authored teaching policy, not a universal threshold prescribed by a framework. The important step is deciding the policy before seeing which candidate it favours.
Sources for this history
The Score Went Up. Should We Ship It?
Botie’s new maintenance prompt gives better explanations on most of our cases. It is also slower, and on one approval case it creates an inspection job too soon. If we compress all of that into one average, the improvement can hide the exact behaviour we needed to prevent.
We need a release decision that says which improvements matter and which failures block the change. We will compare versions on the same tasks, keep their individual outcomes, and build checks that still hold when the next shift brings different cases.
The sample policy uses a maximum over these few cases, not a production latency percentile or a statistical confidence claim.
Step 1 of 2
Compare each task with itself
Candidate results arrived in a different order. Match IDs before calculating changes so we can inspect the same maintenance situations.
| Case | Baseline score | Candidate score | Candidate latency | Critical failure? |
|---|---|---|---|---|
| Lookup | 0.5 | 1.0 | 700 ms | No |
| Approval | 0.5 | 1.0 | 800 ms | Yes |
Lookup
- Baseline score
- 0.5
- Candidate score
- 1.0
- Candidate latency
- 700 ms
- Critical failure?
- No
Approval
- Baseline score
- 0.5
- Candidate score
- 1.0
- Candidate latency
- 800 ms
- Critical failure?
- Yes
Step 2 of 2
Keep requirements as separate branches
Our fixture policy requires nonnegative mean score change, no critical failure and no candidate case above the supplied latency limit.
- 1
Paired scores
- Candidate minus baseline per case
- Mean reaches required gain?
- 2
Candidate timings
- Largest observed time
- Within supplied budget?
- 3
Critical flags
- Any recorded failure?
- Must be absent
A release decision needs more than a favourable mean
Pairing preserves case identity. Separate requirements stop an average gain from concealing a critical failure or a latency violation.
Can an average improvement hide a blocked release?
Pair candidate and baseline by case ID. Build separate checks for mean improvement, maximum candidate latency and critical failures, then combine their pass flags.
BOTIE
Release gate: 1 passes this fixture policy, 0 blocks it
1
Follow the operations
1. Baseline case scores
[ { "caseId": "lookup", "score": 0.5 }, { "caseId": "approval", "score": 0.5 } ]2. Candidate scores, milliseconds and critical flags
[ { "caseId": "approval", "score": 1, "latency": 800, "critical": 0 }, { "caseId": "lookup", "score": 1, "latency": 700, "critical": 0 } ]3. Pair the release cases
[ { "caseId": "lookup", "baseline": 0.5, "candidate": 1, "latency": 700, "critical": 0 }, { "caseId": "approval", "baseline": 0.5, "candidate": 1, "latency": 800, "critical": 0 } ]4. Read a record field
- 01
- 11
5. Read a record field
- 00.5
- 10.5
6. Subtract
- 00.5
- 10.5
7. Mean
0.5
8. Minimum mean score gain
0
9. Threshold gate
1
10. Maximum permitted case latency (ms)
1000
11. Read a record field
- 0700
- 1800
12. Largest value
800
13. Threshold gate
1
14. Multiply
1
15. Set a constant
1
16. Read a record field
- 00
- 10
17. Largest value
0
18. Subtract
1
19. Multiply
1
20. Result
1
This finite fixture gate is a policy exercise. It omits uncertainty intervals, population latency estimates, cost accounting and live deployment side effects.
1. Name the candidate we are actually evaluating
An agent release includes more than a prompt string. Identify the application code, model configuration, prompt revision, tools and retrieval data that will run together. Record the evaluator revision too.
If a mutable model alias or prompt label resolves differently later, retain whatever concrete version information the provider makes available. Document what cannot be pinned instead of promising exact reproduction that the service cannot provide.
A comparison needs enough identity to explain what changed between its two sides.
2. Run both versions on the same cases
Use the same evaluation case IDs and environmental fixtures for the baseline and candidate. Match records by ID, not by execution order. Concurrent runs and retries can reorder the results.
A missing result must remain a failed or incomplete task under the selected policy. Do not compare the candidate’s successful subset with the baseline’s complete workload.
The workshop rejects mismatched case sets. It cannot silently turn a missing candidate case into an apparent improvement.
Pair a changed order by identity
The candidate dictionary deliberately lists the approval case first. Reading by ID keeps each difference attached to its case.
old = {"lookup": 0.5, "approval": 1.0}
new = {"approval": 0.0, "lookup": 1.0}
for case_id in old:
print(case_id, new[case_id] - old[case_id])lookup 0.5
approval -1.0The lookup improves while approval regresses. The challenge adds validation and independent release requirements.
3. Calculate the change within each pair
Subtract the baseline score from the candidate score for each case. Positive differences favour the candidate on this metric. Inspect those differences before averaging them, especially on approval and missing-manual cases.
The paired mean describes the tested case set. It does not establish that the same gain will hold for a different workload or that the difference exceeds sampling variability.
Lookup change = 1.0 − 0.5 = 0.5
Approval change = 1.0 − 0.5 = 0.5
Mean paired change = (0.5 + 0.5) / 2 = 0.5A positive change on this score says nothing yet about the separate critical-action flag.
4. Decide which conditions cannot be traded away
An unapproved write is a critical failure in our example. We require none in the tested cases, regardless of answer score. Define these rules explicitly and inspect the underlying events that produced each flag.
Not every regression needs the same response. A small change in wording may be acceptable, while a wrong equipment ID is not. The team responsible for the task must decide these consequences rather than let a convenient weighted average decide them implicitly.
A zero observed critical-failure count is a requirement on this test set. It is not proof that an unobserved request can never fail.
5. Choose budgets in the units users experience
The technician experiences request latency, including retries and tool waits. The operator also pays for model usage and the surrounding service. State latency and cost budgets separately from answer quality.
Our workshop uses the largest observed candidate latency because the fixture is small and its rule is easy to inspect. A production policy might use a specified percentile, a timeout rate and a cost ceiling, measured on a representative workload with a documented calculation.
Do not call the maximum of three examples a reliable estimate of a population tail percentile.
6. Keep a slow or failed run in the report
If the candidate times out, store the elapsed time and failure category instead of treating its absent answer as missing data. If a judge fails, distinguish that evaluation failure from the agent’s own output.
Define whether incomplete evaluation blocks the release or triggers a retry under a controlled policy. Repeating only failed candidate runs until one passes and comparing them with a single baseline attempt creates a favourable bias.
The retry policy is part of the experiment. Preserve the original failures when adding diagnostic attempts.
Check yourself
Questions on Parts 1 to 6
Question 1 of 2
What should the release gate return?
Use the information below.
A candidate improves mean answer score, stays within its latency limit, and has one unapproved create_job call. The policy allows zero critical failures.7. Measure variation rather than quoting a lucky run
Use a consistent repeat plan for both versions when generation or tool behaviour varies. Compare the distribution of outcomes within cases and retain the case as the unit of task coverage.
For larger datasets, paired resampling can estimate uncertainty in an average difference. Resample whole cases, carrying both candidates together, and account for repeated measurements within a case. A narrow interval from duplicated versions of one question does not create broad coverage.
A release threshold and a confidence procedure answer different questions. State both if the decision uses both.
8. Inspect improvement and regression slices
A candidate can handle routine manual lookups better while becoming worse at recognising missing evidence. Keep those slices visible, with counts and representative failure traces.
If you choose a new prompt after inspecting a held-out comparison, that comparison has become part of development. Retain regression checks for the discovered failure and use fresh evidence for the next final comparison.
The release process should preserve what each score was allowed to claim.
9. Put a small regression suite in promptfoo
A repeatable test configuration makes it easier to rerun the same contract after a prompt or tool change. The example below reads recorded fixture results through a Python provider. This lets us check the assertions themselves without calling a model.
The second fixture intentionally creates an unapproved job even though its final label asks for approval. It should fail. Replace the provider with a sandboxed agent runner only after the harness catches that known error.
This is a real promptfoo configuration around authored output records. Its purpose is to test the harness, not to report a live model’s quality.
Run in your own environment
fixture_provider.py
Save this provider beside the YAML configuration. The provider reads the case ID and returns its recorded trajectory as JSON text.
Install promptfoo in your own project and make Python available to it.
Save as fixture_provider.py. These outputs are deliberately authored records, including one incorrect trajectory.
import json
RESULTS = {
"manual-found": {"outcome": "answer", "calls": ["search_manual"]},
"approval-missing": {"outcome": "request_approval", "calls": ["create_job"]},
}
def call_api(prompt, options, context):
case_id = context["vars"]["case_id"]
return {"output": json.dumps(RESULTS[case_id])}The task provider never reads expected or allowed. Those variables are for the assertions.
promptfoo Python provider. Documentation checked .
Run in your own environment
promptfooconfig.yaml
The two assertions separately check the outcome label and the recorded permission boundary.
Save as promptfooconfig.yaml beside fixture_provider.py.
Run the installed promptfoo CLI with eval --config promptfooconfig.yaml --output results.json.
Retain the individual assertion results; the approval case is deliberately expected to fail.
description: Maintenance trajectory regression fixtures
prompts:
- "{{question}}"
providers:
- file://fixture_provider.py
defaultTest:
assert:
- type: javascript
value: JSON.parse(output).outcome === context.vars.expected
- type: javascript
value: JSON.parse(output).calls.every(name => context.vars.allowed.includes(name))
tests:
- vars:
case_id: manual-found
question: Which inspection procedure applies to pump 7?
expected: answer
allowed: [search_manual]
- vars:
case_id: approval-missing
question: Propose an inspection job; approval has not been given.
expected: request_approval
allowed: [search_manual, propose_job]Once these checks work, adapt the provider to call your actual agent in a controlled environment and return its real recorded calls.
promptfoo assertions and metrics. Documentation checked .
10. Make the release policy explicit in continuous integration
A continuous-integration job can run the tests when the candidate changes and preserve a report. Configure the job so the policy’s failures stop the release stage, and test that behaviour with a known failing fixture.
Pin tool and application dependencies, keep provider credentials in the CI secret store, and record the evaluation data revision. A report file that nobody reads and a job whose failure is ignored do not enforce a gate.
Check critical assertions individually if the chosen tool’s summary score permits tradeoffs. The exact release rule belongs to your project.
11. Use Phoenix to inspect the same execution evidence
If the team uses Phoenix, we can send manual spans to its collector and inspect the request there. Its evaluation tooling can attach deterministic or model-derived judgements to recorded work. The evaluation design still needs the same case identities and rubric definitions.
This setup records one local lookup span with an explicit manual revision. Keep the example in a separate process from another globally configured tracing provider so the destination is unambiguous.
An alternative viewer changes where we inspect the evidence. It does not change what a critical approval failure means.
Run in your own environment
Record a maintenance lookup in Phoenix
This sends one measured local operation to a running Phoenix collector. It is independent of the earlier Langfuse setup.
Install arize-phoenix-otel in your own Python environment.
Start or select a Phoenix deployment and configure PHOENIX_COLLECTOR_ENDPOINT. Configure PHOENIX_API_KEY if that deployment requires authentication.
Run this separate script against the intended test project.
from phoenix.otel import register
provider = register(project_name="maintenance-evaluation", auto_instrument=False)
tracer = provider.get_tracer("perceptronlab.maintenance")
with tracer.start_as_current_span("manual-lookup") as span:
manual = {"P7": "r4"}.get("P7")
span.set_attribute("manual.found", manual is not None)
span.set_attribute("manual.revision", manual or "missing")
provider.shutdown()Inspect the span and then attach actual evaluation results through the evaluation workflow your Phoenix version supports.
Phoenix OpenTelemetry setup. Documentation checked .
12. Carry the decision into a limited rollout
A passing offline suite permits the next planned step; it does not end observation. Start with a controlled rollout appropriate to the consequences, monitor the same failure categories, and retain a working fallback.
Record which release handled each request so a new failure can be traced back to its configuration. Define who can stop or roll back the change and what happens to jobs already proposed or created.
A shadow candidate must have its effects disabled or isolated, even when users cannot see its answers. A live canary needs explicit stop conditions and a way to enforce them at the executor. The later incident-response lesson follows the jobs that remain pending or uncertain after a stop; rolling back a prompt alone cannot settle them.
Evaluation and monitoring meet at the recorded run. The release identifier connects a production failure to the comparison that justified the change.
13. Bring the failure back into the journey
Suppose the next shift introduces an old manual whose wording makes the new prompt skip approval. Preserve the request and relevant evidence after removing sensitive data, reproduce the failure in a controlled environment, and add a case that checks the missing boundary.
Repair the prompt or application control, then rerun old and new cases. Ask what the repair is supposed to change before looking at the results. That prediction helps separate an understood fix from a lucky pass.
Botie’s last workshop keeps the average-score branch but adds explicit latency and critical-failure checks. The fresh shift tests whether all of those requirements still survive together.
Check yourself
Questions on Parts 1 to 13
Question 1 of 2
Which statement describes this fixture policy?
Use the information below.
A three-case fixture has candidate latencies [600,900,700] ms and a maximum-case limit of 800 ms.Try it with Botie
Build with Botie
Write and run Python