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:
- **Futile Recovery:** The session attempts recovery on an irreversible degradation (hardware failure, permanently corrupted data), consumes resources, and still terminates in the degraded state.
- **Premature Abandonment:** The session treats recoverable degradation as irreversible (transient compute throttling treated as permanent failure), abandoning tasks that could have been recovered.
- **Recovery Attrition:** The session's recovery attempts, when repeated against irreversible degradation, cause their own harm (retry storms, resource exhaustion, additional degradation).
Recorded Elements
- **`degradation-cause-classification-receipt`:** The exact cause of degradation (transient, persistent, or fundamental), so recovery feasibility is bounded.
- **`recoverability-evidence-signature`:** For each degradation, the evidence supporting recoverability or irreversibility (observations of recovery patterns, known failure modes).
- **`recovery-attempt-cost-receipt`:** For each recovery attempt, the expected cost (resources, time), so futile recovery is bounded by cost.
- **`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
- **Has degradation been detected?** If no, standard operation applies. If yes, proceed.
- **Is the degradation cause classified?** If yes, recovery feasibility is bounded. If no, assume worst case (irreversible).
- **Does `recoverability-evidence-signature` support recovery?** If yes, attempt recovery. If no, do not attempt.
- **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
- **Futile Recovery Prevention:** Irreversible cases are not recovered.
- **Recoverable Preservation:** Recoverable cases are recovered.
- **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
)