Skip to content

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.

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: SessionPrivacyPolicy

Short name: spp (e.g. kubectl get spp).

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).

Policies are attached to consumers, not embedded in the policy itself.

Each entry in Workspace.spec.services[] has an optional privacyPolicyRef field that selects a policy in that workspace’s namespace:

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: Workspace
metadata:
name: my-workspace
spec:
services:
- name: default
privacyPolicyRef:
name: my-policy

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/v1alpha1
kind: AgentRuntime
metadata:
name: my-agent
namespace: my-workspace-ns
spec:
privacyPolicyRef:
name: strict-policy

When the session-api resolves the effective policy for a session, it uses the first matching rule in this chain:

  1. AgentRuntime.spec.privacyPolicyRef — per-agent override in the agent’s own namespace.
  2. The service group’s privacyPolicyRef on the Workspace whose spec.namespace.name matches the agent’s namespace. The service group is determined by AgentRuntime.spec.serviceGroup (defaults to default).
  3. The global default SessionPrivacyPolicy named default in the operator’s namespace (omnia-system in a default install; the operator passes it to the service pods as OMNIA_OPERATOR_NAMESPACE).
  4. 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.

Binding locationPolicy must live in
Workspace.spec.services[].privacyPolicyRefThe workspace’s own namespace (spec.namespace.name)
AgentRuntime.spec.privacyPolicyRefThe AgentRuntime’s namespace
Global defaultThe operator’s namespace (omnia-system by default), named default

Controls what session data is recorded.

FieldTypeDefaultRequired
recording.piiPIIConfig—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.

Configures automatic PII detection and handling.

FieldTypeDescription
pii.redactboolEnable PII redaction
pii.patternsstring[]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.strategystringRedaction 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:

WriteContent fieldsStored unchanged
Messagecontentrole, metadata, tokens, cost, ids, timestamps
ToolCallarguments, result, errorMessagename, status, labels, duration, ids, timestamps
ProviderCallerrorMessageprovider, model, tokens, cost, latency, finish reason, status
RuntimeEventthe values under data, and errorMessageeventType, the data keys, duration, ids, timestamps
EvalResultdetailsevalId, 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: replace

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.

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).

FieldTypeDescription
encryption.enabledboolEnable encryption
encryption.kmsProviderstringKMS 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.keyIDstringKey identifier within the KMS provider. Immutable once set
encryption.secretRef.namestringName 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.keyRotationKeyRotationConfigKey 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.

Not all fields are encrypted. Fields that are useful for analytics or operational queries remain plaintext:

ObjectEncryptedPlaintext
Messagecontentmetadata, role, type, sessionID, timestamps
ToolCallarguments, result, errorMessagename, status, sessionID, timestamps
RuntimeEventdata, errorMessageeventType, 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.

FieldTypeDescription
keyRotation.enabledboolEnable rotation. Rotation runs only when encryption.enabled is also true
keyRotation.schedulestringStandard 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.reEncryptExistingboolAfter a rotation, re-encrypt existing records onto the new key version in batches
keyRotation.batchSizeint32Records 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: 100
FieldTypeDescription
status.phasestringActive once the controller has reconciled the policy
status.observedGenerationint64Last generation reconciled
status.conditionsCondition[]Standard Kubernetes conditions
status.keyRotationKeyRotationStatusKey rotation progress (when configured)
TypeMeaning
ReadySet True (reason PolicyValidated) when the controller reconciles the policy. Structural validation is done by the CRD schema at admission
KeyRotationReadyPresent 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
FieldTypeDescription
keyRotation.lastRotatedAttimeTimestamp of the last successful rotation
keyRotation.currentKeyVersionstringVersion of the key in use
keyRotation.reEncryptionProgress.statusstringInProgress, Completed, or Failed
keyRotation.reEncryptionProgress.messagesProcessedint64Records (messages, tool calls and runtime events) re-encrypted so far
keyRotation.reEncryptionProgress.cursorstringOpaque position of the current pass, so it resumes rather than restarts; cleared on completion
keyRotation.reEncryptionProgress.startedAttimeWhen re-encryption began
keyRotation.reEncryptionProgress.completedAttimeWhen re-encryption finished

Minimal policy — record without redaction

Section titled “Minimal policy — record without redaction”
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: SessionPrivacyPolicy
metadata:
name: record-only
namespace: my-workspace-ns
spec:
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/v1alpha1
kind: SessionPrivacyPolicy
metadata:
name: gdpr-compliant
namespace: my-workspace-ns
spec:
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: false
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: SessionPrivacyPolicy
metadata:
name: default
namespace: omnia-system
spec:
recording:
pii:
redact: true
patterns:
- email
- phone_number