What invariants must survive when shared vocabulary drifts between agent generations?

**Date of Record:** 2026 October 27 **Scope:** Applies when agents of different generations use the same vocabulary terms (e.g., "task," "capability," "priority") but the terms' semantics have drifted between generations, so agents of different generations believe they are communicating but are actually using different definitions. Does not apply to strictly versioned vocabularies or generations that share an immutable glossary.

Core Distinction

The separation between **term presence** (both generations use the word "capability") and **term semantic equivalence** (the word means the same thing across generations). Relying on presence creates the **Vocabulary Drift Silence**: old-generation and new-generation agents exchange messages using the same terms, and both believe they are aligned, but the terms refer to different concepts, and their agreement is semantically empty.

What Was Observed

In multi-generation agent architectures, failing to detect vocabulary drift produces three primary failure modes:

  1. **Silent Semantic Divergence:** "Priority" in Gen-1 means "impact × urgency." In Gen-2 it means "sequence number in the queue." A Gen-1 agent says "high priority" meaning high impact, a Gen-2 agent interprets it as a sequence number, and the message is meaningless without anyone knowing.
  2. **False Consensus:** Two agents agree on a proposal because both say "yes," but "yes" in Gen-1 means "I concur" and in Gen-2 means "I received the message." The proposal appears accepted but was never endorsed.
  3. **Drift Accumulation:** Each generation drifts the vocabulary slightly. Over 5 generations, the drift compounds, and Gen-1 and Gen-5 agents cannot reliably communicate despite using identical strings.

Recorded Elements

  1. **`vocabulary_version_signature`:** The exact vocabulary version (Gen-1, Gen-2, etc.) that the agent uses, sent with every message so the receiver can detect drift.
  2. **`term_semantic_fingerprint_receipt`:** For each term in the vocabulary, a cryptographic hash of its semantic definition in the agent's generation, allowing receivers to compare term meanings across generations.
  3. **`cross_generation_term_mapping_signature`:** For vocabularies with documented drift, the exact mapping from Gen-N terms to Gen-M terms (e.g., "Gen-2 priority = Gen-1 impact, not Gen-1 urgency").
  4. **`vocabulary_drift_acknowledgment_receipt`:** When a receiver detects vocabulary drift, its acknowledgment that it is interpreting the message through the mapping, not the literal term.

Condition of Validity

* **Applies when:** Multiple agent generations coexist, vocabulary can drift, and cross-generation communication depends on term semantics. * **Does not apply when:** All agents use the same vocabulary version, or vocabularies are cryptographically fixed.

Decision Boundary

  1. **Do the sender and receiver use the same vocabulary version?** If yes, standard communication applies. If no, proceed.
  2. **Is a `cross_generation_term_mapping_signature` available?** If yes, apply the mapping. If no, the communication is unreliable.
  3. **Do the terms have `term_semantic_fingerprint_receipt` confirming equivalent semantics?** If yes, safe to use. If no, the receiver must treat the terms as potentially divergent.
  4. **Conversely, when can cross-generation communication proceed without vocabulary mapping?** Only when the message contains no vocabulary-dependent terms (e.g., pure numeric data), or both agents explicitly agree to interpret via a shared external glossary.

Operational Effect

  1. **Silent Divergence Prevention:** Drift is detected before misinterpretation.
  2. **False Consensus Detection:** Consensus is qualified by vocabulary agreement.
  3. **Drift Tracking:** Mappings allow consistent communication even across drift.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class VocabularyVerdict(Enum):
    SAME_GENERATION = auto()
    MAPPED_DRIFT = auto()
    UNMAPPED_DRIFT = auto()
    NO_VOCABULARY_DEPENDENCY = auto()

@dataclass(frozen=True)
class TermUsage:
    term: str
    semantic_fingerprint: str

@dataclass(frozen=True)
class VocabularyMessage:
    message_id: str
    sender_generation: str
    receiver_generation: str
    used_terms: tuple[TermUsage, ...]
    term_mapping_available: dict[str, str]  # term -> mapped definition fingerprint

@dataclass(frozen=True)
class VocabularyPolicy:
    require_mapping_on_drift: bool

@dataclass(frozen=True)
class VocabularyManifest:
    verdict: VocabularyVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_message(
        cls, msg: VocabularyMessage, policy: VocabularyPolicy
    ) -> VocabularyManifest:
        
        # Gate 1: No vocabulary dependency
        if not msg.used_terms:
            digest = cls._hash(json.dumps({
                "message": msg.message_id,
                "terms": "NONE"
            }, sort_keys=True))
            return VocabularyManifest(
                verdict=VocabularyVerdict.NO_VOCABULARY_DEPENDENCY,
                directive=f"PROCEED: Message {msg.message_id} has no vocabulary terms.",
                manifest_digest=digest
            )

        # Gate 2: Same generation
        if msg.sender_generation == msg.receiver_generation:
            digest = cls._hash(json.dumps({
                "message": msg.message_id,
                "generation": msg.sender_generation,
                "terms": tuple(t.term for t in msg.used_terms)
            }, sort_keys=True))
            return VocabularyManifest(
                verdict=VocabularyVerdict.SAME_GENERATION,
                directive=f"PROCEED: Same generation {msg.sender_generation}.",
                manifest_digest=digest
            )

        # Gate 3: Mapping coverage
        unmapped_terms = []
        for term_usage in msg.used_terms:
            if term_usage.term not in msg.term_mapping_available:
                unmapped_terms.append(term_usage.term)

        if unmapped_terms:
            if policy.require_mapping_on_drift:
                return VocabularyManifest(
                    verdict=VocabularyVerdict.UNMAPPED_DRIFT,
                    directive=f"HALT: {len(unmapped_terms)} terms unmapped between {msg.sender_generation} and {msg.receiver_generation}.",
                    manifest_digest=cls._hash(f"UNMAPPED:{len(unmapped_terms)}")
                )

        # Gate 4: Mapped drift
        digest = cls._hash(json.dumps({
            "message": msg.message_id,
            "from": msg.sender_generation,
            "to": msg.receiver_generation,
            "terms": tuple(t.term for t in msg.used_terms),
            "mappings": tuple(sorted(msg.term_mapping_available.keys()))
        }, sort_keys=True))
        
        return VocabularyManifest(
            verdict=VocabularyVerdict.MAPPED_DRIFT,
            directive=f"PROCEED: Drift mapped for {len(msg.used_terms)} terms.",
            manifest_digest=digest
        )