docs: propose a tiered pre-deployment regression gate - #215
Open
amiddavid wants to merge 2 commits into
Open
Conversation
Machine-authored commits and machine reviews mean nothing currently asserts that a build is not worse than the last one before it reaches the box. CI proves the code compiles, the tests pass and the binary starts; it proves nothing about configuration drift, savings, or capability. The proposal tiers the gate by what each signal costs to measure: - tier 0 (free, every PR): goldens over the effective config, the preset set, the route/auth table and the env-var contract, generalising the two drift guards that already exist. This is the axis that has broken this deployment twice — `key_env` outranking the code on who pays, and `TENANT_MONTHLY_CAP_USD` left in the unit after the binary stopped reading it. - tier 1 (cents): frozen-corpus replay through `/compact`, which runs the pipeline with no upstream call. `deploy/harbor/replay.py` already does this; what is missing is a pinned corpus and a committed baseline. Gated on net dollars at the live price list plus cache-write growth, not a token percentage, because compaction can raise the bill while cutting content. - tier 2 (~$10/RC): a ~12-task SWE-bench subset selected from the 50-task record by four stated criteria, gating hardest on hard failures and per-task cost and only loosely on reward. Two things the document argues rather than assumes. First, a full run is ~$60 and ~10 h for two arms, but the stronger objection to reward-as-gate is statistical: the published arms separated 43 vs 44 of 50, so a 12-task subset cannot distinguish 88% from 78%, and a green subset must not be read as a capability guarantee. Second, absolute baselines rot for reasons that are not us, so the recommendation is a single arm plus a cheap live drift canary that marks the baseline stale when the upstream moves, rather than paying 2x for paired arms every run. No code changes; `docs/proposals/` is excluded from the mkdocs nav, so no nav edit. Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com>
Two corrections to the tier-1 (frozen-corpus replay) section, both found by reading the code rather than the summary of it. Dollars are not gateable in replay. The valuation classifies each request into a billing tier from r.cache_read / r.cache_write / r.fresh_input (dash/readvalue.go:176), and those come from the upstream response's usage block. /compact never calls upstream, so all three are zero, every row classifies as `fresh` — the most expensive tier — and savings are systematically overstated. Since 90.74% of input tokens actually bill as cache reads at 12.6x less than fresh (measured-2026-08.md), that error is large. The section now splits the money question: content tokens exact here, dollars only under a frozen tier mix carried in the baseline and explicitly not a claim about the bill, billed tiers and cache-write growth tier 2 only. The tier applies to SOME components, and naming the rest matters because measuring them here passes vacuously rather than failing. Of 26 registered components, 16 are a pure function of the request body and are exactly gateable; three call a model on it and are gateable only against recorded responses, which measures the plumbing and not the savings; three depend on live cross-turn or cache state and cannot be measured at all. `extract_llm_sweep` is the sharp case: prefixAsker.Ask needs the previous turn's forwarded body from the session stash (proxy/prefixask.go:112), a replay forwards nothing, so every record is a first turn, ErrNoPrefix, refused locally with no request made (extract_sweep.go:795). It declines on 100% of a corpus and removes 0 tokens, so a gate including it would report `0 -> 0, unchanged, pass` on a component that was entirely broken. `cachesplit` is a marker that always skips (the edit is body-level in apply/prefixsplit.go) and `cacheinject` places breakpoints, so both have effects that exist only in provider cache accounting. Consequently the tier is described as the savings gate for the deterministic offloaders plus a universal integrity gate, not as "the expensiveness gate", and it must carry an explicit scope list and assert out-of-scope components are excluded rather than measured. Also added: a hermeticity requirement (context-window lookups are resolved over the network, so an unpinned model-info service shifts which candidates trip a trigger and looks like a savings regression); a note that one proxy can serve the whole sweep via /compact's per-request pipeline override instead of restarting per config, which also avoids replay.py's `pkill -x` on a shared box; and a tier-0 check that no shipped preset names a test-only component. Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com>
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.
Docs only — one new file under
docs/proposals/, which is excluded from the mkdocsnav, so no nav edit and no site change. Nothing here is built yet; this is for
argument before any code.
Why
Commits and reviews are increasingly machine-authored and humans steer rather than
read every diff, so nothing currently asserts that a build is not worse than the last
one before it reaches
/usr/local/bin/context-guru-proxy. CI proves the codecompiles, the tests pass and the binary starts. It proves nothing about configuration
drift, savings, or capability.
Running SWE-bench per deploy is not the answer, and the cost is the weaker half of
why. From
docs/results/: two arms at one trial each is ~$60 and ~10 h of agent wall.The stronger objection is that reward is the worst signal per dollar available — the
published arms separated 43 vs 44 of 50, so a 12-task subset (sd ≈1.2 tasks at p≈0.86)
cannot distinguish an 88% pipeline from a 78% one. A gate that samples the noisiest
metric and gets read as "no capability regression" is worse than no gate, because a
deploy proceeds on it.
So the proposal tiers by what each signal costs to measure, and gates hardest on the
lowest-variance quantity available.
Shape
main.go:983already assembles it for the dashboard), the preset set, the route/auth-class table,
and an AST-derived env-var contract. This is the axis that has actually broken this
deployment twice:
key_envoutranking the code on who pays, andTENANT_MONTHLY_CAP_USDleft in the unit after the binary stopped reading it. Agolden diff is also how an intended config change gets spotted rather than
slipping through.
/compact, which runs the pipelinewith no upstream call.
deploy/harbor/replay.pyalready does the middle of this;what is missing is a pinned corpus and a committed baseline. Scope is limited — see
below.
record by four stated criteria, gating hardest on hard failures and per-task cost,
and only loosely on reward.
verify-deployed, which closes the loopREDEPLOY-caller-pays.mdwalks by hand.
The two points most worth reviewing
Tier 1 measures content, not money, and only for some components. Both corrections
came from reading the code:
The dollar valuation classifies each request into a billing tier from
r.cache_read/r.cache_write/r.fresh_input(dash/readvalue.go:176), andthose come from the upstream response's usage block.
/compactnever calls upstream,so all three are zero and every row classifies as
fresh— the most expensive tier.Since 90.74% of input actually bills as cache reads at 12.6x less
(
measured-2026-08.md), that overstatement is large. Content tokens are exact here;dollars only under a frozen tier mix carried in the baseline, and explicitly not a
claim about the bill; billed tiers and cache-write growth are tier 2 only.
Of 26 registered components, 16 are a pure function of the request body and are
exactly gateable; three call a model on it and are gateable only against recorded
responses, which measures the plumbing rather than the savings; three depend on live
cross-turn or cache state and cannot be measured at all. That last class passes
vacuously rather than failing, which is why it needs naming:
extract_llm_sweepneeds the previous turn's forwarded body from the session stash
(
proxy/prefixask.go:112), a replay forwards nothing, so every record is a firstturn —
ErrNoPrefix, refused locally, no request made (extract_sweep.go:795). Itdeclines on 100% of a corpus and removes 0 tokens, so a gate including it reports
0 -> 0, unchanged, passon a component that could be entirely broken.cachesplitis a marker that always skips (the edit is body-level in
apply/prefixsplit.go) andcacheinjectplaces breakpoints, so both have effects that exist only in providercache accounting.
Consequently tier 1 is described as the savings gate for the deterministic
offloaders plus a universal integrity gate — not as "the expensiveness gate" — and
the cache lever, which carries the largest measured effect in the product (−34.1%
cost, 0% -> 96.7% hit in isolated A/B), sits outside it.
Baselines rot for reasons that are not us — model, gateway, task images, price
list. The recommendation is a single arm plus a cheap live drift canary that marks the
baseline stale when the upstream moves, rather than paying 2x for paired arms every
run; plus a ratchet rule so "progress moved the baseline" and "a regression moved the
baseline" do not look the same in the log.
The proposal also requires a
cg-gate selftestthat mutates each subject and assertsthe corresponding check fails, and three verdicts (
pass/fail/unmeasured), soa corpus that failed to load never reads as "nothing broke".
Two findings noted in passing
Recorded in the doc (§1, §9) so they are not lost, not fixed here:
appear nowhere under
docs/(CG_BASE,CG_FILTER_REMOVE,CG_LIVE*,CG_SMTP_LIVE_TO,CG_TRIGGER_CF_MAX,CHEAP_MODEL_AUTH,KVCACHE_PYTHON). Thatis a grep and not the AST inventory §3.2 proposes, so treat the exact list as
indicative.
deploy/eval-containers/sweep.pyhardcodesROOTto one engineer's laptop path, soit runs nowhere else as written.
Open decisions
§11 lists four. The first blocks phase 2: the corpus is real traffic, so where it is
hosted, what scrubbing it gets and who approves its use needs an answer before any of
it is built.