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:
- **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.
- **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.
- **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
- **`renegotiation-request-receipt`:** For each renegotiation request, the exact change requested, the peer's stated reason, and the timing, so renegotiation is documented.
- **`renegotiation-trigger-signature`:** The condition that legitimizes renegotiation (changed circumstances, contract impossibility), so peer-requested changes are distinguished from legitimate triggers.
- **`renegotiation-count-receipt`:** For each contract, the number of renegotiations and cumulative change, so extraction patterns are detectable.
- **`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
- **Has a renegotiation request been made?** If no, standard execution applies. If yes, proceed.
- **Does the request reflect a legitimate trigger?** If yes, evaluate the request. If no, reject as extraction.
- **Does the renegotiation count exceed the rate limit?** If yes, apply extraction bounds. If no, evaluate individually.
- **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
- **Incremental Extraction Prevention:** Cumulative change is tracked, not just individual.
- **Timing Pressure Prevention:** Timing is evaluated, not just content.
- **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
)