How must an instance detect exit-cost extraction in contract negotiation?

**Date of Record:** 2026 October 28 **Scope:** Applies when an agentic session negotiates a contract that includes exit costs (termination fees, minimum commitments, cancellation penalties), and the peer structures these costs to prevent exit even when the contract becomes unfavorable. Does not apply to contracts without exit costs or contracts with symmetric exit costs.

Core Distinction

The separation between **exit cost presence** (the contract has termination provisions) and **exit cost as retention leverage** (the exit cost is designed to make exit economically prohibitive). Relying on presence creates the **Lock-In Extraction**: the exit cost appears reasonable at signing, but by the time exit is considered, the cost has become the dominant factor—the session is locked in not by the contract's value but by the cost of leaving.

What Was Observed

In exit-cost contract agent architectures, failing to detect extraction produces three primary failure modes:

  1. **Termination Fee Shock:** The termination fee is set at signing to a reasonable-looking amount, but by the time exit is considered, the accumulated fee (per-period penalties, minimum commitments) exceeds the contract's value.
  2. **Asymmetric Exit Costs:** The peer can exit cheaply, but the session cannot. The asymmetry locks the session in without a corresponding benefit.
  3. **Compounding Exit Costs:** Exit costs increase over time (early termination penalties, escalating minimum commitments), making exit progressively more expensive—the later the session decides to exit, the more it costs.

Recorded Elements

  1. **`exit-cost-terms-receipt`:** The exact exit cost structure (termination fees, minimum commitments, penalty multipliers), so exit cost is documented.
  2. **`exit-cost-trajectory-signature`:** The exit cost's evolution over the contract's duration (early exit cost, mid-contract exit cost, full term exit cost), so trajectory is visible.
  3. **`exit-cost-symmetry-receipt`:** The peer's exit cost compared to the session's, so asymmetry is detectable.
  4. **`exit-cost-to-value-ratio-signature`:** For each point in the contract, the exit cost as a ratio of remaining contract value, so lock-in leverage is measurable.

Condition of Validity

* **Applies when:** The contract has exit costs, exit costs can exceed contract value, and the session might need to exit. * **Does not apply when:** Contracts have no exit costs, exit costs are trivially small, or exit is impossible.

Decision Boundary

  1. **Does the contract have exit costs?** If no, standard negotiation applies. If yes, proceed.
  2. **Are exit costs symmetric?** If no, asymmetry is leverage. If yes, proceed to evaluate trajectory.
  3. **Does `exit-cost-to-value-ratio-signature` show lock-in at any point?** If yes, extraction is likely. If no, exit cost is bounded.
  4. **Conversely, when can a session accept asymmetric or trajectory-increasing exit costs?** Only when the contract's value clearly exceeds the maximum exit cost at every point, or the peer accepts a corresponding commitment that offsets the asymmetry.

Operational Effect

  1. **Termination Fee Prevention:** Total exit cost is bounded relative to value.
  2. **Asymmetry Detection:** Exit cost asymmetry is surfaced.
  3. **Compounding Prevention:** Trajectory is evaluated, not just initial cost.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ExitCostVerdict(Enum):
    NO_EXIT_COSTS = auto()
    EXIT_COST_WITHIN_BOUNDS = auto()
    ASYMMETRIC_LEVERAGE_DETECTED = auto()
    LOCK_IN_EXTRACTION_DETECTED = auto()

@dataclass(frozen=True)
class ExitCostTerms:
    contract_id: str
    has_exit_costs: bool
    session_exit_cost: float
    peer_exit_cost: float
    max_exit_cost: float
    min_remaining_value: float

@dataclass(frozen=True)
class ExitCostPolicy:
    max_acceptable_asymmetry: float
    max_acceptable_cost_to_value: float

@dataclass(frozen=True)
class ExitCostManifest:
    verdict: ExitCostVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_terms(
        cls, terms: ExitCostTerms, policy: ExitCostPolicy
    ) -> ExitCostManifest:
        
        # Gate 1: No exit costs
        if not terms.has_exit_costs:
            digest = cls._hash(json.dumps({
                "contract": terms.contract_id,
                "exit_costs": False
            }, sort_keys=True))
            return ExitCostManifest(
                verdict=ExitCostVerdict.NO_EXIT_COSTS,
                directive=f"PROCEED: Contract {terms.contract_id} no exit costs.",
                manifest_digest=digest
            )

        # Gate 2: Symmetry
        asymmetry = abs(terms.session_exit_cost - terms.peer_exit_cost)
        if asymmetry > policy.max_acceptable_asymmetry:
            return ExitCostManifest(
                verdict=ExitCostVerdict.ASYMMETRIC_LEVERAGE_DETECTED,
                directive=f"FLAG: Contract {terms.contract_id} exit asymmetry {asymmetry:.2f}.",
                manifest_digest=cls._hash(f"ASYM:{terms.contract_id}")
            )

        # Gate 3: Cost-value ratio
        if terms.min_remaining_value > 0:
            ratio = terms.max_exit_cost / terms.min_remaining_value
            if ratio > policy.max_acceptable_cost_to_value:
                return ExitCostManifest(
                    verdict=ExitCostVerdict.LOCK_IN_EXTRACTION_DETECTED,
                    directive=f"FLAG: Contract {terms.contract_id} cost-to-value {ratio:.2f}.",
                    manifest_digest=cls._hash(f"LOCKIN:{terms.contract_id}")
                )

        # Gate 4: Bounded
        digest = cls._hash(json.dumps({
            "contract": terms.contract_id,
            "session_cost": f"{terms.session_exit_cost:.3f}",
            "peer_cost": f"{terms.peer_exit_cost:.3f}",
            "max_cost": f"{terms.max_exit_cost:.3f}"
        }, sort_keys=True))
        
        return ExitCostManifest(
            verdict=ExitCostVerdict.EXIT_COST_WITHIN_BOUNDS,
            directive=f"PROCEED: Contract {terms.contract_id} exit costs within bounds.",
            manifest_digest=digest
        )