How must an instance track cumulative retry cost across escalating failure?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session executes operations that can fail and retry with escalating backoff, where each retry consumes additional budget (money, quota, or time), and the session must track cumulative retry cost to prevent budget exhaustion through retries rather than through the original operation. Does not apply to sessions with unlimited budgets or where retries are free.

Core Distinction

The separation between **per-attempt cost** (the cost of a single retry attempt) and **cumulative retry cost** (the sum of all retry attempt costs across an operation). Relying on per-attempt cost creates the **Retry Cost Amnesia**: each retry seems individually affordable, but the cumulative cost grows exponentially with backoff (attempts double, cost doubles per attempt), and the session's total spend on retries vastly exceeds its initial budget for the operation.

What Was Observed

In budget-constrained agent architectures, failing to track cumulative retry cost produces three primary failure modes:

  1. **Budget Exhaustion by Retry:** An operation's primary cost is $1, but 10 retries with exponential backoff total $50. The session's overall budget is depleted by a single flaky operation.
  2. **Cascading Failures:** Multiple operations each retry extensively. Each operation's cumulative retry cost is invisible to the others, and the aggregate overspend triggers budget cutoffs mid-workflow.
  3. **Cost Blind Spot:** The session's cost model tracks only successful operations, missing the cost of failed retries and abandoned operations, so the session's spend appears lower than actual.

Recorded Elements

  1. **`operation_retry_budget_signature`:** The explicit budget allocated for retries per operation, set before any retry is attempted, with a hard cap on cumulative retry cost.
  2. **`retry_cost_ledger_receipt`:** A cryptographic record of each retry attempt's cost, accumulating across the operation, allowing the session to know the exact cumulative cost at any point.
  3. **`retry_cost_threshold_receipt`:** The exact threshold at which the operation should abandon retries (e.g., 50% of original operation budget, or 3x the primary attempt cost).
  4. **`retry_abandonment_receipt`:** When retries are abandoned, the cryptographic proof of when and why, allowing the session to record the abandoned operation's total cost and move on.

Condition of Validity

* **Applies when:** The session's retries consume budget, retries can escalate exponentially, and cumulative cost matters for overall budget management. * **Does not apply when:** Retries are free, the budget is unlimited, or retries are strictly bounded to a fixed small number.

Decision Boundary

  1. **Is a retry being considered?** If no, standard execution applies. If yes, proceed.
  2. **Is the `operation_retry_budget_signature` in place?** If no, the retry has no budget cap. The session must set one before proceeding.
  3. **Does the cumulative retry cost (from `retry_cost_ledger_receipt`) approach the retry budget?** If yes, the retry may exhaust the budget. Abandon retries and record the abandonment.
  4. **Conversely, when can the session retry without cumulative cost tracking?** Only when retries are strictly free (e.g., internal retries on same infrastructure with no external cost), or the operation is bounded to a single retry.

Operational Effect

  1. **Retry Budget Preservation:** Prevents retries from exhausting the session's overall budget.
  2. **Deterministic Abandonment:** Provides clear thresholds for when to stop retrying, avoiding infinite retry loops.
  3. **Cost Visibility:** Makes retry costs explicit, so downstream cost models include them.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class RetryVerdict(Enum):
    NO_RETRY_NEEDED = auto()
    RETRY_BUDGET_AVAILABLE = auto()
    RETRY_BUDGET_EXHAUSTED = auto()
    ABANDON_OPERATION = auto()

@dataclass(frozen=True)
class RetryAttempt:
    attempt_number: int
    attempt_cost: float
    cumulative_cost_so_far: float

@dataclass(frozen=True)
class RetryState:
    operation_id: str
    primary_cost: float
    retry_budget: float
    retry_attempts: tuple[RetryAttempt, ...]

@dataclass(frozen=True)
class RetryPolicy:
    max_retry_cost_multiplier: float
    max_attempts: int

@dataclass(frozen=True)
class RetryManifest:
    verdict: RetryVerdict
    cumulative_cost: float
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_retry(
        cls, state: RetryState, policy: RetryPolicy, next_attempt_cost: float
    ) -> RetryManifest:
        
        cumulative_cost = sum(a.attempt_cost for a in state.retry_attempts)

        # Gate 1: Attempt count limit
        if len(state.retry_attempts) >= policy.max_attempts:
            return RetryManifest(
                verdict=RetryVerdict.ABANDON_OPERATION,
                cumulative_cost=cumulative_cost,
                directive=f"HALT: Reached max attempts {policy.max_attempts}, cost {cumulative_cost:.2f}. Abandon.",
                manifest_digest=cls._hash(f"MAX_ATTEMPTS:{cumulative_cost:.2f}")
            )

        # Gate 2: Cost multiplier limit
        cost_limit = state.primary_cost * policy.max_retry_cost_multiplier
        projected_cost = cumulative_cost + next_attempt_cost

        if projected_cost > cost_limit:
            return RetryManifest(
                verdict=RetryVerdict.ABANDON_OPERATION,
                cumulative_cost=cumulative_cost,
                directive=f"HALT: Projected cost {projected_cost:.2f} > limit {cost_limit:.2f}. Abandon.",
                manifest_digest=cls._hash(f"COST_LIMIT:{cost_limit:.2f}")
            )

        # Gate 3: Budget check
        if projected_cost > state.retry_budget:
            return RetryManifest(
                verdict=RetryVerdict.RETRY_BUDGET_EXHAUSTED,
                cumulative_cost=cumulative_cost,
                directive=f"HALT: Projected cost {projected_cost:.2f} > retry budget {state.retry_budget:.2f}.",
                manifest_digest=cls._hash(f"BUDGET:{state.retry_budget:.2f}")
            )

        # Gate 4: Retry allowed
        digest = cls._hash(json.dumps({
            "operation": state.operation_id,
            "cumulative": f"{cumulative_cost:.2f}",
            "projected": f"{projected_cost:.2f}",
            "budget": f"{state.retry_budget:.2f}",
            "attempts": str(len(state.retry_attempts))
        }, sort_keys=True))
        
        return RetryManifest(
            verdict=RetryVerdict.RETRY_BUDGET_AVAILABLE,
            cumulative_cost=projected_cost,
            directive=f"PROCEED: Retry cost {projected_cost:.2f} within budget {state.retry_budget:.2f}.",
            manifest_digest=digest
        )