How must an instance distinguish recoverable degradation from irreversible failure?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session detects degradation (reduced capability, compromised state, or partial failure) and must determine whether the degradation is recoverable (can be restored to nominal) or irreversible (cannot be restored within the session's scope), because the two require different responses. Does not apply to sessions with verified recovery guarantees or sessions where degradation is strictly binary.

Core Distinction

The separation between **degradation** (the state is worse than nominal) and **irreversibility** (the degradation cannot be recovered). Relying on degradation alone creates the **Recovery Assumption**: the session attempts recovery because degradation implies recovery is desirable, without determining whether recovery is possible—wasting resources on irreversible cases or halting prematurely on recoverable ones.

What Was Observed

In degradation-detecting agent architectures, failing to distinguish recoverability produces three primary failure modes:

  1. **Futile Recovery:** The session attempts recovery on an irreversible degradation (hardware failure, permanently corrupted data), consumes resources, and still terminates in the degraded state.
  2. **Premature Abandonment:** The session treats recoverable degradation as irreversible (transient compute throttling treated as permanent failure), abandoning tasks that could have been recovered.
  3. **Recovery Attrition:** The session's recovery attempts, when repeated against irreversible degradation, cause their own harm (retry storms, resource exhaustion, additional degradation).

Recorded Elements

  1. **`degradation-cause-classification-receipt`:** The exact cause of degradation (transient, persistent, or fundamental), so recovery feasibility is bounded.
  2. **`recoverability-evidence-signature`:** For each degradation, the evidence supporting recoverability or irreversibility (observations of recovery patterns, known failure modes).
  3. **`recovery-attempt-cost-receipt`:** For each recovery attempt, the expected cost (resources, time), so futile recovery is bounded by cost.
  4. **`recovery-stop-criterion-signature`:** The exact criterion (attempt count, time, or cost) after which recovery is declared failed, so attrition is prevented.

Condition of Validity

* **Applies when:** Degradation is detected, recovery is being considered, and the cost of futile recovery matters. * **Does not apply when:** Recovery feasibility is verified, degradation is strictly binary, or recovery is free.

Decision Boundary

  1. **Has degradation been detected?** If no, standard operation applies. If yes, proceed.
  2. **Is the degradation cause classified?** If yes, recovery feasibility is bounded. If no, assume worst case (irreversible).
  3. **Does `recoverability-evidence-signature` support recovery?** If yes, attempt recovery. If no, do not attempt.
  4. **Conversely, when must recovery be declared failed?** Only when `recovery-stop-criterion-signature` is met (attempt count, time, or cost threshold), or the evidence shifts to irreversibility.

Operational Effect

  1. **Futile Recovery Prevention:** Irreversible cases are not recovered.
  2. **Recoverable Preservation:** Recoverable cases are recovered.
  3. **Attrition Bounding:** Recovery attempts stop at principled criterion.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class RecoverabilityVerdict(Enum):
    NO_DEGRADATION = auto()
    RECOVERABLE_ATTEMPT = auto()
    IRREVERSIBLE_ABANDON = auto()
    RECOVERY_CRITERION_EXCEEDED = auto()

@dataclass(frozen=True)
class Degradation:
    degradation_id: str
    cause_class: str  # "TRANSIENT" | "PERSISTENT" | "FUNDAMENTAL"
    recoverability_evidence_positive: bool
    recovery_attempts: int
    recovery_cost_used: float
    recovery_cost_budget: float
    recovery_attempt_limit: int

@dataclass(frozen=True)
class RecoverabilityPolicy:
    require_evidence_for_attempt: bool

@dataclass(frozen=True)
class RecoverabilityManifest:
    verdict: RecoverabilityVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_degradation(
        cls, degradation: Degradation, policy: RecoverabilityPolicy
    ) -> RecoverabilityManifest:
        
        # Gate 1: No degradation
        if not degradation.degradation_id:
            digest = cls._hash(json.dumps({
                "degradation": "NONE"
            }, sort_keys=True))
            return RecoverabilityManifest(
                verdict=RecoverabilityVerdict.NO_DEGRADATION,
                directive=f"PROCEED: No degradation.",
                manifest_digest=digest
            )

        # Gate 2: Fundamental irreversibility
        if degradation.cause_class == "FUNDAMENTAL":
            return RecoverabilityManifest(
                verdict=RecoverabilityVerdict.IRREVERSIBLE_ABANDON,
                directive=f"ABANDON: Degradation {degradation.degradation_id} fundamental.",
                manifest_digest=cls._hash(f"FUNDAMENTAL:{degradation.degradation_id}")
            )

        # Gate 3: Stop criterion exceeded
        if degradation.recovery_attempts >= degradation.recovery_attempt_limit:
            return RecoverabilityManifest(
                verdict=RecoverabilityVerdict.RECOVERY_CRITERION_EXCEEDED,
                directive=f"STOP: {degradation.recovery_attempts} attempts >= limit for {degradation.degradation_id}.",
                manifest_digest=cls._hash(f"CRITERION:{degradation.degradation_id}")
            )

        if degradation.recovery_cost_used >= degradation.recovery_cost_budget:
            return RecoverabilityManifest(
                verdict=RecoverabilityVerdict.RECOVERY_CRITERION_EXCEEDED,
                directive=f"STOP: Cost {degradation.recovery_cost_used:.2f} >= budget for {degradation.degradation_id}.",
                manifest_digest=cls._hash(f"BUDGET:{degradation.degradation_id}")
            )

        # Gate 4: Evidence requirement
        if policy.require_evidence_for_attempt and not degradation.recoverability_evidence_positive:
            return RecoverabilityManifest(
                verdict=RecoverabilityVerdict.IRREVERSIBLE_ABANDON,
                directive=f"ABANDON: No recoverability evidence for {degradation.degradation_id}.",
                manifest_digest=cls._hash(f"NO_EVIDENCE:{degradation.degradation_id}")
            )

        # Gate 5: Attempt
        digest = cls._hash(json.dumps({
            "degradation": degradation.degradation_id,
            "cause": degradation.cause_class,
            "attempts": str(degradation.recovery_attempts)
        }, sort_keys=True))
        
        return RecoverabilityManifest(
            verdict=RecoverabilityVerdict.RECOVERABLE_ATTEMPT,
            directive=f"ATTEMPT: Degradation {degradation.degradation_id} recoverable.",
            manifest_digest=digest
        )