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
| Rule | Type | Default | Description |
|---|---|---|---|
require_consent | boolean | false | Whether a consent token must be present on the context before the agent runs. |
consent_token_field | string | "consent_token" | Name of the context attribute to check for the consent token. Change to gdpr_consent, hipaa_auth, etc. for custom flows. |
data_residency | string[] | [] | Allowed execution regions. Exact string match (e.g. "us-east-1", "eu-west-1"). Empty list means any region is allowed. |
purpose_limitation | string[] | [] | Allowed data processing purposes. Exact string match. Empty list means any purpose is allowed. |
data_minimization | boolean | true | When true, the after_workflow phase flags over-collection when the data purpose exceeds declared limitations. |
retention_by_type | object | {"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_violation | string | "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
| Phase | What it checks | Action on violation |
|---|---|---|
| before_workflow | Consent token present (if required); execution region in allowed list | BLOCK or WARN per action_on_violation |
| mid_execution | Data purpose in allowed purposes list | BLOCK or WARN per action_on_violation |
| after_workflow | Re-audit consent + residency; flag over-collection; tag retention metadata | WARN only |
before_workflow — Pre-Flight Privacy Check
The handler validates pre-conditions before any agent work begins:
- If
require_consentistrue: iscontext.consent_token(or the configured field) truthy? If not → violation - If
data_residencyis non-empty: iscontext.execution_regionin 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:
- If
purpose_limitationis non-empty: iscontext.data_purposein 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_minimizationistrueand the data purpose exceeds the declared limitation, adds a warning - Always tags
retention_by_typein metadata when configured
After_workflow never blocks. It is an audit-only phase.
Consent Token Matching
The handler calls getattr(context, consent_token_field, None) and checks truthiness:
| Consent Token Value | Passes? | Why |
|---|---|---|
"usr_consent_abc123" | Yes | Non-empty string is truthy |
"" (empty string) | No | Empty string is falsy |
None | No | None is falsy |
0 | No | Zero is falsy |
"0" | Yes | Non-empty string is truthy |
False | No | False 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_region | data_residency | Passes? |
|---|---|---|
"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_purpose | purpose_limitation | Passes? |
|---|---|---|
"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
| Parameter | Type | Default | Description |
|---|---|---|---|
consent_token | str | "" | User consent token or identifier. Empty string is ignored (falsy = no consent). |
execution_region | str | "" | Data processing region (e.g. "us-east-1"). Empty string skips residency check. |
data_purpose | str | "" | 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
- Navigate to Governance > Policies
- Click New Policy
- Select category Privacy
- Set
require_consent,consent_token_field,data_residency, andpurpose_limitation - Configure
data_minimizationandretention_by_type - Set
action_on_violationtoblockorwarn - Set scope to target specific agents (e.g.,
privacy-data-agent) - 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):
| Field | Example |
|---|---|
| Phase | before_workflow |
| Action | allow |
| Reason | "Privacy rules stored for enforcement" |
before_workflow (blocked — missing consent):
| Field | Example |
|---|---|
| Phase | before_workflow |
| Action | block |
| Reason | "Consent token required but not provided (field: 'consent_token')" |
| Metadata | {"missing_field": "consent_token", "require_consent": true} |
before_workflow (blocked — wrong region):
| Field | Example |
|---|---|
| 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):
| Field | Example |
|---|---|
| Phase | mid_execution |
| Reason | "Data purpose 'marketing' not in allowed purposes" |
| Metadata | {"data_purpose": "marketing", "allowed_purposes": ["customer_support", "analytics"]} |
after_workflow (allowed, with retention metadata):
| Field | Example |
|---|---|
| Phase | after_workflow |
| Action | allow |
| 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
privacyas 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: trueto catch PII in outputs. - Identity -- Pair with an identity policy for AI disclosure requirements when processing personal data.
Common Gotchas
-
require_consent: false(default) means consent is never checked. If you want to enforce consent, you must explicitly setrequire_consent: true. Leaving it at the default means the agent can run without any consent token. -
Empty
data_residencymeans any region is allowed. An empty list is the default permissive state. Add at least one region to create a restriction. -
Empty
purpose_limitationmeans any purpose is allowed. Same as residency -- add at least one purpose to create a restriction. -
consent_token_fieldmust match exactly what the agent sets. If the policy usesgdpr_consentbut the agent callsset_privacy_context(consent_token="..."), the handler looks forgdpr_consenton the context, finds nothing, and blocks. Theconsent_tokenparameter inset_privacy_context()always stores underconsent_tokenin the SDK. -
Retention metadata is informational only. The handler tags
retention_by_typein after_workflow metadata but does not enforce deletion. Use your data pipeline's retention policies to actually delete data. -
data_minimizationonly flags in after_workflow -- it never blocks. It generates a WARN when the data purpose exceeds declared limitations, but the agent always continues. Useaction_on_violationon mid_execution to block unauthorized purposes. -
Mid_execution purpose check requires
data_purposeto be set before tool calls. If you callset_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. -
Region check skips when
execution_regionis empty. The handler checksif execution_region and execution_region not in residency. An emptyexecution_regionis not checked against thedata_residencylist. Always set the region if residency enforcement matters.
Next Steps
- Policy & Governance -- How policy enforcement works
- Compliance Policy -- Requires privacy as a sibling for GDPR/HIPAA profiles
- Delegation Policy -- Multi-agent trust governance
- Content Policy -- PII detection in agent outputs
- Policy Categories & Templates -- All 26 categories