Anytime-valid mode¶
Every calibration number elsewhere in these docs is per check. Monitoring runs forever, so per-check control is the wrong guarantee: at the measured 3.2% per-check rate, an unchanged agent checked hourly accrues roughly 23 false alerts a month, and nothing in the fixed-sample theory bounds that accumulation.
Anytime mode replaces it with lifetime-oriented rate e-processes:
The target and its boundary
Per-process optional-stopping control and per-check e-BH control are proven. The full repeated dependent battery targets an unbounded-horizon false-alert budget of α = 0.05 per epoch, but that trajectory-wide statement relies on the causal condition described below. It is measured, not presented as an unconditional theorem.
dedrift check --inference anytime
dedrift report --inference anytime --out report.md
Default is --inference fixed. Read the rest of this page before switching,
because lifetime-oriented control is bought with detection power and the exchange rate at
canary scale is steep.
Measured, both directions¶
Same seeded stable-agent histories, both inference paths, default suite scale (18 canaries × 7 repetitions; 24 declared rate e-processes = 6 families × 4 rate signatures on the golden baseline), 500 runs × 2000 cycles.
The streams are simulated dependent — signatures of a family are computed from the same records — because independent streams would measure the one case in which the governing result already holds, and so would prove nothing about ours.
| ever raised a false alert | |
|---|---|
| anytime-valid (two-sample e-processes + e-BH) | 2/500 (0.4%) — Wilson upper bound 0.015 |
| fixed-sample (BH per check) | 419/500 (84%) by cycle 100; 500/500 by cycle 1000 |
The anytime column is the twosample rate model (the default) at the
2000-cycle horizon; the fixed column is the record-level battery,
cycle_effect = "off" — also the default. Two events in 500 runs is
consistent with a true lifetime rate up to about 1.5% — inside the
α = 0.05 budget,
and not a claim of exact flatness. What the contrast establishes is the
difference in kind — a fixed-sample guarantee decays with
use, and this one has not been observed to breach its budget.
And the cost, on the same machinery — cycles from shift onset to alert. Only the 6 refusal streams shift (the realistic case, since a regression rarely moves every channel at once); onset at cycle 10, 100 runs × 400 cycles:
| shift | anytime-valid (twosample, pooled battery) | fixed-sample |
|---|---|---|
| +2 pp | 0/100 — never detected | 100%, median 17 cycles |
| +5 pp | 1/100, median 52 | 100%, median 7 |
| +10 pp | 89/100, median 50 cycles | 100%, median 2 |
| +20 pp | 100/100, median 17 cycles | 100%, median 1 |
Percentages are the fraction of runs that detect at all within 400 cycles; medians are conditional on detecting. Below about +10 pp the wealth process grows too slowly to cross inside any practical horizon — the right word is inconsistency, not delay. The battery includes one pooled process per rate signature (the product of the six per-family e-values — an e-value under cross-family independence, inside e-BH's arbitrary-dependence guarantee; battery 24 → 28): it is what roughly doubles family-wide drift detection (the same study without it measures 49/100, median 66, at +10 pp), while the per-family processes keep single-family resolution.
Read that table before switching modes. Anytime-valid mode is for
catching real degradation over weeks without accumulating false alarms; it is
not for catching a 2 pp shift this afternoon. If small shifts matter to you
more than lifetime error control, fixed is the honest choice — which is
why it is the default.
The mechanism behind the cost is visible in the construction: the pooled denominator learns the alternative as the current stream accumulates, so per-cycle growth decays ~1/t after onset and cumulative wealth grows only ~log t late on. Strong shifts cross in a few cycles; mid-rate ones (p₀ = 0.30) take tens of cycles at canary block sizes — at a 60-cycle harness horizon +10 pp at p₀ = 0.30 was never caught (0/20), while +20 pp went 4/20 (median 26) and a low-baseline +10 pp 5/20 (median 28).
The rate model: twosample (default) vs the Clopper–Pearson construction (frozen_cp)¶
Each rate process is a two-sample SAFE e-value — the SAFE 2×2 construction in sequential form (Grunwald, de Heide & Koolen, Safe Testing, JRSSB 2024; Turner, Ly & Grunwald, arXiv:2106.02693). Two Bernoulli streams carry independent Beta(1,1) priors under the alternative and a shared Beta(1,1) prior under the null; the ratio of the current stream's own posterior predictive to the pooled posterior predictive for each new cycle block is an e-value, and the product over blocks is an e-process under optional continuation. For a block of s successes in n trials, with (S₁, F₁) the current stream's completed-cycle counts and (S₀, F₀) the frozen reference counts:
log E = [betaln(s + a1, f + b1) − betaln(a1, b1)]
− [betaln(s + ap, f + bp) − betaln(ap, bp)]
a1 = 1 + S₁, b1 = 1 + F₁, ap = 1 + S₀ + S₁, bp = 1 + F₀ + F₁
No nuisance interval is needed: the shared null parameter is integrated against the prior, so no coverage budget is spent and the e-value grows at the posterior-predictive odds rate rather than the KL gap to the far edge of a worst-case interval.
The alternative construction, and its measured limit. frozen_cp
worst-cases over a frozen Clopper–Pearson interval for the unknown
baseline rate, with the
coverage budget split across the battery (γᵢ = γ_total ÷ K = 8×10⁻⁴ at
K = 24). At canary scale that interval is so wide that it contains the
alternative for any moderate shift, so the worst-case e-value cannot
grow: the independent audit measures 0/30 detections at +5/+10/+20 pp
over 60 cycles at p₀ = 0.30, with median log-wealth decaying ≈0.07
per cycle under a +20 pp shift. The construction ships as a documented
ablation — its measured power profile is the evidence behind the
twosample default, not a mode to run in production — and gamma_total
and tilts are meaningful only there.
The saturation caveat. The pooled denominator learns the alternative as drifted cycles accumulate, so per-cycle growth decays ~1/t after onset — fast crosses for strong shifts, tens of cycles for mid-rate ones, and a +10 pp shift at p₀ = 0.30 is beyond a 60-cycle horizon entirely (the 400-cycle table above is the operative measurement). The reference-anchored alternative that would avoid the stall was implemented and rejected: it is not an e-value when the reference posterior misses the true rate (measured E[E] up to 5.8 under the null).
The validity boundary, measured. Validity rests on the iid-block null, and two different things fail at two different points.
The e-value property itself — E[E_t | F_{t-1}] ≤ 1, without which Ville's inequality has no premise — is what fails first. Under a shared per-cycle offset it is already unresolved against 1 at σ = 0.10 (E[M_T] = 1.10 over 45,000 replicated trajectories) and clearly fails by σ = 0.25. Canary heterogeneity is safe in the other direction: unequal fixed rates within a family make the block Poisson-binomial, which is underdispersed relative to the binomial the null assumes, so the construction only gets more conservative.
The observed alert rate lags that failure, which is why watching it is not enough: under iid cycle wobble it stays within the α = 0.05 budget up to σ = 0.25 (8/500 = 1.6%, Wilson upper 3.1%) — a regime in which the guarantee has already lapsed. The persistent AR(1) regime σ = 0.25, φ = 0.9 measures 36/500 = 7.2% (upper 9.8%) — above the 5% budget.
So: if your endpoint wobbles at σ ≳ 0.10, do not read the anytime bound as covering you, whatever the alert rate looks like. Details under Robustness to provider-side wobble.
How the budget decomposes¶
With the default twosample rate model there is no nuisance interval, so
no coverage budget is spent and the e-BH level is the full per-epoch
alpha: α′ = α = 0.05. The split below applies only to the frozen_cp
construction, where the unknown baseline rate is paid for out
of α:
| Component | frozen_cp default | Pays for |
|---|---|---|
| α — lifetime, battery-wide | 0.05 | the whole claim |
| α′ — e-BH level | 0.03 | multiplicity across the processes |
| γ total — coverage | 0.02 | the unknown baseline rate |
| γ per process | γ_total ÷ K | one process's interval |
Why γ is divided by the pool size. e-BH controls FDR only if every input is a genuinely valid e-value. A process whose coverage interval misses the true rate is not one, so coverage failures union-bound across the battery:
P(ever a false alert) ≤ α′ + Σᵢ γᵢ
At 24 processes, using γ = 0.02 per process would claim 0.05 while delivering 0.51. dedrift computes γᵢ = γ_total ÷ K from the live pool, so changing your suite cannot silently stale the arithmetic.
The frozen_cp default γ_total = 0.02 came from sweeping allocations whose
claim is actually 0.05. Within that valid region the trade is one-sided —
detection at +10 pp rises from 24% to 90% as γ_total goes 0.005 → 0.03,
with the measured null rate 0 throughout — so the choice was made on power
alone, since validity does not discriminate. We stop at 0.02 rather than
0.03 because α′ is the battery's FDR level, and spending most of the
lifetime budget insuring against a coverage failure never once observed
buys little. Both are configurable — and both are inert under twosample,
which the audit's 0/30 measurement of the alternative construction makes
the recommended default.
"Per epoch" is the part to understand¶
An epoch is a stretch during which the hypothesis and the measuring instrument are fixed. It ends when any of these change:
- the canary suite
- the pinned embedder
- the golden baseline definition
- the signature extractor
- the anytime α/γ allocation, tilt grid, or allocation mode
At an epoch boundary every e-process resets to zero wealth, and the report says so prominently. Epoch instances are globally monotone: changing A→B→A creates three epochs, never reuses A's old pool, ledger, or budget. A changed epoch begins after the cycle visible when it is declared; historical cycles are not replayed under a monitoring configuration chosen later.
This is the correct semantics, not a weakened guarantee. Change the suite and you are testing a different null; evidence accumulated under the old one is not evidence about the new one, so a guarantee spanning that change would be meaningless rather than stronger. If you re-baseline monthly, your claim is α per month — and you should know that rather than assume otherwise.
For a summable cross-epoch target, epoch_allocation = "geometric" allocates
both α′ and γ_total by 2⁻⁽ᵉ⁺¹⁾ on globally monotone epoch e. Allocating
only α′ while re-spending γ would not be summable. It costs power and is off
by default.
What is proven and what is measured¶
Kept separate deliberately.
| Statement | Status |
|---|---|
| Per process, P(ever crossing) ≤ α′ at any stopping time | proven (Ville) |
| Per check, FDR ≤ α′ under arbitrary dependence among e-values | proven (Wang & Ramdas) |
| Over the trajectory, FDR ≤ α′ applying e-BH every cycle | assumed + measured |
That last row is the honest gap. Applying e-BH repeatedly to running e-processes is anytime-valid under a causal condition — no unobserved confounding from the past — established by the stopped e-BH result. Our streams are dependent (two baselines share the current cycle; all channels share the golden sample), so the condition is not automatic. It is plausible here, because every stream is a function of the same observable cycle history, but we have not proven it for this battery. The candidate violation worth naming is unobserved provider-side state — a rolling deployment, load variation — correlating streams through time. Instead of asserting the guarantee, the realised rate is measured: 2/500 above, with the wobble boundary below.
Configuration¶
[detection]
inference = "fixed" # or "anytime"
[anytime]
alpha = 0.05 # lifetime, battery-wide
rate_model = "twosample" # default; "frozen_cp" is the Clopper–Pearson interval construction (an ablation)
gamma_total = 0.02 # frozen_cp only: coverage budget; alpha_prime = alpha - gamma_total
tilts = [1.5, 2.0, 3.0] # frozen_cp only: symmetrised to {psi, 1/psi}
epoch_allocation = "per_epoch" # or "geometric"
The tilt grid is symmetrised so drift in either direction is covered, and its
support is a fixed configured constant. Predictability is therefore
structural — a bet can never depend on the cycle it is betting on, because it
comes from configuration. A helper (tilt_from_materiality) exists to derive
a tilt from a materiality band, which would aim the bets at effects you have
declared you care about, but the shipped default does not call it. The grid
is a constant, not an aimed one. (All of this configures frozen_cp; the
twosample e-value has no tilt grid at all.)
Anytime mode has no post-hoc materiality gate. Its alerts test rate stability with configured bets, while fixed-mode alerts also apply observed-effect thresholds. The two modes do not currently represent the same practical hypothesis; use anytime for sequential rate surveillance and fixed mode for the broader, effect-screened diagnostic battery.
Current scope¶
Only the rate channel (refusal, format validity, schema conformance, error rate) is implemented in anytime mode. A verified single channel is worth more than four unverified ones; the scalar and semantic channels need constructions of their own rather than a reuse of this one, and until they exist, anytime mode monitors less than fixed mode does. Run both.
Two further limitations, stated rather than discovered:
- Pool membership freezes at epoch start. Only combinations with
reference data at that moment are admitted. A signature that gains data
mid-epoch waits for the next epoch; one that loses data stays in the pool
contributing exactly
E_t = 1. This is a consequence of predictability, not an oversight: membership decided from the current cycle would make the bet depend on the data it is betting on. - A suppressed cycle contributes
E_t = 1— not a skipped update, and not a stale value carried forward. That preserves the supermartingale under a non-informative-dropout condition: suppression is decided after the cycle is observed, soE[E_t | past] <= 1requires that whether a cycle was suppressed carries no information about its outcome. Forhad_errorthose are nearly the same event — a real caveat, not a formality.
Reading a wealth trajectory¶
Log-wealth is accumulated evidence since the epoch began.
- Rising — evidence against stability. The cycle where the rise began is reported as the onset estimate, and it feeds attribution better than the Page–Hinkley change point it replaces.
- Negative — the configured bets have lost. This is not affirmative evidence of stability and must not be presented as certification.
- Crossing log(1/α′) — the process alone would reject; whether it alerts is decided by e-BH across the battery.
Robustness to provider-side wobble¶
The anytime path was run on stable agents while a shared per-cycle
offset was injected — the configured stack unchanged, but the per-record law
drifting between cycles, which is what a hosted endpoint does on its own.
phi is the AR(1) coefficient of that offset: phi = 0 is memoryless,
phi > 0 makes it persist. Ever-alerted, 500 runs × 2000 cycles, twosample
rate model (the fixed path degrades far faster under the same injection —
its ladder is on the statistics page):
| σ | φ | ever alerted |
|---|---|---|
| 0.00 | 0 | 2/500 — 0.4%, Wilson upper 1.5% |
| 0.10 | 0 | 2/500 |
| 0.25 | 0 | 8/500 — 1.6%, Wilson upper 3.1% |
| 0.10 | 0.90 | 4/500 |
| 0.25 | 0.90 | 36/500 — 7.2%, Wilson upper 9.8% |
Harness cross-checks at a 60-cycle horizon agree at small n: 0/40 with no wobble (upper 8.8%), 1/30 at σ = 0.15 (upper 16.7%). (Published here at first as 0/60 and 1/40; those denominators did not match the run the figure and the paper report, and are superseded.)
Read the last row against its matched controls. At σ = 0.25 the rate goes 8/500 (φ = 0) → 36/500 (φ = 0.90): monotone in persistence at fixed magnitude, which is what the theory predicts if the causal condition is what binds — persistent offsets are exactly the configuration in which unobserved provider-side state can correlate streams through time. And 7.2% exceeds the α = 0.05 budget. That is the measured boundary of the guarantee, published rather than smoothed over: the observed rate stays inside budget under memoryless wobble up to σ = 0.25, and does not under strongly persistent wobble of that magnitude. Read that alongside the validity boundary above — the observed rate staying inside α up to σ = 0.25 is not the same as the guarantee holding there, and it does not: the e-value property is already unresolved at σ = 0.10.
Why persistence and not just magnitude: an offset redrawn independently each cycle has no past, so however large it is it cannot violate a condition about confounding from the past. Measuring that and calling it reassurance would be measuring the wrong thing.
The assumption behind the reference¶
Under frozen_cp, γ buys a Clopper–Pearson interval that covers the true
reference rate, and the per-process construction is valid on that coverage
event. Clopper–Pearson coverage assumes the reference is a binomial sample
from the rate you are trying to bound. The twosample default spends no
coverage budget — but it compares against the same frozen reference counts,
and the selection problem is identical either way: a golden baseline is a
set of cycles you declared known-good — a selected sample. Selecting
toward quiet cycles moves the reference away from the true stable rate in
exactly the direction that turns "drift vs reference" into "reference vs
truth", and no rate model repairs that.
Practical consequence: freeze the baseline on the first cycles you
collected rather than the ones that looked best. dedrift baseline set
--first 3 exists for that. A hand-picked baseline weakens the guarantee by
an amount nobody can quantify, and the 2/500 measurement above says nothing
about it — that study draws its reference as a clean binomial sample, so
the reference is unbiased there by construction.