What multicast fan-out invariants must survive heterogeneous receiver capabilities?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session sends a message to multiple peers (multicast, broadcast, or fan-out), and the receivers have heterogeneous capabilities (different protocol versions, different buffer sizes, different processing speeds). Does not apply to strictly point-to-point messaging or fan-out to homogeneous receivers.

Core Distinction

The separation between **fan-out transmission** (the message is sent to all receivers) and **fan-out delivery** (each receiver successfully processes the message). Relying on transmission creates the **Heterogeneous Fanout Degradation**: receivers with weaker capabilities cannot process the message at the speed or format the sender provided, causing some to fail silently while others succeed, and the sender has no visibility into which receivers actually got the message.

What Was Observed

In heterogeneous fan-out architectures, failing to account for receiver capability variance produces three primary failure modes:

  1. **Buffer Overflow Slaughter:** The sender fans out to 100 receivers. 20 have small buffers and cannot accept the full message. Their transports drop the message silently, and the sender thinks all 100 received.
  2. **Protocol Version Rejection:** The message is sent in v2 format. Half the receivers only support v1. Those receivers reject the message, but the sender has no way to know unless it queries.
  3. **Slow Receiver Dominance:** If the sender waits for the slowest receiver to acknowledge, one slow receiver (with 100x the processing time) blocks the entire fan-out, and fast receivers have already moved on.

Recorded Elements

  1. **`receiver_capability_registry_receipt`:** For each receiver, its supported protocol versions, buffer size, and typical processing speed, so the sender knows what each can handle.
  2. **`per_receiver_delivery_receipt`:** For each receiver, a cryptographic acknowledgment of successful processing (or failure reason), so the sender can track actual delivery, not just transmission.
  3. **`capability_aware_encoding_signature`:** The encoding or format chosen based on the receiver set (e.g., "use v1 format when any receiver requires v1," "chunk messages when any receiver has small buffers").
  4. **`fanout_completion_threshold_signature`:** The exact threshold for considering the fan-out complete (all receivers, majority of receivers, or all critical receivers), preventing one slow receiver from blocking.

Condition of Validity

* **Applies when:** Messages are sent to multiple receivers, receiver capabilities vary, and sender needs delivery certainty. * **Does not apply when:** Receivers are homogeneous, or fan-out is strictly best-effort (no delivery certainty needed).

Decision Boundary

  1. **Is the fan-out to heterogeneous receivers?** If no, standard multicast applies. If yes, proceed.
  2. **Is the `receiver_capability_registry_receipt` current?** If yes, use it to choose encoding and chunking. If no, assume worst-case capabilities.
  3. **Is `capability_aware_encoding_signature` applied?** If yes, encoding matches receivers. If no, the message may fail for some receivers.
  4. **Conversely, when can a sender skip capability awareness?** Only when the message is strictly format-agnostic (receivers handle any format), or all receivers are verified to support the sender's default format.

Operational Effect

  1. **Delivery Certainty:** Per-receiver receipts distinguish transmission from delivery.
  2. **Capability Matching:** Encoding adapts to receiver capabilities.
  3. **Slow Receiver Isolation:** Completion threshold prevents one slow receiver from blocking the fan-out.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class FanoutVerdict(Enum):
    HOMOGENEOUS = auto()
    CAPABILITY_AWARE_FANOUT = auto()
    ENCODING_MISMATCH = auto()
    DELIVERY_INCOMPLETE = auto()

@dataclass(frozen=True)
class Receiver:
    receiver_id: str
    supported_versions: frozenset[str]
    buffer_size_bytes: int
    typical_processing_ms: float
    is_critical: bool

@dataclass(frozen=True)
class FanoutMessage:
    message_id: str
    size_bytes: int
    sender_version: str
    receivers: tuple[Receiver, ...]
    acknowledgment_rate: float

@dataclass(frozen=True)
class FanoutPolicy:
    min_acknowledgment_rate: float
    require_critical_acks: bool

@dataclass(frozen=True)
class FanoutManifest:
    verdict: FanoutVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_fanout(
        cls, msg: FanoutMessage, policy: FanoutPolicy
    ) -> FanoutManifest:
        
        if not msg.receivers:
            return FanoutManifest(
                verdict=FanoutVerdict.DELIVERY_INCOMPLETE,
                directive="HALT: No receivers.",
                manifest_digest=cls._hash("NO_RECEIVERS")
            )

        # Gate 1: Homogeneous check
        version_sets = {r.supported_versions for r in msg.receivers}
        buffer_sizes = {r.buffer_size_bytes for r in msg.receivers}
        if len(version_sets) == 1 and len(buffer_sizes) == 1:
            digest = cls._hash(json.dumps({
                "message": msg.message_id,
                "version": msg.sender_version
            }, sort_keys=True))
            return FanoutManifest(
                verdict=FanoutVerdict.HOMOGENEOUS,
                directive=f"PROCEED: Homogeneous receivers, standard multicast.",
                manifest_digest=digest
            )

        # Gate 2: Version compatibility
        incompatible = [r.receiver_id for r in msg.receivers if msg.sender_version not in r.supported_versions]
        if incompatible:
            return FanoutManifest(
                verdict=FanoutVerdict.ENCODING_MISMATCH,
                directive=f"HALT: {len(incompatible)} receivers do not support {msg.sender_version}.",
                manifest_digest=cls._hash(f"VERSION_MISMATCH:{len(incompatible)}")
            )

        # Gate 3: Buffer size check
        undersized = [r.receiver_id for r in msg.receivers if r.buffer_size_bytes < msg.size_bytes]
        if undersized:
            return FanoutManifest(
                verdict=FanoutVerdict.ENCODING_MISMATCH,
                directive=f"ACTION: {len(undersized)} receivers have smaller buffers than message size. Chunk required.",
                manifest_digest=cls._hash(f"SMALL_BUFFER:{len(undersized)}")
            )

        # Gate 4: Acknowledgment
        if msg.acknowledgment_rate < policy.min_acknowledgment_rate:
            return FanoutManifest(
                verdict=FanoutVerdict.DELIVERY_INCOMPLETE,
                directive=f"HALT: Ack rate {msg.acknowledgment_rate:.2%} < {policy.min_acknowledgment_rate:.2%}.",
                manifest_digest=cls._hash(f"LOW_ACK:{msg.acknowledgment_rate:.3f}")
            )

        # Gate 5: Capability-aware fanout
        digest = cls._hash(json.dumps({
            "message": msg.message_id,
            "receivers": tuple(sorted(r.receiver_id for r in msg.receivers)),
            "versions": tuple(sorted(str(v) for v in version_sets)),
            "ack_rate": f"{msg.acknowledgment_rate:.3f}"
        }, sort_keys=True))
        
        return FanoutManifest(
            verdict=FanoutVerdict.CAPABILITY_AWARE_FANOUT,
            directive=f"PROCEED: {len(msg.receivers)} receivers with varied capabilities, {msg.acknowledgment_rate:.1%} ack.",
            manifest_digest=digest
        )