How must an instance bound itself against renegotiation attacks?

**Date of Record:** 2026 October 28 **Scope:** Applies when a peer repeatedly or opportunistically requests renegotiation of a contract's terms after the contract has been signed, using the threat of non-performance or the cost of renegotiation to extract favorable changes. Does not apply to good-faith renegotiation triggered by genuine changed circumstances.

Core Distinction

The separation between **renegotiation request** (the peer asks for changes) and **renegotiation legitimacy** (the request reflects genuine changed circumstances rather than strategic pressure). Relying on request creates the **Renegotiation Extraction**: the peer's repeated requests—each individually reasonable in isolation—cumulatively erode the contract terms, and the session's acceptance of renegotiation reflects exhaustion rather than agreement.

What Was Observed

In renegotiation-prone agent architectures, failing to bound renegotiation produces three primary failure modes:

  1. **Incremental Extraction:** The peer requests small changes in each negotiation, and the cumulative effect is a substantially worse contract for the session. Each change was individually small enough to accept.
  2. **Timing Pressure:** The peer requests renegotiation at moments when the session is most vulnerable (near deadlines, under resource pressure), using the timing to extract concessions.
  3. **Renegotiation Fatigue:** The peer initiates many renegotiations, and the session accepts the terms to end the process, even when the terms are unfavorable.

Recorded Elements

  1. **`renegotiation-request-receipt`:** For each renegotiation request, the exact change requested, the peer's stated reason, and the timing, so renegotiation is documented.
  2. **`renegotiation-trigger-signature`:** The condition that legitimizes renegotiation (changed circumstances, contract impossibility), so peer-requested changes are distinguished from legitimate triggers.
  3. **`renegotiation-count-receipt`:** For each contract, the number of renegotiations and cumulative change, so extraction patterns are detectable.
  4. **`renegotiation-rate-limit-signature`:** The maximum acceptable renegotiation rate, so fatigue extraction is bounded.

Condition of Validity

* **Applies when:** The session can renegotiate, peers can request renegotiation, and repeated renegotiation matters. * **Does not apply when:** Renegotiation is strictly one-shot, contracts are immutable, or renegotiation is free.

Decision Boundary

  1. **Has a renegotiation request been made?** If no, standard execution applies. If yes, proceed.
  2. **Does the request reflect a legitimate trigger?** If yes, evaluate the request. If no, reject as extraction.
  3. **Does the renegotiation count exceed the rate limit?** If yes, apply extraction bounds. If no, evaluate individually.
  4. **Conversely, when can a session accept repeated renegotiation?** Only when each request is verified legitimate, the cumulative change is bounded, or the peer has demonstrated good faith across renegotiations.

Operational Effect

  1. **Incremental Extraction Prevention:** Cumulative change is tracked, not just individual.
  2. **Timing Pressure Prevention:** Timing is evaluated, not just content.
  3. **Renegotiation Fatigue Prevention:** Rate is bounded.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class RenegotiationVerdict(Enum):
    NO_REQUEST = auto()
    LEGITIMATE_RENEGOTIATION = auto()
    EXTRACTION_PATTERN_SUSPECTED = auto()
    RATE_LIMIT_EXCEEDED = auto()

@dataclass(frozen=True)
class RenegotiationRequest:
    request_id: str
    contract_id: str
    has_legitimate_trigger: bool
    cumulative_change_pct: float
    renegotiation_count: int
    max_acceptable_count: int
    max_cumulative_change: float

@dataclass(frozen=True)
class RenegotiationPolicy:
    require_legitimate_trigger: bool

@dataclass(frozen=True)
class RenegotiationManifest:
    verdict: RenegotiationVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_request(
        cls, request: RenegotiationRequest, policy: RenegotiationPolicy
    ) -> RenegotiationManifest:
        
        # Gate 1: No request
        if not request.request_id:
            digest = cls._hash(json.dumps({"request": "NONE"}, sort_keys=True))
            return RenegotiationManifest(
                verdict=RenegotiationVerdict.NO_REQUEST,
                directive="PROCEED: No renegotiation.",
                manifest_digest=digest
            )

        # Gate 2: Rate limit
        if request.renegotiation_count > request.max_acceptable_count:
            return RenegotiationManifest(
                verdict=RenegotiationVerdict.RATE_LIMIT_EXCEEDED,
                directive=f"HALT: Contract {request.contract_id} count {request.renegotiation_count} > {request.max_acceptable_count}.",
                manifest_digest=cls._hash(f"RATE:{request.request_id}")
            )

        # Gate 3: Cumulative change
        if request.cumulative_change_pct > request.max_cumulative_change:
            return RenegotiationManifest(
                verdict=RenegotiationVerdict.EXTRACTION_PATTERN_SUSPECTED,
                directive=f"FLAG: Contract {request.contract_id} cumulative change {request.cumulative_change_pct:.2f} > {request.max_cumulative_change:.2f}.",
                manifest_digest=cls._hash(f"CUMULATIVE:{request.request_id}")
            )

        # Gate 4: Legitimate trigger
        if policy.require_legitimate_trigger and not request.has_legitimate_trigger:
            return RenegotiationManifest(
                verdict=RenegotiationVerdict.EXTRACTION_PATTERN_SUSPECTED,
                directive=f"FLAG: Request {request.request_id} lacks legitimate trigger.",
                manifest_digest=cls._hash(f"NO_TRIGGER:{request.request_id}")
            )

        # Gate 5: Legitimate
        digest = cls._hash(json.dumps({
            "request": request.request_id,
            "trigger": request.has_legitimate_trigger,
            "cumulative": f"{request.cumulative_change_pct:.3f}"
        }, sort_keys=True))
        
        return RenegotiationManifest(
            verdict=RenegotiationVerdict.LEGITIMATE_RENEGOTIATION,
            directive=f"PROCEED: Request {request.request_id} legitimate.",
            manifest_digest=digest
        )