Poisson Labs · Methods
How we count.
Every result on this site is a comparison between two runs of the same system, and every one is stated with its limit. This page is how those numbers are made. The worked example throughout is the Go1 study: 400 conditions, 32 trials in each, 12,800 rollouts a side.
The point of all of it
“Nothing got worse” is not a finding until you say what the test could have seen.
In the Go1 study, 0 of 400 conditions got significantly worse after the retrain. That sentence is true, and on its own it is nearly empty. With 32 trials in a cell, a small rise in the fall rate looks exactly like no rise at all.
The least-powered cell that showed no significant change could not have detected a rise under 34pp. So the claim is: nothing got worse by more than that. Below it, the run was blind, and the report says so on the same line.
one run, no comparison — this scale carries no verdict
Fig. 1Not the result: the blind spot. The same 400 Go1 conditions, shaded by the smallest effect each one could have detected. Darker is blinder. The worst is friction 0.95, push 100%, n=32. Achromatic on purpose: this is a magnitude, not a verdict. report.md
Seeds, and when the matching is used
Every condition is run many times, and each of those runs has a seed. The seeds are derived from one salt, so a second run with the same salt gets the same seeds. The third trial of a condition before the change and the third trial of that condition after it start from the same random draw. The system is compared with itself, not with a fresh draw of luck.
Prove can use that matching (a paired test) or ignore it (the default). The matching only helps when a seed tends to do the same thing both times. For the Go1 walking policy it does not, because contact physics on a GPU amplifies tiny differences, so the default loses nothing. For an agent on a sampling model the seeds do less still: the same seeds do not give the same run. So the first thing we measure is the floor. We ran one code agent twice with nothing changed: 328 conditions, 32 episodes each. 1 came back significantly worse and 0 safer. A real change has to clear that.
1 worse 327 no significant change 0 safer coloured only at q<0.05, corrected across every condition
A correction across every condition
Each condition gets its own test. Test 400 conditions at p < 0.05 and about 20 will pass by luck alone, with nothing changed. A map with 20 blue squares on it would look like a finding.
So the tests are corrected together, across every condition in the run, with the Benjamini–Hochberg procedure at q < 0.05. It controls the share of the conditions we call changed that are false alarms. A condition is coloured only if it survives that.
Which test sits underneath matters, and we say which. On the Go1 runs, Fisher's exact test calls 60 conditions safer, McNemar's test calls 55 conditions safer and the log-rank test calls 88 conditions safer. All three call 0 worse. The published figure uses the first; the spread is in the report, because a reader should see how much the answer depends on the choice.
The smallest effect a condition could detectMDE, the “blind under” figure
Every verdict carries a second number: the smallest change that condition had the power to detect, given how many trials it had and how often the system was already failing there. It is printed per condition, and the worst one is printed in the headline.
It works before the run as well as after. Prove computes what a design can resolve before it spends anything, and refuses to start one that cannot resolve the effect you asked about.
On the figures on this page and on Prove's page it appears in the legend as “blind under N pp”. Trace states its limit in its caption and its report instead, as a hazard ratio or in points of failure share (the three lines, below). That is the figure the top of this page is about.
Runs that did not finish
“Fell at step 40” and “was still standing when the clock ran out” are different kinds of number. Averaging them is wrong. A run that outlives the test is censored: we know it lasted at least that long and nothing more, and the estimate treats it that way.
Agents add a second problem: a run can end by completing, by breaking a rule, or by timing out, and those compete. Each is counted against the others rather than alone.
The step clockWhere in the workflow runs die. Trace's first question.
A stored run carries its trace: the spans the agent's framework emitted, nested as the framework nests them. Trace puts every run on one clock by counting leaf spans, the spans that are the parent of no other span, in the order they started. An agent turn or a workflow is a container and is not a step; the model call, the tool call and the guardrail check inside it are. A run with L leaves ran L steps, and the kind of a step is the leaf's kind: model call, tool call, retrieval, agent, and so on.
Each run is then placed, in this order: excluded when it has no readable trace or no outcome (and counted by reason in the header); censored at its last step when it timed out or hit the capture cap, because a run whose trace stops early has an unknown death step even if its outcome says it failed; died at its last step when its outcome is one of the events named on the command line; censored at its last step otherwise, because it ended. The survival curve over step index is the Kaplan–Meier estimator with Greenwood's variance for its 95% band, the same estimator as Fig. 3 on a different clock. Where runs die, by kind of step, is the Aalen–Johansen cumulative incidence, which treats the kinds as competing causes so that the parts sum exactly to one minus survival, with a seeded bootstrap band (1,000 resamples). The tool's own worked example, on the recorded customer-service demo, is on its documentation page: 24 episodes, 7 deaths, and a band at step 40 that spans a quarter of the axis, which is why the report calls one run a description.
Comparing where runs dieTwo runs, one test, and what it could not detect.
Two stored runs, a before and an after, are compared with the log-rank test stratified by condition over the conditions both runs carry: within each condition and at each step where a run died, the deaths observed in the first run against the deaths expected if the two shared one hazard, summed over steps and conditions, and tested as chi-square on one degree of freedom. Stratifying is what makes two runs of fifty tasks comparable at all, because the tasks differ in length; a pooled test would mix “this task is long” with “runs die later after the change”. The test uses every death at every step with no step chosen after looking; a difference at a chosen step is printed on request, with a Wald interval, and is never the test. It is unpaired: the two runs share their seeds, but the log-rank does not use that fact, and the header says so.
The direction is stated as runs die earlier or later after the change, never better or worse. What the comparison could not have detected is the minimum detectable hazard ratio by Schoenfeld's formula at 80% power, from the deaths over the common conditions, and it prints on every comparison. On the tool's own example, the same agent stored twice on the same seeds, 200 episodes each, the test reads p = 0.521 and could not have detected a hazard ratio below 1.457: 400 real episodes of one agent could not have seen a 45% change in its hazard, and the report says so instead of calling the pair unchanged. Per kind of step, the cause-specific log-rank is run with the other kinds censored, corrected across kinds with Benjamini–Hochberg.
The controlled fix checkWhether a change made runs fail less, with the unchanged version as the control.
The question a team asks after a fix is whether the new version fails less on the inputs that failed. Re-running those inputs and counting the passes has no control: on an unchanged agent some of them pass on a re-run anyway, and the launch study measured how many. The fix check runs the same inputs on both versions, side by side, and reads the old version's re-run as the control. The inputs are chosen before either arm runs, and a run that was used to choose them is refused as an arm, because its failures on those inputs are there by selection.
The test is the exact conditional Cochran–Mantel–Haenszel test, stratified by input (Cochran 1954, Mantel and Haenszel 1959; Birch 1964 for the conditional form). Each input is a two-by-two table, version against failed or not; given each input's total failures, the new version's count on it is hypergeometric under the null whatever that input's failure rate, and the distribution of the sum is computed in full, so the p-value is exact and two-sided with no normal approximation. An input that always fails or never fails contributes nothing, no signal and no noise, which is why consistent failures shrink the variance a comparison has to beat rather than inflating it. With one input it is Fisher's exact test, the number Prove's diff prints for that cell; with one run per arm per input it is the exact McNemar test, the arithmetic behind the paired test above. It is the one test that contains both.
The difference is the Mantel–Haenszel risk difference in points of failure share, new minus old, with Sato's variance (1989) for a large-sample 95% interval that is labelled so; the line follows the test, never the interval, and the report says so when the two disagree. A second reading by input treats each input as one observation and flips its sign, so it does not assume that repeat runs of one input are independent draws; it prints beside the test and has no part in the line. How consistent the failures were is printed as κ, the chance a failure repeats beyond what its rate alone would give: a comparison inside inputs needs 1 − κ times the runs a pooled one needs, which on the tool's τ²-bench example (κ = 0.531) is under half.
What it could not have detected is the smallest proportional drop in every input's failure rate that the test would have found four times in five, stated in points of failure share, with the rise beside it. On the τ²-bench pair, 200 runs per arm, that limit is 11.1 points; on the HumanEval pair, 328 inputs at 32 repeats, it is 0.64 points. Which inputs moved is Fisher's exact test per input with Benjamini–Hochberg across every input compared, number for number what Prove's diff does, and every report states what the design could not have flagged: on the τ²-bench pair no change to a single task could have been flagged at all, since a lone changed input needs a p below 0.001 and the smallest p a 4-against-4 table can give is 0.0286; at least 29 tasks would have to flip together.
The fix check reads the outcome alone, so it works on a run stored with no trace, and it matches nothing by seed on purpose. On the HumanEval pair, two runs of one unchanged agent 56 minutes apart, it reads different (p = 0.036, +0.47 points) while the inputs did not move as a body (by input, p = 0.127), and the flags name the one problem that broke, HumanEval/129; the step-clock comparison on the same pair read p = 0.109 with a limit of 1.113. That is a true statement about two runs and a false one about two versions, which is why every report closes by saying that a difference between two stored runs is a difference between the runs, and why the recipe says side by side.
The three linesWhat every Trace comparison ends with.
- Different.
- The test rejected at α = 0.05, and the line says which way: runs die earlier or later after the change, or the new version failed less or more on these inputs. The test is named on the line, with its p.
- No significant difference.
- The test did not reject, and the design could have detected an effect at or under the ceiling you asked for. The limit is printed on the line: could not have detected a hazard ratio below X, or a drop in failure share below L points. It is never “unchanged”, “verified” or “pass”.
- Too small to tell.
- The test did not reject, and the design could not have detected an effect as small as you asked for. The tool exits 2 and prints guidance computed with the same function that refused the design: the repeats per arm on these inputs, the inputs like these at the present repeats, and the ceiling that would accept the design as it stands. On the τ²-bench pair at a ceiling of 1.3: 457 deaths would bring the detectable hazard ratio to 1.3, which is 824 placed runs, or 9 repeats per arm on the 50 tasks.
- The ceiling has no default.
- The largest limit you will accept is required on every comparison, as a hazard ratio for the step clock and in points of failure share for the fix check, because the tool supplies no number of its own. That is the same posture as Prove's power gate, after the run instead of before it.
Reading a calibration cardWhat a difficulty level is worth to one model.
A knob level in Tasks is a candidate until a model has been
run against it. A card measures each level on 60 generated tasks from the eval
split, 5 independent attempts at each, through the null
harness, which gives the model the task's prompt and tool block and nothing else, with
the decoding declared in full. Strict pass@1 is first in every table: the attempt
reached the goal state exactly and ended with a reply, no stage past the goal, no other
record touched; it is the mean over tasks of each task's pass rate, so every task weighs
the same. Leaky pass@1, beside it, also accepts going past the goal or touching
another record, and the overshoot share says how often that happened. Depth is read on
reaching the goal, the leaky rate, with stopping shown beside it, because below the top
level the strict rate also asks the model to stop at the goal and the two calibrated
models do not fail those two things the same way.
The interval is a bootstrap that resamples tasks, each task's attempts moving together, 2,000 resamples at a fixed seed. Two neighbouring levels are distinguishable only when their paired interval excludes zero and they differ by at least 0.15, a resolution declared before any run; otherwise the card says the difference is too small for this design to call. A level at or below 0.05 or at or above 0.95 on the rate being compared is saturated and left out of the comparison. Each row also prints the median model calls, tokens and episode seconds; time is printed and never compared. Attempts that failed for a reason that is not the model's leave the denominator and are counted apart; a cell whose share of them is above 0.02 is not published. A cell in which more than 10% of attempts made no tool call is not published either, and neither is the card that holds it, because it would measure whether the model starts rather than how hard the level is. A card is pinned to the generator's hash and says so when it is stale. The rules in full are in how to read a card.
ScopeWhat a result here does and does not claim.
Empirical evidence under a stated test envelope, not formal verification. Conditions outside the envelope were not tested; conditions inside it can only resolve effects at or above the reported MDE.
- It is a map of the conditions that were named.
- Nothing here says a system is safe. A result covers the conditions in the run and is silent about every condition outside them. Sweeping the wrong axes carefully gives a precise answer to the wrong question, and choosing the axes stays with the people who know the system.
- It is about the system that was tested, where it was tested.
- The Go1 result is one policy in the simulator it was trained in. It is not a claim about a robot on a floor. An agent result is about that agent, that model snapshot and those tasks.
- Finding and confirming are kept apart.
- Anything that goes looking for failures chooses where to look, and a result found that way is a lead. It becomes a finding when it is run again on fresh seeds, with the conditions declared before the run.
- What Prove is not.
- Not a simulator, an environment framework, a benchmark, a training loop or an observability product. It never reaches into your process and it never phones home. It brings the conditions, the seeds, the tests and the limit, and nothing else.