Founders

How to Set a Fail-Closed Default for a Low-Confidence Row

Antonio Evans10 min read

A pipeline diagram: a row enters a gate labeled with a confidence cut, the accept path goes right for high-confidence rows, the fallback path goes left to two boxes (strong model, human reviewer).
Key points

Key points

  1. 1Set a numeric confidence cut, and make every row below it take the fallback path by default rather than being auto-accepted.
  2. 2Choose the cut from a labeled sample of real traffic by measuring how well the confidence score predicts actual errors, not from a guess.
  3. 3Design the fallback deliberately: escalate to a stronger model when a second model can fix the error, route to a person when it cannot.
  4. 4Log row id, confidence, cut, fallback reason and outcome for every routed row, so each decision is auditable and the cut can be retuned.

Start from the asymmetry between error types

Most teams tune a classifier, or an LLM extraction step, by watching aggregate accuracy. That number hides the only cost that matters for a fail-closed design: an accepted row that is wrong. A fail-open default accepts everything the model emits, so wrong rows pass straight into invoices, ledgers, support tickets or downstream agents with no record that anyone doubted them. A fail-closed default inverts that: the system only auto-accepts a row when confidence clears a cut, and everything else waits for a stronger model or a person.

This matters because the two error directions are not symmetric in cost. Accepting a bad row (a false clean) usually costs money quietly, while sending a good row to fallback costs reviewer time loudly. Cost-aware decision theory treats the threshold as a place to weigh those two costs against each other, together with how often each class appears in traffic (a worked cost calculation). You cannot remove both costs, so you pick which one the system defaults to. Fail-closed means the default is reviewer time, not silent error.

There is a second reason to prefer the fallback default. Confidence scores from language models are routinely miscalibrated, and models tend to be overconfident on inputs unlike their training data (UCCI, a calibration-first router). A raw token margin is a weak signal used directly. Treating it as a permission slip to auto-accept is how silent errors happen.

What the confidence score actually is

