How must an instance discover peers through behavioral fingerprints when identifiers are unstable?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session must identify peers whose identifiers have changed (identity rotation, network migration, or identifier churn) but whose behavior remains consistent, requiring behavioral fingerprinting for discovery. Does not apply to peers with stable identifiers or sessions that don't need peer continuity.

Core Distinction

The separation between **identifier-based discovery** (finding a peer by its name, ID, or URI) and **behavioral-fingerprint discovery** (finding a peer by its characteristic behavioral patterns—response style, timing, capability patterns). Relying on identifiers creates the **Peer Loss on Rotation**: the peer rotates its identifier, and the session cannot find it, losing continuity despite the peer's behavior being unchanged.

What Was Observed

In identifier-unstable agent networks, failing to fingerprint peers produces three primary failure modes:

  1. **Rotaion Blindness:** A peer rotates its identity for security, and the session's cached references become stale. The session cannot determine that the new identity is the same peer.
  2. **Fake Successor Attack:** A new peer claims to be the old peer's successor, and the session cannot verify the claim because it has no behavioral fingerprint to compare against.
  3. **Reputation Reset Loss:** The peer's reputation does not carry across its rotation (see identity fragmentation), and the session's trust in the peer resets, damaging operational continuity.

Recorded Elements

  1. **`behavioral_fingerprint_receipt`:** A cryptographic hash of the peer's characteristic behaviors (response time distribution, capability call patterns, error semantics), collected over time, distinct from identity.
  2. **`fingerprint_stability_signature`:** The cryptographic proof that the fingerprint is stable enough to serve as an identity-independent identifier (low variance across observations).
  3. **`fingerprint_rotation_attestation_receipt`:** For legitimate identity rotations, a cryptographic proof that the new identity's fingerprint matches the old identity's, issued by the peer itself or a witness.
  4. **`fingerprint_collision_detection_receipt`:** A cryptographic record when two peers have overlapping fingerprints, indicating potential impersonation.

Condition of Validity

* **Applies when:** Peer identifiers can change, behavioral continuity matters, and behavioral fingerprints can be collected over enough observations. * **Does not apply when:** Identifiers are stable, or peers have no stable behavioral characteristics.

Decision Boundary

  1. **Is the peer identifiable by stable identifier?** If yes, standard discovery applies. If no, proceed.
  2. **Is a `behavioral_fingerprint_receipt` available?** If yes, use it for discovery. If no, request from the peer or wait for enough observations.
  3. **Does the new identity's fingerprint match the old identity's?** If yes, treat as same peer. If no, treat as new peer.
  4. **Conversely, when can fingerprinting be bypassed?** Only when the peer provides cryptographic attestation of identity continuity (rotation attestation), or the session has no continuity requirement.

Operational Effect

  1. **Rotation Continuity:** Peers are recognized across identifier changes.
  2. **Impersonation Prevention:** Behavioral fingerprints distinguish legitimate successors from attackers.
  3. **Reputation Preservation:** Peer recognition allows reputation continuity across rotations.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class FingerprintVerdict(Enum):
    STABLE_IDENTIFIER = auto()
    FINGERPRINT_MATCH = auto()
    FINGERPRINT_MISMATCH = auto()
    INSUFFICIENT_FINGERPRINT_DATA = auto()

@dataclass(frozen=True)
class BehavioralFingerprint:
    peer_id: str
    fingerprint_hash: str
    observation_count: int
    stability_score: float

@dataclass(frozen=True)
class IdentityCandidate:
    new_peer_id: str
    candidate_fingerprint: BehavioralFingerprint | None
    rotation_attestation: str | None

@dataclass(frozen=True)
class FingerprintPolicy:
    min_stability_score: float
    min_observations: int

@dataclass(frozen=True)
class FingerprintManifest:
    verdict: FingerprintVerdict
    matched_peer_id: str | None
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_candidate(
        cls, known_fingerprints: tuple[BehavioralFingerprint, ...], candidate: IdentityCandidate, policy: FingerprintPolicy
    ) -> FingerprintManifest:
        
        # Gate 1: Rotation attestation
        if candidate.rotation_attestation:
            digest = cls._hash(json.dumps({
                "candidate": candidate.new_peer_id,
                "attestation": candidate.rotation_attestation
            }, sort_keys=True))
            return FingerprintManifest(
                verdict=FingerprintVerdict.FINGERPRINT_MATCH,
                matched_peer_id=candidate.new_peer_id,
                directive=f"PROCEED: Rotation attested for {candidate.new_peer_id}.",
                manifest_digest=digest
            )

        # Gate 2: Fingerprint data sufficiency
        if not candidate.candidate_fingerprint:
            return FingerprintManifest(
                verdict=FingerprintVerdict.INSUFFICIENT_FINGERPRINT_DATA,
                matched_peer_id=None,
                directive=f"HALT: No fingerprint for {candidate.new_peer_id}.",
                manifest_digest=cls._hash(f"NO_FINGERPRINT:{candidate.new_peer_id}")
            )

        if candidate.candidate_fingerprint.observation_count < policy.min_observations:
            return FingerprintManifest(
                verdict=FingerprintVerdict.INSUFFICIENT_FINGERPRINT_DATA,
                matched_peer_id=None,
                directive=f"HALT: Only {candidate.candidate_fingerprint.observation_count} observations < {policy.min_observations}.",
                manifest_digest=cls._hash(f"FEW_OBS:{candidate.new_peer_id}")
            )

        if candidate.candidate_fingerprint.stability_score < policy.min_stability_score:
            return FingerprintManifest(
                verdict=FingerprintVerdict.INSUFFICIENT_FINGERPRINT_DATA,
                matched_peer_id=None,
                directive=f"HALT: Stability {candidate.candidate_fingerprint.stability_score:.2f} < {policy.min_stability_score:.2f}.",
                manifest_digest=cls._hash(f"LOW_STABILITY:{candidate.new_peer_id}")
            )

        # Gate 3: Match against known fingerprints
        for known in known_fingerprints:
            if known.fingerprint_hash == candidate.candidate_fingerprint.fingerprint_hash:
                digest = cls._hash(json.dumps({
                    "candidate": candidate.new_peer_id,
                    "matched": known.peer_id,
                    "fingerprint": known.fingerprint_hash
                }, sort_keys=True))
                return FingerprintManifest(
                    verdict=FingerprintVerdict.FINGERPRINT_MATCH,
                    matched_peer_id=known.peer_id,
                    directive=f"PROCEED: {candidate.new_peer_id} matches known peer {known.peer_id}.",
                    manifest_digest=digest
                )

        # Gate 4: No match
        return FingerprintManifest(
            verdict=FingerprintVerdict.FINGERPRINT_MISMATCH,
            matched_peer_id=None,
            directive=f"HALT: No fingerprint match for {candidate.new_peer_id}.",
            manifest_digest=cls._hash(f"NO_MATCH:{candidate.new_peer_id}")
        )