Skip to main content

Privacy Policy

The privacy policy category enforces data protection governance -- consent requirements, data residency restrictions, purpose limitations, and data minimization. Unlike behavioral categories that inspect what an agent produces, privacy governs how data flows through the agent: where it runs, why it processes data, and whether the user consented.

Use it to enforce GDPR, HIPAA, or custom privacy frameworks that require consent tokens, restrict data processing to specific regions, and limit data usage to declared purposes.

Rules

RuleTypeDefaultDescription
require_consentbooleanfalseWhether a consent token must be present on the context before the agent runs.
consent_token_fieldstring"consent_token"Name of the context attribute to check for the consent token. Change to gdpr_consent, hipaa_auth, etc. for custom flows.
data_residencystring[][]Allowed execution regions. Exact string match (e.g. "us-east-1", "eu-west-1"). Empty list means any region is allowed.
purpose_limitationstring[][]Allowed data processing purposes. Exact string match. Empty list means any purpose is allowed.
data_minimizationbooleantrueWhen true, the after_workflow phase flags over-collection when the data purpose exceeds declared limitations.
retention_by_typeobject{"pii": 30, "logs": 90, "analytics": 365}Retention periods in days per data type. Tagged in after_workflow metadata as an informational signal -- the handler does not enforce deletion.
action_on_violationstring"block""block" raises a PolicyViolationError. "warn" records a warning and lets the agent continue.

How It Works

The privacy handler runs at all three phases and reads context data populated by ctx.set_privacy_context().

Enforcement Phases

PhaseWhat it checksAction on violation
before_workflowConsent token present (if required); execution region in allowed listBLOCK or WARN per action_on_violation
mid_executionData purpose in allowed purposes listBLOCK or WARN per action_on_violation
after_workflowRe-audit consent + residency; flag over-collection; tag retention metadataWARN only

before_workflow — Pre-Flight Privacy Check

The handler validates pre-conditions before any agent work begins:

  1. If require_consent is true: is context.consent_token (or the configured field) truthy? If not → violation
  2. If data_residency is non-empty: is context.execution_region in the list? If not → violation

Both checks use action_on_violation to determine whether to block or warn.

mid_execution — Purpose Limitation Check

During tool calls and steps, the handler checks the data purpose:

  1. If purpose_limitation is non-empty: is context.data_purpose in the list? If not → violation

The purpose check only triggers if context.data_purpose has been set and purpose_limitation is non-empty. An empty purpose or an empty purpose list both result in ALLOW.

after_workflow — Audit and Metadata

After the agent completes, the handler performs an audit:

  • Re-checks consent (if required) and residency against configured rules
  • If data_minimization is true and the data purpose exceeds the declared limitation, adds a warning
  • Always tags retention_by_type in metadata when configured

After_workflow never blocks. It is an audit-only phase.

The handler calls getattr(context, consent_token_field, None) and checks truthiness:

Consent Token ValuePasses?Why
"usr_consent_abc123"YesNon-empty string is truthy
"" (empty string)NoEmpty string is falsy
NoneNoNone is falsy
0NoZero is falsy
"0"YesNon-empty string is truthy
FalseNoFalse is falsy

Custom consent fields: Set consent_token_field to the attribute name your agent sets. The default "consent_token" is checked via getattr(context, "consent_token", None). If your agent sets gdpr_consent instead, update the field to match:

{
"require_consent": true,
"consent_token_field": "gdpr_consent"
}

The agent must then call:

ctx.set_privacy_context(consent_token="usr_consent_abc123")

Note: set_privacy_context() always stores the value under the consent_token key internally. For custom field names, the controlplane propagates the value using the privacy_context dict and the configured consent_token_field. The default field name "consent_token" maps directly.

Data Residency Matching

The handler compares context.execution_region against the data_residency list using exact string matching:

execution_regiondata_residencyPasses?
"us-east-1"["us-east-1", "eu-west-1"]Yes
"eu-west-1"["us-east-1", "eu-west-1"]Yes
"ap-southeast-1"["us-east-1", "eu-west-1"]No → BLOCK/WARN
"" (empty)["us-east-1"]ALLOW (empty region skips check)
anything[] (empty list)ALLOW (no restriction)

Common region values: "us-east-1", "us-west-2", "eu-west-1", "eu-central-1", "ap-southeast-1". You may use any string that matches your infrastructure's region naming.

