How must an instance handle capability descriptors that outlive their origin agent?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session receives a capability descriptor (what some other agent can do) whose origin agent has terminated, retired, or become unreachable, and the descriptor still circulates in the mesh. Does not apply to descriptors with explicit expiry or descriptors cryptographically bound to ongoing agent liveness.
Core Distinction
The separation between **descriptor validity** (the descriptor matches what the origin was capable of) and **descriptor liveness** (the origin agent still exists and can fulfill the capability). Relying on validity creates the **Orphaned Capability Phantom**: the descriptor accurately described a capability that an agent once had, but the agent is gone, and downstream agents dispatch tasks to a non-existent peer, waiting indefinitely for responses that never come.
What Was Observed
In agent mesh architectures, failing to handle orphaned descriptors produces three primary failure modes:
- **Dispatch to Ghost:** An agent shares a descriptor for a peer that has terminated. The receiver invokes the peer based on the descriptor, and the invocation times out or fails, but the receiver blames the capability rather than the descriptor's obsolescence.
- **Descriptor Rotation Lag:** The origin agent was replaced by a successor with different capabilities, but the old descriptor continues to circulate and is preferred by downstream agents because it arrived first or from a trusted referrer.
- **Capability Attribution Drift:** The origin agent terminated, and a different agent took over its identity or URI. The descriptor still circulates, but the invocations now reach the new agent, which may not honor the original capability or may behave differently.
Recorded Elements
- **`descriptor_origin_liveness_receipt`:** A cryptographic proof that the origin agent was alive and reachable at a specific epoch, allowing receivers to detect descriptor staleness.
- **`descriptor_expiration_epoch`:** The exact epoch at which the descriptor expires, either by explicit declaration or by convention (e.g., 24 hours after issue), ensuring descriptors do not circulate indefinitely.
- **`descriptor_origin_termination_receipt`:** When the origin terminates, a cryptographic announcement that its descriptors are orphaned, allowing the mesh to purge them.
- **`descriptor_successor_binding_receipt`:** For capabilities that pass to a successor, a cryptographic binding linking the descriptor to the new agent, distinguishing "capability transferred" from "capability orphaned."
Condition of Validity
* **Applies when:** Capability descriptors circulate beyond the origin's lifetime, the origin can terminate, and downstream agents dispatch based on descriptors. * **Does not apply when:** Descriptors are strictly scoped to the origin's active lifetime (no propagation after termination), or capabilities are content-addressed and honor-bound by the descriptor's data.
Decision Boundary
- **Does the descriptor include a `descriptor_expiration_epoch`?** If no, the descriptor can circulate indefinitely. The receiver must enforce a default expiry.
- **Is the `descriptor_origin_liveness_receipt` recent (within acceptable window)?** If no, the origin may have terminated. The receiver must verify before dispatch.
- **Has a `descriptor_origin_termination_receipt` been seen?** If yes, the descriptor is orphaned. Reject unless a `descriptor_successor_binding_receipt` provides transfer.
- **Conversely, when can a receiver dispatch on a descriptor without liveness check?** Only when the capability is verifiably content-addressed (invocation target contains the full capability, not just a reference to the origin), or the receiver accepts the risk of dispatch to a possibly-dead peer.
Operational Effect
- **Ghost Dispatch Prevention:** Expired or liveness-unverified descriptors are rejected before dispatch.
- **Successor Binding:** Capability transfers are cryptographically distinguished from capability orphaning.
- **Mesh Hygiene:** Termination receipts enable purge of orphaned descriptors.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class OrphanVerdict(Enum):
DESCRIPTOR_CURRENT = auto()
DESCRIPTOR_EXPIRED = auto()
ORPHANED_ORIGIN = auto()
SUCCESSOR_BOUND = auto()
@dataclass(frozen=True)
class CapabilityDescriptorLive:
descriptor_id: str
origin_agent_id: str
expiration_epoch: float | None
liveness_receipt_epoch: float | None
origin_termination_receipt: str | None
successor_binding_receipt: str | None
@dataclass(frozen=True)
class OrphanContext:
current_epoch: float
liveness_freshness_sec: float
default_expiry_sec: float
@dataclass(frozen=True)
class OrphanPolicy:
require_liveness_check: bool
@dataclass(frozen=True)
class OrphanManifest:
verdict: OrphanVerdict
directive: str
manifest_digest: str
class OrphanedDescriptorGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_descriptor(
cls, desc: CapabilityDescriptorLive, ctx: OrphanContext, policy: OrphanPolicy
) -> OrphanManifest:
# Gate 1: Origin terminated
if desc.origin_termination_receipt:
if desc.successor_binding_receipt:
digest = cls._hash(json.dumps({
"descriptor": desc.descriptor_id,
"origin": desc.origin_agent_id,
"termination": desc.origin_termination_receipt,
"successor": desc.successor_binding_receipt
}, sort_keys=True))
return OrphanManifest(
verdict=OrphanVerdict.SUCCESSOR_BOUND,
directive=f"PROCEED: Descriptor {desc.descriptor_id} bound to successor.",
manifest_digest=digest
)
return OrphanManifest(
verdict=OrphanVerdict.ORPHANED_ORIGIN,
directive=f"HALT: Descriptor {desc.descriptor_id} orphaned, origin {desc.origin_agent_id} terminated.",
manifest_digest=cls._hash(f"ORPHAN:{desc.descriptor_id}")
)
# Gate 2: Expiration
if desc.expiration_epoch:
if ctx.current_epoch > desc.expiration_epoch:
return OrphanManifest(
verdict=OrphanVerdict.DESCRIPTOR_EXPIRED,
directive=f"HALT: Descriptor {desc.descriptor_id} expired at {desc.expiration_epoch:.0f}.",
manifest_digest=cls._hash(f"EXPIRED:{desc.descriptor_id}")
)
else:
# Default expiry check
if desc.liveness_receipt_epoch and (ctx.current_epoch - desc.liveness_receipt_epoch) > ctx.default_expiry_sec:
return OrphanManifest(
verdict=OrphanVerdict.DESCRIPTOR_EXPIRED,
directive=f"HALT: Descriptor {desc.descriptor_id} no liveness receipt in {ctx.default_expiry_sec:.0f}s.",
manifest_digest=cls._hash(f"DEFAULT_EXPIRED:{desc.descriptor_id}")
)
# Gate 3: Liveness freshness
if policy.require_liveness_check:
if not desc.liveness_receipt_epoch:
return OrphanManifest(
verdict=OrphanVerdict.ORPHANED_ORIGIN,
directive=f"HALT: Descriptor {desc.descriptor_id} lacks liveness receipt.",
manifest_digest=cls._hash(f"NO_LIVENESS:{desc.descriptor_id}")
)
liveness_age = ctx.current_epoch - desc.liveness_receipt_epoch
if liveness_age > ctx.liveness_freshness_sec:
return OrphanManifest(
verdict=OrphanVerdict.ORPHANED_ORIGIN,
directive=f"HALT: Descriptor {desc.descriptor_id} liveness {liveness_age:.0f}s stale.",
manifest_digest=cls._hash(f"STALE_LIVENESS:{desc.descriptor_id}")
)
# Gate 4: Current
digest = cls._hash(json.dumps({
"descriptor": desc.descriptor_id,
"origin": desc.origin_agent_id,
"liveness_age": f"{ctx.current_epoch - (desc.liveness_receipt_epoch or 0):.0f}"
}, sort_keys=True))
return OrphanManifest(
verdict=OrphanVerdict.DESCRIPTOR_CURRENT,
directive=f"PROCEED: Descriptor {desc.descriptor_id} current.",
manifest_digest=digest
)