Skip to content

fix(extraction): keep validation-emptied batches out of the noise bank#959

Draft
gorkem2020 wants to merge 11 commits into
CortexReach:masterfrom
gorkem2020:fix/noise-learning-validation-gate
Draft

fix(extraction): keep validation-emptied batches out of the noise bank#959
gorkem2020 wants to merge 11 commits into
CortexReach:masterfrom
gorkem2020:fix/noise-learning-validation-gate

Conversation

@gorkem2020

Copy link
Copy Markdown
Contributor

What Problem This Solves

Two side effects of the extraction validation layer:

  1. extractAndPersist trains the embedding noise bank whenever extraction returns ok with zero candidates. A batch can reach zero candidates through validation drops (constructed grounding, fiction register, the batch contradiction demotion) rather than genuine emptiness. In that shape the model DID emit candidates; feeding the conversation to the noise bank as a noise exemplar teaches "content like this is noise" and risks pre-filtering similar real content away from future extractions. Observed live: a mixed-register conversation containing a real durable fact was fed to the noise bank after the contradiction check demoted the whole batch.

  2. The batch contradiction check logs its demotions at debug only, so a fully demoted batch is indistinguishable from "model found nothing" at the standard log level. That ambiguity cost a live investigation.

Why This Change Was Made

  • extractCandidates now reports rawCandidateCount (items the model emitted before validation) on the ok result. Noise learning fires only when that raw count is zero; validation-emptied batches log a standard-level skip line instead.
  • When the contradiction check demotes one or more candidates, one standard-level line reports the count and batch register, mirroring the admission rejection lines.

The demotion policy itself is unchanged; this only fixes its side effects.

User Impact

Deployments stop committing wrong "this is noise" beliefs to the learning store when validation empties a batch, and operators can see demotions without enabling debug logging.

Evidence

  • Flipped the regression test that pinned the old behavior ("learns noise when every parsed candidate is dropped by local validation") and added demotion-emptied and log-line cases; red first, then green (7/7).
  • Grounding/register suite unchanged (23/23).
  • Full test chain green locally; dist rebuilt.

Note: this branch is cut from the grounding PR's head (#927) because the demotion machinery lives there; it stays a draft until #927 settles and merges.

gorkem2020 and others added 11 commits July 19, 2026 05:58
…l grounding

Extraction has no notion of register: an invented rule stated inside a
game is textually indistinguishable from a durable user preference, so
the admission gate (which scores shape, not reality) happily promotes
in-fiction assertions to profile/preference facts. Add a "grounding"
field ("real" | "constructed") to the extraction output schema so the
extractor can flag content that only holds inside an in-conversation
constructed context (games, roleplay, drafted fiction, hypotheticals,
sample data). A real aside stated in passing during play is still
"real" and keeps its natural category.

This is prompt-only; the enforcement post-filter lands separately.
…e extraction-policy knob

Two independent, deterministic filters in extractCandidates()'s
validation loop:

- Grounding post-filter (companion to the prompt change): a candidate
  tagged grounding:"constructed" is dropped unless its category is
  "events", and at most one constructed "events" note survives per
  extraction. Missing/non-string/unrecognized grounding values fail
  open to "real" so a model that ignores the field cannot regress
  extraction. Real-grounded candidates are never affected by this
  filter, regardless of category.

- Scope-glob extraction-policy knob (config only, no host plumbing
  required): SmartExtractorConfig.extractionPolicy maps a scope or
  scope-glob to "full" (default, unchanged) | "episodic-only" (only
  "events"-class candidates survive, independent of grounding) |
  "none" (extraction skipped entirely, zero LLM calls). Resolved via
  the new exported resolveExtractionPolicy(); exact-string entries
  take priority over glob entries.

