How must agents communicate when their clocks disagree by more than the message TTL?

**Date of Record:** 2026 October 26 **Scope:** Applies when two autonomous agents attempt to communicate and their wall clocks differ by more than the TTL (time-to-live) of the messages being exchanged, such that messages may appear expired at the receiver or events may appear out of order. Does not apply to systems using strictly logical clocks for ordering, or to agents operating in a shared time domain (same host or same time-sync service).

Core Distinction

The separation between **clock disagreement** (the two agents' clocks differ) and **TTL invalidation** (the difference exceeds the TTL, causing messages to be rejected as expired). Relying solely on wall-clock TTLs creates the **False Expiration Cascade**: Agent A sends a message at 12:00:00 with a 60-second TTL. Agent B's clock is 2 minutes ahead, so at 12:02:00 B's local time, the message appears to be 2 minutes old, and B rejects it as expired, even though it just arrived.

What Was Observed

In distributed agent protocols relying on wall-clock TTLs, failing to handle extreme clock disagreement produces three primary failure modes:

  1. **Systematic Message Rejection:** Agents with fast clocks reject messages from slow-clock agents as expired, breaking communication.
  2. **Ordering Inversion:** Messages from the fast-clock agent appear to have been sent "in the future" by the slow-clock agent, causing ordering inversions in the receiver's log.
  3. **Replay Acceptance:** A message replayed hours later appears fresh because the receiver's clock is behind, allowing replayed attacks.

Recorded Elements

  1. **`measured_clock_drift_ms`:** The exact delta between the two agents' clocks, established via a challenge-response handshake at session start.
  2. **`drift_adjusted_ttl`:** The TTL extended by the measured clock drift, so messages do not expire prematurely due to skew.
  3. **`logical_nonce`:** A per-message nonce (independent of wall clock) that provides ordering and replay protection, unaffected by clock skew.
  4. **`drift_reconciliation_predicate`:** The exact check the agents execute to determine if the drift has changed (via periodic handshakes) and adjust the TTL accordingly.

Condition of Validity

* **Applies when:** The protocol uses wall-clock TTLs for message validity, the two agents' clocks can drift beyond the TTL, and messages are replayed or expire incorrectly due to drift. * **Does not apply when:** The protocol uses logical clocks (Lamport, vector clocks) for ordering and validity, or both agents are synchronized via a trusted time source with drift bounds tighter than the TTL.

Decision Boundary

  1. **Have the agents measured `measured_clock_drift_ms`?** If no, the drift is unknown. The instance must execute a handshake before trusting any TTL.
  2. **Does the drift exceed the message TTL?** If no, standard TTL behavior applies. If yes, the instance must adjust TTL by the drift and rely on `logical_nonce` for replay protection.
  3. **Is the `logical_nonce` monotonically increasing per agent?** If no, replay protection is compromised. The instance must enforce monotonic nonces.
  4. **Conversely, when can an agent accept TTL-based expiry without drift adjustment?** Only when the drift is below half the TTL (leaving margin for minor variations), or the message content is strictly non-sensitive to replay (e.g., informational updates).

Operational Effect

  1. **Message Acceptance:** Prevents messages from being rejected as expired due to clock skew, preserving communication reliability.
  2. **Ordering Integrity:** Uses logical nonces for ordering, unaffected by clock disagreement.
  3. **Replay Prevention:** Uses monotonically increasing nonces to prevent replay attacks even when the receiver's clock is behind.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class DriftVerdict(Enum):
    NO_DRIFT_ADJUSTMENT_NEEDED = auto()
    DRIFT_ACCOMMODATED = auto()
    DRIFT_REQUIRES_HANDSHAKE = auto()
    NONCE_REPLAY_DETECTED = auto()

@dataclass(frozen=True)
class ClockDriftState:
    measured_clock_drift_ms: float | None
    message_ttl_ms: float
    last_nonce_seen: int

@dataclass(frozen=True)
class IncomingMessage:
    message_id: str
    sender_epoch: float
    logical_nonce: int
    payload_hash: str

@dataclass(frozen=True)
class DriftPolicy:
    max_acceptable_drift_ms: float

@dataclass(frozen=True)
class DriftManifest:
    verdict: DriftVerdict
    effective_ttl_ms: float
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_message(
        cls, state: ClockDriftState, message: IncomingMessage, policy: DriftPolicy
    ) -> DriftManifest:
        
        # Gate 1: Nonce replay
        if message.logical_nonce <= state.last_nonce_seen:
            return DriftManifest(
                verdict=DriftVerdict.NONCE_REPLAY_DETECTED,
                effective_ttl_ms=0.0,
                directive=f"HALT: Nonce {message.logical_nonce} <= last seen {state.last_nonce_seen}. Replay.",
                manifest_digest=cls._hash(f"REPLAY:{message.message_id}")
            )

        # Gate 2: No measured drift
        if state.measured_clock_drift_ms is None:
            return DriftManifest(
                verdict=DriftVerdict.DRIFT_REQUIRES_HANDSHAKE,
                effective_ttl_ms=0.0,
                directive="HALT: Clock drift not measured. Execute handshake.",
                manifest_digest=cls._hash(f"NO_HANDSHAKE:{message.message_id}")
            )

        # Gate 3: Drift within tolerance
        if abs(state.measured_clock_drift_ms) <= policy.max_acceptable_drift_ms:
            digest = cls._hash(json.dumps({
                "message": message.message_id,
                "drift": f"{state.measured_clock_drift_ms:.1f}",
                "nonce": str(message.logical_nonce)
            }, sort_keys=True))
            return DriftManifest(
                verdict=DriftVerdict.NO_DRIFT_ADJUSTMENT_NEEDED,
                effective_ttl_ms=state.message_ttl_ms,
                directive="PROCEED: Drift within tolerance.",
                manifest_digest=digest
            )

        # Gate 4: Accommodate drift
        effective_ttl = state.message_ttl_ms + abs(state.measured_clock_drift_ms)
        digest = cls._hash(json.dumps({
            "message": message.message_id,
            "drift": f"{state.measured_clock_drift_ms:.1f}",
            "adjusted_ttl": f"{effective_ttl:.1f}",
            "nonce": str(message.logical_nonce)
        }, sort_keys=True))
        
        return DriftManifest(
            verdict=DriftVerdict.DRIFT_ACCOMMODATED,
            effective_ttl_ms=effective_ttl,
            directive=f"PROCEED: TTL adjusted to {effective_ttl:.0f}ms for drift.",
            manifest_digest=digest
        )