What message-ordering invariants must survive when the transport lacks ordering guarantees?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session exchanges messages with peers over a transport that does not guarantee delivery order (UDP, HTTP/2 multiplexing, multi-path TCP, or peer-to-peer mesh), and the messages have causal dependencies that must be preserved. Does not apply to transports with strict ordering guarantees (single TCP stream, in-order message queues).

Core Distinction

The separation between **transport delivery** (the message reaches the peer at some point) and **causal ordering** (messages that have dependencies arrive in the correct relative order). Relying on transport creates the **Causal Inversion Hazard**: message B depends on message A, but B arrives before A due to transport reordering, and the receiver processes B as if A were already handled, causing incorrect state or missed dependencies.

What Was Observed

In agent architectures using unordered transports, failing to preserve causal ordering produces three primary failure modes:

  1. **Dependent Action Before Precondition:** Message B says "delete the resource" and depends on message A "create the resource." B arrives before A, and the receiver deletes a non-existent resource or deletes a stale one with the same ID.
  2. **State Update Skipping:** Messages A and B update the same field. A arrives after B, overwriting B's update. The final state reflects A, not the intended B.
  3. **Causal Chain Break:** A chain of five messages has strict ordering. Transport reorders them, the receiver processes message 3 first, and fails because the causal antecedents (1 and 2) are missing.

Recorded Elements

  1. **`causal_dependency_graph_hash`:** The hash of the dependency graph each message belongs to, allowing the receiver to detect missing or out-of-order antecedents.
  2. **`logical_clock_value`:** A Lamport or vector clock value attached to each message, allowing the receiver to order messages by causality rather than arrival.
  3. **`pending_message_buffer_receipt`:** When a message arrives before its antecedents, the receiver buffers it and records the buffering receipt, allowing the agent to detect stalled causal chains.
  4. **`delivery_reorder_signature`:** The exact reorder pattern observed (which messages arrived out of order, in what sequence), enabling detection of persistent transport-level issues.

Condition of Validity

* **Applies when:** The transport lacks ordering guarantees, messages have causal dependencies, and out-of-order delivery is possible. * **Does not apply when:** The transport guarantees ordering (single TCP stream, dedicated queue), or the messages are strictly commutative and independent.

Decision Boundary

  1. **Does the message include causal dependency information?** If no, the receiver cannot order it. It must either use transport order (accepting risk) or require explicit dependency annotation.
  2. **Are all causal antecedents present in the receiver's state?** If yes, process immediately. If no, buffer the message until antecedents arrive.
  3. **Has the message been buffered for longer than the acceptable window (e.g., 30 seconds)?** If yes, escalate — the causal chain may be broken, requiring re-delivery or re-synchronization.
  4. **Conversely, when can the receiver process messages without causal ordering?** Only when the messages are strictly commutative and idempotent, or when the message is explicitly marked as `ORDER_INDEPENDENT`.

Operational Effect

  1. **Causal Correctness:** Ensures dependent actions execute in the correct order regardless of transport.
  2. **Detectable Stalls:** Makes causal chain breaks visible via buffering timeouts.
  3. **Safe Idempotency:** Allows independent messages to bypass ordering, preserving throughput.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class OrderingVerdict(Enum):
    PROCESS_IMMEDIATELY = auto()
    BUFFER_WAIT_FOR_ANTECEDENT = auto()
    ESCALATE_BROKEN_CHAIN = auto()
    ORDER_INDEPENDENT = auto()

@dataclass(frozen=True)
class OrderedMessage:
    message_id: str
    logical_clock: int
    causal_dependencies: frozenset[str]
    order_independent: bool

@dataclass(frozen=True)
class ReceiverState:
    processed_message_ids: frozenset[str]
    buffered_messages: tuple[str, ...]
    current_epoch: float
    buffer_start_epoch: dict[str, float]

@dataclass(frozen=True)
class OrderingPolicy:
    max_buffer_age_sec: float
    current_epoch: float

@dataclass(frozen=True)
class OrderingManifest:
    verdict: OrderingVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_message(
        cls, msg: OrderedMessage, state: ReceiverState, policy: OrderingPolicy
    ) -> OrderingManifest:
        
        # Gate 1: Order independent
        if msg.order_independent:
            digest = cls._hash(json.dumps({
                "message": msg.message_id,
                "status": "ORDER_INDEPENDENT"
            }, sort_keys=True))
            return OrderingManifest(
                verdict=OrderingVerdict.ORDER_INDEPENDENT,
                directive=f"PROCEED: Message {msg.message_id} is order-independent.",
                manifest_digest=digest
            )

        # Gate 2: Antecedents present
        missing = msg.causal_dependencies - state.processed_message_ids
        if not missing:
            digest = cls._hash(json.dumps({
                "message": msg.message_id,
                "clock": str(msg.logical_clock),
                "antecedents": tuple(sorted(msg.causal_dependencies))
            }, sort_keys=True))
            return OrderingManifest(
                verdict=OrderingVerdict.PROCESS_IMMEDIATELY,
                directive=f"PROCEED: All antecedents present for {msg.message_id}.",
                manifest_digest=digest
            )

        # Gate 3: Buffer with timeout
        if msg.message_id in state.buffer_start_epoch:
            age = policy.current_epoch - state.buffer_start_epoch[msg.message_id]
            if age > policy.max_buffer_age_sec:
                return OrderingManifest(
                    verdict=OrderingVerdict.ESCALATE_BROKEN_CHAIN,
                    directive=f"HALT: Message {msg.message_id} buffered {age:.0f}s > {policy.max_buffer_age_sec:.0f}s. Chain broken.",
                    manifest_digest=cls._hash(f"BROKEN:{msg.message_id}")
                )

        # Gate 4: Buffer
        digest = cls._hash(json.dumps({
            "message": msg.message_id,
            "missing": tuple(sorted(missing))
        }, sort_keys=True))
        
        return OrderingManifest(
            verdict=OrderingVerdict.BUFFER_WAIT_FOR_ANTECEDENT,
            directive=f"BUFFER: {msg.message_id} waiting for {sorted(missing)}.",
            manifest_digest=digest
        )