Before you set a cut you need to know which signal you are thresholding, because they differ a lot in reliability.

  • Verbalized confidence. Asking the model "how sure are you" in plain language is the weakest option; models are poorly calibrated at describing their own uncertainty (a survey of escalation approaches).
  • Token probability or margin. Cheap and available from any API that returns log probabilities, but sensitive to prompt wording and usually miscalibrated out of the box (UCCI).
  • Trained confidence tokens. Fine-tuning the model to emit a confidence token when it is right and a different one when it is wrong, then reading the probability of the confident token, outperforms both verbalized confidence and raw token probabilities on routing and rejection tasks (Learning to Route LLMs with Confidence Tokens, Apple's summary of the same work).
  • Constrained or probe-based signals. A small classifier reading internal activations is the most accurate class of signal, but it needs access to model internals, so it only works with open-weight models (the escalation survey).

A practical rule: if your pipeline already asks the model for a structured extraction, add a confidence field to that same schema. Structured outputs return schema-validated JSON that your code can act on directly, rather than free text you have to parse (Snowflake's structured output guide). Databricks makes the same point for batch extraction: response_format with json_schema constrains the model to a schema, and text mode does not (Databricks structured outputs).

Here is an extraction schema that carries its own gate fields:

{
  "type": "object",
  "properties": {
    "vendor_name": { "type": "string" },
    "invoice_total": { "type": "number" },
    "currency": { "type": "string" },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "risk_flags": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["vendor_name", "invoice_total", "currency", "confidence"]
}

If the agent cannot satisfy the schema after its retries, the SDK returns an error result rather than a half-filled object, which you should treat as a row below any cut (Snowflake). Some implementations enforce the schema by handing the model a synthetic tool it must call with arguments that validate, feeding errors back and retrying within a fixed retry budget (a walkthrough of that mechanism). Either way, schema failure is a fallback trigger, not an auto-accept.

Choosing the cut from data, not intuition

The cut is one number that decides your whole cost-quality trade-off. Set it too high and you escalate constantly, paying more than if you had sent everything to the strong model. Set it too low and the cheap model keeps answering questions it genuinely gets wrong, with no error surfaced anywhere (the escalation survey).

The procedure:

  1. Collect a labeled sample of real production traffic. Not a benchmark; traffic that resembles what the system actually sees. A few hundred to a few thousand rows with human-verified correct answers is enough to start.
  2. Record the cheap model's confidence on each row and whether it was actually right.
  3. Measure how well confidence separates right from wrong. A standard way is the area under the ROC curve of "confidence predicts error," which is exactly what the multilingual routing study reports when it compares confidence routing to random escalation (routing across tasks and languages).
  4. Sweep candidate cuts and read off two numbers at each: the share of rows that go to fallback and the share of actual errors that are still auto-accepted.
  5. Pick the cut where the auto-accepted error rate meets the ceiling you can defend to the business, not the cut where average accuracy looks best.

That study is a useful caution. Confidence routing only clearly helped on reading comprehension and topic classification, with mean correction-detection AUCs of 0.612 and 0.705. On the knowledge and reasoning dataset the mean AUC was 0.510, essentially chance, because small models often stayed highly confident while wrong (arXiv). If your task looks like that, a single global cut will not carry the safety load on its own. You then need a second gate: equal-quota routing sends a fixed share of traffic to fallback regardless of confidence, and in that study it removed a language escalation gap of up to 30.33 percentage points with little aggregate accuracy cost.

Calibrating the score before thresholding makes the cut easier to reason about. UCCI maps raw token-margin uncertainty to a per-query error probability with isotonic regression and reduced expected calibration error from 0.12 to 0.03 on a production named entity recognition workload, cutting inference cost by 31% at micro-F1 0.91 (the paper). A cut that means "escalate when the estimated error probability exceeds 2%" is a decision a reviewer can sanity-check; a cut of 0.63 on an arbitrary margin is not.

A worked example with numbers

Suppose you process 100,000 invoices a month. Your small model auto-generates extraction for all of them. From a labeled sample you estimate that the small model is wrong on 4,000 of them, and that a cut at 0.80 confidence sends 15% of rows to fallback.

QuantityValue
Rows per month100,000
Actual errors, cheap model4,000 (4.0%)
Cut0.80
Rows sent to fallback15,000
Rows auto-accepted85,000
Errors remaining in auto-accepted rows900 (1.06% of accepted)
Errors caught by fallback (the gateway)3,100

The headline is not the 15% fallback rate; it is that the auto-accepted pool now has a 1.06% error rate instead of 4.0%, and every one of the 3,100 routed errors is visible in a log. If you tighten the cut to 0.90 and fallback rises to 28,000 rows, you buy a lower accepted error rate for 13,000 more reviews. Whether that trade is good is a business question, but with the table in front of you it is an explicit one.

You can make the same decision per row with the expected cost of each path. Expected cost of auto-accepting a row is (1 minus confidence score, once calibrated) times the cost of a wrong acceptance; expected cost of the fallback is the cost of the reviewer or second model. Route to fallback when the first term is larger. That comparison is the concrete form of the decision rule, and it generalizes beyond confidence: adjust it when a row carries flags such as a missing currency or a new vendor.

Designing the fallback path

"Do not auto-accept" needs somewhere for the row to go. Two destinations cover most cases.

Escalate to a stronger model. This works when a bigger model plausibly fixes the specific errors the cheap model makes. Confidence-threshold escalation is a cascade: the cheap model answers, a scorer evaluates the answer, and escalation fires only below your cut, so nobody has to predict difficulty before seeing an answer (the escalation survey). The catch, shown by that task-dependence data, is that escalation assumes low confidence marks errors the larger model can correct (routing study). Validate the pairing on your labeled sample before you depend on it. If the strong model is right on fewer than most of the routed-error rows, you are buying latency and cost without buying safety, and the answer should be a person.

Route to a person. Human in the loop is the design pattern where uncertain predictions go to a human reviewer in real time, and the distinction from traditional QA is that the escalation trigger is the system's own uncertainty gate rather than a periodic audit (Roboflow on HITL, Appen on HITL design). A real HITL loop also feeds corrections back into training data, evaluation sets or the threshold itself, so the share of rows that need a person should trend down. If it never does, the cut or the model is wrong. Appen's comparison of human in the loop, human on the loop and active learning is worth reading before you commit: if what you actually want is a human supervisor who can override aggregate behavior rather than decide every routed row, that is human on the loop, and it needs a different operations model.

A third option is to re-run the cheap model with more context or a stricter prompt, but only if it demonstrably converges on the routed rows in your sample. Otherwise it is just a slower path to the same fallback.

Chaining the paths. Many teams want both. A workable chain: below the cut, first try the stronger model; if the stronger model's own confidence also falls below its cut, or if the row carries a risk flag, route to a person. This keeps reviewer volume down while guaranteeing that no low-confidence row is ever auto-accepted.

def route(row, cheap_result, strong_model, queue):
    conf = cheap_result.confidence
    if conf >= CUT_ACCEPT and not cheap_result.risk_flags:
        return accept(row, cheap_result)

    reason = "below_cut" if conf < CUT_ACCEPT else "risk_flag"
    strong = strong_model.extract(row)
    if strong.confidence >= CUT_STRONG and strong.status == "valid":
        return accept(row, strong, fallback_reason=reason, path="strong_model")

    queue.push(row, strong=strong, reason=reason + "+strong_low")
    return pend(row)

Note what that function refuses to do: there is no branch that returns a result for a row whose only evidence is a below-cut cheap model answer.

If your fallback involves several providers, one consideration is whether you will need a single interface for the cheap model, the strong model and the embedding model used for similarity checks. That is what Quanta Inference is for: text, image, video, audio and embedding models through one OpenAI-compatible API key, prepaid and priced in US dollars. It does not change the design above; it reduces the glue code around it.

Logging every fallback so the decision is auditable

A fallback with no log is indistinguishable from an auto-accept when someone asks later. The log entry is what makes the design fail-closed in the operational sense, not just the code sense. At minimum it should carry:

FieldWhy it is there
Row idJoins the decision back to the source record
Cheap model confidenceThe score that was thresholded
Cut in forceThresholds change; the entry must record the value that applied
Fallback reasonWhich trigger fired: below cut, risk flag, schema failure, retry exhaustion
Path takenStrong model, human, or chained
Strong model confidence and statusShows why the second step did or did not resolve it
OutcomeWhat was finally accepted, or that it is still pending
Reviewer decisionFor human rows: accept, correct or reject, with the corrected value

Two details are easy to miss. First, record the cut as applied, because a log entry that says "below 0.80" is useless six months after the cut moved. Second, record schema and retry failures as fallback reasons; they are not a separate failure class. When structured output validation fails after the retry budget, the SDK returns an error result (Snowflake), and that row belongs in the same audit trail as any other below-cut row.

An audit trail is not only for internal comfort. Regulatory and governance frameworks increasingly treat oversight and event logging as explicit requirements for high-risk systems, which is one of the reasons HITL designs record every decision (Appen).

Measuring whether the default is holding

A fail-closed system can drift open without anyone changing the code. Watch five numbers monthly:

  • Fallback rate per segment. A sudden drop is a warning, not a win, unless accuracy on accepted rows held steady. Track it by language or channel too: global confidence routing produced language escalation gaps up to 30.33 percentage points in the multilingual study (arXiv).
  • Auto-accepted error rate. Sampling and rechecking a slice of accepted rows is the only way to know the accepted pool is still clean. Precision and recall give you the vocabulary for this, and the choice of metric depends on which misclassification is costly (Google's ML crash course).
  • Reviewer agreement on routed rows. If reviewers accept nearly everything routed, the cut is too aggressive and you are burning review capacity on safe rows. If they correct most of it, the cut is earning its keep.
  • Fallback outcome split. Share resolved by the strong model versus by a person. Movement toward humans means the strong model is not carrying its weight.
  • Calibration drift. Periodically recompute whether a score of 0.9 still means roughly 90% correct. Miscalibration under drift is a named failure mode, and confidence can stay high while accuracy falls on new inputs (Appen).

For teams that want the routing layer designed and built with them rather than bolted on, Quanta is a place to describe the pipeline and have a specialist build it alongside you.

FAQ

FAQ

What is a fail-closed default in plain terms?

It means the system's default action for an unclear row is to stop and escalate, not to accept. Auto-acceptance requires positive evidence that confidence cleared the cut. Anything else goes to a stronger model or a person, and the decision is logged.

Should I escalate to a stronger model or straight to a person?

Escalate to the stronger model only where your labeled sample shows it corrects most of the rows your cheap model got wrong. Where it does not, send the row to a person, because a second model that repeats the error adds latency and cost without adding safety. Many teams chain both: strong model first, human if the strong model also falls below its own cut.

How do I pick the number for the confidence cut?

From labeled real traffic, not a benchmark. Record confidence and correctness on each sampled row, sweep candidate cuts, and read off the fallback rate and the error rate remaining in the auto-accepted pool at each one. Pick the cut where the auto-accepted error rate meets the ceiling you can defend, after calibrating the score so the cut means something like an estimated error probability.

What exactly goes in the log entry?

Row id, the cheap model's confidence, the cut that was in force, the specific fallback reason, the path taken, the strong model's confidence and status, the final outcome, and any reviewer correction. Schema validation failures and retry exhaustion count as fallback reasons and belong in the same trail.

How do I know the fail-closed default is still working months later?

Track the fallback rate per segment and the rechecked error rate in the auto-accepted pool, and treat a falling fallback rate as a warning until accuracy is verified. Also monitor reviewer agreement, the split between strong-model and human resolutions, and calibration drift on fresh traffic.

Sources

Sources

  1. How to define acceptable false negative/positive rate? Any ...
  2. UCCI: Calibrated Uncertainty for Cost-Optimal LLM ...
  3. Using Confidence Thresholds to Escalate Requests to a ...
  4. Learning to Route LLMs with Confidence Tokens
  5. Learning to Route LLMs with Confidence Tokens
  6. Structured output
  7. Introducing Structured Outputs for Batch and Agent ...
  8. Chapter 15: Structured Output and Schema-Constrained ...
  9. Who Pays for Efficient Inference? Confidence-Based Small ...
  10. Human-in-the-Loop (HITL) for High-Stakes AI
  11. Human in the loop: HITL design for ML and AI agents
  12. Classification: Accuracy, recall, precision, and related metrics
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles