5. Calibrated abstention thresholds
- Status: Superseded by 0012 (2026-07-05) — the numbers below (abstain 0.45, low-confidence 0.62, logistic midpoint 0.22/steepness 12.0) were never the values actually shipped in code, and running the calibration suite against them fails the ECE gate (0.184 > 0.15). This record is kept verbatim, append-only, for history; do not treat its numbers as current. See ADR-0012.
- Date: 2026-06-22
- Author: Chelsea Kelly-Reif
- Deciders: Chelsea Kelly-Reif (maintainer)
Context
The third behavioral promise is calibrated uncertainty: "below a confidence threshold it abstains rather than guesses," and the stated confidence must track correctness — a 0.6 confidence should be right about 60% of the time. Two anti-patterns to avoid:
- Confidence from fluency. Asking the model how sure it is, or scoring on answer fluency, rewards confident nonsense — the more fluent the fabrication, the higher the self-reported confidence. That inverts the signal we want.
- A magic number with no audit. A threshold nobody validates is a guess wearing a
number.
AI-EVALUATION-STANDARDrequires calibration to be a measured, gated layer (reliability + ECE), not a claim.
Confidence has real consequences here — below a threshold the assistant refuses entirely — so the number must be defensible and the thresholds must be auditable.
Decision
Confidence is a transparent function of retrieval evidence only, and abstention is
driven by two thresholds over it (confidence.py, config.py::ConfidenceConfig).
- Score.
score_confidence()maps the best passage's cosine through a fixed logistic (midpoint 0.22, steepness 12.0), nudged up by the margin over the runner-up passage. It deliberately ignores answer fluency. A refusal scores 0.0 (maximally uncertain about the answer it declined to give). - Two thresholds. Below
abstain_threshold(default 0.45) the assistant refuses rather than guesses, returning a routed refusal withabstained=True. Belowlow_confidence_threshold(default 0.62) it answers but flags the answer for human review (low_confidence=True). The two-band design separates "don't answer" from "answer, but caveat." - Held to the claim. The
calibrationeval suite builds a reliability diagram and computes Expected Calibration Error (ECE) over (confidence, correct) pairs (confidence.py,eval/calibration.py), so the harness verifies the stated confidences actually track correctness. The judge config hash is folded into the run fingerprint, so a calibration record is invalidated when the judge or thresholds change (ADR-0009).
Consequences
- Positive. Confidence cannot be gamed by fluency; it is grounded in the same retrieval evidence that decides answer-vs-refuse, so the whole pipeline tells one consistent story.
- Positive. The thresholds are not asserted — they are audited by ECE and the reliability diagram in a committed suite, and re-validated on every run.
- Positive. Abstention below threshold means the assistant's worst case is an honest "I don't know," not a confident wrong answer — the safe failure mode.
- Negative — the honest limit. Because confidence derives from retrieval similarity over a token-hashing embedder (ADR-0001), it measures "how well did we retrieve," not "is the extracted sentence correct for this user's exact situation." A well-retrieved but situation-mismatched passage can read as high-confidence. The model card states this.
- Negative. The logistic constants and thresholds are tuned against the eval set; on a swapped corpus they should be re-validated (re-run the calibration suite) rather than assumed.
- Neutral. Thresholds are config, but they are a named guardrail — changing them is an ADR-class change per the README.