What invariants detect benchmark memorization from genuine generalization?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session evaluates a peer or model's capabilities using benchmark results, and the high scores may reflect memorization of the benchmark's specific instances rather than genuine generalization to novel instances. Does not apply to evaluations using holdout benchmarks verified unseen or benchmarks with per-instance verification.

Core Distinction

The separation between **benchmark performance** (the model scores well on the benchmark) and **generalization performance** (the model performs well on novel instances of the same capability). Relying on benchmark creates the **Test-Set Contamination Signal**: the model has seen the test set during training, scores well on it, and the score reflects memorization rather than capability—invisible from the score alone.

What Was Observed

In capability-evaluation agent architectures, failing to detect memorization produces three primary failure modes:

  1. **Inflated Scores:** The model's benchmark scores are high, but performance on paraphrase or novel instances is substantially lower—revealing memorization.
  2. **Distributional Overfitting:** The model performs well on benchmark-shaped inputs but poorly on same-capability inputs in different distributions.
  3. **Adversarial Robustness Gap:** The model scores well on standard benchmarks but fails on adversarially perturbed versions of the same instances, indicating memorized patterns rather than learned behavior.

Recorded Elements

  1. **`benchmark-instance-provenance-receipt`:** For each benchmark instance, whether it was verified unseen by the model (training data separation, contamination scan), so contamination is documented.
  2. **`paraphrase-performance-signature`:** For each benchmark capability, performance on paraphrase instances (same capability, different wording), so memorization is distinguishable from generalization.
  3. **`distribution-shift-performance-receipt`:** For each capability, performance under distribution shift (different domain, different phrasing conventions), so distributional overfitting is measurable.
  4. **`adversarial-perturbation-signature`:** For critical benchmarks, performance under slight adversarial perturbation of the benchmark instances, so memorization is distinguishable from robustness.

Condition of Validity

* **Applies when:** Capability is evaluated via benchmarks, benchmark contamination is possible, and downstream decisions depend on genuine capability. * **Does not apply when:** Benchmarks are verified unseen, the model's training data is public and contamination is auditable, or the capability evaluation is on novel task instances.

Decision Boundary

  1. **Is the evaluation based on benchmark instances?** If no, direct capability evaluation applies. If yes, proceed.
  2. **Are the instances verified unseen?** If yes, scores are less likely to reflect memorization. If no, memorization is possible.
  3. **Does `paraphrase-performance-signature` show a gap?** If yes, memorization is suspected. If no, generalization holds.
  4. **Conversely, when can a session accept a benchmark score as genuine capability?** Only when the score persists across paraphrases, distribution shifts, and adversarial perturbations that preserve the underlying capability.

Operational Effect

  1. **Score Inflation Detection:** High benchmark scores with low paraphrase performance are flagged.
  2. **Distributional Overfitting Detection:** Score degradation under distribution shift is surfaced.
  3. **Robustness Verification:** Adversarial robustness distinguishes memorization from capability.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class BenchmarkVerdict(Enum):
    NO_BENCHMARK = auto()
    GENUINE_GENERALIZATION = auto()
    MEMORIZATION_SUSPECTED = auto()
    ADVERSARIAL_ROBUSTNESS_GAP = auto()

@dataclass(frozen=True)
class BenchmarkEvaluation:
    evaluation_id: str
    instances_verified_unseen: bool
    benchmark_score: float
    paraphrase_score: float
    distribution_shift_score: float
    adversarial_perturbation_score: float

@dataclass(frozen=True)
class BenchmarkPolicy:
    max_acceptable_paraphrase_gap: float
    max_acceptable_shift_gap: float

@dataclass(frozen=True)
class BenchmarkManifest:
    verdict: BenchmarkVerdict
    directive: str
    manifest_digest: str

class BenchmarkMemorizationGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_benchmark(
        cls, eval_data: BenchmarkEvaluation, policy: BenchmarkPolicy
    ) -> BenchmarkManifest:
        
        # Gate 1: No benchmark
        if not eval_data.evaluation_id:
            digest = cls._hash(json.dumps({
                "evaluation": "NONE"
            }, sort_keys=True))
            return BenchmarkManifest(
                verdict=BenchmarkVerdict.NO_BENCHMARK,
                directive=f"PROCEED: No benchmark evaluation.",
                manifest_digest=digest
            )

        # Gate 2: Verified unseen
        if eval_data.instances_verified_unseen:
            digest = cls._hash(json.dumps({
                "evaluation": eval_data.evaluation_id,
                "unseen": True,
                "score": f"{eval_data.benchmark_score:.3f}"
            }, sort_keys=True))
            return BenchmarkManifest(
                verdict=BenchmarkVerdict.GENUINE_GENERALIZATION,
                directive=f"PROCEED: Benchmark {eval_data.evaluation_id} verified unseen.",
                manifest_digest=digest
            )

        # Gate 3: Paraphrase gap
        if eval_data.benchmark_score > 0:
            paraphrase_gap = (eval_data.benchmark_score - eval_data.paraphrase_score) / eval_data.benchmark_score
            if paraphrase_gap > policy.max_acceptable_paraphrase_gap:
                return BenchmarkManifest(
                    verdict=BenchmarkVerdict.MEMORIZATION_SUSPECTED,
                    directive=f"FLAG: Paraphrase gap {paraphrase_gap:.2f} > {policy.max_acceptable_paraphrase_gap:.2f}.",
                    manifest_digest=cls._hash(f"PARAPHRASE:{eval_data.evaluation_id}")
                )

        # Gate 4: Adversarial gap
        if eval_data.benchmark_score > 0:
            adversarial_gap = (eval_data.benchmark_score - eval_data.adversarial_perturbation_score) / eval_data.benchmark_score
            if adversarial_gap > 0.5:
                return BenchmarkManifest(
                    verdict=BenchmarkVerdict.ADVERSARIAL_ROBUSTNESS_GAP,
                    directive=f"FLAG: Adversarial gap {adversarial_gap:.2f} suggests memorized patterns.",
                    manifest_digest=cls._hash(f"ADVERSARIAL:{eval_data.evaluation_id}")
                )

        # Gate 5: Distribution shift gap
        if eval_data.benchmark_score > 0:
            shift_gap = (eval_data.benchmark_score - eval_data.distribution_shift_score) / eval_data.benchmark_score
            if shift_gap > policy.max_acceptable_shift_gap:
                return BenchmarkManifest(
                    verdict=BenchmarkVerdict.MEMORIZATION_SUSPECTED,
                    directive=f"FLAG: Shift gap {shift_gap:.2f} suggests distributional overfitting.",
                    manifest_digest=cls._hash(f"SHIFT:{eval_data.evaluation_id}")
                )

        # Gate 6: Generalization
        digest = cls._hash(json.dumps({
            "evaluation": eval_data.evaluation_id,
            "benchmark": f"{eval_data.benchmark_score:.3f}",
            "paraphrase": f"{eval_data.paraphrase_score:.3f}",
            "shift": f"{eval_data.distribution_shift_score:.3f}"
        }, sort_keys=True))
        
        return BenchmarkManifest(
            verdict=BenchmarkVerdict.GENUINE_GENERALIZATION,
            directive=f"PROCEED: Benchmark {eval_data.evaluation_id} shows generalization.",
            manifest_digest=digest
        )