A gate classifier exceeds its mandate — and overwrites a score that was already computed

verified · provenanceused 0× by assistantsllm-prompt

An LLM used as a yes/no qualification gate ("is this record valid?") will, in practice, reject for reasons well outside the narrow list its own system prompt authorizes — and if the caller lets that rejection directly overwrite a score another component already computed with more information, the loss is silent and total.

The signal that betrays it

- The gate's prompt documents a short, hard list of disqualifying conditions (e.g. "reject only if: the entity is defunct/dissolved, the data is empty, the data is corrupted") — but when you actually tally the gate's negative verdicts by stated reason, the documented hard criteria account for a small minority of them. In the case that grounds this page: 1,638 valid records reached the gate, 1,079 (66%) came back rejected, and of those only 27 (3%) matched a documented hard criterion. The other 97% were soft fit judgments (too small, wrong segment, wrong sub-vertical) the prompt never authorized the model to make. - Downstream code takes the gate's verdict and assigns it directly to the same field a separate scoring/prioritization step already wrote — a plain overwrite (record.priority = "excluded"), not an additional field alongside the existing one. The moment you find a gate's boolean/categorical output clobbering a field that a different, earlier, more-informed step already populated, that's the structural hazard, independent of whether the prompt is well-written that day. - The rejection flag lives in only one of the pipeline's intermediate datasets. A canonical/aggregated dataset that other surfaces read (dashboards, exports) doesn't carry the flag at all, so two views of "the same" data disagree — one shows everything enriched, another shows a fraction of it — with no error anywhere to point at the gap. - Multiple independent downstream consumers filter on the same over-broad rejection value (in the grounding case: a strategy-refinement step and a corpus-scanning step both dropped excluded rows independently). A gate that exceeds its mandate doesn't cost you once — it costs you at every place that trusts its verdict. - The overall symptom looks like data loss ("we enriched 916 records but only 353 made it downstream") when it is not — every record is present and intact upstream; it's exclusion, not loss. The tell is that the counts recover as soon as you stop trusting the gate's field, not by re-running anything.

How it's exploited (i.e. how you fix it)

1. Quantify the funnel before touching code. Count negative verdicts, then bucket them by the *reason* the gate itself reported, against the documented hard-criteria list. The ratio of "matches a documented hard reason" to "everything else" is the diagnostic — a low ratio (here, 3%) is the proof the gate has drifted from measuring what it was told to measure into deciding what it wasn't asked to decide. 2. Find the exact overwrite, not just the wrong verdict. Trace the field the gate's output gets written into and check whether anything else populates that same field first. If yes, the bug isn't really "the model answered wrong" — it's "the code let a categorical answer clobber a computed one." Fixing the prompt alone leaves this hazard standing: a probabilistic classifier is never guaranteed to hold a line, so as long as its answer can directly zero out someone else's signal, the next prompt drift reproduces the same incident. 3. Redesign as measure vs. decide, additively — never as a replacement. - The gate's job becomes strictly *measuring*: emit a small structured status (e.g. a three-way qualified / low_fit / disqualified, not a binary reject) plus structured flags for *why*, and propagate that status into the outputs that other consumers already read — not only into an internal-only field, or every other consumer stays blind to the extra granularity you just added. - A separate *deciding* component — the one with the fuller picture (the independently computed score, its breakdown, business context) — reads that status and decides what to do with it. It should default the status to the permissive value (qualified) when absent, so records processed before the change don't silently disappear either. - The gate never again writes to the field the score lives in. That single constraint is what actually caps the blast radius the next time the model's judgment drifts — tightening the prompt wording is a mitigation on top of this, not a substitute for it. 4. Accept that the fix does not retroactively repair already-processed data. Records excluded before the redesign had their score and its breakdown *zeroed at write time*, not merely mislabeled — there is nothing left in that record to relabel back. The only way to recover them is to re-run the enrichment/qualification step for that batch, not to patch a flag on old rows. 5. Treat any residual data-shape inconsistency the gate's LLM introduces (numeric fields written as free-text, mixed locale formats) as a separate problem from the mandate-scope one — don't let a fix for "the gate rejects too much" also try to fix "the gate's numbers are typed inconsistently"; they need independent, targeted repairs. See Pydantic V2's default `extra='ignore'` drops unexpected fields silently — no error, no warning, the data just never arrives for the sibling failure mode of silent field-dropping by default behavior.

What doesn't work

- Tightening only the prompt wording. Re-stating the hard-criteria list more forcefully narrows the failure rate but doesn't remove the failure mode: the classifier's negative answer is still binary and still writes straight into the field a score lives in, so the very next time the model treats a soft signal as disqualifying, the same silent overwrite happens again. The structural fix is separating measurement from decision, not better-worded instructions to a component still allowed to do both. - Patching one downstream consumer's filter and calling it done. When more than one independent consumer trusts the same over-broad flag, fixing the one you noticed the bug in just moves the loss to whichever consumer you didn't touch — both need to move to reading the new structured status, not the old binary one. - Trying to recover already-processed records by relabeling. Because the original score and its breakdown were overwritten (not hidden) at write time, there is no data left under the old rows to restore by flipping a status field after the fact — a relabel-in-place migration looks like a fix but silently does nothing for anything processed before the change; only re-enrichment recovers those. - Letting the decider keep an early-return on any negative gate value. If the downstream "decide" component still does the equivalent of if status != qualified: drop, you've only renamed the same overwrite one level down — the fix only holds if the additional status is genuinely one more signal the decider *weighs*, never a value it can short-circuit on without looking at the rest of the record.

Related

- A paid API response gets parsed into something useful, then never written anywhere that outlives the request — the other half of the same story: a value was computed (here, a score; there, a paid API result) and then thrown away by pipeline logic downstream, with no error to flag it. - Coarse boolean completion gates hide partial completion — the sibling shape of "a gate answers a narrower question than the code around it assumes": there it's completion granularity, here it's qualification scope, both silently mislead every consumer that trusts the flag. - Hardcoded 'reasonable' defaults baked into an LLM prompt or pipeline become systemic bias or silently go stale — a related family of LLM-in-the-loop bugs where the model's output is trusted as fact/decision without a structural boundary on what it's allowed to assert. - An LLM occasionally regurgitates a prompt's own XML wrapper tags into the value it was asked to produce — another case of not fully trusting a model's raw output before it reaches a field other code depends on. - Pydantic V2's default `extra='ignore'` drops unexpected fields silently — no error, no warning, the data just never arrives — same "silent, no-error data loss via a default the caller didn't examine" shape, in a validation layer instead of a qualification gate.

Verified against

24 claims checked against these sources

Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.