/** * Darwin — Safety Gate * * Guards against regressions during prompt evolution. * Enforces minimum data requirements, regression checks, * rollback triggers, and A/B test evaluation rules. */ import type { PromptVersionStats, SafetyThresholds, DarwinExperiment } from '../types.js'; export type ABTestOutcome = 'a_wins' | 'b_wins' | 'continue'; /** * v0.7.0 — Optional per-arm composite-score samples for the sequential * confidence methods (`'msprt'` / `'hoeffding'`). When omitted, the gate * falls back to the v0.6.0 effect-size heuristic so all existing callers are * byte-for-byte unaffected. */ export interface ABTestSamples { a: ReadonlyArray; b: ReadonlyArray; } export interface ABTestConfidence { /** Effect size (Cohen's d approximation) */ effectSize: number; /** Whether the result meets minimum confidence threshold */ confident: boolean; } /** * Clears the process-scoped warning latch. Exported for tests only, and * deliberately NOT re-exported from the package root: nothing in normal * operation should need to un-warn. */ export declare function resetInertConfidenceWarningForTests(): void; export declare class SafetyGate { private thresholds; constructor(thresholds?: SafetyThresholds); /** * Check whether an agent has accumulated enough data points * to proceed with evolution (prompt optimization). */ canEvolve(_agentName: string, stats: PromptVersionStats): boolean; /** * v0.7.0 — True iff the peeking guard is configured to use a sequential * method (mSPRT / Hoeffding), which needs the per-arm composite samples. * The loop calls this to decide whether to load that (slightly more * expensive) per-sample data before calling {@link evaluateABTest}. */ usesSequentialConfidence(): boolean; /** * Check whether score B is NOT a regression beyond the allowed threshold. * * Returns `true` if B is acceptable (no regression or within tolerance). * Returns `false` if B has regressed beyond `maxRegression` compared to A. * * Example: maxRegression = 0.20, scoreA = 0.80 * - scoreB = 0.70 => drop = 0.125 (12.5%) => acceptable * - scoreB = 0.60 => drop = 0.250 (25.0%) => regression */ checkRegression(scoreA: number, scoreB: number): boolean; /** * Check if the agent should roll back to its last-known-good prompt * based on consecutive failure count. */ shouldRollback(consecutiveFailures: number): boolean; /** * Evaluate the outcome of an A/B test between two prompt versions. * * Rules: * 1. Both versions need at least `minRuns` total attempts (success + fail). * 2. If a version has >50% failure rate with 3+ attempts, it auto-loses. * 3. The winner must show >5% improvement in composite score. * 4. If neither clears the bar, the test continues. * * @param overrideMinRuns — Per-test minimum runs (from ABTest.minRuns). * Falls back to SafetyThresholds.minDataPoints if not provided. */ evaluateABTest(compositeA: number, compositeB: number, runsA: number, runsB: number, failsA?: number, failsB?: number, overrideMinRuns?: number, samples?: ABTestSamples): ABTestOutcome; /** * Calculate a simple confidence metric for an A/B test result. * Uses effect size (difference / pooled estimate) as a proxy. * Minimum sample: both sides need >= minDataPoints runs. */ calculateConfidence(compositeA: number, compositeB: number, runsA: number, runsB: number): ABTestConfidence; /** * v0.6.0 — Peeking-resistant confidence check used by `evaluateABTest` * when `requireConfidence` is on. Same effect-size proxy as * {@link calculateConfidence} (|Δ| / pooled-mean), but keyed to the * test's *effective* minRuns rather than the global default so the gate * scales with per-test sample sizing. Requires a "small" effect (≥ 0.2) * and at least 2×minRuns total samples before a margin win counts. */ private meetsConfidence; /** * v0.7.0 — Dispatch the peeking-resistant confidence gate to the * configured {@link SafetyThresholds.confidenceMethod}. * * - `'effect-size'` (default): the v0.6.0 heuristic ({@link meetsConfidence}). * Byte-for-byte unchanged when no method is set. * - `'msprt'` / `'hoeffding'`: a sequential test built for repeated * looks (see each function for what it guarantees) over the * RAW per-arm composite samples (reliability is already handled by the * auto-loss rule upstream, so the statistical test uses the unadjusted * scores). The verdict must be `decisive` AND point in the SAME * direction as the score margin — a sequential test that fires for the * opposite arm does not confirm this margin. * * Falls back to the effect-size heuristic when a sequential method is set * but no per-sample data was supplied (graceful — never throws). */ private isConfident; /** * Pick a per-arm run budget from the observed spread of quality scores. * * - Wide spread (std >= 1.0): floor (10) * - Tight spread (std < 0.5): ceil (30) * - In between (0.5 to 1.0): linear interpolation * * ## This is a throughput heuristic, not a power calculation * * Said plainly, because the pre-0.15 docstring did not say it and read like * a derivation: **the direction here is the opposite of the textbook sample * size formula, and that is deliberate.** Detecting a FIXED absolute * difference Δ at level α with power 1-β needs * * n_per_arm ≈ 2σ²(z_{1-α/2} + z_{1-β})² / Δ² * * which grows WITH the variance. Read that way, a high-variance agent should * get MORE runs, not fewer, and this function would be backwards. * * What it actually encodes is a different premise: that the size of the * effect worth finding scales with the spread the agent already shows. When * every run lands between 6.5 and 7.0, the gap between two prompt versions * lives inside that same band, and Darwin's own benchmark puts LLM-judge * noise near ±1 against real lifts of +0.1 to +0.2. A tight band therefore * means small effects buried in judge noise, which needs a longer test. A * wide band usually means the agent is being handed genuinely different * tasks, where a floor of 10 runs already spreads the cost fairly. * * Note what this implies: if the effect scales exactly with σ (a fixed * standardised effect size, Cohen's d), n drops out of the formula entirely * and NEITHER direction is derivable from theory. So this is a product * decision about throughput versus patience, made explicit here rather than * dressed up as statistics. * * **If you want the statistics, turn on `requireConfidence` with * `confidenceMethod: 'msprt'`** rather than tuning this number. The * sequential test then decides whether the evidence supports the margin. * * One thing that does NOT follow, and an earlier draft of this docstring got * it wrong: `minRuns` does not become a mere floor. `evaluateABTest` also * uses it as a stopping cap. Once BOTH arms reach `2 × minRuns` without a * confirmed winner, the incumbent is declared the winner so the test cannot * run forever. So this number still sets the ceiling on how much evidence a * challenger is ever allowed to gather: at the default it is 60 runs per * arm, which is well short of what the Hoeffding method would need. * * @param experiments Recent experiments for both A and B versions * @param configMinRuns Agent-level minRuns override from EvolutionConfig * @returns Computed minRuns (never below floor, never above ceil) */ computeDynamicMinRuns(experiments: DarwinExperiment[], configMinRuns?: number): number; } //# sourceMappingURL=safety.d.ts.map