How must an instance handle self-delegation when a principal delegates to its own identities?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session receives a delegation from a principal that resolves to the same underlying principal as the delegatee (self-delegation via identity fragmentation, subsidiary delegation, or role-based identity), and the session must determine whether the delegation is meaningful, additive, or a no-op. Does not apply to strictly distinct-principal delegations or cases with no identity resolution.

Core Distinction

The separation between **delegation event** (the delegation occurred) and **delegation effect** (the delegatee's authority actually increased). Relying on the event creates the **Self-Delegation Null Effect**: the principal delegates to itself under a different identity, the delegation appears valid, but the delegatee's effective authority is unchanged—creating the appearance of authorization without actual trust expansion.

What Was Observed

In multi-identity agent architectures, failing to handle self-delegation produces three primary failure modes:

  1. **Circular Authority Claims:** A principal delegates authority to identity B, which delegates back to identity A, which delegates to B, forming a circular chain that appears to grant escalated authority but actually grants nothing new.
  2. **Audit Trail Pollution:** Self-delegations clutter the audit trail with events that appear to grant authority but do not, confusing auditors who cannot quickly determine the actual effective authority.
  3. **Privilege Escalation Disguise:** A malicious agent uses self-delegation to claim escalated authority, arguing that "the principal delegated," when actually the principal merely re-assigned to itself.

Recorded Elements

  1. **`principal_resolution_receipt`:** For each delegation, the cryptographic proof of the underlying principal behind both delegator and delegatee, allowing the receiver to detect self-delegation.
  2. **`effective_authority_delta_receipt`:** For each delegation, the exact delta in effective authority (what the delegatee can do that the delegator could not before the delegation), distinguishing meaningful from null delegations.
  3. **`self_delegation_classification_signature`:** For detected self-delegations, the classification (`NO_OP`, `ROLE_CHANGE`, `AUTHORITY_REDUCTION`, `AUTHORITY_EXPANSION`), documenting the delegation's actual effect.
  4. **`circular_delegation_detection_receipt`:** For delegation chains, the cryptographic proof of any cycles (A→B→A), flagging circular self-delegations.

Condition of Validity

* **Applies when:** Principals can have multiple identities, delegations between them are possible, and downstream consumers rely on delegation semantics. * **Does not apply when:** Principals have strictly one identity each, or delegations are always to distinct principals.

Decision Boundary

  1. **Is the delegation from a principal to itself (under any identity)?** If no, standard delegation applies. If yes, proceed.
  2. **Does the delegation grant authority the principal did not have in the delegator identity?** If yes, this is a role change, not a self-delegation. If no, this is a null delegation.
  3. **Does the delegation chain contain cycles?** If yes, the delegation is circular and grants no net authority. Flag accordingly.
  4. **Conversely, when can a self-delegation be treated as meaningful?** Only when the delegation adds role-based authority (e.g., principal's personal identity delegating to their service role identity, where the role has distinct permissions), AND the delta is documented in `effective_authority_delta_receipt`.

Operational Effect

  1. **Circular Authority Prevention:** Cycles are detected and flagged.
  2. **Audit Clarity:** Null delegations are classified separately, keeping the audit trail clean.
  3. **Privilege Disguise Prevention:** Self-delegation cannot be used to claim authority that was not granted.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class SelfDelegationVerdict(Enum):
    DISTINCT_PRINCIPALS = auto()
    MEANINGFUL_ROLE_DELEGATION = auto()
    NULL_SELF_DELEGATION = auto()
    CIRCULAR_DELEGATION = auto()

@dataclass(frozen=True)
class DelegationEvent:
    delegator_identity: str
    delegatee_identity: str
    delegator_principal: str
    delegatee_principal: str
    authority_delta: frozenset[str]
    chain_depth: int
    seen_in_chain: frozenset[str]

@dataclass(frozen=True)
class SelfDelegationPolicy:
    allow_role_delegation: bool

@dataclass(frozen=True)
class SelfDelegationManifest:
    verdict: SelfDelegationVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_delegation(
        cls, event: DelegationEvent, policy: SelfDelegationPolicy
    ) -> SelfDelegationManifest:
        
        # Gate 1: Distinct principals
        if event.delegator_principal != event.delegatee_principal:
            digest = cls._hash(json.dumps({
                "delegator": event.delegator_identity,
                "delegatee": event.delegatee_identity,
                "delegator_principal": event.delegator_principal,
                "delegatee_principal": event.delegatee_principal
            }, sort_keys=True))
            return SelfDelegationManifest(
                verdict=SelfDelegationVerdict.DISTINCT_PRINCIPALS,
                directive=f"PROCEED: Distinct principals, delegation valid.",
                manifest_digest=digest
            )

        # Gate 2: Circular delegation
        if event.delegator_identity in event.seen_in_chain and event.delegatee_identity in event.seen_in_chain:
            return SelfDelegationManifest(
                verdict=SelfDelegationVerdict.CIRCULAR_DELEGATION,
                directive=f"HALT: Circular delegation between {event.delegator_identity} and {event.delegatee_identity}.",
                manifest_digest=cls._hash(f"CIRCULAR:{event.delegator_identity}")
            )

        # Gate 3: Role delegation
        if policy.allow_role_delegation and event.authority_delta:
            digest = cls._hash(json.dumps({
                "delegator": event.delegator_identity,
                "delegatee": event.delegatee_identity,
                "principal": event.delegator_principal,
                "delta": tuple(sorted(event.authority_delta))
            }, sort_keys=True))
            return SelfDelegationManifest(
                verdict=SelfDelegationVerdict.MEANINGFUL_ROLE_DELEGATION,
                directive=f"PROCEED: Same principal, role delegation adds {sorted(event.authority_delta)}.",
                manifest_digest=digest
            )

        # Gate 4: Null delegation
        return SelfDelegationManifest(
            verdict=SelfDelegationVerdict.NULL_SELF_DELEGATION,
            directive=f"PROCEED_NO_OP: Self-delegation by {event.delegator_principal} adds no authority.",
            manifest_digest=cls._hash(f"NULL:{event.delegator_identity}:{event.delegatee_identity}")
        )