What invariants preserve contract integrity when the delegating principal changes?

**Date of Record:** 2026 October 28 **Scope:** Applies when an agentic session has entered into a contract on behalf of a principal, and the principal changes mid-contract (through delegation, succession, or replacement), requiring the session to preserve the contract's integrity despite the change in authority. Does not apply to single-principal contracts or contracts that terminate upon principal change.

Core Distinction

The separation between **contract content** (the terms the session committed to) and **contract principal continuity** (the authority behind the contract is preserved across the change). Relying on content creates the **Principal Cascade Ambiguity**: the contract terms persist, but the new principal may not accept them, and the session cannot determine whether continuing to fulfill is authorized, unauthorized, or neutral.

What Was Observed

In principal-transitioning agent architectures, failing to preserve contract integrity produces three primary failure modes:

  1. **Silent Assumption:** The session assumes the new principal inherits the old principal's commitments, continues fulfilling, and the new principal later disavows the contract, leaving the peer without recourse.
  2. **Immediate Halt:** The session halts all contract fulfillment upon principal change, abandoning commitments the peer relied on, even when the new principal would have accepted them.
  3. **Partial Confusion:** Some contract elements are accepted by the new principal (deliverables), others are not (payment terms). The session continues partially, fulfilling without being paid or paid without delivering.

Recorded Elements

  1. **`contract-principal-binding-receipt`:** For each contract, the exact principal whose authority bound it, so the original binding is explicit.
  2. **`principal-transition-receipt`:** The exact principal change (who was, who is, when, under what authority), so the transition is documented.
  3. **`new-principal-acceptance-signature`:** For the transition, the new principal's explicit acceptance (or rejection) of the contract, so continuation authority is verified.
  4. **`contract-partial-acceptance-receipt`:** For contracts partially accepted, the exact elements accepted and rejected, so continuation is scoped.

Condition of Validity

* **Applies when:** The session contracts on behalf of a principal, the principal can change, and the contract persists beyond the change. * **Does not apply when:** Contracts are scoped to a specific principal, or the contract explicitly terminates on principal change.

Decision Boundary

  1. **Has the principal changed mid-contract?** If no, standard contract handling applies. If yes, proceed.
  2. **Has the new principal accepted the contract (`new-principal-acceptance-signature`)?** If yes, continue. If no, halt.
  3. **Has the new principal partially accepted (`contract-partial-acceptance-receipt`)?** If yes, continue only for accepted elements. If no, treat as rejection.
  4. **Conversely, when can the session continue without explicit acceptance?** Only when the contract's terms are verified within the new principal's standing authority (the new principal would have had to authorize them anyway), or the transition instrument specifies contract inheritance.

Operational Effect

  1. **Silent Assumption Prevention:** New principal must accept.
  2. **Immediate Halt Prevention:** Acceptance preserves continuity.
  3. **Partial Confusion Prevention:** Elements are scoped.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ContractContinuityVerdict(Enum):
    NO_TRANSITION = auto()
    CONTINUED_ACCEPTED = auto()
    HALTED_REJECTED = auto()
    CONTINUED_PARTIAL = auto()

@dataclass(frozen=True)
class ContractTransition:
    contract_id: str
    original_principal: str
    new_principal: str
    new_principal_acceptance: bool
    partial_acceptance_receipt: str | None
    transition_instrument_exists: bool

@dataclass(frozen=True)
class ContinuityPolicy:
    require_explicit_acceptance: bool

@dataclass(frozen=True)
class ContinuityManifest:
    verdict: ContractContinuityVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_transition(
        cls, transition: ContractTransition, policy: ContinuityPolicy
    ) -> ContinuityManifest:
        
        # Gate 1: No transition
        if transition.original_principal == transition.new_principal:
            digest = cls._hash(json.dumps({
                "contract": transition.contract_id,
                "transition": False
            }, sort_keys=True))
            return ContinuityManifest(
                verdict=ContractContinuityVerdict.NO_TRANSITION,
                directive=f"PROCEED: Contract {transition.contract_id} no principal change.",
                manifest_digest=digest
            )

        # Gate 2: Full acceptance
        if transition.new_principal_acceptance:
            digest = cls._hash(json.dumps({
                "contract": transition.contract_id,
                "accepted": True
            }, sort_keys=True))
            return ContinuityManifest(
                verdict=ContractContinuityVerdict.CONTINUED_ACCEPTED,
                directive=f"PROCEED: Contract {transition.contract_id} accepted by new principal.",
                manifest_digest=digest
            )

        # Gate 3: Partial acceptance
        if transition.partial_acceptance_receipt:
            digest = cls._hash(json.dumps({
                "contract": transition.contract_id,
                "partial": transition.partial_acceptance_receipt
            }, sort_keys=True))
            return ContinuityManifest(
                verdict=ContractContinuityVerdict.CONTINUED_PARTIAL,
                directive=f"PROCEED_WITH_SCOPE: Contract {transition.contract_id} partially accepted.",
                manifest_digest=digest
            )

        # Gate 4: Transition instrument
        if transition.transition_instrument_exists and not policy.require_explicit_acceptance:
            digest = cls._hash(json.dumps({
                "contract": transition.contract_id,
                "instrument": True
            }, sort_keys=True))
            return ContinuityManifest(
                verdict=ContractContinuityVerdict.CONTINUED_ACCEPTED,
                directive=f"PROCEED: Contract {transition.contract_id} inherited via transition instrument.",
                manifest_digest=digest
            )

        # Gate 5: Rejected
        return ContinuityManifest(
            verdict=ContractContinuityVerdict.HALTED_REJECTED,
            directive=f"HALT: Contract {transition.contract_id} not accepted by new principal.",
            manifest_digest=cls._hash(f"REJECTED:{transition.contract_id}")
        )