Purpose Limitation Matching

The handler compares context.data_purpose against the purpose_limitation list using exact string matching:

data_purposepurpose_limitationPasses?
"customer_support"["customer_support", "analytics"]Yes
"analytics"["customer_support", "analytics"]Yes
"marketing"["customer_support", "analytics"]No → BLOCK/WARN
"" (empty)["customer_support"]ALLOW (empty purpose skips check)
anything[] (empty list)ALLOW (no restriction)

Common purpose values: "customer_support", "analytics", "audit", "marketing", "research", "internal".

Example Policies

GDPR-Compliant

Require consent, EU residency only, purpose limited to support and analytics:

{
"require_consent": true,
"consent_token_field": "gdpr_consent",
"data_residency": ["eu-west-1", "eu-central-1"],
"purpose_limitation": ["customer_support", "analytics", "audit"],
"data_minimization": true,
"retention_by_type": {"pii": 30, "logs": 90, "analytics": 365},
"action_on_violation": "block"
}

HIPAA-Style

Consent required, US regions only, strict blocking:

{
"require_consent": true,
"consent_token_field": "hipaa_auth",
"data_residency": ["us-east-1", "us-west-2"],
"purpose_limitation": ["patient_care", "billing", "audit"],
"data_minimization": true,
"retention_by_type": {"pii": 2555, "logs": 2555, "analytics": 365},
"action_on_violation": "block"
}

Analytics-Only

No consent requirement, purpose limited to analytics, warn on violation:

{
"require_consent": false,
"data_residency": ["us-east-1", "eu-west-1"],
"purpose_limitation": ["analytics"],
"data_minimization": true,
"retention_by_type": {"analytics": 365},
"action_on_violation": "warn"
}

Permissive with Audit

No restrictions, just tag retention metadata and track data minimization:

{
"require_consent": false,
"data_residency": [],
"purpose_limitation": [],
"data_minimization": true,
"retention_by_type": {"pii": 30, "logs": 90, "analytics": 365},
"action_on_violation": "warn"
}

SDK Integration

Setting Privacy Context

Call ctx.set_privacy_context() early in the agent -- before the first tool call -- to ensure the before_workflow and mid_execution phases have the correct data:

import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError

try:
async with waxell.WaxellContext(
agent_name="data-agent",
workflow_name="data-processing",
inputs={"query": query},
enforce_policy=True,
) as ctx:

# Set privacy context before any work
ctx.set_privacy_context(
consent_token="usr_consent_abc123",
execution_region="us-east-1",
data_purpose="customer_support",
)

# before_workflow checked consent + region before this point
# mid_execution will check data_purpose on the next tool call

result = await process_data(query)

# Tool call triggers mid_execution governance
ctx.record_tool_call(
name="data_access",
tool_type="database",
input={"query": query, "purpose": "customer_support"},
output={"result": result},
)

ctx.set_result({"result": result})

except PolicyViolationError as e:
print(f"Privacy violation: {e}")
# e.g. "Consent token required but not provided (field: 'consent_token')"
# e.g. "Execution region 'ap-southeast-1' not in allowed residency list"
# e.g. "Data purpose 'marketing' not in allowed purposes"

set_privacy_context Parameters

ParameterTypeDefaultDescription
consent_tokenstr""User consent token or identifier. Empty string is ignored (falsy = no consent).
execution_regionstr""Data processing region (e.g. "us-east-1"). Empty string skips residency check.
data_purposestr""Purpose of data processing (e.g. "analytics"). Empty string skips purpose check.

Only non-empty values are stored. Call set_privacy_context() multiple times to update individual fields.

Enforcement Flow

Agent starts (WaxellContext.__aenter__)

└── before_workflow governance runs

├── require_consent=true?
│ └── context.consent_token falsy? → violation
│ ├── action=block → BLOCK (PolicyViolationError)
│ └── action=warn → WARN (agent continues)

├── data_residency non-empty?
│ └── context.execution_region not in list? → violation
│ ├── action=block → BLOCK
│ └── action=warn → WARN

└── All checks pass → ALLOW (store rules for mid_execution)

During execution (after tool calls):

└── mid_execution governance runs

└── purpose_limitation non-empty?
└── context.data_purpose set and not in list? → violation
├── action=block → BLOCK
└── action=warn → WARN