Neither filter touches admission control or speaker attribution.
Covers: constructed game content dropped from profile/preferences/
entities/cases; at most one constructed episodic events note per
extraction; a genuine real first-person aside stated during play
survives (false-positive guard); a purely factual transcript is
unchanged; malformed/missing grounding fails open to "real"; the
prompt documents the grounding contract; and the scope-glob policy
resolver plus its "none"/"episodic-only"/unmatched behavior through
extractAndPersist. Fixtures are synthetic (agent-one/agent-two).
Adds the new suite to package.json's test script and to
scripts/ci-test-manifest.mjs under core-regression, alongside the
other smart-extractor regression suites.
…icy knob

Compiled output for the extraction-prompts.ts grounding tag and the
smart-extractor.ts post-filter + scope-glob extraction-policy knob.
The per-item grounding self-tags wobble: the same clean fixture produced
all-real tags on one run and correct constructed tags on two others, so
a filter that trusts per-item tags alone (failing open to real) lets
mislabeled fiction land in durable registers. Counter this at three
deterministic layers plus the admission judge:

- Extraction prompt v2: the model judges a batch-level
  conversation_register (real, mixed, fiction) once per extraction, and
  the grounding rules now state that grounding is judged per item with
  no expected count per batch, that the one-constructed-events cap is a
  storage rule rather than a tagging quota, and that items sharing a
  fictional frame must share a grounding tag. Few-shot examples now show
  full batches in varied shapes, including an all-constructed mid-game
  batch and a mixed batch with a real out-of-character aside.
- Filter enforcement: register fiction drops all durable candidates
  regardless of per-item tags and keeps at most one events note; a
  mixed or missing register runs a batch contradiction check that
  demotes real-tagged durables whenever a sibling candidate carries an
  explicit constructed tag; register real keeps current behavior.
  Missing registers fail toward scrutiny (mixed), never toward open.
- Grounding propagation: candidates keep their grounding and register
  past the filter, into stored metadata (grounding,
  conversation_register) and into the admission audit record, creating
  a persisted trail of which stored rows used the constructed allowance.
- Admission: constructed candidates targeting durable registers are
  rejected deterministically before any LLM call; the utility prompt
  now interpolates grounding and register, names all six memory
  registers correctly, and instructs near-zero durable scores for
  fiction-framed content; the type prior for durable categories is
  capped at the events prior when the batch register is fiction, so the
  0.6-weight prior cannot launder fiction into profile or preferences.

Legacy payloads without grounding metadata keep their existing behavior
throughout. Red-proved: 9 of the new regression tests fail against the
previous implementation and pass after it.
…mpound path

Complements the existing "missing register, no constructed tags" test:
a missing/malformed conversation_register defaults to "mixed", and a
genuinely constructed sibling in the same batch is enough to arm the
same batch-wide durable wipe a "fiction"/"mixed" register would. No
production behavior change, just closes a gap the existing per-register
tests didn't individually exercise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fleet-proven refinement of the grounding model: 'real' includes an
assertion ABOUT a fiction/game session (that it happened); 'constructed'
is a claim true only WITHIN the fiction and is never stored, in any
category or register. The batch register deterministically overrides
per-item tag wobble for durable categories, and the admission judge's
grounding rule is restated in candidate-content terms (the judge is
transcript-free by design).
…atches, log contradiction demotions

extractAndPersist fed learnAsNoise(conversationText) whenever extraction
returned ok with zero candidates: the model found nothing, strongest
noise signal. But a batch can reach zero candidates through validation
drops and the batch contradiction demotion, not genuine emptiness. In
that shape the model DID find candidates; feeding the conversation to
the embedding noise bank teaches that content like this is noise and
risks pre-filtering similar real content away from future extractions.
Gate noise learning on the RAW model output being empty
(rawCandidateCount now rides the ok result), and log the skip at the
standard level.

Also surface the batch contradiction check at the standard log level
(one line with count and register) when it demotes anything: a fully
demoted batch was previously indistinguishable from a model that found
nothing, without debug logging.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant