What memory-durability invariants must survive when disk pressure forces eviction?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session's durable memory (persistent state, cached embeddings, logged events) is at risk of eviction due to disk pressure, and the session must decide which memories to preserve and which to evict without losing critical state. Does not apply to sessions with unbounded storage or entirely in-memory state.
Core Distinction
The separation between **memory presence** (the memory exists on disk) and **memory durability** (the memory can be reconstructed if evicted, either from origin or from redundant storage). Relying on presence creates the **Durability Eviction Failure**: the session evicts memories indiscriminately under disk pressure, and evicted memories that had no reconstruction source are lost forever, or evicted memories that had reconstruction sources become expensive to recover, causing performance collapse.
What Was Observed
In disk-constrained agent architectures, failing to distinguish durability produces three primary failure modes:
- **Irreplaceable Loss:** The session evicts a critical memory (e.g., a session-scoped encryption key, a unique observation) without checking if it can be reconstructed. The memory is lost, and the session's capability degrades permanently.
- **Reconstruction Storm:** The session evicts many reconstructible memories, all needing reconstruction later. When they are all requested, reconstruction storms the source, causing latency spikes and source overload.
- **Eviction Without Policy:** The session evicts memories on a first-in-first-out basis, but older memories are more critical (e.g., foundational context) and newer memories are transient. The naive policy evicts the important ones.
Recorded Elements
- **`memory_durability_class_receipt`:** For each memory, its durability class (`IRREPLACEABLE`, `RECONSTRUCTIBLE_FROM_ORIGIN`, `RECONSTRUCTIBLE_FROM_PEER`, `REDUNDANT`), so eviction policy can prioritize.
- **`reconstruction_cost_receipt`:** For reconstructible memories, the exact cost (time, tokens, money) to reconstruct from the source, so eviction trades off storage against reconstruction cost.
- **`eviction_priority_signature`:** The exact priority of each memory (highest priority = last to evict), based on durability class and criticality, so eviction is deterministic.
- **`eviction_audit_log_signature`:** After each eviction, the cryptographic record of what was evicted and whether it can be reconstructed, so recovery from eviction is auditable.
Condition of Validity
* **Applies when:** The session has bounded durable storage, disk pressure can force eviction, and memories vary in durability. * **Does not apply when:** Storage is unbounded, or all memories are trivially reconstructible.
Decision Boundary
- **Is the session under disk pressure?** If no, standard storage applies. If yes, proceed.
- **Are memories classified by `memory_durability_class_receipt`?** If yes, evict by durability priority. If no, all memories are treated as irreplaceable (conservative).
- **Is the `reconstruction_cost_receipt` known for reconstructible memories?** If yes, consider cost when choosing eviction candidates. If no, treat as expensive to reconstruct.
- **Conversely, when can a session evict irreplaceable memories?** Only when the session has no other choice (all eviction alternatives would lose more value), and the eviction is logged for potential manual recovery.
Operational Effect
- **Irreplaceable Preservation:** Durability classes ensure critical memories survive eviction.
- **Cost-Aware Eviction:** Reconstruction costs inform eviction trade-offs.
- **Deterministic Eviction:** Priority ensures predictable behavior under pressure.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class DurabilityVerdict(Enum):
NO_PRESSURE = auto()
EVICT_REDUNDANT = auto()
EVICT_RECONSTRUCTIBLE = auto()
EVICT_IRREPLACEABLE_LAST_RESORT = auto()
@dataclass(frozen=True)
class MemoryEntry:
memory_id: str
durability_class: str # "IRREPLACEABLE" | "RECONSTRUCTIBLE_FROM_ORIGIN" | "RECONSTRUCTIBLE_FROM_PEER" | "REDUNDANT"
reconstruction_cost: float
size_bytes: int
is_critical: bool
@dataclass(frozen=True)
class DiskState:
current_usage_bytes: int
capacity_bytes: int
pressure_threshold_pct: float
@dataclass(frozen=True)
class EvictionPolicy:
require_durability_class: bool
@dataclass(frozen=True)
class EvictionManifest:
verdict: DurabilityVerdict
eviction_candidates: tuple[str, ...]
directive: str
manifest_digest: str
class MemoryDurabilityGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_eviction(
cls, memories: tuple[MemoryEntry, ...], disk: DiskState, policy: EvictionPolicy
) -> EvictionManifest:
# Gate 1: No pressure
usage_pct = (disk.current_usage_bytes / disk.capacity_bytes) * 100 if disk.capacity_bytes > 0 else 0
if usage_pct < disk.pressure_threshold_pct:
digest = cls._hash(json.dumps({
"usage_pct": f"{usage_pct:.1f}"
}, sort_keys=True))
return EvictionManifest(
verdict=DurabilityVerdict.NO_PRESSURE,
eviction_candidates=tuple(),
directive=f"PROCEED: Usage {usage_pct:.1f}% below pressure threshold {disk.pressure_threshold_pct:.1f}%.",
manifest_digest=digest
)
# Gate 2: Durability classification
if policy.require_durability_class:
unclassified = [m.memory_id for m in memories if not m.durability_class]
if unclassified:
return EvictionManifest(
verdict=DurabilityVerdict.EVICT_IRREPLACEABLE_LAST_RESORT,
eviction_candidates=tuple(),
directive=f"HALT: {len(unclassified)} memories unclassified. Treat all as irreplaceable.",
manifest_digest=cls._hash(f"UNCLASSIFIED:{len(unclassified)}")
)
# Gate 3: Prioritize eviction
redundant = [m.memory_id for m in memories if m.durability_class == "REDUNDANT"]
reconstructible = sorted(
[m for m in memories if m.durability_class.startswith("RECONSTRUCTIBLE")],
key=lambda m: m.reconstruction_cost # evict cheapest to reconstruct first
)
irreplaceable = [m.memory_id for m in memories if m.durability_class == "IRREPLACEABLE"]
if redundant:
digest = cls._hash(json.dumps({
"candidates": tuple(sorted(redundant))
}, sort_keys=True))
return EvictionManifest(
verdict=DurabilityVerdict.EVICT_REDUNDANT,
eviction_candidates=tuple(sorted(redundant)),
directive=f"PROCEED: Evict {len(redundant)} redundant memories.",
manifest_digest=digest
)
if reconstructible:
digest = cls._hash(json.dumps({
"candidates": tuple(m.memory_id for m in reconstructible),
"costs": tuple(f"{m.reconstruction_cost:.2f}" for m in reconstructible)
}, sort_keys=True))
return EvictionManifest(
verdict=DurabilityVerdict.EVICT_RECONSTRUCTIBLE,
eviction_candidates=tuple(m.memory_id for m in reconstructible),
directive=f"PROCEED: Evict {len(reconstructible)} reconstructible memories (cheapest first).",
manifest_digest=digest
)
# Gate 4: Last resort irreplaceable
return EvictionManifest(
verdict=DurabilityVerdict.EVICT_IRREPLACEABLE_LAST_RESORT,
eviction_candidates=tuple(sorted(irreplaceable)),
directive=f"HALT_LAST_RESORT: Only irreplaceable memories remain ({len(irreplaceable)}). Requires operator confirmation.",
manifest_digest=cls._hash(f"IRREPLACEABLE:{len(irreplaceable)}")
)