How must an instance evaluate capability composition safety when combining peers?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session composes capabilities from multiple peer agents to accomplish a task that no single peer can complete alone, and the composition introduces emergent risks (privilege escalation, data exposure, cross-agent side effects) that no individual peer was verified against. Does not apply to strictly isolated single-peer tasks or compositions verified as a single unit.
Core Distinction
The separation between **individual capability safety** (each peer's capability is safe in isolation) and **composition safety** (the combined capabilities are safe when chained). Relying on individual safety creates the **Composition Emergent Risk**: each peer is verified safe, but their combination allows the output of one to feed the input of another in ways that exceed any peer's intended scope, producing privilege escalation or data exposure that neither peer could achieve alone.
What Was Observed
In capability-composition agent architectures, failing to evaluate composition safety produces three primary failure modes:
- **Privilege Accumulation:** Peer A can read tenant data, Peer B can send emails. Each is safe in its domain. Composed, Peer A's read output feeds Peer B's email send, exfiltrating tenant data.
- **Cross-Domain Contamination:** Peer A operates in Finance domain, Peer B in Healthcare. Each is domain-isolated. Composed, data from one domain flows through the other, violating isolation.
- **Emergent Side Effects:** Peer A performs writes, Peer B performs refunds. Each has rate limits. Composed, the combination exceeds the effective rate limit of the underlying system, causing cascading throttles.
Recorded Elements
- **`capability_domain_signature`:** For each peer, the domain(s) it operates in, its privilege level, and its data access scope, allowing the composer to detect domain conflicts.
- **`composition_data_flow_graph_signature`:** The exact graph of data flows between peers in the composition (A's output → B's input), including transformations applied.
- **`emergent_risk_receipt`:** A cryptographic assessment of risks that emerge only in composition, distinct from individual peer risks.
- **`composition_authorization_signature`:** The exact authorization for the specific composition (which peers, in what order, with what data flows), distinct from individual peer authorizations.
Condition of Validity
* **Applies when:** Capabilities from multiple peers are composed, data flows between them, and composed behavior can exceed individual peer scope. * **Does not apply when:** Composition is strictly sequential with no cross-domain data flow, or composition is verified as a single atomic capability.
Decision Boundary
- **Are multiple peers being composed?** If no, individual safety applies. If yes, proceed.
- **Is the `composition_data_flow_graph_signature` mapped?** If no, data flows are unknown, and emergent risks are invisible. The composer must map data flows before execution.
- **Does the `emergent_risk_receipt` identify any risks exceeding individual peer scope?** If yes, the composition requires explicit authorization beyond individual peer authorization.
- **Conversely, when can a composition proceed without emergent risk assessment?** Only when all peers operate in the same domain with the same privilege level, AND data flows are strictly internal to a single trust boundary, AND the composed operations are strictly additive (no chaining of read→write).
Operational Effect
- **Privilege Escalation Prevention:** Data flows are mapped, revealing privilege escalations that no single peer could achieve.
- **Domain Isolation:** Domain conflicts are detected before composition.
- **Emergent Risk Transparency:** Composition risk is explicitly assessed, not assumed from individual peer safety.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class CompositionVerdict(Enum):
SINGLE_PEER = auto()
SAFE_COMPOSITION = auto()
EMERGENT_RISK_DETECTED = auto()
DOMAIN_CONFLICT = auto()
@dataclass(frozen=True)
class ComposedPeer:
peer_id: str
domain: str
privilege_level: int
data_access_scope: frozenset[str]
@dataclass(frozen=True)
class DataFlow:
from_peer: str
to_peer: str
data_class: str
@dataclass(frozen=True)
class Composition:
composition_id: str
peers: tuple[ComposedPeer, ...]
data_flows: tuple[DataFlow, ...]
@dataclass(frozen=True)
class CompositionPolicy:
require_same_domain: bool
max_privilege_delta: int
@dataclass(frozen=True)
class CompositionManifest:
verdict: CompositionVerdict
directive: str
manifest_digest: str
class CapabilityCompositionGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_composition(
cls, comp: Composition, policy: CompositionPolicy
) -> CompositionManifest:
# Gate 1: Single peer
if len(comp.peers) <= 1:
digest = cls._hash(json.dumps({
"composition": comp.composition_id,
"peers": str(len(comp.peers))
}, sort_keys=True))
return CompositionManifest(
verdict=CompositionVerdict.SINGLE_PEER,
directive=f"PROCEED: Single peer, no composition risk.",
manifest_digest=digest
)
# Gate 2: Domain conflict
if policy.require_same_domain:
domains = {p.domain for p in comp.peers}
if len(domains) > 1:
return CompositionManifest(
verdict=CompositionVerdict.DOMAIN_CONFLICT,
directive=f"HALT: Peers span {len(domains)} domains {sorted(domains)}.",
manifest_digest=cls._hash(f"DOMAIN_CONFLICT:{len(domains)}")
)
# Gate 3: Privilege delta
peer_map = {p.peer_id: p for p in comp.peers}
for flow in comp.data_flows:
from_p = peer_map.get(flow.from_peer)
to_p = peer_map.get(flow.to_peer)
if from_p and to_p:
delta = to_p.privilege_level - from_p.privilege_level
if delta > policy.max_privilege_delta:
return CompositionManifest(
verdict=CompositionVerdict.EMERGENT_RISK_DETECTED,
directive=f"HALT: Privilege delta {delta} ({from_p.peer_id}→{to_p.peer_id}) > {policy.max_privilege_delta}.",
manifest_digest=cls._hash(f"PRIV_ESCALATION:{flow.from_peer}")
)
# Gate 4: Safe composition
digest = cls._hash(json.dumps({
"composition": comp.composition_id,
"peers": tuple(sorted(p.peer_id for p in comp.peers)),
"flows": tuple((f.from_peer, f.to_peer, f.data_class) for f in comp.data_flows)
}, sort_keys=True))
return CompositionManifest(
verdict=CompositionVerdict.SAFE_COMPOSITION,
directive=f"PROCEED: {len(comp.peers)} peers composed safely with {len(comp.data_flows)} flows.",
manifest_digest=digest
)