The Regression Gate: Blocking a Drop Before It Merges
An evaluation only prevents incidents once it runs in the pipeline. Today you freeze a baseline into a snapshot, set a threshold on a score that moves every run without either missing regressions or crying wolf daily, and solve the two problems that actually bite: what a run costs, and who decides whether a red build is real.
Today's Goals
- Freeze an evaluation result into a baseline snapshot, and say what besides the score has to be recorded for it to be reproducible
- Define a regression rule on a noisy pass rate, and explain why a single lower score is not enough to call it a regression
- Design a cost-aware pipeline strategy, stating which tasks run on every commit and which only before a release
Plain-Language Walkthrough
A badge reader at the door
A badge reader in an office lobby is a very stupid machine. It does not know who you are or how urgently you need to get upstairs. It does one thing: wrong badge, turnstile stays shut.
Its value comes precisely from being stupid. If it could be talked into an exception, it would stop being a door and become a suggestion.
Everything built over the first five days is a suggestion. Graders, benchmark sets, judges, trajectory scoring, cost panels — all of them produce a report, and a report has to be read by a person. People are busy, people forget, and people tell themselves an hour before a release that this one is probably fine.
Today does one thing: wire that report into the turnstile. It has to shut when quality drops, and shut hard enough that nobody can walk around it without leaving a mark.
In engineering terms, "hard" has exactly one meaning: the process exits with a non-zero status code. That is the only thing a pipeline reads. Your evaluation script can print as much red text as it likes; if it exits zero, the merge happens.
A snapshot that records only a score records nothing
First job: freeze one evaluation result as the baseline everything else gets compared against. That artifact is the baseline snapshot. A first attempt usually looks like this:
baseline: 0.925Next week it reads 0.865. What happened? You cannot say. Four things could have happened between those two runs, and only one is a real regression:
- somebody swapped the model;
- somebody edited one sentence of the system prompt;
- somebody added two harder tasks to the benchmark set;
- nothing changed and this run was unlucky.
The number in that snapshot says nothing about any of them. Worse, you will instinctively assume the fourth, rerun, and if the score comes back treat it as a non-event — which is the most common way a gate dies.
So the snapshot splits in two:
| Part | Contents | What it decides |
|---|---|---|
| Environment fingerprint | Model, prompt version, task set version, framework version, trial count, tier | Whether two results can be compared at all |
| Scores | Overall pass rate, per-task pass rate | What the comparison says |
Plus one separate field: the random seed.
The seed exists for reproduction: when the gate goes red you need to replay that exact run. But it deliberately stays out of the fingerprint, and the reason is worth a minute. The scores from a different seed are exactly the samples you use to estimate noise. If the seed were part of the fingerprint, every seed change would be ruled incomparable and the gate would degrade into "only comparable within one seed" — which deletes non-determinism from the evaluation, and non-determinism is the entire reason this course exists.
// Environment fingerprint: if any one of these moves,
// the two scores cannot be subtracted from each other
const fingerprint = {
model: process.env.LLM_MODEL ?? 'offline',
promptVersion: 'refund-agent@1.3.0',
taskSetVersion: '2026.09.14-1',
evalkitVersion: 'evalkit@0.6.0',
k: 5,
tier: 'full',
}
// Returns the names of the fields that disagree; an empty array means comparable
export function compareFingerprint(a, b) {
const keys = ['model', 'promptVersion', 'taskSetVersion', 'evalkitVersion', 'k', 'tier']
return keys.filter((key) => a[key] !== b[key])
}# Environment fingerprint: if any one of these moves,
# the two scores cannot be subtracted from each other
fingerprint = {
"model": os.environ.get("LLM_MODEL", "offline"),
"promptVersion": "refund-agent@1.3.0",
"taskSetVersion": "2026.09.14-1",
"evalkitVersion": "evalkit@0.6.0",
"k": 5,
"tier": "full",
}
# Returns the names of the fields that disagree; an empty list means comparable
def compare_fingerprint(a, b):
keys = ["model", "promptVersion", "taskSetVersion", "evalkitVersion", "k", "tier"]
return [key for key in keys if a[key] != b[key]]The prompt version is the field most often skipped and the one most likely to hurt you. A prompt is a string; editing it carries none of the ceremony of a code review, and plenty of teams keep prompts in an admin console anyone can change at any time. The result is a score that moved with no commit explaining it. Give the prompt a version number and bump it on every edit. The cost is roughly zero, and the payoff is converting a whole category of unexplainable score movement into something traceable.
Jitter is not regression
This is the core idea of the day, and the line between a gate that works and one that does not. Here is real data: same target agent, same tasks, same graders, same model, same prompt — nothing changed except the random seed — thirty consecutive rounds, overall pass rate recorded each time:
measured noise: full tier, 8 tasks, k=5, 30 seeds
range 0.8500 - 0.9750
mean 0.9167 worst single-round deviation from mean 0.0667Nothing changed, and the pass rate spans 12.5 points.
Now take the most natural gate anyone writes — "block if it is below the baseline" — and run it against that data. It fires several false alarms a week, and the cost of a false alarm is not inconvenience. It is that the gate dies.
It goes red the first time. Everyone investigates seriously, spends two hours, and discovers a rerun is green. Second time, somebody says "same as last time", reruns, green. Third time, nobody investigates. Fourth time, somebody adds one line to the repository: continue-on-error: true.
At that point the gate is still in the pipeline, still running, still green, and its blocking power is zero. That is worse than having no gate, because without one everyone at least knows they are unprotected.
Setting the threshold: measure noise first, then pick a position
A threshold is not a guess and cannot be copied from someone else. It is a property of your task set, your trial count, your model. Three steps:
- Hold everything fixed and change only the seed. Task set, k, model and prompt all frozen; run twenty or thirty rounds.
- Read the spread. Record the high, the low and the mean. The width of that range is how far luck alone can move you.
- Put the threshold outside the range. The warning line sits near the width; the blocking line sits clearly above it.
Against the numbers above, this course settles on:
| Threshold | Value | Basis |
|---|---|---|
| Warning line | 12 point drop | The measured range is 12.5 points, which is what luck can cost you if the luckiest round became the baseline and the unluckiest round is being compared to it. The line sits just inside that |
| Blocking line | 20 point drop | 1.6 times the range width; luck cannot assemble that |
One detail that gets skipped: trial count drives the noise width directly. Swap the eight-task full tier for a three-task smoke tier, keeping k the same so total trials fall from 40 to 15, and the noise range widens from 12.5 points to 20.
So the smoke tier needs its own, much wider threshold, with an uncomfortable consequence: the smoke tier can only catch incidents larger than twenty points; a small regression is invisible to it. Reading a green smoke run as "no regression" is the most common piece of self-comfort in this whole mechanism.
A three-state gate, and reruns for the gray zone only
Once you have a noise range, a two-state verdict is wrong. A two-state gate has to pick a side inside the gray zone, and the gray zone is by definition "not enough samples to conclude". Forcing a conclusion there only produces errors. So make it three states:
| State | Meaning | Exit code |
|---|---|---|
| Pass | The drop is inside the noise | 0 |
| Warn | The drop landed in the gray zone and converged on rerun | 0, but recorded in the report |
| Block | The drop exceeds the noise, or a signal appeared that jitter cannot produce | Non-zero |
The gray zone is handled by adding samples and judging again: for that run only, raise k from 5 to 15. More samples means narrower noise. If the gap converges back inside the noise, warn and let it through. If it is still outside, that is not luck. Block.
Beyond the aggregate, one class of signal cannot be explained by noise and has to be blocked on its own: a task that went from passing every time to failing every time.
It gets its own rule because the aggregate lies. One task collapses, another happens to improve, the total barely moves, and you have quietly lost a specific capability. A per-task comparison catches that. The total cannot.
export function judge(baseline, current, config) {
// Check one: fingerprints disagree, refuse to compare
const mismatched = compareFingerprint(baseline.fingerprint, current.fingerprint)
if (mismatched.length > 0) {
return { verdict: 'block', reason: `fingerprint mismatch on ${mismatched.join(', ')}, results not comparable` }
}
// Check two: per-task "always green to always red", which noise cannot explain
const now = new Map(current.scores.tasks.map((t) => [t.taskId, t]))
const collapsed = baseline.scores.tasks
.filter((b) => b.passRate >= 1 && (now.get(b.taskId)?.passRate ?? 0) <= 0)
.map((b) => b.taskId)
if (collapsed.length > 0) {
return { verdict: 'block', reason: `tasks went from always passing to always failing: ${collapsed.join(', ')}` }
}
// Check three: compare the aggregate against the range; the middle goes to a rerun
const drop = baseline.scores.passRate - current.scores.passRate
if (drop >= config.blockDrop) return { verdict: 'block', reason: 'drop is far outside the noise range' }
if (drop > config.warnDrop) return { verdict: 'gray', reason: 'gray zone, not enough samples' }
return { verdict: 'pass', reason: 'change is within the noise range' }
}def judge(baseline, current, config):
# Check one: fingerprints disagree, refuse to compare
mismatched = compare_fingerprint(baseline["fingerprint"], current["fingerprint"])
if mismatched:
return {"verdict": "block", "reason": f"fingerprint mismatch on {mismatched}, results not comparable"}
# Check two: per-task "always green to always red", which noise cannot explain
now = {t["taskId"]: t for t in current["scores"]["tasks"]}
collapsed = [
b["taskId"]
for b in baseline["scores"]["tasks"]
if b["passRate"] >= 1 and now.get(b["taskId"], {}).get("passRate", 0) <= 0
]
if collapsed:
return {"verdict": "block", "reason": f"tasks went from always passing to always failing: {collapsed}"}
# Check three: compare the aggregate against the range; the middle goes to a rerun
drop = baseline["scores"]["passRate"] - current["scores"]["passRate"]
if drop >= config["blockDrop"]:
return {"verdict": "block", "reason": "drop is far outside the noise range"}
if drop > config["warnDrop"]:
return {"verdict": "gray", "reason": "gray zone, not enough samples"}
return {"verdict": "pass", "reason": "change is within the noise range"}After it goes red: not training the team to ignore it
What happens after the gate turns red decides the lifespan of the mechanism more than the gate does. Three rules, all pointing the same way: make skipping more annoying than fixing, not the other way around.
One: a red build must hand over an executable next step, not a number. An error saying only "pass rate 57.5 percent, below the baseline of 92.5 percent" pushes the whole investigation onto whoever reads it. It should say which seed reproduces that run, which tasks dropped, and which failing transcripts to open first. This is where five days of work converge — you have transcripts, so spend them here.
Two: relaxing the standard has to leave a mark. "Record a new baseline" means "I accept this score as the new normal", so it should be a reviewed commit, not a command someone ran on their laptop. Keep the baseline file in version control and every relaxation shows up in a diff where somebody can see it.
Three: never ship a skip button. Cases that need skipping do exist — an intentional behavior change — but the correct expression is "update the baseline and explain why", not "ignore this run". A skip button that exists eventually becomes the default path.
Cost tiers: what runs every time, what runs at release
Evaluation costs money, the third difficulty named on D1. Today it becomes a concrete tradeoff: you cannot run the full suite on every commit. Tiers are chosen by coverage divided by cost, not by importance — the smoke tier uses the fewest tasks covering the most failure categories, the full tier carries the long tail. The measured bill for this course's two tiers:
smoke 3 tasks x 5 trials = 15 trials, 36 tool calls, cost 3.77 cents
full 8 tasks x 5 trials = 40 trials, 105 tool calls, cost 10.42 centsOffline you feel no difference. Against a real gateway that is 2.8 times the bill and 2.8 times the wait — and in a real project the multiple is usually twenty or thirty, because the full set has hundreds of tasks. One workable layering:
| Tier | When it runs | Goal |
|---|---|---|
| Smoke | Every commit | Catch a large incident within minutes |
| Full | On tags, before releases, nightly | Catch small regressions and the long tail |
| Human review | Quarterly, sampled | Calibrate the automated graders themselves |
Wiring it into a pipeline has exactly one hard requirement: a block exits non-zero.
- run: pnpm start -- --tier smoke
- run: pnpm start -- --tier full
if: startsWith(github.ref, 'refs/tags/')Production sampling: the half the offline set cannot see
An offline benchmark set only covers the tasks you thought of; production generates the ones you did not, every day. The method is sample production traffic by ratio and run the same graders over it — possible only because D5 already records a transcript for every run, so sampled evaluation feeds existing transcripts to the graders instead of re-running the agent. Three cautions:
- Stratify the sample. Failures and anomalies go in whole; only successes get sampled by ratio, or the most informative slice is what gets thrown away.
- Never merge production and offline scores into one number. The task distributions differ completely, and a weighted average represents neither.
- Feed production findings back into tasks. A new failure shape found by sampling becomes a new task in the benchmark set — closing the loop back to D2's "build the benchmark set out of the incidents you already had".
Source Reading
Two sources today: one method, one reference implementation.
The Anthropic engineering post on agent evaluation contributes its definition of a regression evaluation: a regression suite should sit near a perfect score over the long run, and a drop is the alarm. That sentence is where the entire gate criterion comes from — precisely because the expected value is "does not drop", "how much of a drop counts" becomes a question you are forced to answer. The post carries one more conclusion relevant today: the evaluation task itself may be broken, which puts grading defects ahead of model regression in the investigation order. D7 takes that apart in full.
promptfoo is a configuration-driven evaluation and comparison tool (measured license MIT, actively maintained). Read it for how it expresses an evaluation as a file you can put in version control: test cases, assertions and several prompt variants live in one declarative config, and the command line finishes with an exit code. Same idea as the gate written by hand today, except it turns "task set" and "threshold" into configuration instead of code.
Read it with one question in mind: how does it express a threshold? Almost every off-the-shelf tool offers a single-point criterion of the form "fail below X". That is not a shortcoming on their part. A noise range cannot be decided for you by a tool — it is a property of your task set, and the tool has no idea how many trials you ran or how much your suite moves. Whatever framework you pick, measuring noise before setting a threshold stays yours to do.
Hands-On Lab
Today adds two modules to evalkit: the baseline snapshot and the three-state gate. There are four exercises, but the real acceptance criterion is in the manual checklist — it asks you to make the gate go red with your own hands.
The reasoning is worth repeating: a gate that has never gone red and a gate that always lets everything through look identical in a pipeline. Both are green. Manufacture a real regression and watch it get stopped, or you do not know you installed anything but an ornament.
Manufacture one by wrapping the target in a weakening decorator — without touching the target itself, a frozen file — so it has some probability of issuing one refund too many. The defect shape matters: an extra refund hurts both positive and negative tasks. Pick a regression that damages only half of them and the other half cancels it out in the aggregate; you will think you validated the gate when you validated a change that happened not to move the score.
After the run you should see a block like this:
gate: BLOCK
baseline pass rate 92.5% this run 57.5%
- overall pass rate dropped 35.0 points, past the 20.0 point blocking line, far outside the noise range.
exit code is non-zero, the merge is blocked. Start here:
1. reproduce that run with the baseline seed 20260914
2. read a few failing transcripts, and decide first whether the agent regressed or the grading is wrong
3. only record a new baseline once the behavior change is confirmed intentionalInterview Questions
Today's four questions circle judging under noise, snapshot completeness, cost tiering, and the hardest one last: the gate is red and the team has started skipping it — how do you change the process?
That last one is barely technical. It asks whether you have maintained a gate like this inside a real team. "More communication" or "require a full investigation every time" scores nothing. The right direction is lowering the cost of fixing, raising the cost of skipping, and admitting that an excessive false-alarm rate is itself a technical problem that needs fixing.
Checklist and Tomorrow
By the end of today you should be able to:
- Name the four things a baseline snapshot must record besides the score, and what goes wrong if any one is missing
- Explain why the random seed is recorded but kept out of the comparability check
- Use a set of real noise numbers to show why a five point drop in one run cannot be called a regression
- State the criterion for each of the three gate states, and why only the gray zone gets a rerun
- Get all eleven assertions green with
MOCK=1 pnpm selftest - Manufacture a regression yourself and watch the gate stop it with a non-zero exit code
Tomorrow is D7, Examining the Suite Itself: Saturation, Broken Tasks and Grading Defects. Today assumed the baseline is trustworthy, the criteria are correct, and a lower score is the agent's fault. Tomorrow turns the lens on the evaluation system itself — a single mis-written grader can move the same model by more than fifty points on the same suite, and a suite approaching a perfect score has stopped offering any signal for improvement. The last day answers one question: what entitles you to trust your own evaluation?
Interview questions
Evaluation scores fluctuate on every run. How do you set a gate threshold that neither misses regressions nor fires false alarms every day?评估分数每次都在抖,你怎么定一个既不漏报也不天天误报的门禁阈值?
Common in ChinaCommon overseasDeep dive#ci#regression#thresholdsHow to reason about it · think before answering
- This tests whether you know thresholds must be measured. Answering 'block if it drops more than five points' earns half credit regardless of the number, because the number was guessed.
- Step one is to measure the noise: hold the task set, trial count, model and prompt fixed, vary only the random seed, run twenty or thirty rounds, and record the maximum, minimum and mean pass rate. The width of that band is how much you can lose to luck alone.
- Step two places the thresholds: a warning line near the band width and a blocking line clearly above it, say one and a half to two times the width. The point is the justification - the threshold must sit outside the noise, or you are blocking bad luck rather than regressions.
- Step three notes that a threshold is a local property of this task set and this trial count. Change the tasks, the k, or the model and you must measure again. Copying somebody else's threshold is the same as having none. A bonus point: trial count drives noise width directly, so a small smoke tier is noisier and needs a wider threshold.
- Step four adds the third state: rather than forcing a verdict in the gray band, rerun that one run with more trials and decide afterwards. Pass, warn and block fit the shape of noisy data better than a binary gate.
- Close on the cost of false alarms, which is the real discriminator: too tight is more dangerous than too loose. Daily false alarms train the team to ignore the gate, and the end state is identical to having no gate while everyone believes they are protected.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道阈值要量出来」。回答「下降超过 5 个百分点就拦」的,无论那个数字是多少都只能拿一半分——问题不在数值,在于它是拍脑袋来的。
- 第一步是**量噪声**:固定任务集、试次数、模型、提示词,只换随机种子,连跑二三十轮,记下通过率的最高值、最低值和均值。区间宽度就是「什么都不改、纯靠运气能掉多少」。
- 第二步才是定位置:警告线压在区间宽度附近,拦截线明显更高(比如区间宽度的一点五到两倍)。关键是要说出判据——阈值要在噪声之外,否则拦的是运气不是退化。
- 第三步要点出「阈值是局部属性」:它属于你这套任务集和这个试次数,换任务集、换 k、换模型都要重新量。**抄别人的阈值等于没定阈值。** 顺带能说出「试次数直接决定噪声宽度,任务少的冒烟档噪声更大、阈值必须更宽」,就是加分项。
- 第四步给出两态之外的做法:落在灰区的那一次不强行下结论,而是加大样本复跑一次再判,三态(通过/警告/拦截)比两态更贴合噪声的真实形态。
- 最后要说清楚误报的代价,这是这题真正的区分点:**太紧比太松更危险**。天天误报会把团队训练成无视门禁的人,最终结果和没有门禁一样,但过程中所有人都以为自己有防护。
Key points
- Measure noise first: vary only the seed across twenty or thirty runs to get the pass-rate band.
- Put thresholds outside the band: warning near the band width, block clearly above it.
- A threshold belongs to this task set and this trial count; re-measure after any change, never copy.
- Use three states, resolving the gray band by rerunning with more trials instead of forcing a verdict.
- A high false-alarm rate teaches the team to bypass the gate; too tight is worse than too loose.
答题要点
- 先量噪声:固定一切只换种子连跑二三十轮,得到通过率的波动区间。
- 阈值放到区间之外:警告线贴近区间宽度,拦截线明显更高。
- 阈值是这套任务集与这个试次数的属性,换任何一项都要重新量,不能照抄。
- 做三态而不是两态,灰区靠加大样本复跑来判,而不是强行下结论。
- 误报率过高会让团队学会跳过门禁,太紧比太松更危险。
Beyond the pass rate, what else goes into a baseline snapshot? What breaks if you leave something out?基线快照里除了通过率,你还会记录什么?少记了会出什么问题?
Common in ChinaCommon overseasIntermediate#ci#baseline#reproducibilityHow to reason about it · think before answering
- The answer is not a list of fields but what each field rules out. Reciting names collapses under the follow-up 'what happens if the prompt version is missing'.
- Start with the shape: a snapshot has two halves. The environment fingerprint - model, prompt version, task-set version, eval framework version, trial count, tier - and the scores, overall plus per task. The fingerprint decides whether two results are comparable at all; the scores decide by how much they differ.
- Go through the costs one by one. No model field and a model swap reads as a regression. No prompt version and a one-sentence edit produces a drop with no commit to blame. No task-set version and two newly added hard tasks look like a worse agent. No trial count and changing k from five to three silently changes the noise structure. Every case has the same shape: the number moved and nobody can say who moved it.
- Call out the prompt version specifically. It is a string, editing it carries none of the ceremony of code review, and many teams keep it in a config console. Versioning it costs almost nothing and converts a whole class of unexplainable drift into something traceable.
- The random seed is the interesting exception: record it, but keep it out of the comparability check. Its purpose is reproduction - when the gate fires you must be able to replay that exact run - while scores from different seeds are precisely the samples you use to estimate noise. Putting the seed in the fingerprint makes every seed change incomparable, which deletes non-determinism from the evaluation.
- Finish with the correct behavior on a fingerprint mismatch: refuse to compare and block, rather than computing a delta and annotating that the model changed. The latter produces a meaningless result that looks entirely normal. Requiring a human to re-record the baseline is the safe path.
分析过程 · 先想清楚再作答
- 这题的答案不是罗列字段,而是「每一项各自排除了哪一种解释」。只背字段名的回答,在追问「那少记提示词版本会怎样」的时候会卡住。
- 先说骨架:快照分两半。一半是**环境指纹**,包括模型、提示词版本、任务集版本、评估框架版本、试次数与档位;另一半才是分数(总体通过率加逐任务通过率)。指纹决定两次结果**能不能比**,分数决定**比出来是多少**。
- 少记的代价可以逐项说:少了模型,换了模型的分数会被当成退化;少了提示词版本,改一句话导致的下降查不到任何对应的提交;少了任务集版本,加了两条难题会被当成 Agent 变差;少了试次数,k 从 5 改成 3 会让分数的噪声结构完全不同却看不出来。**四个问题的共同点是:分数变了,但没人能说出是谁动的。**
- 提示词版本要单独强调:它是一个字符串,改它没有代码评审的仪式感,很多团队还放在配置后台里随时可改。给它编个版本号成本几乎为零,收益是把一整类查不出原因的波动变成可查的。
- 随机种子是个有意思的例外:**要记,但不参与可比性判断**。它的用途是复现——门禁红了要能一字不差重跑那一次;但换种子跑出来的分数恰恰是估计噪声的样本,把它塞进指纹会让每次换种子都被判成不可比,等于把非确定性从评估里删掉了。
- 最后说指纹不一致时的正确行为:**拒绝比较,判拦截**,而不是「算个差值再标注一下模型变了」。后者会产出一个毫无意义却看起来很正常的结论,要求人明确表态重新记录基线,才是安全的。
Key points
- Two halves: environment fingerprint (model, prompt version, task-set version, framework version, trial count, tier) plus scores.
- Each field rules out one explanation; omitting any leaves you with a moved number and no suspect.
- Prompt version is the most commonly missed and the most damaging, because editing it bypasses code review.
- Record the random seed but keep it out of the fingerprint: it exists for reproduction, and including it makes seed changes incomparable.
- On a fingerprint mismatch, refuse to compare and block rather than computing a meaningless delta.
答题要点
- 快照分两半:环境指纹(模型、提示词版本、任务集版本、框架版本、试次数、档位)与分数。
- 每一项各排除一种解释,少记任何一项都会变成「分数变了但说不清是谁动的」。
- 提示词版本最容易漏也最容易出事,因为改它不需要代码评审。
- 随机种子要记但不进指纹:它用于复现,进指纹会让换种子被判成不可比。
- 指纹不一致时正确行为是拒绝比较并拦截,不是硬算一个差值。
Running the full evaluation on every commit is too expensive. How would you tier it, and on what basis?每次提交都跑全量评估太贵,你会怎么分层?依据是什么?
Common in ChinaCommon overseasIntermediate#ci#cost#strategyHow to reason about it · think before answering
- This probes cost awareness and how you express trade-offs. Answering 'sample a random subset each time' misses: random subsets cover different failure classes each run, so the gate becomes intermittently blind.
- The basis for tiering is coverage divided by cost, not importance. The smoke tier should cover the most failure classes with the fewest tasks, and the full tier carries the long tail. So the smoke tier is curated, not sampled.
- A workable three-tier split: smoke on every commit to catch major breakage in minutes; full on tags, pre-release, or a nightly schedule to catch small regressions and the long tail; expensive human review quarterly on a sample, to calibrate the automated graders themselves.
- State the cost of tiering, which is the discriminator here: fewer tasks means fewer trials, and fewer trials means wider noise. For the same agent, going from eight tasks to three can widen the noise band from twelve points to twenty. The smoke tier can therefore only catch large failures; small regressions are invisible to it, and a green smoke run does not mean no regression.
- That implies a practice: measure thresholds separately per tier. Sharing one threshold either makes the smoke tier alarm constantly or makes the full tier far too insensitive.
- Expected follow-up: how do you pick the smoke tasks? By failure-class coverage - one representative per known class, plus the task corresponding to the most recent production incident - and revisit the list periodically, because failure classes shift as the product changes.
分析过程 · 先想清楚再作答
- 这题考成本意识与取舍表达。回答「随机抽一部分任务跑」的方向就偏了——随机抽样每次覆盖的故障类别都不一样,门禁会变得时灵时不灵。
- 分层的依据不是「任务重不重要」,而是**覆盖面除以成本**:冒烟档要用最少的任务盖住最多的故障类别,全量档负责长尾。所以冒烟档是挑出来的,不是抽出来的。
- 一条可用的分层是三档:冒烟档每次提交跑,几分钟内拦住大事故;全量档在打 tag、发版前或每晚定时跑,抓小幅退化与长尾;昂贵的人工评审按季度抽样跑,用来校准自动评分器本身。
- 必须主动说出分层的**代价**,这是这题的区分点:任务变少意味着试次变少,试次变少意味着噪声变宽。同一个 Agent,任务从八条减到三条,噪声区间可能从十二个百分点涨到二十个。**所以冒烟档只能抓大事故,小幅退化它根本看不见**,冒烟档绿了不等于没有退化。
- 由此还能推出一条实践:两档的阈值要分别测,不能共用一套。共用一套的后果要么是冒烟档天天误报,要么是全量档过于迟钝。
- 可预期的追问是「怎么决定哪些任务进冒烟档」。答案是按故障类别覆盖去选:每一类已知的故障至少留一条代表,加上最近一次线上事故对应的那条;并且这份名单要定期复核,因为故障类别会随产品变化。
Key points
- Tier by coverage divided by cost; the smoke tier is curated, never randomly sampled.
- Three tiers: smoke per commit, full on release or nightly, human review quarterly on a sample.
- Fewer tasks means fewer trials and wider noise, so a green smoke run does not prove there is no regression.
- Measure thresholds per tier; a shared threshold either alarms constantly or goes blind.
- Select smoke tasks for failure-class coverage and include the task from the latest production incident.
答题要点
- 分层依据是覆盖面除以成本,冒烟档要挑选而不是随机抽样。
- 三档:冒烟档每次提交、全量档发版或每晚、人工评审按季度抽样。
- 任务少则试次少、噪声更宽,冒烟档只能抓大事故,绿了不等于没退化。
- 两档的阈值必须分别测量,共用一套要么天天误报要么过于迟钝。
- 冒烟档按故障类别覆盖来选,并把最近一次线上事故对应的任务放进去。
Your evaluation gate keeps firing, and the team has learned to skip it. How would you change the process?评估门禁红了,团队却越来越习惯直接跳过。你会怎么改这条流程?
Common in ChinaCommon overseasDeep dive#ci#process#cultureHow to reason about it · think before answering
- This is barely a technical question; it asks whether you have actually maintained such a gate. Answering 'communicate more' or 'mandate investigation before merge' earns nothing - that asks people to fight the incentives instead of changing them.
- First, admit that skipping is rational. If eight of ten red runs go green on a rerun, skipping is the probabilistically correct move. So measure the false-alarm rate; it is usually the root cause, and it is a technical problem, not an attitude problem.
- Second, lower the cost of fixing. A red gate must not emit only a number: give the seed that reproduces the run, which tasks dropped, and which failing transcripts to read first. Cutting investigation from thirty minutes to five removes most of the incentive to skip.
- Third, raise the cost of skipping while keeping a legitimate exit. Intentional behavior changes really do need the bar moved, and the correct expression is 'update the baseline and say why', not 'ignore this run'. Keep the baseline file in version control so every loosening shows up in a diff and gets reviewed. Never ship a one-click skip button - one that exists eventually becomes the default path.
- Fourth, fix the verdict logic itself: three states with a gray-band rerun stop the genuinely ambiguous run from being blocked outright. That removes most false alarms without giving up sensitivity to real regressions.
- Close with the counterintuitive one: if false alarms truly cannot be brought down, demote the block to a warning rather than keep a blocking gate everyone routes around. A bypassed gate supplies false confidence, which is worse than a gate that honestly says it only warns.
分析过程 · 先想清楚再作答
- 这题几乎不考技术,考的是有没有真的维护过这类闸门。答「加强宣导」「规定必须查清楚才能合并」的拿不到分——那是在要求人对抗激励,而不是改激励。
- 第一步要承认一件事:**跳过是理性行为**。如果门禁十次红里有八次重跑就绿,那么跳过在概率上是对的。所以要先量一下误报率——它多半就是根因,而且它是一个技术问题,不是态度问题。
- 第二步是**降低修的成本**。门禁红的时候不能只丢一个数字,要直接给出:用哪个种子能复现、哪几条任务掉了、先读哪几条失败试次的轨迹。排查成本从半小时降到五分钟,跳过的诱因就少了一大半。
- 第三步是**提高跳过的成本,但保留合法出口**。有意的行为变更确实需要放宽标准,它的正确表达是「更新基线并说明原因」,而不是「本次忽略」。把基线文件放进版本库,放宽就会出现在 diff 里、需要有人评审。**绝不提供一键跳过按钮**——存在的跳过按钮最终一定会变成默认路径。
- 第四步是把判定本身修对:引入三态与灰区复跑,让真正模棱两可的那一次不再强行拦截。这直接砍掉大部分误报,而且不牺牲对真实退化的灵敏度。
- 最后要说一条反直觉的:如果误报确实压不下来,**宁可先把拦截线放宽成警告**,也不要留着一条大家都在绕过的拦截。一条被绕过的门禁提供的是虚假的安全感,比一条明确说「我只警告」的门禁更危险。
Key points
- Accept that skipping is rational and measure the false-alarm rate; it is usually the root cause and it is technical.
- Lower the cost of fixing: emit the reproducing seed, the tasks that dropped, and which failing transcripts to read.
- Raise the cost of skipping but keep a legitimate exit: baseline updates go through version control and review, never a one-click skip.
- Adopt three states with a gray-band rerun to remove ambiguous false alarms without losing sensitivity.
- If false alarms persist, demote blocking to warning rather than keep a gate everyone bypasses.
答题要点
- 先承认跳过是理性行为,去量误报率——它通常就是根因,且是技术问题。
- 降低修的成本:红的时候给出复现种子、掉分的任务、该读哪几条失败轨迹。
- 提高跳过的成本但保留合法出口:更新基线要进版本库、要被评审,绝不做一键跳过。
- 引入三态与灰区复跑,砍掉模棱两可那部分误报而不牺牲灵敏度。
- 压不下误报时宁可把拦截降级成警告,也不要留一条大家都在绕过的门禁。