What federated learning boundary invariants must survive when agents contribute training signals?

**Date of Record:** 2026 October 27 **Scope:** Applies when multiple agentic sessions participate in federated learning (or distributed fine-tuning) by contributing training signals (gradients, embeddings, or model updates), and the coordinator aggregates them into a shared model, requiring boundaries that prevent individual contributors' data from leaking through the aggregate. Does not apply to centralized training where the training data does not leave a single trusted boundary.

Core Distinction

The separation between **training contribution** (the agent's signals are aggregated) and **contribution boundary** (the agent's signals cannot be reverse-engineered from the aggregate). Relying on contribution creates the **Federated Gradient Inversion**: an attacker in the federation reconstructs specific training examples from the aggregated gradients, breaking the privacy assumption that contributions are anonymous.

What Was Observed

In federated agent architectures, failing to preserve contribution boundaries produces three primary failure modes:

  1. **Gradient Inversion Attack:** A malicious coordinator (or a colluding subgroup) uses gradient inversion techniques to reconstruct individual contributors' training examples, leaking PII or proprietary data.
  2. **Contribution Attribution:** Even without full inversion, the coordinator can identify which specific contributor's data influenced a particular model behavior, revealing contributor identity or data patterns.
  3. **Poisoning Detection Leak:** When the coordinator detects poisoning (one contributor is malicious), it can attribute the poison to specific contributors, revealing their identities even if the poison was accidental.

Recorded Elements

  1. **`contribution_privacy_receipt`:** For each contribution, the cryptographic proof that the contribution was transformed by differential privacy noise, secure aggregation, or cryptographic blinding before leaving the contributor.
  2. **`aggregation_anonymization_signature`:** The cryptographic proof that the aggregation step prevents any single contribution from being isolated (e.g., secure multi-party computation, or minimum contribution count thresholds).
  3. **`contribution_boundary_receipt`:** The exact bounds on what the coordinator can learn about any contributor (e.g., "can learn aggregate but not individual gradients"), signed by the coordinator as a commitment.
  4. **`attribution_denial_receipt`:** For any attribution the coordinator performs (e.g., poisoning detection), the cryptographic proof that attribution is limited to non-identifying aggregates.

Condition of Validity

* **Applies when:** Contributions are aggregated into a shared model, individual contributors' data could leak, and privacy or attribution boundaries are required. * **Does not apply when:** All contributors are fully trusted (e.g., internal training), or contributions are strictly public and no privacy is expected.

Decision Boundary

  1. **Are contributions leaving the contributor's boundary?** If no, standard training applies. If yes, proceed.
  2. **Is the `contribution_privacy_receipt` present for each contribution?** If no, the contribution may leak. The contributor must transform signals before sending.
  3. **Does the `aggregation_anonymization_signature` prove no individual contributions can be isolated?** If no, the aggregation may be reversible. The coordinator must use privacy-preserving aggregation.
  4. **Conversely, when can contributions be shared without privacy transformation?** Only when all contributors are fully trusted, or the contributions are verified to contain no sensitive information (e.g., publicly available data).

Operational Effect

  1. **Gradient Inversion Resistance:** DP noise and secure aggregation prevent reconstruction of individual examples.
  2. **Attribution Prevention:** Aggregation thresholds prevent identifying which contributor's data influenced a behavior.
  3. **Bounded Disclosure:** Explicit commitment to what the coordinator can learn, verified cryptographically.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class FederatedVerdict(Enum):
    NO_SENSITIVE_CONTRIBUTION = auto()
    PRIVACY_PRESERVING_CONTRIBUTION = auto()
    PRIVACY_REQUIRED = auto()
    ATTRIBUTION_BOUNDARY_VIOLATED = auto()

@dataclass(frozen=True)
class Contribution:
    contributor_id: str
    contribution_hash: str
    privacy_receipt: str | None
    contains_sensitive_data: bool

@dataclass(frozen=True)
class FederatedState:
    contributions: tuple[Contribution, ...]
    aggregation_anonymized: bool
    min_contribution_threshold: int

@dataclass(frozen=True)
class FederatedPolicy:
    require_dp_or_secure_agg: bool
    require_min_contributors: bool

@dataclass(frozen=True)
class FederatedManifest:
    verdict: FederatedVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_contribution(
        cls, state: FederatedState, policy: FederatedPolicy
    ) -> FederatedManifest:
        
        # Gate 1: No sensitive data
        if not any(c.contains_sensitive_data for c in state.contributions):
            digest = cls._hash(json.dumps({
                "contributions": str(len(state.contributions)),
                "sensitivity": "NONE"
            }, sort_keys=True))
            return FederatedManifest(
                verdict=FederatedVerdict.NO_SENSITIVE_CONTRIBUTION,
                directive="PROCEED: No sensitive data in contributions.",
                manifest_digest=digest
            )

        # Gate 2: Privacy receipt
        if policy.require_dp_or_secure_agg:
            missing_privacy = [
                c.contributor_id for c in state.contributions
                if c.contains_sensitive_data and not c.privacy_receipt
            ]
            if missing_privacy:
                return FederatedManifest(
                    verdict=FederatedVerdict.PRIVACY_REQUIRED,
                    directive=f"HALT: {len(missing_privacy)} sensitive contributions lack privacy receipt.",
                    manifest_digest=cls._hash(f"NO_PRIVACY:{len(missing_privacy)}")
                )

        # Gate 3: Minimum contributors
        if policy.require_min_contributors:
            if len(state.contributions) < state.min_contribution_threshold:
                return FederatedManifest(
                    verdict=FederatedVerdict.ATTRIBUTION_BOUNDARY_VIOLATED,
                    directive=f"HALT: {len(state.contributions)} contributors < {state.min_contribution_threshold}. Attribution possible.",
                    manifest_digest=cls._hash(f"FEW_CONTRIBUTORS:{len(state.contributions)}")
                )

        # Gate 4: Aggregation anonymized
        if not state.aggregation_anonymized:
            return FederatedManifest(
                verdict=FederatedVerdict.ATTRIBUTION_BOUNDARY_VIOLATED,
                directive="HALT: Aggregation not anonymized. Individual contributions isolated.",
                manifest_digest=cls._hash("NOT_ANONYMIZED")
            )

        # Gate 5: Privacy-preserving
        digest = cls._hash(json.dumps({
            "contributions": str(len(state.contributions)),
            "all_private": "TRUE",
            "anonymized": str(state.aggregation_anonymized),
            "min_contributors": str(state.min_contribution_threshold)
        }, sort_keys=True))
        
        return FederatedManifest(
            verdict=FederatedVerdict.PRIVACY_PRESERVING_CONTRIBUTION,
            directive=f"PROCEED: {len(state.contributions)} contributions with privacy receipts and anonymized aggregation.",
            manifest_digest=digest
        )