What communication invariants must survive when agents speak different protocol versions?

**Date of Record:** 2026 October 26 **Scope:** Applies when two autonomous agents attempt to communicate using different versions of the same protocol (e.g., v1.0 vs. v2.0), and the message format, semantics, or field requirements have changed between versions. Does not apply to fully backward-compatible protocol evolutions, or agents negotiating a common version before any communication.

Core Distinction

The separation between **syntactic compatibility** (the message parses in both versions, e.g., JSON field names overlap) and **semantic compatibility** (the fields mean the same thing, have the same constraints, and produce the same downstream behavior). Relying on syntax creates the **Version Semantic Drift**: the message parses, but a field that meant "priority" in v1 now means "deprecated" in v2, and the receiving agent behaves incorrectly.

What Was Observed

In multi-version agent protocols, failing to preserve semantic invariants produces three primary failure modes:

  1. **Field Semantic Shift:** A field named `status` changes from an enum (`active`, `inactive`) in v1 to a free-form string in v2. The v1 agent sends `active`, the v2 agent interprets it as arbitrary text and processes downstream differently.
  2. **Required Field Addition:** V2 requires a new field `tenant_id` that v1 does not send. The v2 receiver rejects the message, or worse, defaults `tenant_id` to a value that causes cross-tenant operations.
  3. **Constraint Tightening:** V2 enforces a maximum payload size of 1MB that v1 did not enforce. V1 agents that sent 2MB messages are now silently truncated or rejected, causing data loss.

Recorded Elements

  1. **`protocol_version_pair`:** The exact (sender, receiver) version pair, used to select the compatibility profile.
  2. **`field_semantic_registry`:** The authoritative mapping of field names to their meaning across protocol versions, allowing the receiver to detect semantic drift.
  3. **`field_constraint_delta`:** For each field, the explicit delta in constraints (required, type, size, allowed values) between versions.
  4. **`compatibility_profile_hash`:** The hash of the compatibility profile the agents agreed to use, ensuring both sides apply the same interpretation.

Condition of Validity

* **Applies when:** The protocol has multiple active versions in the wild, the message format parses across versions, but the semantics or constraints have changed. * **Does not apply when:** All agents use the same protocol version, or the protocol evolution is strictly backward-compatible by design (e.g., adding optional fields only).

Decision Boundary

  1. **Are both agents using the same protocol version?** If yes, standard processing applies. If no, proceed.
  2. **Do the agents have a shared `field_semantic_registry` and `compatibility_profile_hash`?** If no, semantic drift cannot be detected. The receiver must reject the message.
  3. **Do any fields exhibit `field_constraint_delta` (required, type, or size changes)?** If yes, the receiver must apply the compatibility profile to translate or reject based on the delta.
  4. **Conversely, when can a receiver accept a different-version message without compatibility checks?** Only when the protocol has an explicit, versioned compatibility contract that guarantees backward and forward compatibility, and the agents have verified they are using the same contract.

Operational Effect

  1. **Semantic Drift Detection:** Prevents silent misinterpretation of fields with changed meaning across versions.
  2. **Constraint Enforcement:** Rejects messages that would violate the receiving version's constraints, preventing data loss or corruption.
  3. **Deterministic Interop:** Provides a clear compatibility profile for cross-version communication, eliminating ad-hoc translation logic.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ProtocolCompatVerdict(Enum):
    SAME_VERSION = auto()
    COMPATIBLE_WITH_PROFILE = auto()
    SEMANTIC_DRIFT_DETECTED = auto()
    CONSTRAINT_VIOLATION = auto()

@dataclass(frozen=True)
class VersionPair:
    sender_version: str
    receiver_version: str

@dataclass(frozen=True)
class FieldDelta:
    field_name: str
    sender_meaning: str
    receiver_meaning: str
    constraint_changed: bool

@dataclass(frozen=True)
class CompatibilityProfile:
    profile_hash: str
    known_deltas: tuple[FieldDelta, ...]

@dataclass(frozen=True)
class CompatManifest:
    verdict: ProtocolCompatVerdict
    drift_fields: tuple[str, ...]
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_compatibility(
        cls, pair: VersionPair, profile: CompatibilityProfile
    ) -> CompatManifest:
        
        # Gate 1: Same version
        if pair.sender_version == pair.receiver_version:
            digest = cls._hash(json.dumps({
                "version": pair.sender_version
            }, sort_keys=True))
            return CompatManifest(
                verdict=ProtocolCompatVerdict.SAME_VERSION,
                drift_fields=tuple(),
                directive="PROCEED: Same protocol version.",
                manifest_digest=digest
            )

        # Gate 2: Profile hash presence
        if not profile.profile_hash:
            return CompatManifest(
                verdict=ProtocolCompatVerdict.SEMANTIC_DRIFT_DETECTED,
                drift_fields=tuple(),
                directive="HALT: No compatibility profile. Cannot verify cross-version safety.",
                manifest_digest=cls._hash("NO_PROFILE")
            )

        # Gate 3: Semantic drift
        drift_fields = tuple(
            d.field_name for d in profile.known_deltas
            if d.sender_meaning != d.receiver_meaning
        )
        if drift_fields:
            return CompatManifest(
                verdict=ProtocolCompatVerdict.SEMANTIC_DRIFT_DETECTED,
                drift_fields=drift_fields,
                directive=f"HALT: Semantic drift in fields {drift_fields}.",
                manifest_digest=cls._hash(f"DRIFT:{','.join(drift_fields)}")
            )

        # Gate 4: Constraint violations
        constraint_fields = tuple(
            d.field_name for d in profile.known_deltas if d.constraint_changed
        )
        if constraint_fields:
            return CompatManifest(
                verdict=ProtocolCompatVerdict.CONSTRAINT_VIOLATION,
                drift_fields=constraint_fields,
                directive=f"HALT: Constraint changes in fields {constraint_fields}.",
                manifest_digest=cls._hash(f"CONSTRAINT:{','.join(constraint_fields)}")
            )

        # Gate 5: Compatible
        digest = cls._hash(json.dumps({
            "sender": pair.sender_version,
            "receiver": pair.receiver_version,
            "profile": profile.profile_hash
        }, sort_keys=True))
        
        return CompatManifest(
            verdict=ProtocolCompatVerdict.COMPATIBLE_WITH_PROFILE,
            drift_fields=tuple(),
            directive=f"PROCEED: Cross-version compatible via profile {profile.profile_hash}.",
            manifest_digest=digest
        )