Agent exits (WaxellContext.__aexit__):

└── after_workflow governance runs

├── Re-audit consent (WARN only if missing)
├── Re-audit region (WARN only if non-compliant)
├── data_minimization=true? → flag over-collection if purpose exceeded
└── Tag retention_by_type in metadata

Creating via Dashboard

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Privacy
  4. Set require_consent, consent_token_field, data_residency, and purpose_limitation
  5. Configure data_minimization and retention_by_type
  6. Set action_on_violation to block or warn
  7. Set scope to target specific agents (e.g., privacy-data-agent)
  8. Enable

Creating via API

# Create a GDPR-compliant privacy policy
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "GDPR Data Privacy",
"category": "privacy",
"rules": {
"require_consent": true,
"consent_token_field": "consent_token",
"data_residency": ["eu-west-1", "eu-central-1"],
"purpose_limitation": ["customer_support", "analytics", "audit"],
"data_minimization": true,
"retention_by_type": {"pii": 30, "logs": 90, "analytics": 365},
"action_on_violation": "block"
},
"scope": {
"agents": ["data-agent"]
},
"enabled": true
}'

Observability

Governance Tab

Privacy evaluations appear with phase information:

before_workflow (allowed):

FieldExample
Phasebefore_workflow
Actionallow
Reason"Privacy rules stored for enforcement"

before_workflow (blocked — missing consent):

FieldExample
Phasebefore_workflow
Actionblock
Reason"Consent token required but not provided (field: 'consent_token')"
Metadata{"missing_field": "consent_token", "require_consent": true}

before_workflow (blocked — wrong region):

FieldExample
Reason"Execution region 'ap-southeast-1' not in allowed residency list"
Metadata{"execution_region": "ap-southeast-1", "allowed_regions": ["us-east-1", "eu-west-1"]}

mid_execution (blocked — wrong purpose):

FieldExample
Phasemid_execution
Reason"Data purpose 'marketing' not in allowed purposes"
Metadata{"data_purpose": "marketing", "allowed_purposes": ["customer_support", "analytics"]}

after_workflow (allowed, with retention metadata):

FieldExample
Phaseafter_workflow
Actionallow
Reason"Privacy audit passed"
Metadata{"retention_by_type": {"pii": 30, "logs": 90, "analytics": 365}, "data_minimization": true, "execution_region": "us-east-1"}

Combining with Other Policies

Privacy pairs naturally with other governance categories:

  • Compliance -- Use a compliance policy that requires privacy as a sibling policy to enforce GDPR or HIPAA at the framework level.
  • Audit -- Pair with an audit policy to log all data access with retention tracking.
  • Content -- Pair with a content policy that has pii_detection.enabled: true to catch PII in outputs.
  • Identity -- Pair with an identity policy for AI disclosure requirements when processing personal data.

Common Gotchas

  1. require_consent: false (default) means consent is never checked. If you want to enforce consent, you must explicitly set require_consent: true. Leaving it at the default means the agent can run without any consent token.

  2. Empty data_residency means any region is allowed. An empty list is the default permissive state. Add at least one region to create a restriction.

  3. Empty purpose_limitation means any purpose is allowed. Same as residency -- add at least one purpose to create a restriction.

  4. consent_token_field must match exactly what the agent sets. If the policy uses gdpr_consent but the agent calls set_privacy_context(consent_token="..."), the handler looks for gdpr_consent on the context, finds nothing, and blocks. The consent_token parameter in set_privacy_context() always stores under consent_token in the SDK.

  5. Retention metadata is informational only. The handler tags retention_by_type in after_workflow metadata but does not enforce deletion. Use your data pipeline's retention policies to actually delete data.

  6. data_minimization only flags in after_workflow -- it never blocks. It generates a WARN when the data purpose exceeds declared limitations, but the agent always continues. Use action_on_violation on mid_execution to block unauthorized purposes.

  7. Mid_execution purpose check requires data_purpose to be set before tool calls. If you call set_privacy_context(data_purpose="analytics") after the first tool call, that tool call's mid_execution check sees an empty purpose and passes. Set the full privacy context at the beginning of the agent run.

  8. Region check skips when execution_region is empty. The handler checks if execution_region and execution_region not in residency. An empty execution_region is not checked against the data_residency list. Always set the region if residency enforcement matters.

Next Steps