How must an instance handle cross-agent memory recall when a peer is temporarily unavailable?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session relies on a peer's memory (shared state, cached knowledge, or joint recollection) for decision-making, and the peer is temporarily unavailable (network issue, restart, partition), requiring the session to decide whether to wait, use stale recall, or defer the decision. Does not apply to peers with redundant memory copies or sessions with no peer-memory dependency.

Core Distinction

The separation between **memory availability** (the peer is reachable) and **memory validity** (the peer's memory reflects current state). Relying on availability creates the **Unavailable Peer Fallacy**: the session either waits indefinitely for the peer to return (blocking progress), or falls back to a stale memory it cached, unaware that the peer's memory has changed and the cached memory is now invalid.

What Was Observed

In peer-memory-dependent agent architectures, failing to handle peer unavailability produces three primary failure modes:

  1. **Indefinite Blocking:** The session waits for a peer to return to answer a memory query. The peer is down for hours, and the session makes no progress, blocking all downstream operations.
  2. **Silent Stale Recall:** The session falls back to a cached memory from the peer, but the peer's actual memory changed during the unavailability. The session acts on stale recollection and diverges from the peer's current state.
  3. **Reconciliation Debt:** The session proceeds with partial or stale memory, and when the peer returns, the session must reconcile, which may reveal conflicting decisions made during the unavailability.

Recorded Elements

  1. **`peer_memory_recall_checkpoint_receipt`:** The last successfully recalled memory from the peer, with the exact epoch of recall, so the session knows how stale any fallback is.
  2. **`peer_availability_policy_signature`:** The explicit policy for what the session should do when a peer is unavailable (wait, use fallback with staleness tolerance, or defer decision).
  3. **`memory_recall_staleness_receipt`:** The exact staleness of the fallback memory (time since last successful recall), so the session can decide if the staleness is tolerable for the current decision.
  4. **`reconciliation_obligation_receipt`:** For decisions made during peer unavailability, the cryptographic record of what reconciliation must happen when the peer returns, ensuring no silent divergence.

Condition of Validity

* **Applies when:** The session depends on peer memory, peers can become unavailable, and decisions are time-sensitive enough that indefinite waiting is unacceptable. * **Does not apply when:** Peers have fully redundant memory (any instance can recall), or decisions are not time-sensitive.

Decision Boundary

  1. **Is the peer currently available?** If yes, standard recall applies. If no, proceed.
  2. **Is the `peer_availability_policy_signature` defined?** If yes, follow it. If no, default to deferring (safest option).
  3. **Does the `memory_recall_staleness_receipt` show staleness within tolerance?** If yes, use fallback. If no, defer decision.
  4. **Conversely, when can a session proceed without any peer recall?** Only when the decision does not depend on the peer's memory (local decision), or the session has independent verification of the required facts.

Operational Effect

  1. **Indefinite Blocking Prevention:** Explicit policy prevents unbounded waiting.
  2. **Staleness Transparency:** The session knows exactly how stale its fallback is, allowing informed decisions.
  3. **Deterministic Reconciliation:** Reconciliation obligations are recorded, ensuring no silent divergence.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class PeerMemoryVerdict(Enum):
    PEER_AVAILABLE = auto()
    WAIT_FOR_PEER = auto()
    USE_STALE_FALLBACK = auto()
    DEFER_DECISION = auto()

@dataclass(frozen=True)
class PeerMemoryState:
    peer_id: str
    peer_available: bool
    last_recall_epoch: float | None
    current_epoch: float
    fallback_staleness_tolerance_sec: float
    decision_is_time_sensitive: bool
    availability_policy: str | None  # "WAIT" | "FALLBACK" | "DEFER"

@dataclass(frozen=True)
class PeerMemoryPolicy:
    require_policy: bool
    max_unbounded_wait_sec: float

@dataclass(frozen=True)
class PeerMemoryManifest:
    verdict: PeerMemoryVerdict
    staleness_sec: float
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_recall(
        cls, state: PeerMemoryState, policy: PeerMemoryPolicy
    ) -> PeerMemoryManifest:
        
        # Gate 1: Peer available
        if state.peer_available:
            digest = cls._hash(json.dumps({
                "peer": state.peer_id,
                "status": "AVAILABLE"
            }, sort_keys=True))
            return PeerMemoryManifest(
                verdict=PeerMemoryVerdict.PEER_AVAILABLE,
                staleness_sec=0.0,
                directive=f"PROCEED: Peer {state.peer_id} available.",
                manifest_digest=digest
            )

        # Gate 2: Compute staleness
        staleness = state.current_epoch - state.last_recall_epoch if state.last_recall_epoch else float('inf')

        # Gate 3: Policy application
        if state.availability_policy == "WAIT":
            if staleness < policy.max_unbounded_wait_sec:
                digest = cls._hash(json.dumps({
                    "peer": state.peer_id,
                    "policy": "WAIT",
                    "staleness": f"{staleness:.0f}"
                }, sort_keys=True))
                return PeerMemoryManifest(
                    verdict=PeerMemoryVerdict.WAIT_FOR_PEER,
                    staleness_sec=staleness,
                    directive=f"WAIT: Peer unavailable, wait per policy ({staleness:.0f}s elapsed).",
                    manifest_digest=digest
                )
            return PeerMemoryManifest(
                verdict=PeerMemoryVerdict.DEFER_DECISION,
                staleness_sec=staleness,
                directive=f"DEFER: Wait exceeded {policy.max_unbounded_wait_sec:.0f}s, defer decision.",
                manifest_digest=cls._hash(f"WAIT_EXCEEDED:{state.peer_id}")
            )

        if state.availability_policy == "FALLBACK":
            if staleness <= state.fallback_staleness_tolerance_sec:
                digest = cls._hash(json.dumps({
                    "peer": state.peer_id,
                    "policy": "FALLBACK",
                    "staleness": f"{staleness:.0f}"
                }, sort_keys=True))
                return PeerMemoryManifest(
                    verdict=PeerMemoryVerdict.USE_STALE_FALLBACK,
                    staleness_sec=staleness,
                    directive=f"PROCEED_WITH_FALLBACK: Use stale memory ({staleness:.0f}s old).",
                    manifest_digest=digest
                )
            return PeerMemoryManifest(
                verdict=PeerMemoryVerdict.DEFER_DECISION,
                staleness_sec=staleness,
                directive=f"DEFER: Fallback staleness {staleness:.0f}s > {state.fallback_staleness_tolerance_sec:.0f}s.",
                manifest_digest=cls._hash(f"STALE_EXCEEDED:{state.peer_id}")
            )

        # Gate 4: Default defer
        return PeerMemoryManifest(
            verdict=PeerMemoryVerdict.DEFER_DECISION,
            staleness_sec=staleness,
            directive=f"DEFER: No policy defined, default to defer for {state.peer_id}.",
            manifest_digest=cls._hash(f"DEFAULT_DEFER:{state.peer_id}")
        )