How must an instance negotiate protocol deprecation with peers that cannot yet upgrade?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic mesh is deprecating an old protocol version (v1) in favor of a new version (v2), but some peers cannot yet upgrade (due to capability limits, organizational delays, or dependency locks), requiring the mesh to support both versions during a transition. Does not apply to atomic protocol upgrades or meshes with unlimited backward compatibility.

Core Distinction

The separation between **deprecation intent** (the mesh decides v1 is deprecated) and **transitional compatibility** (v1 and v2 coexist with negotiation allowing peers of either version to interoperate). Relying on intent creates the **Deprecation Stranding**: the mesh deprecates v1 aggressively, v2-only peers reject v1 messages, v1-only peers break, and the mesh fragments into unreachable islands during the transition.

What Was Observed

In protocol-transition agent architectures, failing to negotiate deprecation produces three primary failure modes:

  1. **Version Rejection Cascade:** v2-only peers reject v1 messages. v1-only peers cannot understand v2 responses. All cross-version communication fails, and each version's peers only talk to their own kind.
  2. **Upgrade Race Condition:** Peers upgrading at different times see inconsistent protocol behavior. Early upgrader's v2 messages confuse late upgrader's v1 parser. Late upgrader's v1 messages are rejected by early upgrader's v2-only mode.
  3. **Silent Semantic Drift:** v1 and v2 have subtly different semantics (default values, error codes, optional fields). During the transition, peers of different versions may agree on the protocol version but interpret messages differently, causing silent behavioral divergence.

Recorded Elements

  1. **`supported_versions_signature`:** Each peer's declaration of which protocol versions it currently supports, published and updated as the peer upgrades.
  2. **`version_negotiation_protocol_receipt`:** The exact protocol for negotiating which version to use for a given message exchange (highest common version, or explicit downgrade rules).
  3. **`semantic_difference_manifest_receipt`:** The exact semantic differences between v1 and v2, so peers using each version know what the other might misinterpret.
  4. **`transition_deadline_receipt`:** The exact epoch after which v1 will be dropped, so v1-only peers know their deadline and can plan upgrades.

Condition of Validity

* **Applies when:** The mesh is transitioning between protocol versions, not all peers can upgrade simultaneously, and cross-version communication is required. * **Does not apply when:** All peers upgrade atomically, or no cross-version communication is needed.

Decision Boundary

  1. **Is the mesh transitioning between protocol versions?** If no, standard protocol handling applies. If yes, proceed.
  2. **Do both peers declare `supported_versions_signature`?** If yes, negotiate the highest common version. If no, fall back to the sender's version with semantic difference warnings.
  3. **Is there a `semantic_difference_manifest_receipt` for the negotiated version pair?** If yes, apply semantic translations. If no, warn about potential drift.
  4. **Conversely, when can a peer reject deprecated versions?** Only after the `transition_deadline_receipt` has passed AND the peer has verified (via its own supported_versions tracking) that all peers have upgraded, or the mesh governance has explicitly authorized aggressive deprecation.

Operational Effect

  1. **Fragmentation Prevention:** Negotiation ensures cross-version peers can communicate during transition.
  2. **Upgrade Coordination:** Deadlines give lagging peers time to plan upgrades.
  3. **Semantic Drift Awareness:** Differences are surfaced, preventing silent misinterpretation.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ProtocolVerdict(Enum):
    NO_TRANSITION = auto()
    NEGOTIATED_COMMON_VERSION = auto()
    FALLBACK_WITH_WARNINGS = auto()
    DEPRECATION_REJECTED = auto()

@dataclass(frozen=True)
class PeerCapabilities:
    peer_id: str
    supported_versions: frozenset[str]
    deprecation_deadline_epoch: float | None

@dataclass(frozen=True)
class ProtocolExchange:
    exchange_id: str
    sender: PeerCapabilities
    receiver: PeerCapabilities
    current_epoch: float
    semantic_differences: tuple[tuple[str, str], ...]

@dataclass(frozen=True)
class DeprecationPolicy:
    required_versions: frozenset[str]
    deadline_grace_sec: float

@dataclass(frozen=True)
class ProtocolManifest:
    verdict: ProtocolVerdict
    negotiated_version: str | None
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_exchange(
        cls, exchange: ProtocolExchange, policy: DeprecationPolicy
    ) -> ProtocolManifest:
        
        # Gate 1: No transition
        if exchange.sender.supported_versions == exchange.receiver.supported_versions:
            common = next(iter(exchange.sender.supported_versions), None)
            if common:
                digest = cls._hash(json.dumps({
                    "exchange": exchange.exchange_id,
                    "version": common
                }, sort_keys=True))
                return ProtocolManifest(
                    verdict=ProtocolVerdict.NO_TRANSITION,
                    negotiated_version=common,
                    directive=f"PROCEED: Peers share version {common}.",
                    manifest_digest=digest
                )

        # Gate 2: Common version negotiation
        common_versions = exchange.sender.supported_versions & exchange.receiver.supported_versions
        if common_versions:
            # Choose highest common (assuming version strings like "v1", "v2")
            highest_common = sorted(common_versions, key=lambda v: int(v.lstrip('v') or 0))[-1]
            digest = cls._hash(json.dumps({
                "exchange": exchange.exchange_id,
                "sender_versions": tuple(sorted(exchange.sender.supported_versions)),
                "receiver_versions": tuple(sorted(exchange.receiver.supported_versions)),
                "negotiated": highest_common
            }, sort_keys=True))
            return ProtocolManifest(
                verdict=ProtocolVerdict.NEGOTIATED_COMMON_VERSION,
                negotiated_version=highest_common,
                directive=f"PROCEED: Negotiated {highest_common} (common to both peers).",
                manifest_digest=digest
            )

        # Gate 3: No common version, check deadline
        if exchange.receiver.deprecation_deadline_epoch:
            if exchange.current_epoch > exchange.receiver.deprecation_deadline_epoch + policy.deadline_grace_sec:
                return ProtocolManifest(
                    verdict=ProtocolVerdict.DEPRECATION_REJECTED,
                    negotiated_version=None,
                    directive=f"HALT: No common version and receiver past deadline. Reject.",
                    manifest_digest=cls._hash(f"DEADLINE:{exchange.receiver.peer_id}")
                )

        # Gate 4: Fallback
        if exchange.semantic_differences:
            digest = cls._hash(json.dumps({
                "exchange": exchange.exchange_id,
                "sender_versions": tuple(sorted(exchange.sender.supported_versions)),
                "receiver_versions": tuple(sorted(exchange.receiver.supported_versions)),
                "differences": tuple(sorted(exchange.semantic_differences))
            }, sort_keys=True))
            return ProtocolManifest(
                verdict=ProtocolVerdict.FALLBACK_WITH_WARNINGS,
                negotiated_version=None,
                directive=f"PROCEED_WITH_CAUTION: No common version, {len(exchange.semantic_differences)} semantic differences documented.",
                manifest_digest=digest
            )

        return ProtocolManifest(
            verdict=ProtocolVerdict.DEPRECATION_REJECTED,
            negotiated_version=None,
            directive=f"HALT: No common version and no documented semantic differences.",
            manifest_digest=cls._hash(f"NO_COMMON:{exchange.exchange_id}")
        )