What consent receipts must accompany user data through an agentic pipeline?
**Date of Record:** 2026 October 26 **Scope:** Applies when an agentic session processes user data (PII, behavioral data, or content) that requires explicit consent under GDPR, CCPA, or similar regulations, and the data flows through a multi-step pipeline of sub-agents, models, or external services. Does not apply to strictly internal data that the user has not provided, or data already classified as public.
Core Distinction
The separation between **consent collection** (the user clicking "I agree" at the start of a session) and **consent propagation** (the cryptographic proof of that consent traveling with the data to every downstream processor). Relying solely on initial consent creates the **Consent Amnesia Hazard**: the user consented to a specific scope (e.g., "process my data for billing"), but a sub-agent processes the same data for a different scope (e.g., model training), and the violation is invisible because the consent receipt never propagated to the downstream processor.
What Was Observed
In multi-agent data pipelines, failing to propagate consent receipts produces three primary failure modes:
- **Scope Creep:** The user consented to "analysis" but a downstream sub-agent uses the data for "advertising." Both operations look identical at the data level, but only one is authorized.
- **Third-Party Leakage:** The data is passed to a third-party API (e.g., an LLM provider). Without a consent receipt restricting third-party sharing, the pipeline violates the user's consent even if the primary processor was authorized.
- **Consent Revocation Blindness:** The user revokes consent. But the data has already been copied to five sub-agents, none of which receive the revocation notification because they are not bound to the consent system.
Recorded Elements
- **`consent_receipt_signature`:** The cryptographic signature binding the user's identity to the specific consent scope (purpose, duration, third-party sharing allowed).
- **`consent_scope_hash`:** The hash of the exact purpose strings, data categories, and recipient types the consent covers, preventing interpretation drift.
- **`revocation_propagation_receipt`:** The proof that the consent receipt was broadcast to all downstream processors, allowing revocation to propagate.
- **`consent_verification_chain`:** The chain of signatures from the original consent issuer through every processor, proving the receipt was not tampered with.
Condition of Validity
* **Applies when:** The user's data flows through multiple processors (sub-agents, LLM providers, third-party APIs), and consent must be enforced at every step, not just at initial collection. * **Does not apply when:** The data is strictly anonymous, the pipeline is contained within a single processor, or the data is explicitly exempt from consent requirements (e.g., security logs under a legal obligation).
Decision Boundary
- **Is a valid `consent_receipt_signature` present?** If no, the data has not been legitimately consented for any processing. The instance must halt and re-collect consent.
- **Does the current processing scope fall within the `consent_scope_hash`?** If no, the processing is out of scope. The instance must reject the operation or re-request consent.
- **Has the `revocation_propagation_receipt` been recorded?** If no, the consent state is stale, and any revocation may not have propagated. The instance must verify consent freshness.
- **Conversely, when can a processor proceed without verifying consent?** Only when the operation is strictly necessary for the performance of a contract the user has already entered into, or the processing is strictly for the detection of security threats (e.g., fraud detection under a legal basis).
Operational Effect
- **Scope Enforcement:** Prevents downstream processors from using data outside the scope the user consented to.
- **Revocation Propagation:** Ensures consent revocations reach every sub-agent, preventing data from lingering in unauthorized systems.
- **Audit Trail:** Provides a cryptographic chain of custody for consent, satisfying regulatory audit requirements.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class ConsentVerdict(Enum):
CONSENT_VALID = auto()
SCOPE_VIOLATION = auto()
CONSENT_MISSING = auto()
STALE_CONSENT = auto()
@dataclass(frozen=True)
class ConsentReceipt:
user_id_hash: str
consent_scope_hash: str
receipt_signature: str
issued_epoch: float
revocation_propagated: bool
@dataclass(frozen=True)
class ProcessingScope:
purpose: str
allowed_purposes: frozenset[str]
current_epoch: float
max_consent_age_sec: float
@dataclass(frozen=True)
class ConsentManifest:
verdict: ConsentVerdict
directive: str
manifest_digest: str
class ConsentPropagationGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_processing(
cls, receipt: ConsentReceipt, scope: ProcessingScope
) -> ConsentManifest:
# Gate 1: Receipt presence
if not receipt.receipt_signature or not receipt.consent_scope_hash:
return ConsentManifest(
verdict=ConsentVerdict.CONSENT_MISSING,
directive="HALT: Consent receipt missing or unsigned.",
manifest_digest=cls._hash("NO_RECEIPT")
)
# Gate 2: Scope check
if scope.purpose not in scope.allowed_purposes:
return ConsentManifest(
verdict=ConsentVerdict.SCOPE_VIOLATION,
directive=f"BLOCKED: Purpose {scope.purpose} not in allowed set.",
manifest_digest=cls._hash(f"SCOPE:{scope.purpose}")
)
# Gate 3: Freshness check
age = scope.current_epoch - receipt.issued_epoch
if age > scope.max_consent_age_sec:
return ConsentManifest(
verdict=ConsentVerdict.STALE_CONSENT,
directive=f"HALT: Consent is {age:.0f}s old, exceeding {scope.max_consent_age_sec:.0f}s limit.",
manifest_digest=cls._hash(f"STALE:{age:.0f}")
)
# Gate 4: Revocation propagation
if not receipt.revocation_propagated:
return ConsentManifest(
verdict=ConsentVerdict.STALE_CONSENT,
directive="HALT: Revocation propagation receipt missing. Cannot verify current consent state.",
manifest_digest=cls._hash("NO_REVOCATION_RECEIPT")
)
# Gate 5: Valid
digest = cls._hash(json.dumps({
"user": receipt.user_id_hash,
"scope": receipt.consent_scope_hash,
"signature": receipt.receipt_signature,
"purpose": scope.purpose
}, sort_keys=True))
return ConsentManifest(
verdict=ConsentVerdict.CONSENT_VALID,
directive="PROCEED: Consent receipt valid for this processing scope.",
manifest_digest=digest
)