Repository navigation
fix(policy)!: gate cost/latency budgets on paired, spike-robust ratios - #70
Merged
Merged
Conversation
max_cost_increase / max_latency_increase divided the target's mean over all its error-free calls by the source's. On one real 21-example suite that read +27.2% (pass) and +35.6% (fail) against a 30% latency budget; 86-95% of the excess was two or three 3-5 s target responses on different examples each run. Both budgets now pair calls by (prompt_id, example_id); an example counts only when each side has an error-free call. Latency gates the 20%-trimmed geometric mean of per-example target/source ratios (+13.2% / +21.6% on those runs, both pass); cost gates the paired total, untrimmed. With samples_per_example > 1 each side collapses to one value per example (median latency, mean cost). Cache hits keep counting with their recorded latency. Also: observed is signed (savings no longer clip to 0; the gate stays one-sided), the denominator counts example pairs instead of calls on both sides, and the report's economics card and insights FACTS show the figures the budgets were judged on beside the run-wide deltas. BREAKING CHANGE: the two budgets keep their config keys but measure a paired, spike-robust change, so the same run can get a different verdict; observed and PolicyMetricSummary cost/latency_increase_rate can now be negative; their denominator is example pairs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cy-budgets # Conflicts: # CHANGELOG.md
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
max_latency_increaseflipped the verdict of one real suite between two runs. The suite was 21 examples (Sonnet 4.6 → 5.5) with a 0.30 budget:9b78e413d053In both runs, 86–95% of the excess came from 2–3 target calls of 3–5 s. They landed on different examples each run, and the same examples took 1.1–1.9 s in the other run. That is provider tail noise.
What changes
(prompt_id, example_id), and an example counts only when each side has an error-free call. An example the target failed on drops out of both sides, so the source's slowest example is no longer compared against a target that never answered it.target/sourceratios. The trim dropsfloor(0.2·n)pairs from each end of the sorted log-ratios.latency_ms0 on either side are left out, because 0 is a missing timing.Σt/Σs − 1, untrimmed. Cost follows tokens, so an expensive answer is a real bill, and trimming would hide a verbose-reasoning blow-up. With no failed calls this is the same number as before (−28.5% / −28.9% on the runs above).samples_per_example > 1: each side collapses to one value per example (median latency, mean cost). An errored repeat drops only that sample.ci_low/ci_highstaynull, matchingBUNDLE_SPEC.md.observed. Savings no longer clip to 0 (−0.289, not 0.0).passed = observed <= allowedstays one-sided.PolicyMetricSummary.cost_increase_rate/latency_increase_ratecarry the same signed values.denominatorcounts example pairs (21), not calls on both sides (42).cost_budget_change_pct/latency_budget_change_pct, and the instruction and templated fallback say which figure each ceiling was judged on. The failed-budget table prints increase budgets with their sign.This changes what two public config keys mean.
CHANGELOG.mdsays so under[Unreleased], andDOCS.md,docs/configuration.md,docs/methodology.md(new section with the evidence) andllms-full.txtare updated.Hosted bundle
The schema already allows negative
observed/*_increase_rateand nullable CIs. The server's re-derive path (app/policy/service.py) comparescost <= allowed, so signed values gate correctly.run_policyruns use the uploaded rows as-is. However,../evalshift-server/BUNDLE_SPEC.md§"The cost and latency ratios" still describes the old unpaired, clamped mean and the calls-on-both-sides denominator. It needs a matching edit in the server repo; that is not done in this PR.Verification
TestPairedCallBudgetsintests/unit/test_policy.pycovers spikes not flipping, uniform and >1/5 slowdowns failing, signed savings, one-side-failed pairing, untrimmed cost, samples, errored repeats, the sameexample_idunder two prompts, cached rows, untimed pairs, and the summary values. Existing denominator and zero-valued tests were updated. Facts and report tests were added.analyzeon read-only copies of both runs. Only the two call-derived rows changed.13d053goes fromfail(the latency row alone) toconditional_pass.9b78e4staysinconclusive(its equivalence rate).make cigreen: ruff, format,mypy --strict, 3418 passed.pre-commit run --all-filesgreen. One pre-existing test,test_cli_capture_remote.py::test_online_sync_says_it_ignores_limits_it_cannot_read, fails locally at narrow terminal widths, onmaintoo. It passes withCOLUMNS=300.🤖 Generated with Claude Code