A first-principles guide and worked engineering build
Engineering a CI-Triage Decision System
A red build at 02:47 looks like a classification problem. It is really a decision problem with asymmetric costs, unreliable evidence, correlated observers, and a human who still owns the release.
What this article builds. We start from an empty system and reconstruct the path from raw CI artifacts to a human-facing verdict. Along the way, we define labels, reject a convenient dataset on licensing grounds, catch leakage, build a run-level trust gate, compare three observers, design a typed evidence contract, test five fusion strategies, and stop a distillation run before spending money on a GPU.
This is also a record of how the system was built with AI assistance. The model wrote and inspected code, ran experiments, and proposed decisions. The human fixed the boundary, chose the costs, rejected flattering interpretations, and stopped work when the evidence did not support the next step.
Start here / No CI or machine-learning background required
What did the red light actually tell us?
A developer changes code and pushes a commit. Continuous integration (CI) runs a repeatable recipe: fetch the code, install dependencies, compile it, run tests, and save reports. A test checks an expected behavior. A build is one execution of that recipe. Red means some check failed. It does not identify the cause.
Suppose CheckoutTest.appliesDiscount expected ₹900 but observed ₹1,000. Perhaps the discount code broke. Perhaps two tests share a database and one erased a fixture. Perhaps the database never started. These explanations require different repairs even though the dashboard displays the same red icon.
Triage means deciding what deserves attention and what evidence to collect next. A flaky test produces different outcomes across reruns of the same code under the studied conditions. That behavior may arise from ordering, timing, randomness, shared state, or environment instability. Calling it flaky does not mean the underlying bug is harmless.
“Has this test shown nondeterminism?” describes a test’s history. “Did today’s failure reveal a new defect?” describes an incident. A historically flaky test can still catch a real regression today. The Week 4 labels primarily support the first question. Using them to answer the second requires additional incident evidence and validation.
Three objects you must keep separate
| Object | Example | What it establishes |
|---|---|---|
| Observation | 1 failure in 20 recorded runs | What happened under those conditions |
| Estimate | A model emits a flakiness score of 0.8 | A learned association; probability meaning needs calibration |
| Action | Investigate, rerun, or hold a release | A policy choice involving costs, severity, and ownership |
We will write x for evidence available at decision time, y for a later label, and p = P(y = 1 | x) for a conditional probability. Here y = 1 means flaky. The vertical bar means “given.” A probability is conditional on what the system observed; it cannot certify what the system never measured.
The incident
The clock is part of the specification
It is 02:47. The release build is red and the release is due at 09:00. One failed test can mean a real defect, a flaky test, or a broken machine. Each interpretation leads to a different action.
One failure, four actions
The on-call engineer can stop the release, isolate the test, rerun the job, or investigate manually. A model cannot choose among those actions responsibly until the team writes down what its mistakes cost. That is why this build begins with the operational decision rather than a model.
The system estimates. The engineer acts.
The thinking loop used in every phase
A new component should not appear because an algorithm sounds suitable. It should appear because the current system cannot answer a concrete question. We will call these “observer questions”: the specific uncertainty a component is meant to reduce. Here is the seven-step loop used throughout.
Build notebook · Use this template on any system
The rest of this article follows that loop. Expand a build notebook to inspect the project reasoning. File chips identify local artifact paths; they are provenance references, not public download links. The main explanation stands on its own.
Part I / Decision before prediction
Specify what the system may say
The first three phases lock down scope, loss, and measurement. This prevents a later model from redefining success around whichever number looks best.
Draw the boundary
Build notebook · Reasoning path
What is the smallest useful thing the system can do without taking operational authority away from the on-call engineer?
Automatically rerun the job. Automatically quarantine the test. Stop the release. Or estimate the probability that the failure is flaky.
The first three mutate the world. The probability is information the engineer can combine with incident context.
P(flaky | current report, test history). The
human chooses the action.
Before choosing a model, ask: what decision is a person trying to make, and which part of that decision can the system safely inform?
The system reads the current CI report and a test's run history. It emits a probability that the failure is flaky. It may refuse when evidence is thin. It does not merge, quarantine, delete, or stop anything.
Inside the system
Read evidence, estimate probability, declare uncertainty, preserve provenance.
Outside the system
Choose the operational action and accept its consequence.
This boundary matters because a probability and an action have different owners. The model can be technically correct about uncertainty while the release policy still chooses to stop. Conversely, the model can be confident and the engineer can override it because the incident carries context the model does not have.
Why the four verdicts need more than a binary classifier
1 − P(flaky) is a probability of “not flaky” under the model’s label definition. It is not automatically P(real defect). Non-flaky cases can include infrastructure faults, deterministic test defects, and incomplete evidence. ABSTAIN is a decision to withhold a verdict, not a fourth physical cause. UNKNOWN at ingestion and a model timeout are different again.
The lab combines a run-level gate, binary observers, and a human boundary. It has not trained a validated four-cause incident classifier. That gap matters: never let the product UI give a binary model a broader meaning than its training labels support.
Price the mistakes
Build notebook · Reasoning path
Draw a truth-by-verdict table. For every wrong cell, write the physical consequence first: “defect ships,” “release waits,” “engineer reruns,” or “test is quarantined.” Add a rough cost only after the consequence is clear.
ABSTAIN.
Imagine 100 uncertain builds. Stopping all 100 may avoid one $5,000 defect, but 99 unnecessary delays at roughly $500 already cost about $49,500. Conservative does not automatically mean cheap.
The external contract contains four verdicts:
REAL_DEFECT, FLAKY, INFRA,
and ABSTAIN. The fourth exists because a first-time
failure with no logs and no history does not fit safely into the
first three.
| Truth | Wrong verdict | Working cost |
|---|---|---|
| Real defect | FLAKY or INFRA | About $5,000 |
| Flaky test | REAL_DEFECT or INFRA | About $500 |
| Infra failure | FLAKY | Ongoing, not bounded |
| Any cause | ABSTAIN | About $750 |
These are estimates, not audited finance figures. Writing them down still improves the system because the team can challenge a visible assumption. An unwritten cost is harder to inspect and easier for an accuracy metric to erase.
ABSTAIN is cheap relative to shipping a defect, but
it is not free. If every case is escalated, model risk falls while
the system stops doing useful work. Coverage therefore has to sit
beside risk.
Derive the decision threshold instead of inheriting 0.5
For this derivation only, assume two mutually exclusive incident states: flaky and real defect. Assume correct decisions have zero incremental cost, missed defects cost Cmiss, and unnecessary holds cost Chold. This deliberately simplified model is not the full four-verdict system. It also assumes p estimates the incident state, a stronger claim than the project’s historical flakiness label supports.
Risk(hold as defect | x) = p × Chold
Dismiss only when (1 − p)Cmiss < pChold
Therefore p > Cmiss / (Cmiss + Chold)
With $5,000 and $500, the threshold is 0.9091. At p = 0.8, dismissal has expected loss $1,000 and a hold has expected loss $400. The more likely class is flaky, yet a hold has lower expected loss. The costs explain why. On an equality, keep the more cautious action in this illustration. Threshold selection is separate from fitting a score; the scikit-learn threshold guide makes the same distinction.
Interactive 01 · Cost and action · illustration
A probability does not contain your policy
Review is modeled as a fixed fee with perfect resolution and no additional delay. At the default $750 it is never optimal: the best binary action costs at most $454.55. Lower review cost to $200 to create a review region. Real review can make mistakes and consume queue time.
The general rule is a*(x) = argmina Σy P(y | x)L(a, y): for each allowed action, add its losses across possible states, weighted by their probabilities, and choose the smallest. This supports three causes and a review action once the probabilities and loss table exist. It does not invent the missing probabilities for you.
Why abstention can be rational even when the toy cost says otherwise
The arithmetic above assumes reliable probabilities and a correct loss model. Missing logs, a new project, or an unvalidated score can violate those assumptions. Abstention can then be a policy constraint: “we do not issue a verdict outside the validated population.” If review is imperfect, its loss is its fee plus expected downstream mistakes and delay. Budget that honestly. A single $750 cell is not a full model of the review process.
Build one evaluator
Build notebook · Reasoning path
Accuracy asks whether answers are right overall. Recall and precision expose the rare class. AUC checks ranking. Calibration checks whether a stated 0.8 behaves like 0.8. Cost-weighted risk maps errors to consequences. Risk-coverage checks whether low risk was purchased by abstaining on everything.
evaluate(probs, labels) call. A later observer
cannot report accuracy without the rest of the evidence
appearing beside it.
If 3 of 100 tests are flaky, a model that says “not flaky” 100 times is 97 percent accurate. It catches zero flaky tests. Accuracy answered its question correctly; we asked an incomplete question.
The evaluator accepts probabilities and labels. It does not know whether the probabilities came from a tree, a GRU, retrieval, or an LLM. Every observer passes through the same function.
# Condensed artifact interface; imports and helper bodies omitted.
# Stored cost weighting is historical, as explained above.
def evaluate(probs, labels, threshold=0.5):
probs, labels = _check(probs, labels)
preds = (probs >= threshold).astype(int)
return {
"accuracy": accuracy_score(labels, preds),
"precision": precision_score(labels, preds, zero_division=0),
"recall": recall_score(labels, preds, zero_division=0),
"roc_auc": roc_auc_score(labels, probs) if len(set(labels)) > 1 else None,
"brier": brier_score_loss(labels, probs),
"ece_equal_width": _ece(probs, labels, "equal_width"),
"ece_equal_freq": _ece(probs, labels, "equal_freq"),
"cost_weighted_risk": _cost_weighted_risk(preds, labels),
"risk_coverage": _risk_coverage(probs, labels, threshold),
}
The first measurement experiment used an always-not-flaky predictor against a roughly 3 percent positive rate. It achieved more than 95 percent accuracy and exactly zero recall. A second experiment tried to expose a calibration binning trap, failed to construct it, then corrected the probability distribution and confirmed it. This pattern becomes the unit of work for the rest of the build: state the expected behavior, construct a case, run it, and keep falsification visible.
Read a score by reconstructing its denominator
With 1 = flaky, precision is TP/(TP+FP): among flagged tests, how many carry a flaky label? Recall is TP/(TP+FN): among flaky-labeled tests, how many did we flag? ROC AUC measures ranking across positive-negative pairs. None directly prices a release mistake. The operational two-state loss would be (5,000 FP + 500 FN)/N, the reverse of the stored lab weighting.
Calibration asks whether cases assigned a probability near 0.8 are positive about 80% of the time. Fit the model on training data, fit calibration using independent held-out predictions, choose the decision policy on validation data, and inspect the final frozen policy on an untouched test set. For small data, use a properly nested cross-validation design. The calibration documentation describes both calibration curves and the need to separate calibration data from model fitting.
Interactive 02 · Base-rate shift · illustration
The same detector can produce a different work queue
The bar represents flagged cases, not all 10,000 cases. Rates are fixed assumptions to isolate prevalence. In practice these rates can shift too.
Coverage has two denominators
Execution coverage is completed requests divided by submitted requests. Decision coverage is issued verdicts divided by submitted requests. A completed request may abstain. Report both, plus the conditional error rate on issued verdicts and the total cost across all submitted requests.
A risk-coverage curve that drops refused cases from its denominator describes the accepted subset. It is useful, but it does not show total service cost. Nor is distance from 0.5 necessarily decision confidence when the action threshold is 0.9091. Sort by a validated risk or decision-margin estimate appropriate to the policy.
Small samples and calibration traps
Expected calibration error (ECE) partitions scores into bins and averages the gap between mean score and observed positive rate. Different bin edges can change the answer; sparse bins have uncertain rates. Brier loss averages (p − y)² and reflects more than calibration alone. Report a reliability diagram, bin counts, and class-specific behavior alongside aggregates. A single-class test set has no defined ROC AUC.
On 12 examples, one prediction changes accuracy by 8.33 percentage points. A tie at 8/12 justifies a precommitted spending stop; it does not prove that no larger model could learn. For uncertainty estimates, resample the independent unit—often test identity or project—not repeated rows that share a test. A confidence interval over one project cannot establish transfer to other projects.
Part II / Evidence before models
Decide what can be trusted
Most of the engineering work happens before model fitting. The project has to define a label, prove it can use the data, prevent joins from changing the population, remove leakage, and reject corrupted CI runs.
A label with provenance
Build notebook · Reasoning path
One failure does not prove flakiness. One pass does not prove stability. A pass and a failure across reruns of the same code is the smallest observable flip.
test_id, label, and
num_reruns together. Refuse a missing rerun
count. A label without sample size hides how hard the system
looked.
Test A runs ten times and produces P P P F P....
The flip demonstrates differing outcomes under the recorded rerun conditions.
Test B runs ten times and produces ten passes. We know what
happened ten times; we do not know what would happen on run
eleven or run ten thousand.
A test is labeled flaky when identical code produces both a pass and a failure across reruns:
The rule matched all 26,765 rows in test_results.csv.
Yet the two classes do not have equal certainty. An observed flip establishes nondeterminism under the recorded conditions; infrastructure and uncontrolled environment changes still need investigation. A test that never flipped only proves that
no flip was observed within its rerun budget.
This resembles a positive-unlabeled problem: some apparent negatives may be undiscovered positives. If observed positives are correct and errors only hide positives among negatives, measured precision on the same cases is a lower bound on true precision. That is a conditional statement. Infrastructure contamination can violate the first assumption, and selective rerunning can make the sample unrepresentative. Record rerun counts; do not turn this observation into an unconditional production guarantee.
How much can ten clean reruns tell you?
Assume each rerun independently fails with constant probability q. The chance of seeing no failures in n reruns is (1 − q)n. For a 1% failure probability and ten reruns, that is about 90.4%. A flaky test can look completely stable in a short observation window.
Interactive 03 · Observability · illustration
A label depends on how hard you looked
For fresh reruns, P(observed flip) = 1 − (1 − q)ⁿ − qⁿ. If a failure is already established and all other assumptions hold, one passing rerun is enough to establish a flip; the observation scheme is different. This slider uses fresh reruns.
The independence assumption is strong. A shared machine outage can correlate outcomes; test order can change the per-run failure probability. Record commit, environment, seed, order, and run identity where possible. “Same code” controls only one source of variation.
There is also selection bias: engineers may rerun suspicious tests more often. Then num_reruns partly measures human attention. A model can learn the investigation policy rather than the behavior of the software. Use rerun budget as label provenance; admitting it as a predictive feature requires proving it exists at the intended decision time and does not encode the answer.
Licence, join shape, and leakage
Build notebook · Reasoning path
May we legally reuse this dataset? What key represents the same test in both files? Can rows disappear or multiply? Can a feature reveal the label directly or through missingness?
Project spellings differ. Class and method separators differ. An inner join may lose projects. A many-to-many key may duplicate rows. History-derived features may be impossible to compute at live prediction time.
When two datasets must be joined, write the ways a row can disappear, duplicate, mismatch, or leak the answer before writing the join.
The most convenient dataset, IDoFT, had no detected license in the checked repository. Public access did not grant reuse rights. The build chose the FlakeFlagger dataset from Zenodo under CC BY 4.0 and kept attribution as part of the design.
The join looked simple until it returned zero rows. One file used
names such as apache-commons-exec; the other used
commons-exec. Normalizing test identity and matching
the project suffix repaired the join. The finished table contained
26,142 rows across 25 projects with a positive rate of 0.0316.
key = normalized_project + "::" + normalized_class + "::" + normalized_method
assert joined_rows are within the expected source bounds
assert no feature is near-perfectly correlated with label
assert no feature's missingness reveals label
The last check found a real leak:
flaky_source revealed the answer through missingness.
It was non-null exactly where the label was positive. The team
also removed history-derived modification windows that could not
be recreated at prediction time from the available snapshots.
Freeze the world at the moment of prediction
Imagine the failing build arrives Tuesday at 02:47. Wednesday’s fix commit, Friday’s incident label, and a retrieval neighbor added next month are unavailable on Tuesday. A retrospective join can silently import all three. Every feature needs an event timestamp, an availability timestamp, and a rule that says why it was knowable at inference.
Store later outcomes in the label table. A test’s identity is not enough to guarantee a safe join: a feature with the right ID but the wrong time can still leak. Normalize identifiers, assert key uniqueness before joining, report unmatched rows by project and class, and inspect any suffix matching for collisions. Expected row counts alone cannot detect a wrong one-to-one match.
Gate poisoned runs before learning
Build notebook · Reasoning path
Read one maven.log. Emit a verdict and a reason
containing measured counts. Refuse with
UNKNOWN when the file is missing, unparseable,
or shows a class that started without finishing.
If a JVM crash causes 40 tests to fail together, counting those 40 as independent flaky-test observations poisons labels, retrieval neighbors, and sequence histories. The trust gate removes the run once, upstream of every observer.
A failed build can make many tests look broken even when they
never ran. The trust gate reads maven.log and emits
one verdict with a numerical reason. Precedence is fixed:
BUILD_FAILED, LOG_TRUNCATED,
MASS_FAILURE, TRUSTED, then
UNKNOWN.
The 5 percent threshold came from a real run with 13 failures out of 163 tests, about 8 percent, sharing SSL and query-parameter failure shapes. A proposed 15 percent threshold would have admitted that run, so it was lowered.
The gate later exposed a deeper lesson. The first parser read only
the final Maven summary in a multi-module build. Another parser
missed inline <<< ERROR! failure lines.
These bugs silently removed failures from square-okhttp and
distorted the sequence, retrieval, fusion, and distillation phases
downstream.
run_id. The repository
records this as a missing join, not as completed architecture.
A gate changes the population you claim to understand
Let G = 1 mean the gate admitted a run. Downstream metrics then describe P(error | G = 1). They do not describe all CI traffic. A strict gate can improve that score while refusing most difficult incidents. Report admission rates by project and failure type, review samples of rejected runs, and retain rejection reasons.
The 5% mass-failure rule is a heuristic motivated by an observed case. Many failures can also arise from a legitimate shared-code regression. Treat the gate’s diagnosis as evidence requiring inspection, not proof of infrastructure causation. A parser correction must invalidate affected histories, retrieval indexes, and evaluations together.
Make the split answer a deployment question
Build notebook · Reasoning path
Random folds answer “can the model generalize to held-out tests when project-specific patterns appear on both sides?” Grouped folds answer “can it transfer to a project absent from training?”
Training on chapters 1 to 9 of one textbook and testing on chapter 10 is different from training on one textbook and testing on another author's book. Both are “held out,” but they measure different kinds of transfer.
A random split mixed tests from the same project across train and test. A grouped split held out whole projects. The model, rows, and features stayed fixed; only the question changed.
The 0.206 gap is consistent with the model relying on project-specific patterns; it does not by itself isolate identity leakage from other distribution shifts. The grouped result also varied sharply depending on which project was held out. A single average would have hidden that instability.
At this point the team faced a deployment decision. Should one global model work cold on a new codebase, or should each team train a local instance on its own history? The first requires grouped evaluation. The second permits within-project evaluation but creates cold-start and model-maintenance costs. The system eventually chose the second shape and kept both results visible.
Interactive 04 · The deployment question
Change the split, change the claim
Blue = train; burgundy = test, with text labels on every cell. Assignments are schematic, not the lab’s actual folds.
For a team’s future incidents, use a chronological boundary and build every historical aggregate and retrieval index as of that boundary. For unseen tests within a familiar project, separate test identities. For a new customer repository, hold out projects. These are different targets; choose the one implied by deployment and use additional splits as stress tests.
A strong evaluation records the target population, independent unit, time cutoff, allowed inputs, baseline, metric, and decision threshold before fitting. Keep feature selection, calibration, threshold tuning, and learned fusion inside the training/validation process. A final test set stops being a final test set once it repeatedly chooses the next design.
Part III / Three observers
Let different representations inspect the same case
The system tries numeric features, ordered run history, and failure-text retrieval. Each observer answers a different question. Their failures are as useful as their scores.
Observer 1: tabular features and the pivot
Build notebook · How we arrive at a tabular observer
Logistic regression gives a linear baseline. One decision tree is readable but unstable. Random forests average many trees. Gradient boosting adds trees sequentially, with each new tree correcting residual errors from the existing ensemble.
The lab chose histogram gradient boosting as one reasonable experiment. The architecture does not depend on it being the only acceptable algorithm.
Imagine a row such as test_length=42,
third_party_libraries=3,
resource_optimism=0.18, plus thirteen other
measurements. The tree learns partitions of this feature space.
It does not know whether yesterday's run passed, what exception
appeared, or which old failure looks similar. Those missing
views motivate later observers.
HistGradientBoostingClassifier reads 16 leak-checked
numeric features. It handles missing values internally. The
repository does not implement the earlier idea of per-row
abstention for missing features, and the design document was
corrected to say so.
Sigmoid calibration improved ECE from 0.0312 to 0.0225 but
slightly reduced AUC. The original hypothesis said ranking would
remain identical. It did not, because
CalibratedClassifierCV(cv=3) trained three smaller
base models and averaged their outputs. It did more than apply one
monotone transform to a fixed score vector.
The 0.88 figure is not an improved global model. It comes from random folds within each project, evaluated on unseen tests from a known project. Nineteen projects had enough positives; six were skipped. This pivot trades cold-start generalization for local history.
What the numeric observer actually learns
A tabular row turns a test into numbers such as execution duration or coverage statistics. A decision tree partitions that feature space with questions of the form “is duration greater than this value?” Gradient boosting adds trees to improve the current predictions. It can discover interactions without you enumerating every combination.
That strength can also hide shortcuts. “Long runtime” may identify one repository’s tooling rather than a general cause of flakiness. Feature importance describes how this fitted model uses its inputs; it does not establish that changing a feature would change flakiness. Compare against a simple baseline, inspect errors by project, and remove suspicious features in an ablation before telling a causal story.
Observer 2: sequence, reformulation, discard
Build notebook · How we arrive at a sequence observer
Take one test's ordered history. Show the model a prefix
such as P P P F P P. Hide the later suffix.
Label the example positive if the suffix contains a
pass-to-fail or fail-to-pass transition.
P P P F F F and P F P F P F contain
three passes and three failures. A tabular count sees the same
row. A sequence representation can see one late cluster and one
alternating pattern. The experiment asks whether that extra
structure predicts the withheld future.
Choose a sequence model only after you can name what order contains that an aggregate destroys.
A naive sequence model would read the same pass/fail history used
to define IsFlaky. It could learn the label rule
directly: spot at least one pass and one failure. The build
avoided that circularity by asking the prefix to predict whether
the future suffix would flip.
Only one original project had enough trusted, nameable failures to build positive sequence examples. The small GRU achieved raw AUCs around 0.43 to 0.54. A free control, failure count in the prefix, scored roughly 0.52 to 0.58 and beat the GRU in nearly every fold.
The keep rule had been written before training: the GRU had to beat the control by at least 0.05 without collapsed calibrated probabilities. It failed. The team discarded it and retained the heuristic. More epochs or a larger hidden state were rejected because 23 distinct tests did not contain enough diversity for capacity tuning to repair.
What would make order worth modeling?
Consider P P P F F F and F P F P F P. Both fail three times in six runs. The first might suggest a change point; the second might suggest alternation. A count-based feature cannot distinguish them. A recurrent model maintains a hidden state: hₜ = update(hₜ₋₁, outcomeₜ), then predicts from that state. A GRU uses learned gates to control what it retains and replaces.
The model only earns its complexity if those ordering patterns predict a held-out future target better than counts do. Keep the prefix and future suffix separate; never derive an input statistic from the suffix you are trying to predict. Randomize order as a diagnostic: if performance is unchanged, the learned system may not need sequence structure. Discarding this observer after it lost to its simple control is a valid engineering result.
Observer 3: retrieval and the parser correction
Build notebook · How we arrive at a retrieval observer
Convert every historical failure message into an embedding. For a new message, retrieve the nearest messages, remove duplicate test identities, inspect their labels, and use the flaky fraction as a probability.
The query can retrieve itself. Ten runs of one test can become ten fake votes. A one-class index can report perfect-looking precision. Weak neighbors can manufacture confidence. Passing tests have no real Maven failure text, which can force a synthetic-text shortcut.
Suppose the five nearest distinct tests have labels
FLAKY, FLAKY, NOT, FLAKY, NOT. Retrieval emits
3/5 = 0.60 and the neighbor identities. If all five
rows came from repeated runs of one test, they count as one
opinion, not five.
Retrieval embeds a failure message, finds nearby historical cases, deduplicates neighbors by test identity, and returns the fraction labeled flaky. It refuses to build a single-class index and abstains when it cannot find enough distinct tests.
query = embed(failure_text)
neighbors = nearest(index, query)
neighbors = dedupe_by_test_identity(neighbors)
if len(neighbors) < min_distinct:
return ABSTAIN
p_flaky = mean(neighbor.label for neighbor in neighbors[:k])
The first corpus used real exception text for failures and synthetic “test passed” sentences for the other class. That created a possible style shortcut. The corrected square-okhttp corpus used real trusted exception text for both classes: 102 cross-run deterministic tests and 76 tests labeled flaky by the ground-truth table.
The pre-registered prediction expected performance to fall when the style cue disappeared. It did not. That prediction was falsified. The result is still within-project. A separate historical 17-project reference scored 0.474 against a 0.715 majority baseline, so the article cannot claim cross-project retrieval works.
Similarity is a representation choice, not a causal explanation
An embedding maps text to a numeric vector. Cosine similarity compares vector direction. Nearest-neighbor search retrieves cases close under that representation; it does not know whether the exception actually has the same cause. Library names, boilerplate, and duplicated stack frames can dominate similarity.
The vote 3/5 = 0.6 is a local class fraction, not automatically a calibrated incident probability. Distinct test identities prevent literal duplicate votes, but five tests from one shared helper can still carry one correlated failure. Inspect neighbor distance, shared code, label provenance, and age. Minimum neighbor count alone does not ensure relevant neighbors.
precision_at_k computes the mean fraction of neighbors whose label matches each query’s label. It includes negative queries. That is neighborhood label agreement, not positive-class precision of the final detector, not 98.31% incident accuracy, and not probability calibration. The name comes from the artifact; the interpretation must come from the code. The evaluator also uses a different minimum-neighbor check from the voting helper, an edge case to reconcile before reuse.Part IV / Fusion in detail
Combine evidence without counting the same mistake twice
Fusion is not a vote over three anonymous numbers. The system needs to know where each probability came from, whether it is calibrated, whether evidence exists, and which observers share upstream dependencies.
Make every observer sign the same contract
Build notebook · Reasoning path
Who produced it? Which case? What probability? Is it calibrated? Did the observer have evidence? What source did it read? What did it cost? How long did it take?
calibrated or has_evidence is
optional prose, a later strategy can forget it. A frozen
dataclass makes omission a construction error and prevents
mutation after the observer signs the record.
has_evidence is separate
A placeholder 0.5 can mean “the model genuinely
estimates fifty-fifty” or “the observer could not run.” Those
states must fuse differently. The record carries both the
numeric slot and the evidence flag.
@dataclass(frozen=True)
class EvidenceRecord:
observer: str
case_id: str
probability: float
calibrated: bool
has_evidence: bool
read: str # the checked-in contract uses a source-description string
cost_dollars: float
latency_ms: float
The constructor rejects probabilities outside [0,1],
negative cost, negative latency, and an empty
read declaration. Absence of evidence is carried
explicitly rather than hidden as a neutral-looking probability.
All downstream strategies receive exactly three records, one per
observer.
A richer LLM prompt might include the raw log, test name, or retrieval neighbors. This experiment deliberately withheld them. Otherwise a better LLM result could come from having more information rather than from better fusion.
How one fusion case is assembled
build_fusion_cases() begins with the Phase 4 feature
table and its test identifiers. It holds out square-okhttp for
tabular scoring, loads deterministic-test exclusions and cached
trusted runs once, and builds the corrected real-text retrieval
corpus from that same cache. It then joins the tabular and
retrieval populations by test identity.
The label join is defensive. If the Phase 4 label and corrected retrieval label disagree for the same test, construction raises instead of choosing one silently. Only shared, aligned tests enter evaluation. The tabular model is fit on every other project. Sequence evidence uses only the first 30 trusted outcomes. Retrieval leaves the query test out of its own index before finding neighbors.
shared_ids = align_or_raise(phase04_labels, retrieval_labels)
tabular.fit(other_projects)
for test_id in shared_ids:
tabular_p = tabular.predict(test_features)
sequence_p = mean(first_30_trusted_outcomes)
retrieval_p = vote(index_without(test_id), k=5)
emit(three_evidence_records)
The embedding model is loaded once. Corpus text and all query text are embedded in batches. The earlier implementation loaded the sentence-transformer inside the per-case path, which caused a 120-second smoke run to time out. Batching fixed the execution path without changing the experiment population.
When sequence or retrieval has no evidence, its record carries
has_evidence=False and a placeholder probability of
0.5. Every strategy filters on has_evidence. This
detail prevents the placeholder from becoming a real vote.
Model shared dependencies, not just agreement
Build notebook · Reasoning path
Tabular reads the Phase 4 aggregate feature table. Sequence and retrieval both read raw archives after the Phase 5 parser and trust gate. They use different transformations but share an upstream failure domain.
Count independent failure domains, not the number of boxes in the diagram.
The tabular observer reads the aggregate feature CSV. Sequence and retrieval both depend on raw archives and the trust gate. They may use different representations, but a parser bug can move both together. Two agreeing numbers can therefore be one upstream mistake repeated twice.
INDEPENDENT_GROUPS = [
{"tabular"},
{"sequence", "retrieval"},
]
An override requires agreement across at least two groups. This is the same principle used in reliable services: replication does not protect against a shared dependency failure.
probability does not make them comparable. A production contract must name the target event and horizon, then validate any mapping onto a common target before fusion. A calibration flag alone cannot repair a target mismatch.Why counting votes can count a bug three times
Suppose two sensors each have a 10% error rate. If their errors were independent, both would be wrong on 1% of cases. If a shared parser makes them fail together, their joint error could be 10%. Knowing their individual error rates does not tell you which world you are in.
Interactive 05 · Failure propagation · illustration
Break one upstream dependency
Dependency groups are a practical warning system, not a mathematical proof of independent errors. Measure paired mistakes on the same held-out cases. If you learn a combiner, train it on out-of-fold observer predictions rather than their fitted training predictions. Compare against the best individual observer, remove each observer in turn, and keep the population fixed across comparisons.
Five strategies, one input
Build notebook · How we arrive at fusion
Trust the most confident observer. Average available probabilities. Use fixed threshold rules. Use cheap rules on agreement and escalate disagreement. Or ask an LLM to arbitrate every case.
EvidenceRecord objects. The LLM does not receive
test name, log text, neighbor text, or ground truth. Otherwise
“better fusion” and “more input” become indistinguishable.
Compare 0.49 vs 0.59 with
0.89 vs 0.99. Both gaps are 0.10. At the lab’s 0.5 threshold, the first pair straddles the boundary and the second does not. At the illustrative cost-derived threshold of 0.9091, the second pair straddles it instead. You must specify the action threshold before interpreting disagreement.
| Strategy | Mechanism | Main risk |
|---|---|---|
| Most confident | Choose probability farthest from 0.5 | Rewards overconfidence |
| Mean | Average evidenced records only | Treats correlated evidence equally |
| Threshold rules | Apply deterministic agreement and abstention rules | Rigid at boundary cases |
| Escalation LLM | Use rules normally, call LLM on disagreement | Partial coverage, cost, latency |
| LLM everything | Ask the model to fuse every case | Same problems on every request |
Absolute probability distance is the lab’s disagreement heuristic. It measures numerical separation, not threshold crossing by itself; a decision-aware policy must also check which side of its actual cost-derived threshold each score falls on. The case 0.49 versus
0.59 matters operationally even if a logit-space
measure would describe 0.89 versus
0.99 differently.
The LLM sees only the records and must return JSON with a probability and one-sentence reason. Its probability is marked uncalibrated. A 30-second timeout is an execution error. It is not an abstention, because the model never completed a decision.
run_comparison originally counted LLM timeouts as
abstentions. That made broken execution look like cautious model
behavior. The code and test were corrected; the stored result
remains as-run so the reporting bug is not erased.
Why timeout handling reaches the process group
The LLM strategy invokes a command-line client as a subprocess. A normal child-process timeout can kill the direct child while one of its descendants still owns the standard-output pipe. The parent then waits for an end-of-file marker that never arrives. The implementation starts the client in a new session and kills the complete process group on timeout.
proc = subprocess.Popen(
["claude", "-p", prompt, "--output-format", "text"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
start_new_session=True,
)
try:
stdout, _ = proc.communicate(timeout=30)
except subprocess.TimeoutExpired:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
proc.communicate() # reap the process group
raise TimeoutError("arbiter timed out")
A dedicated test replaces the process with a hanging stub and
asserts that killpg receives the process-group
identifier and SIGKILL. Another test forces an
arbiter failure and checks that the report increments
n_errors while leaving n_abstained at
zero.
What the corrected fusion run can support
Build notebook · Experiment reading procedure
Before comparing scores, compare denominators, coverage, execution errors, cost, and latency.
The corrected square-okhttp export aligned 178 cases and passed the predeclared variation gate. A stratified sample of 20 cases, roughly balanced by ground truth, went through all five strategies.
| Strategy | Completed | Accuracy on completed subset | ECE |
|---|---|---|---|
| Most confident | 20 / 20 | 0.000 | 1.000 |
| Mean | 20 / 20 | 0.500 | 0.165 |
| Threshold | 20 / 20 | 0.500 | 0.500 |
| Escalation LLM | 8 / 20 | 0.625 | 0.150 |
| LLM everything | 10 / 20 | 0.500 | 0.220 |
The two LLM rows cannot be ranked against the complete local rows. Their scored subsets may be easier, harder, or simply different. Twelve escalation calls and ten all-LLM calls timed out. Mean latency for the partial LLM rows was measured in seconds, while local strategies were effectively immediate.
A historical precomputed run over 74 cases showed best-single, weighted, and stacking strategies at 0.905 accuracy. A naive LLM arbiter scored 0.432; a calibrated version improved to 0.784 but still trailed the local methods. That reference is useful context, but it is not the corrected own run.
The valid fusion conclusion is about architecture and failure handling. The corrected experiment does not establish a five-way winner.
Part V / Distillation in detail
Compress explanation behavior, not decision authority
The expensive model is asked to explain the already-fused evidence. It never becomes the only artifact shown to the operator, and a student is trained only if a cheap baseline first proves that the corpus contains learnable signal.
Separate the decision layer from the explanation layer
Build notebook · How the distillation question appears
Use the capable model as a teacher offline. It labels a bounded corpus with verdicts, confidence, and explanations. Train a smaller local student to imitate that explanation behavior. The decision still comes from fusion.
Raw: tabular=0.14,
sequence=no evidence, retrieval=0.92,
fusion=ABSTAIN. Explanation: “A similar historical
failure points toward flakiness, but the static observer
disagrees and no sequence history is available. The system is
escalating instead of treating one source as decisive.” The
human can compare that sentence with the records.
The teacher prompt receives real failure text and the three evidence records. Ground truth is withheld. The teacher emits a structured label and explanation. The operator-facing output pairs prose with the raw records so a fluent sentence cannot replace its evidence.
This division also limits what a failed student can damage. If the student collapses to one class, the decision layer still exists. Explanation quality can be evaluated, replaced, or removed without changing the triage verdict.
Separate label learning from explanation quality
The pilot asks the teacher to supply both a label and prose. The cheap TF-IDF gate measures label prediction. It does not measure whether an explanation is accurate, complete, useful, or faithful to the fixed fusion verdict. A future student would need a separate explanation evaluation; the current artifact has no such student result.
Start with a deterministic template: “Retrieval found three distinct historical tests; tabular evidence disagrees; the system abstained.” An LLM must improve usefulness beyond that baseline without adding unsupported causes. Require each factual clause to reference an evidence ID. Count unsupported claims, contradictions with the verdict, omitted disagreement, and valid refusals on an independently reviewed sample.
Logs and retrieved text are untrusted input. They may contain secrets or sentences that look like instructions. Redact sensitive material before model calls, delimit evidence, deny tool authority to the explanation component, validate structured output, and check evidence references. An instruction in a stack trace must never become an instruction to the triage service. JSON validity alone cannot establish factual grounding.
Do the memory arithmetic before renting a GPU
Build notebook · Reasoning path
3B × 2 bytes = 6 GB. Training also needs
gradients, optimizer state, activations, runtime buffers, and
temporary tensors.
LoRA reduces trainable gradients and optimizer state by freezing the base and training adapters. Quantization reduces the base-weight term. Checkpointing reduces saved activations by recomputing them. None is a generic “make training smaller” switch.
A 3B model may be called a “6 GB model” because its 16-bit weights occupy about 6 GB. Full Adam training can add roughly 6 GB of gradients and 24 GB of moment estimates before counting activations. Model storage and training memory are different budgets.
The project estimated a 3 billion parameter model in 16-bit precision. The point was not to predict one exact peak-memory number. It was to identify which technique attacks which term.
Full fine-tuning, before activations
| Weights | 3B × 2 bytes | about 6 GB |
| Gradients | 3B × 2 bytes | about 6 GB |
| Adam moments | 3B × 2 states × 4 bytes | about 24 GB |
| Activations | batch, length, layers, checkpointing | variable |
| Subtotal | before activations and runtime overhead | about 36 GB |
LoRA freezes the base model and trains small adapters. It removes base-model gradients and optimizer states from the training burden, but it does not make the frozen weights disappear. Quantization reduces the weight term by storing the base model at lower precision. Activation checkpointing reduces stored activations by recomputing them, trading compute for memory.
LoRA: W + Gadapter + Oadapter + A
Quantized LoRA: Q(W) + Gadapter + Oadapter + Acheckpointed
A rough 4-bit base is about 1.5 GB before quantization metadata and runtime buffers. The project estimated a QLoRA-style job might fit within a 24 GB GPU, but that estimate was never validated by an actual training run because the data gate failed first.
What the memory subtotal leaves out
The 36 GB subtotal assumes 16-bit weights and gradients plus two FP32 Adam moment buffers. Some mixed-precision implementations also maintain FP32 master weights, adding about 12 GB for 3B parameters. Optimizer choices, allocator behavior, attention implementation, sequence length, and temporary buffers change the peak. Decimal GB and binary GiB differ too. Measure peak allocated and reserved memory for the actual configuration.
LoRA represents an update to a weight matrix as a product of two smaller matrices: ΔW = BA, where a rank r much smaller than the matrix dimensions reduces trainable parameters from d × k to r(d + k). The frozen base still participates in the forward pass. The original LoRA paper describes this low-rank adaptation mechanism. Reduced training state does not automatically imply a faster or better model.
Measure teacher cost, then make the student earn a GPU
Build notebook · The complete spend and evidence gate
The rendered prompt was 1,146 characters and 141 words, estimated at roughly 190 to 290 tokens. Using model-list prices, the first paper estimate was about $0.0008 to $0.001 per call. Five hundred teacher examples looked like roughly fifty cents.
NOT_FLAKY, 7 ABSTAIN, and 0
FLAKY. A one-class teaching signal cannot train a
useful detector, so work stopped.
FLAKY plus 27
NOT_FLAKY teacher labels against a hidden 30/30
ground-truth sample.
The exact teacher message shape
You are labelling training data for a CI triage explanation model.
Given the real failure evidence and detector evidence below, write a grounded
2-4 sentence explanation for an on-call engineer. Decide whether the test is
FLAKY or NOT_FLAKY. Do not invent facts absent from the evidence.
State confidence between 0 and 1.
Test: {test_fqn}
Failure evidence:
{failure_text}
Detector evidence:
{evidence}
Respond with ONLY this JSON object:
{"explanation": "...", "verdict": "FLAKY" or "NOT_FLAKY",
"confidence": <float 0-1>}
The true label is used to balance and evaluate the pilot but is not inserted into this message. The teacher must reason from the same failure and detector evidence that would exist at inference time.
Observed batch cost = Σ provider-reported costi
500-call projection after smoke = 500 × $0.0174 ≈ $8.70
The initial estimate treated the prompt as the billed request and predicted roughly $0.001 per call. One smoke call cost $0.0174. The CLI carried system prompts, tool definitions, project context, cache operations, and thinking overhead that the rendered user prompt did not show.
| Step | Estimate or result | Decision |
|---|---|---|
| Paper estimate | about $0.001 per call | Plan 500 examples |
| Smoke call | $0.0174, about 17 times higher | Cut pilot to 60 |
| Historical 60-call pilot | $1.2976, zero FLAKY teacher labels | Do not train |
| Corrected 60-call pilot | $1.4742, 33 FLAKY and 27 NOT_FLAKY | Run cheap learnability gate |
The corrected pilot sampled 60 of 178 valid square-okhttp cases, balanced 30/30 by hidden ground truth. All 60 teacher calls parsed. A group-disjoint split produced 48 training rows and 12 test rows. The student gate was intentionally cheap: TF-IDF over unigrams and bigrams with a class-balanced logistic regression.
The rule said TF-IDF had to beat the majority baseline before any GPU work. It tied. The stop condition fired. No pod was rented, no LoRA job ran, and no student result exists for the corrected corpus.
A separate historical fine-tune reference had collapsed to
predicting FLAKY on every example. It matched a
majority baseline on one majority-FLAKY slice and scored zero on a
deterministic slice. This is evidence about that historical run,
not a substitute result for the corrected pipeline.
The corpus pipeline has its own safety boundary
Importing phase12_prepare.py does not call a teacher
or start a GPU job. Running it without
--run-teacher validates the source cases and exits.
The paid path is therefore an explicit command-line action rather
than an accidental side effect of a test or import.
# Read and validate the Phase 11 export
rows = load_cases("phase11-cases.json")
# Choose 30 + 30 using hidden ground truth
pilot = balanced_cases(rows, limit=60, seed=0)
# Paid operation, allowed only by an explicit flag and budget
labelled, spend = label_with_teacher(
pilot,
model="claude-haiku-4-5-20251001",
budget_usd=2.0,
)
# Keep test identities disjoint
train, test = balanced_group_split(labelled, test_fraction=0.2)
# Gate before GPU work
result = run_baseline(train, test)
The loader requires a case identifier, ground-truth label, real failure text, and Phase 11 observer records. It rejects conflicting labels for a repeated identity and rejects a corpus that does not contain both classes. The teacher never receives the hidden label. It sees the test identity, real failure evidence, and normalized observer lines containing probability, calibration state, and evidence availability.
The response parser accepts only FLAKY or
NOT_FLAKY, checks confidence is between zero and one,
requires an explanation, and preserves the provider's actual cost
field. This last field matters because token estimates had already
failed. The batch loop stops admitting calls when accumulated spend reaches the budget. This is a soft admission limit: the last admitted call can take the total over budget. A hard cap would need a conservative reservation for each call and a provider-side limit where available.
Why the split follows test identity
Repeated examples from one test could share package names,
exception text, and detector patterns. If one landed in train and
another in test, the baseline could memorize the test family.
balanced_group_split() holds the complete
test_fqn on one side and asserts that both labels
remain in both partitions.
The final JSONL files retain prompts, evidence, teacher label, confidence, explanation, token counts, and cost. They are audit records as much as training data. If the student later produces a suspicious explanation, the team can trace it to the teacher example rather than guessing what the model absorbed.
Part VI / AI-native engineering method
Give the model work, keep judgment accountable
AI assistance increased the speed of implementation and inspection. It did not remove the need to decide what the system is allowed to do, what counts as evidence, or when an experiment no longer supports further spend.
The human and AI division of work
| Human-owned judgment | AI-assisted execution |
|---|---|
| Set the system boundary and retain the final action | Draft contracts, functions, tests, and experiment harnesses |
| Choose and challenge the cost table | Compute metrics and inspect returned artifacts |
| Decide which deployment question a split should answer | Implement grouped and stratified folds |
| Precommit keep, discard, and spend gates | Train controls, models, and calibration layers |
| Reject flattering interpretations and scope changes | Search logs, trace parser behavior, and propose fixes |
| Stop paid work when the gate fails | Prepare teacher prompts, parse outputs, and emit JSONL |
The collaboration used a repeated loop:
The AI also made mistakes. It proposed redundant metrics, invented an attribution before checking the real record, wrote docs that claimed unimplemented behavior, undercounted teacher cost, and initially treated a timeout as abstention. These errors became useful only because the workflow preserved design records, experiment predictions, result artifacts, and rejected proposals.
One parser bug can become four false discoveries
The most important lesson in the repository appeared late. The Maven parser dropped legitimate failures in a multi-module project. Four phases then produced separate-looking conclusions from the same damaged evidence.
After the parser repair, square-okhttp produced a two-class real-text retrieval corpus, a fusion population with variation, and a teacher pilot with both classes. The sequence observer still failed its own control, but the other three conclusions changed materially.
This is why an AI-native build needs provenance beyond a final notebook. A result must retain which parser, gate, corpus, split, and execution path produced it. When an upstream assumption changes, the team needs to know which downstream claims expire together.
Preserved as historical
Earlier results remain evidence of the failure mode and the reasoning at that time.
Reported as corrected
New runs are labeled by scope, population, coverage, and whether paid components completed.
Beyond the lab / Proposed design, not implemented infrastructure
Turn the experiment into a service with a memory
The laboratory can run files through functions. A service must also survive duplicate build events, missing artifacts, stale indexes, overloaded reviewers, model failures, and changes to the parser. The design below is an extension of the project. These queues, stores, and monitors are not claimed as shipped features.
Walk one incident through that design
At 02:47 an event arrives for build B-184, commit c7a, and our checkout test. The service assigns a stable request key from repository, commit, build attempt, and test identity. A duplicate delivery finds the existing request. A deliberate rerun has a different attempt ID; deduplication must not erase it.
Raw artifacts are saved immutably with hashes. The parser writes a derived snapshot with its version. If the log is truncated, the run becomes UNKNOWN and routes to artifact repair or review. It does not fabricate passing outcomes for missing test lines.
For an admitted run, each observer receives only its allowed inputs as of 02:47. Tabular emits a score, retrieval emits neighbors and a raw vote, and the history observer either supplies its approved baseline or records unavailable evidence. The discarded GRU does not return through a production diagram simply because there is an empty box.
The policy validates case IDs, score ranges, timestamps, and calibration metadata before computing a result. An optional explanation runs after the verdict. If that call fails, the UI still displays the verdict and structured evidence. If a required decision component fails, the UI displays degraded status and the designated review route.
Interactive 06 · Incident walkthrough · illustration
Follow B-184 from red build to review
Advance at your own pace. The numbers and incident identity are invented to make the proposed design concrete.
Make the evidence record sufficient for replay
// Proposed extension; not the current Python dataclass
{
"case_id": "repo/c7a/B-184/attempt-1/CheckoutTest.appliesDiscount",
"as_of": "2026-09-07T02:47:00+05:30",
"artifact_hash": "sha256:…",
"parser_version": "parser-v2",
"gate_version": "gate-v1",
"observer_version": "retrieval-v3",
"index_snapshot": "history-before-B-184",
"label_definition": "observed-flip-v1",
"calibration_version": null,
"status": "completed",
"has_evidence": true,
"probability": 0.6,
"policy_version": "review-policy-v1"
}
Keep provenance structured when downstream code needs to compare it. A free-text read field is useful for humans but cannot reliably enforce freshness or dependency rules. A Boolean calibrated says little without the calibration population, method, version, and validation date. Store the original verdict and the later correction as separate events.
| State | Meaning | Response |
|---|---|---|
| Abstained | Completed but policy refused a verdict | Review with reason and available evidence |
| Unknown input | Artifact cannot establish what ran | Repair ingestion or review; preserve raw file |
| Execution error | Required component did not finish | Mark degraded; apply explicit fallback |
| Stale evidence | Snapshot violates freshness contract | Recompute or refuse; never silently reuse |
| Human override | Operator chose another action | Record reason; do not treat it as ground truth |
Budget time, capacity, and the next observation
If independent observers run concurrently, their critical path is approximately the slowest observer, plus ingestion, policy, and queue overhead. If an arbiter runs only after disagreement, its time adds to those requests. Measure end-to-end p50 and p95 latency on the actual traffic mix; summing component p95 values does not produce a reliable service p95.
Suppose 100 incidents arrive per hour and the policy sends 25% to review. At ten minutes per review, that is 250 minutes of work per hour. Three reviewers provide only 180 minutes. The queue grows even if the model’s accepted-case score looks excellent. Review capacity belongs in the policy evaluation.
A rerun is an experiment that may change the next decision. Its value is the current minimum expected loss minus the expected minimum loss after seeing the rerun result, minus rerun cost and delay. Estimating that requires a model of how rerun outcomes relate to causes; a pass is not an automatic pardon. This is the bridge from classification to sequential decision-making.
Derivation: the value of another observation
Let R*(x) = mina Σy P(y|x)L(a,y) be the lowest expected loss with today’s evidence. A proposed experiment produces outcome z. Its expected value before cost is R*(x) − Σz P(z|x)R*(x,z). Run it only when this exceeds its compute cost, human effort, and cost of delay, subject to operational constraints.
Updating the cause estimate requires a likelihood: P(y|x,z) ∝ P(z|y,x)P(y|x). Without knowing how likely a passing rerun is under each candidate cause, “it passed on retry” does not tell you how far to move your belief. Under a correct model and freely optional use of new evidence, information cannot worsen the optimal decision before acquisition cost: you can ignore it. In practice, wrong likelihoods, delayed evidence, and changes to the environment can invalidate that idealization.
Roll out against the current workflow
Start in shadow mode: record what the service would recommend while the existing process retains authority. Compare review time, harmful recommendations, refusal rate, execution failures, and total cost per incident. Then trial the advisory UI on a bounded cohort with a rollback to the prior workflow. Monitor by project and gate outcome; aggregate averages can hide one broken customer.
Feedback is selectively observed: cases routed to review get more investigation than dismissed cases. Audit a sample across decision categories, with human authorization and appropriate operational controls. Retraining only on reviewed cases would teach the model from a population selected by its own previous policy.
Use the method on your next project
Generalize the questions, then choose the components
The reusable part is the dependency between decisions: action defines loss; loss defines evaluation; evaluation requires labels; labels require a trustworthy observation process; only then can a model earn a role. Another domain may need one observer and no LLM. Copying all three observers would copy the lab’s shape without its reasoning.
| Question | CI triage | Support routing | Invoice review |
|---|---|---|---|
| What happens next? | Investigate or rerun | Assign team or ask for detail | Approve for review or flag mismatch |
| What can be wrong? | Dismiss a defect | Delay an urgent customer issue | Miss a duplicate or block a valid invoice |
| Who creates truth? | Reruns + incident adjudication | Resolved case + corrected routing | Reconciled invoice + reviewer evidence |
| What can leak? | Future fix or reruns | Final team in ticket text | Post-review approval field |
| What split matters? | Future build / new project | Future tickets / new account | Future invoices / unseen vendor |
| What should refuse? | Missing log or stale history | Missing request context | Unreadable amount or missing purchase order |
A design brief you can fill in before asking AI to code
Person and moment: Who needs to decide, and by when?
Allowed actions: What can change in the world? Who owns it?
Prediction target: What exactly does y mean? What does it exclude?
Loss table: For each truth/action pair, what consequence follows?
Observation: How is y established, and which cases go unobserved?
As-of inputs: What is actually available at the decision time?
Evaluation: Which unit is held out, and what deployment claim follows?
Baseline: What is the simplest existing policy to beat?
Refusal: Which missing evidence or unsupported population blocks a verdict?
Experiment: What result would make us keep, discard, or stop?
Operations: What happens on timeout, duplicate events, or stale state?
Evidence: Which immutable artifacts let another person inspect the claim?
Use AI to make a falsifiable slice concrete
Give the assistant a narrow contract and ask it to expose assumptions before implementation. Supply the relevant schema and a small redacted fixture. Its response should include the proposed code, an independent test case, a command to run, and a result artifact. A plausible explanation is not evidence that the command ran.
You are implementing one CI-triage evaluation slice.
Target: y=1 means observed flaky behavior, not current defect cause.
Inputs: probabilities, binary labels, and stable case IDs.
Illustrative loss: false positive=5000, false negative=500.
Do not alter labels, expand scope, call paid APIs, or select a model.
First, derive the 2x2 confusion table in plain language.
Then implement the evaluator and reject invalid probabilities.
Hand-compute this fixture before using a metrics library:
labels = [0, 0, 1, 1]; predictions = [1, 0, 0, 1].
Expected FP=1, FN=1, TP=1, TN=1; cost per case=1375.
Add a second fixture with unequal FP and FN counts to catch swaps.
Report commands actually run, outputs, and any untested assumptions.
The first fixture checks the arithmetic but cannot catch reversed costs because FP and FN are equal. A second fixture with two false positives and zero false negatives must cost 10,000/4 = 2,500 across four cases. This is why the human should supply an independent oracle rather than ask the same assistant to validate its own assumptions.
“Try to falsify this result. Trace one raw row through parsing, joining, feature construction, prediction, and scoring. Identify fields unavailable at prediction time. Check whether repeated identities cross the split. State what the reported number cannot establish. Do not change the acceptance criterion to fit the result.”
Keep a short decision ledger: hypothesis, expected result, actual output, interpretation, and next action. When a parser changes, ask AI to enumerate downstream artifacts that depend on it. Recompute those artifacts under new version IDs. Retain prior outputs as historical evidence rather than editing numbers inside old JSON.
When should an LLM become part of the product?
First use AI to help build and inspect the system. A runtime model adds a different dependency: provider availability, response variability, cost, latency, and exposure to untrusted text. It earns that role only through a comparison on the same eligible cases against a cheap alternative. Explanations can start as templates; arbitration can start as explicit rules. Add the model where the measured benefit exceeds its operating burden.
Check your understanding
A test is 95% likely to be flaky. Can you release?
The historical flakiness estimate does not resolve today’s failure cause or the release’s other defects. Even the simplified cost derivation only compares two actions for one incident under stated assumptions. Inspect the incident evidence, loss model, and release policy. Never average test scores into a release authorization.
Three observers agree after a parser change. What should you check first?
Whether they read artifacts derived from the same parser, whether the labels are shared, and whether cached histories or indexes were rebuilt consistently. Agreement is weak corroboration when a single upstream error explains all three outputs.
The model has 99% accuracy but abstains on 90% of traffic. What is missing?
Its denominator, execution coverage, accepted-case mix, missed-defect count, review load, and total cost across all arrivals. The accepted 10% might be trivial. Compare the complete workflow with the current baseline.
The teacher’s explanation sounds correct. What would make it evidence?
Each factual claim must be supported by the recorded inputs, and the explanation must preserve disagreement and the actual verdict. Review it against independent adjudication where available. Fluency, valid JSON, and the teacher’s own confidence are insufficient.
Sources and claim boundaries
What is measured, derived, and proposed
Measured in the Week 4 artifact: dataset joins, observer metrics, corrected fusion completion counts, teacher spend, and the baseline tie. These are stored experimental results, not a fresh model-training run for this revision. Historical, corrected, and reference experiments remain separate.
Derived in this article: the two-state cost threshold, rerun-observation probabilities, base-rate illustration, and queue-capacity example. Their assumptions appear beside the arithmetic. Proposed: the production service, replay schema, monitoring, and AI implementation brief. These are guidance for extending the project, not claims about existing infrastructure.
- Alshammari, Morris, Hilton, and Bell: FlakeFlagger dataset, Zenodo 4450723. Source data and attribution; CC BY 4.0. The linked dataset description reports its study population; this article’s row and project counts refer to the local files and joins and should not be conflated with that study summary.
- Scikit-learn: tuning the decision threshold. Separating statistical prediction from the action rule.
- Scikit-learn: probability calibration. Reliability diagrams, held-out calibration, and interpretation of probability scores.
- Hu et al.: LoRA—Low-Rank Adaptation of Large Language Models. The adapter mechanism behind the memory discussion.
How to read and reproduce the repository
Read the project in dependency order rather than filename order:
-
PROBLEM.mdfor the incident, boundary, and cost assumptions. -
design/00throughdesign/13for slice responsibilities and explicit gaps. -
decisions/for the choices that constrain implementation. -
experiments/for predictions written before results. -
ci_triage/andtests/for the actual mechanism and invariants. -
artifacts/results/for executed outputs, including partial coverage. -
ai-ledger/,KNOWNS.md, andFAILURES.mdfor rejected proposals, corrections, and handoff risks.
The core dependency chain is:
A practical rerun should begin with the cheap invariants and local strategies. Paid teacher calls and GPU work need separate budget gates. The checked-out artifact used Python 3.12 dependencies including pandas, scikit-learn, PyTorch, sentence-transformers, pytest, and the Anthropic client. The result files are the evidence for the numbers in this article. They should not be rewritten to make corrected code and historical output look artificially consistent.
The finished artifact is not one model. It is a chain of contracts that makes weak evidence, failed experiments, partial execution, and human judgment visible.
Learn to build systems that can disagree with their own results.
Antern's AI-Native Engineering Sprint teaches the engineering around the model: problem boundaries, evidence, evaluation, orchestration, failure handling, cost, and human review.
Apply to Antern