What a replay is scored on
A replayed session is re-scored on the trace metrics — Tier 1 plus Tier 2 (replay_metrics()), against the replay’s last trace. Between them, Tier 1 carries the outcome trajectory and Tier 2 the step-level verdict, so they cover whatever the notice can have triggered on. Tier 3 is excluded: it never triggers a breach, so re-scoring it would only add cost.
The metric you promote on has to be one that discriminates. Judging a candidate by a broad rollup barely separates a good session from a bad one, which makes the promote/retire decision close to random — so validation re-scores on the specific trace metrics the notice was about.
Which metric decides
A replay produces a delta per metric, and the verdict hangs on one of them. The harness picks the most authoritative metric that this case actually measured, in trust order:outcome_correct— your outcome verifier’s verdict, if one is wired and it had an answer.- The rule’s own declared
metric. - The metric named in the triggering signature.
Replay validation — the strong path
The PandaProbe platform is passive and trace-based: it scores traces a session already produced, so nobody can re-score the past “as if the rule had existed”. Counterfactual evidence requires re-running the scenario — and only you can re-run your agent. That is the replay function:ReplayValidator:
- Selects the newest failing cases whose signatures match the candidate (up to 3), plus up to 2 protected
wincases to catch collateral damage. - Builds a capability-only context — no rule bodies, no index — in which only this candidate is discoverable. Active rules stay visible as the baseline being measured against; other provisional candidates are hidden. Then calls your replay function per case, sequentially and time-bounded.
- Scores each returned session’s last trace on the trace metrics, directly through the evaluator (never through the live turn pipeline).
- Promotes iff the targeted metric improved past
rule_promote_marginon a failing case and nothing regressed attributably. Otherwise retires, with the exact reason and per-case deltas journaled.
replay_attempts advances, and after 3 attempts validation relies on the forward trial instead.
Attribution
Two rules keep a verdict answerable for the candidate rather than for its surroundings:- The candidate must actually be read. A replay that never called a rule tool cannot produce a conclusive verdict — whatever moved, this rule did not move it. The case counts as inconclusive instead.
- Only attributable regressions retire. On a failure case, a drop retires the candidate when it lands on the candidate’s own target metric family or on the authoritative
outcome_correct. A dip in some unrelated judged metric is recorded in telemetry but is not treated as causation. A protectedwincase is exempt from that narrowing: a win is the collateral-damage guard, so any metric regressing there is the signal it exists to catch.
Every candidate gets a verdict
Replays are sequential and slow, and candidates accrue faster than replays decide them. Left alone, the newest candidates are never reached — in one run, six candidates sat withreplay_attempts=0 and no verdict at all, two of which had already earned promotion.
So a round is bounded by validation_round_budget_s and rotates which candidate replays first. Past the budget, remaining candidates still get a decision — the cheap forward-trial one — rather than none.
Where a replay must first acquire a shared environment (one server, one container, one device), queueing would otherwise consume the execution budget and time out having run nothing. Signal arrival with ReplayContext.mark_execution_started() and replay_env_wait_timeout_s covers the wait while replay_timeout_s starts fresh for the actual run.
Forward trials — the automatic fallback
Without a replay function, nothing breaks and nothing is silently faked: the fallback is announced once (log + avalidation journal event) and validation turns statistical.
- Baseline, captured at add time: the fraction of recently-journaled sessions whose notices matched the rule’s metric family (any condition —
stall:,regression:,breach:— on the rule’s metric). With no history the baseline is1.0— the rule was authored against a live failure, so assume it was firing. - Trial: every handled report — healthy or alerting — enrolls its session (up to
rule_trial_min_sessions, default 5, distinct sessions) and records whether that session showed the target condition while the candidate was in force. - Verdict: promote when the trial breach rate is
0, or improved on the baseline by at leastrule_promote_margin; otherwise retire.
The baseline denominator only sees sessions that journaled an incident, which biases it high — making the forward trial lenient about promotion, never about retirement. Replay remains the strong evidence path; wire one if you can.
How validation runs
You never call the validators yourself in normal operation. The hook feeds every handled report into the validation engine and kicks a single-flight background round — validation never blocks a turn, never touches the live evaluation bookkeeping, and any failure inside it degrades to a log line.settle() deliberately does not wait for a round: a replay can need the very resource the current turn holds — an environment lock, a world, a container — so awaiting it inside a per-turn barrier could deadlock until the replay times out.
At a phase boundary, where nothing is held, run it to a standstill:
Observing the lifecycle
Every transition is journaled with its evidence, and the agent can ask directly:rule_retire carries the structured evidence — the per-case deltas that decided it — rather than only prose.
A round emits five event types, so an artifact can explain a pending state as well as a terminal one:
pending_reason is one of trial_in_progress, no_matching_replayable_case, replay_inconclusive, candidate_not_exercised, env_wait_timeout, or round_budget_exhausted — so “no verdict yet” is always distinguishable from “never attempted”.

