Skip to content

Configuration reference

Everything lives in .dedrift/config.toml, created by dedrift init. Every key below is optional; omitted keys use the documented default. Configuration is fail-closed: unknown sections, misspelled keys, wrong primitive types, non-finite numbers, and values outside the domains below stop the command with exit code 1 instead of being ignored.

[project]
name = "my-agent"
canary_repetitions = 7        # N runs per canary per cycle
rolling_window_cycles = 5     # K cycles in the rolling reference
deterministic = false         # true only if the agent is exactly reproducible
custom_scalars = []           # numeric channels read from output.structured

[detection]
fdr_q = 0.05                  # Benjamini–Hochberg level over primary tests
permutations = 500            # resamples for permutation tests (seeded)
seed = 1729                   # global seed, recorded in every report
ph_lambda = 12.0              # Page–Hinkley alarm threshold (robust-scale units)
ph_delta = 0.3                # Page–Hinkley dead-zone
inference = "fixed"           # "fixed" | "anytime" (see docs/anytime.md)
cycle_effect = "off"          # "off" (default) | "auto" — the cluster-aware battery
cycle_effect_icc = 0.02       # ICC threshold for engaging the correction
alert_persistence = 1         # checks a channel must alert in a row to fire

[materiality]
refusal_rate_pp = 2.0         # min refusal-rate shift, percentage points
format_validity_pp = 1.0      # min format-validity shift, pp
rate_default_pp = 2.0         # other rate signatures, pp
ks_distance = 0.15            # min KS statistic D for KS alerts (see note)
scalar_cohen_d = 0.5          # reserved compatibility key; not an alert gate
dispersion_ratio = 1.5        # dispersion gate on the ROBUST scale the test
                              # uses (mean abs deviation from the median),
                              # not the sample variance
p95_relative = 0.10           # tail gate: min relative P95 shift
embedding_mmd2_floor = -1.0   # -1 = auto-calibrate; 0 = off; >0 = explicit

# per-channel overrides; unlisted channels keep the defaults above
[materiality.per_channel.latency_ms]
ks_distance = 0.35

[embeddings]
model = ""                    # set via `dedrift embedder pin`, not by hand

Notes that matter

Counts are validated before use: canary_repetitions >= 2, rolling_window_cycles >= 1, permutations >= 100, and seed >= 0. Probability budgets are strict interior probabilities (0 < fdr_q < 1, 0 < gamma_total < alpha < 1). Percentage-point materiality gates are in [0, 100], ks_distance is in [0, 1], dispersion ratios are at least 1, and other scalar/tail gates are non-negative. The legacy variance_ratio name is accepted by itself, but setting it together with dispersion_ratio is an error.

canary_repetitions (N)

The single biggest power lever. The power table shows what each N buys; cost grows linearly.

This project value is authoritative for both dedrift canary run and dedrift sim. A legacy --repetitions N argument is accepted only when it matches the project value; edit config.toml to change the experiment design.

deterministic

The floor of canary_repetitions >= 2 exists because a single run cannot support a distributional comparison. That reasoning does not apply to an agent that is exactly reproducible at fixed inputs — a temperature-0 model with a pinned seed, or a non-LLM scorer — where the second repetition is a byte-identical copy that costs storage and buys nothing. Setting deterministic = true lowers the floor to 1.

It is a claim about your agent, not a convenience switch. If the agent is in fact stochastic, N = 1 removes the within-cycle variation the tests are built on, and the drift you are looking for will be indistinguishable from the sampling noise you stopped measuring.

custom_scalars

The eight built-in scalar signatures measure properties of generated text. For an agent whose output is a number — a scorer, a ranker, a classifier emitting a calibrated probability — seven of them are constant by construction and the eighth is latency, so the battery observes everything except the quantity that matters.

Listing a key here promotes it from output.structured into the scalar battery, where it gets the same location, dispersion and tail primaries, the same materiality gates, and the same BH adjudication as a built-in signature:

[project]
custom_scalars = ["score", "top_prob"]

Values must be real numbers. A missing key, a string, a boolean, or a nested object becomes NaN rather than a guess — booleans deliberately included, since isinstance(True, int) holds in Python and a flag silently entering the scalar battery as 1.0 would be a rate channel wearing a continuous channel's name.

Two constraints follow from what the battery is. Names must be valid Python identifiers and cannot repeat. And the list is capped at 16 channels: each one adds three primaries per family per baseline, so an uncapped list raises the family-wise alert rate without anyone deciding to. Note also that every calibration figure published for dedrift is measured on the default battery (m ≈ 336); channels of your own enlarge m, and their correlation structure is yours, not ours.

materiality.per_channel

Materiality gates are global by default, which makes retuning one channel expensive: raising ks_distance because latency is noisy on your hardware desensitises every scalar in the battery. A per_channel table overrides a gate for one signature and leaves the rest alone:

