SessionPrivacyPolicy CRD
SessionPrivacyPolicy is a namespaced enterprise CRD that captures privacy rules for session data: PII redaction of what is recorded, how data is encrypted at rest, and encryption key rotation. Policies are reusable documents — a single policy can be referenced by multiple service groups or agent overrides. The policy itself carries no binding information; binding happens at the consumer side.
API version
Section titled “API version”apiVersion: omnia.altairalabs.ai/v1alpha1kind: SessionPrivacyPolicyShort name: spp (e.g. kubectl get spp).
Resource scope
Section titled “Resource scope”SessionPrivacyPolicy is namespace-scoped. Policies must live in the same namespace as the Workspace or AgentRuntime that references them, except for the global default, which lives in the operator’s namespace (omnia-system in a default install).
How policies are bound
Section titled “How policies are bound”Policies are attached to consumers, not embedded in the policy itself.
Service group binding (workspace)
Section titled “Service group binding (workspace)”Each entry in Workspace.spec.services[] has an optional privacyPolicyRef field that selects a policy in that workspace’s namespace:
apiVersion: omnia.altairalabs.ai/v1alpha1kind: Workspacemetadata: name: my-workspacespec: services: - name: default privacyPolicyRef: name: my-policyAgent override (AgentRuntime)
Section titled “Agent override (AgentRuntime)”An AgentRuntime can override its service group’s policy via spec.privacyPolicyRef. The policy must exist in the same namespace as the AgentRuntime:
apiVersion: omnia.altairalabs.ai/v1alpha1kind: AgentRuntimemetadata: name: my-agent namespace: my-workspace-nsspec: privacyPolicyRef: name: strict-policyResolution order
Section titled “Resolution order”When the session-api resolves the effective policy for a session, it uses the first matching rule in this chain:
AgentRuntime.spec.privacyPolicyRef— per-agent override in the agent’s own namespace.- The service group’s
privacyPolicyRefon the Workspace whosespec.namespace.namematches the agent’s namespace. The service group is determined byAgentRuntime.spec.serviceGroup(defaults todefault). - The global default
SessionPrivacyPolicynameddefaultin the operator’s namespace (omnia-systemin a default install; the operator passes it to the service pods asOMNIA_OPERATOR_NAMESPACE). - No policy applies — session data is recorded without redaction or encryption.
A reference to a policy that does not exist falls through to the next step. There is no merge semantics. The first matching policy is used in its entirety.
The Workspace reports whether every service-group reference resolves on its
PrivacyPolicyResolved condition; an AgentRuntime reports its own reference the
same way.
Namespacing rules
Section titled “Namespacing rules”| Binding location | Policy must live in |
|---|---|
Workspace.spec.services[].privacyPolicyRef | The workspace’s own namespace (spec.namespace.name) |
AgentRuntime.spec.privacyPolicyRef | The AgentRuntime’s namespace |
| Global default | The operator’s namespace (omnia-system by default), named default |
Spec fields
Section titled “Spec fields”recording (required)
Section titled “recording (required)”Controls what session data is recorded.
| Field | Type | Default | Required |
|---|---|---|---|
recording.pii | PIIConfig | — | No |
recording carries only pii. Whether sessions are recorded at all is decided
by the topology: a service group with a session-api records, one without does
not. There is no recording.enabled field.
There is no recording.transcript field. For sensitive content use
recording.pii: redaction applies to everything recorded.
recording.pii
Section titled “recording.pii”Configures automatic PII detection and handling.
| Field | Type | Description |
|---|---|---|
pii.redact | bool | Enable PII redaction |
pii.patterns | string[] | PII patterns to detect. Built-in: ssn, credit_card, phone_number, email, ip_address. Custom regex with custom: prefix, e.g. custom:^[A-Z]{2}\d{6}$ |
pii.strategy | string | Redaction method: replace (default, e.g. [REDACTED_SSN]), hash (deterministic truncated SHA-256, e.g. [HASH_SSN:1a2b3c4d5e6f]), mask (preserve last 4 chars) |
Redaction runs only when redact is true and patterns is non-empty. It is
applied by session-api to what it records and by memory-api to memory content.
session-api redacts the content fields of each write. Opt-out suppression (below) replaces the same fields:
| Write | Content fields | Stored unchanged |
|---|---|---|
| Message | content | role, metadata, tokens, cost, ids, timestamps |
| ToolCall | arguments, result, errorMessage | name, status, labels, duration, ids, timestamps |
| ProviderCall | errorMessage | provider, model, tokens, cost, latency, finish reason, status |
| RuntimeEvent | the values under data, and errorMessage | eventType, the data keys, duration, ids, timestamps |
| EvalResult | details | evalId, passed, score, and every other field |
Redaction descends into objects and arrays and rewrites every string value it
reaches. It never rewrites a key, a number, a bool or null. A runtime event’s
data carries reasoning text, streamed partial output and client-tool
arguments. Two event types are exempt: the payloads of policy.decision and
tool.receipt are enforcement records, which session-api also forwards to the
audit hub, so they are stored as written. Their errorMessage is still
redacted.
spec: recording: pii: redact: true patterns: - email - ssn - credit_card strategy: replaceUser opt-out
Section titled “User opt-out”When a user has opted out, session-api still records the write and replaces its
content with the sentinel [OPTED_OUT]. The content fields are the ones
listed under recording.pii: message bodies, tool-call
arguments, results and error text, provider-call error text, the values under a
runtime event’s data together with its error text, and an eval result’s
details. A whole value is replaced, not the strings inside it. An object
value becomes {"_optedOut":"[OPTED_OUT]"}, which keeps fields such as
tool-call arguments decodable as objects. Every other value becomes the
string [OPTED_OUT]. Everything else is stored unchanged: tokens, cost, model,
latency, tool names, statuses, timings and eval verdicts.
Sessions explains why.
Opt-out is decided per write from the user the writer names in the
X-Omnia-User-ID header. The facade and runtime name the user of the
conversation, and the eval worker names the user of the session it evaluates,
so an opted-out user’s eval details are replaced too. A write that names no
user, such as one for an anonymous session, is not suppressed.
There is no flag to disable this: deploying privacy-api enables opt-out. Suppression runs only for a session whose effective policy resolves (steps 1 to 3 of the resolution order); when no policy applies, writes are stored unchanged.
encryption
Section titled “encryption”Configures encryption at rest for session data. session-api encrypts with the configured KMS provider when it writes; the cloud KMS providers use envelope encryption (AES-256-GCM with a KMS-wrapped data key).
| Field | Type | Description |
|---|---|---|
encryption.enabled | bool | Enable encryption |
encryption.kmsProvider | string | KMS provider. One of: aws-kms, azure-keyvault, gcp-kms, vault, or local, which is not a KMS and is for development only (see below). Immutable once set |
encryption.keyID | string | Key identifier within the KMS provider. Immutable once set |
encryption.secretRef.name | string | Name of a Secret containing provider credentials. session-api reads it from its own (workspace) namespace; the key-rotation controller reads it from the operator’s namespace |
encryption.keyRotation | KeyRotationConfig | Key rotation settings |
Both kmsProvider and keyID are required when enabled is true (enforced by CEL validation).
Once set, kmsProvider and keyID cannot be changed or cleared, and the
encryption block cannot be removed from a policy that has a keyID; the API
server rejects the update (CEL transition rules). Every KMS provider decrypts
only under the key it is configured with, and a stored record does not name the
key that sealed it, so a policy pointed at a different key would make every
record already written unreadable — session-api fails those reads. Setting a key
for the first time, setting enabled: false, and editing secretRef or
keyRotation are all allowed. To move to new key material, rotate the key
(below), which keeps the same keyID; for a different key or KMS, see
Rotate encryption keys.
Per-field encryption coverage
Section titled “Per-field encryption coverage”Not all fields are encrypted. Fields that are useful for analytics or operational queries remain plaintext:
| Object | Encrypted | Plaintext |
|---|---|---|
| Message | content | metadata, role, type, sessionID, timestamps |
| ToolCall | arguments, result, errorMessage | name, status, sessionID, timestamps |
| RuntimeEvent | data, errorMessage | eventType, sessionID, timestamps |
| ProviderCall | (none — entirely plaintext) | all fields |
Encrypted string fields (message content, errorMessage) carry the enc:v1:
prefix followed by base64 ciphertext. Encrypted JSON fields (arguments,
result, data) are replaced by an envelope object {"_enc": true, "_payload": "<base64 ciphertext>"}.
Values without these markers are read back as plaintext.
encryption.keyRotation
Section titled “encryption.keyRotation”| Field | Type | Description |
|---|---|---|
keyRotation.enabled | bool | Enable rotation. Rotation runs only when encryption.enabled is also true |
keyRotation.schedule | string | Standard 5-field cron expression, e.g. 0 0 1 * * for monthly. A policy that has never rotated rotates on its first reconcile; after that, at the next scheduled time after status.keyRotation.lastRotatedAt. An invalid expression emits a KeyRotationFailed warning event |
keyRotation.reEncryptExisting | bool | After a rotation, re-encrypt existing records onto the new key version in batches |
keyRotation.batchSize | int32 | Records per re-encryption batch (1–1000, default 100) |
Rotation is performed by the enterprise key-rotation controller, which asks the KMS
to create a new version of encryption.keyID and records it in
status.keyRotation.currentKeyVersion; spec.encryption.keyID itself is not
changed. Annotating the policy with omnia.altairalabs.ai/rotate-key: "true"
triggers an immediate rotation, after which the annotation is removed.
Re-encryption requires the controller to have a session database connection
(--session-postgres-conn); without one, the re-encryption status goes to
Failed.
Re-encryption rewrites exactly the fields listed in
Per-field encryption coverage, in the same
enc:v1: and {"_enc": true, "_payload": ...} formats: it decrypts each with
the policy’s key, whichever version sealed it, and encrypts it again with the
current version. Records carry no key version, so a pass walks every encrypted
message, then tool call, then runtime event once, and keeps its position in
status.keyRotation.reEncryptionProgress.cursor. A record that cannot be
decrypted, such as one sealed by a different key, is counted as an error,
logged as record re-encryption failed, and left unchanged.
spec: encryption: enabled: true kmsProvider: aws-kms keyID: arn:aws:kms:us-east-1:123456789012:key/mrk-abc123 secretRef: name: aws-kms-credentials keyRotation: enabled: true schedule: "0 0 1 * *" reEncryptExisting: true batchSize: 100Status fields
Section titled “Status fields”| Field | Type | Description |
|---|---|---|
status.phase | string | Active once the controller has reconciled the policy |
status.observedGeneration | int64 | Last generation reconciled |
status.conditions | Condition[] | Standard Kubernetes conditions |
status.keyRotation | KeyRotationStatus | Key rotation progress (when configured) |
Conditions
Section titled “Conditions”| Type | Meaning |
|---|---|
Ready | Set True (reason PolicyValidated) when the controller reconciles the policy. Structural validation is done by the CRD schema at admission |
KeyRotationReady | Present when key rotation is enabled: True (reason KeyRotated) after a successful rotation, False (reason KeyRotationFailed) when rotation fails, False (reason ReEncryptionFailed) when re-encrypting existing data fails |
status.keyRotation
Section titled “status.keyRotation”| Field | Type | Description |
|---|---|---|
keyRotation.lastRotatedAt | time | Timestamp of the last successful rotation |
keyRotation.currentKeyVersion | string | Version of the key in use |
keyRotation.reEncryptionProgress.status | string | InProgress, Completed, or Failed |
keyRotation.reEncryptionProgress.messagesProcessed | int64 | Records (messages, tool calls and runtime events) re-encrypted so far |
keyRotation.reEncryptionProgress.cursor | string | Opaque position of the current pass, so it resumes rather than restarts; cleared on completion |
keyRotation.reEncryptionProgress.startedAt | time | When re-encryption began |
keyRotation.reEncryptionProgress.completedAt | time | When re-encryption finished |
Examples
Section titled “Examples”Minimal policy — record without redaction
Section titled “Minimal policy — record without redaction”apiVersion: omnia.altairalabs.ai/v1alpha1kind: SessionPrivacyPolicymetadata: name: record-only namespace: my-workspace-nsspec: recording: {}Full policy — PII redaction and encryption with key rotation
Section titled “Full policy — PII redaction and encryption with key rotation”apiVersion: omnia.altairalabs.ai/v1alpha1kind: SessionPrivacyPolicymetadata: name: gdpr-compliant namespace: my-workspace-nsspec: recording: pii: redact: true patterns: - email - ssn - credit_card - phone_number strategy: replace encryption: enabled: true kmsProvider: aws-kms keyID: arn:aws:kms:us-east-1:123456789012:key/mrk-abc123 secretRef: name: aws-kms-credentials keyRotation: enabled: true schedule: "0 0 1 * *" reEncryptExisting: falseGlobal default in omnia-system
Section titled “Global default in omnia-system”apiVersion: omnia.altairalabs.ai/v1alpha1kind: SessionPrivacyPolicymetadata: name: default namespace: omnia-systemspec: recording: pii: redact: true patterns: - email - phone_numberRelated resources
Section titled “Related resources”- Configure Privacy Policies — step-by-step setup guide
- Manage User Consent and Opt-Out — record and read per-user opt-out and consent via privacy-api
- Handle Data Subject Erasure (DSAR) — right-to-erasure lifecycle owned by privacy-api
- Workspace CRD —
spec.services[].privacyPolicyRefandspec.privacy - AgentRuntime CRD —
spec.privacyPolicyRef