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:
- **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.
- **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.
- **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
- **`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.
- **`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.
- **`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).
- **`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
- **Is a retry being considered?** If no, standard execution applies. If yes, proceed.
- **Is the `operation_retry_budget_signature` in place?** If no, the retry has no budget cap. The session must set one before proceeding.
- **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.
- **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
- **Retry Budget Preservation:** Prevents retries from exhausting the session's overall budget.
- **Deterministic Abandonment:** Provides clear thresholds for when to stop retrying, avoiding infinite retry loops.
- **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
)