[materiality.per_channel.latency_ms]
ks_distance = 0.35
p95_relative = 0.50

Only the three scalar gates — ks_distance, p95_relative, dispersion_ratio — are overridable, and overrides are range-validated exactly like the global values. Rate gates stay global: they are expressed in percentage points of a rate and are already per-signature by construction.

fdr_q

Applied once per check across all primary tests (both baselines together). Corroboration tests (Anderson–Darling, Welch) never enter the pool and never alert.

permutations

This is a requested minimum, not always the effective count. Add-one permutation p-values cannot be smaller than 1/(B+1), so the checker raises B to at least ceil(m_upper/fdr_q)-1, where m_upper conservatively bounds the full primary BH family. Both counts are persisted and printed. P95 permutations run in bounded-memory chunks; semantic MMD still has quadratic kernel memory in the number of records and should be load-tested at your intended scale.

cycle_effect, cycle_effect_icc, alert_persistence

cycle_effect = "off" (the default) is the exact record-level battery: every channel compares records directly against the reference windows. "auto" engages the cluster-aware correction on channels whose golden-window/history cycle means show a shared per-cycle latent offset (hosted-model wobble): within-cycle-standardized KS for shape, design-effect Welch for location, design-effect rate z, and Student-t cycle-level summaries for dispersion/P95. cycle_effect_icc is the engagement threshold on the estimated ICC. alert_persistence = 2 holds a first-time alert until the same channel alerts at the next check — wobble alerts are transient, drift persists; measured false-alert rates by mode are in docs/statistics.md#cycle-effects, including the power cost (one cycle of delay).

ks_distance

The KS channel gates — and reports — the KS statistic D (sup-norm CDF distance), because a shape change with equal means has Cohen's d ≈ 0 and is still real drift. Binding scale, stated plainly: the raw-α=0.05 critical value is ≈ 1.36·√((n+m)/nm), which for equal arms stays above 0.15 until n ≳ 165 per arm — so at typical per-family scale, significance is the stricter filter and this gate exists to stop trivially significant tiny D from alerting at large n.

embedding_mmd2_floor

Left at -1, the MMD² materiality floor is auto-calibrated per (baseline, family) as the 95th percentile of MMD² between pairs of the baseline's own cycles — an empirical null from known-same-distribution data, computed under the same kernel bandwidth as the observed statistic. Needs ≥ 5 reference cycles. Below that the report says UNCALIBRATED and the MMD result cannot alert unless you configured an explicit floor.

ph_lambda, ph_delta

Page–Hinkley is a diagnostic, not an alert: it localizes drift onsets for attribution. The measured per-stream false-flag rate at the defaults is 8.5% on 30-cycle histories and 11.3% on 60-cycle ones (8000 draws); CI asserts a Wilson interval inside [0.06, 0.12]. Centre and scale are estimated causally, from data strictly before each step — a sequential detector that standardised on cycles after the alarm would read its own future and understate the rate roughly sixfold. Flags compound across streams — a stable agent shows ≥1 flag on 69.8% of checks — which is why flags never page anyone.

Embeddings

dedrift embedder pin hash                    # zero-dependency, always available
dedrift embedder pin st:all-MiniLM-L6-v2    # requires dedrift[embeddings]

Pinning is forever per project: changing the embedder invalidates all semantic history, so dedrift refuses to compare across embedder versions rather than produce quietly meaningless numbers. Pick deliberately, then leave it.

[anytime] — anytime-valid mode

[detection]
inference = "fixed"        # or "anytime"

[anytime]
alpha = 0.05               # lifetime, battery-wide false-alert budget
rate_model = "twosample"   # or "frozen_cp" (the Clopper–Pearson interval construction)
gamma_total = 0.02         # frozen_cp only: coverage budget
tilts = [1.5, 2.0, 3.0]    # frozen_cp only: symmetrised to {psi, 1/psi}
epoch_allocation = "per_epoch"   # or "geometric"

With rate_model = "twosample" (default) each rate process is a two-sample SAFE e-value against the frozen reference counts; there is no nuisance interval, so no coverage budget is spent and the e-BH level is the full alpha. gamma_total and tilts apply only to "frozen_cp".

Switches the inference layer from per-check FDR to lifetime-oriented rate e-processes. Per-process optional-stopping and per-check e-BH control are proven; repeated dependent e-BH has the causal assumption documented on the linked page. The power trade is quantified in anytime-valid mode. Default is fixed.

Exit codes

Command Exit Meaning
dedrift check 0 OK with valid evidence coverage
dedrift check 2 DRIFT DETECTED — wire this to alerting
dedrift check 3 Inconclusive: degraded, missing/invalid reference, or partial coverage