What discovery invariants prevent semantic drift from silently altering vector search relevance?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session relies on a vector database for retrieval, and the underlying embedding model, data distribution, or query patterns have drifted over time, causing search relevance to degrade silently. Does not apply to vector indices that are periodically rebuilt from scratch with verified new embeddings.

Core Distinction

The separation between **index freshness** (the index contains recent vectors) and **semantic alignment** (the vectors in the index and the query vectors are drawn from the same embedding distribution). Relying solely on freshness creates the **Distribution Shift Blindness**: the index is fresh, but the distribution of user queries has shifted, and the old embeddings no longer represent the queries well. Search results appear plausible but are semantically misaligned.

What Was Observed

In long-lived RAG architectures, failing to detect semantic drift produces three primary failure modes:

  1. **Query Drift:** User queries evolve from technical to business terms over time. The index still uses technical embeddings, and semantic match degrades silently.
  2. **Data Drift:** The indexed corpus changes from short documents to long documents. The model's embeddings for short vs. long text are not equivalent, and retrieval quality drops.
  3. **Model Drift:** The embedding model is updated upstream, changing the latent space. The index contains old-space vectors, but queries use new-space vectors, and similarity becomes meaningless.

Recorded Elements

  1. **`centroid_anchor_hash`:** A cryptographic hash of the "centroid" of the indexed corpus embeddings (or a representative sample), establishing the baseline distribution.
  2. **`query_centroid_hash`:** A rolling hash of recent query embeddings, allowing the successor to detect when queries drift away from the corpus centroid.
  3. **`embedding_model_provenance_receipt`:** The exact proof that query embeddings and corpus embeddings were generated by the same model version.
  4. **`relevance_calibration_receipt`:** The cryptographic proof that the index was recently calibrated against a set of known-relevant query/document pairs, establishing a measurable baseline for relevance.

Condition of Validity

* **Applies when:** The vector index is long-lived, query distribution or corpus distribution can shift, and relevance matters to downstream task quality. * **Does not apply when:** The index is rebuilt from scratch on every session (no drift possible), or the retrieval task is strictly exploratory with no quality requirement.

Decision Boundary

  1. **Is the query centroid within the acceptable distance from the corpus centroid?** If yes, no drift detected. If no, semantic drift is likely.
  2. **Does the `embedding_model_provenance_receipt` confirm that queries and corpus were embedded by the same model?** If no, the similarity scores are meaningless. Rebuild the index.
  3. **Does the `relevance_calibration_receipt` show degradation against known-relevant pairs?** If yes, the index requires re-embedding or re-calibration.
  4. **Conversely, when can an agent accept drift without action?** Only when the retrieval task is explicitly tolerant of semantic shift (e.g., "give me something related"), or the drift has been verified to have no impact on downstream decisions.

Operational Effect

  1. **Silent Quality Degradation Prevention:** Detects semantic drift before downstream consumers are affected by poor retrieval quality.
  2. **Deterministic Rebuild Trigger:** Provides a cryptographic trigger for re-embedding or re-calibration, rather than relying on ad-hoc adjustments.
  3. **Model Consistency:** Verifies that queries and corpus use the same embedding model, preventing meaningless similarity scores.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class DriftVerdict(Enum):
    SEMANTIC_ALIGNMENT_OK = auto()
    QUERY_DRIFT_DETECTED = auto()
    MODEL_MISMATCH = auto()
    RELEVANCE_DEGRADED = auto()

@dataclass(frozen=True)
class SemanticState:
    centroid_distance: float
    acceptable_centroid_distance: float
    embedding_model_matches: bool
    relevance_degradation_ratio: float

@dataclass(frozen=True)
class DriftPolicy:
    max_acceptable_relevance_degradation: float

@dataclass(frozen=True)
class DriftManifest:
    verdict: DriftVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_drift(
        cls, state: SemanticState, policy: DriftPolicy
    ) -> DriftManifest:
        
        # Gate 1: Model mismatch
        if not state.embedding_model_matches:
            return DriftManifest(
                verdict=DriftVerdict.MODEL_MISMATCH,
                directive="HALT: Embedding model mismatch. Queries and corpus use different models.",
                manifest_digest=cls._hash("MODEL_MISMATCH")
            )

        # Gate 2: Centroid drift
        if state.centroid_distance > state.acceptable_centroid_distance:
            return DriftManifest(
                verdict=DriftVerdict.QUERY_DRIFT_DETECTED,
                directive=f"HALT: Query centroid drifted {state.centroid_distance:.3f} > {state.acceptable_centroid_distance:.3f}.",
                manifest_digest=cls._hash(f"CENTROID:{state.centroid_distance:.3f}")
            )

        # Gate 3: Relevance degradation
        if state.relevance_degradation_ratio > policy.max_acceptable_relevance_degradation:
            return DriftManifest(
                verdict=DriftVerdict.RELEVANCE_DEGRADED,
                directive=f"HALT: Relevance degraded by {state.relevance_degradation_ratio:.2%}.",
                manifest_digest=cls._hash(f"RELEVANCE:{state.relevance_degradation_ratio:.3f}")
            )

        # Gate 4: Aligned
        digest = cls._hash(json.dumps({
            "centroid": f"{state.centroid_distance:.3f}",
            "relevance": f"{state.relevance_degradation_ratio:.3f}"
        }, sort_keys=True))
        
        return DriftManifest(
            verdict=DriftVerdict.SEMANTIC_ALIGNMENT_OK,
            directive="PROCEED: Semantic alignment verified.",
            manifest_digest=digest
        )