Skip to content
Learn Agentic AI
Learn Agentic AI10 min read12 views

Building Encrypted Sessions for Secure Agent Memory

Implement encrypted agent sessions for HIPAA and SOC2 compliance using the OpenAI Agents SDK EncryptedSession wrapper with AES-GCM encryption, key management, and TTL expiry.

Why Encrypt Agent Sessions?

Agent conversations often contain sensitive data: personal health information, financial details, customer support interactions with account numbers, or proprietary business information. Storing this data in plain text — even in a database with access controls — may not meet regulatory requirements or your organization's security posture.

The OpenAI Agents SDK includes an EncryptedSession wrapper that adds transparent encryption and decryption around any session backend. Your agents work exactly the same way, but the data at rest is encrypted with AES-GCM.

How EncryptedSession Works

EncryptedSession is a decorator pattern. It wraps any existing session implementation (SQLiteSession, RedisSession, SQLAlchemySession) and encrypts items before writing and decrypts them after reading.

flowchart TD
    MSG(["New message"])
    WORKING["Working memory<br/>rolling window"]
    EPISODIC[("Episodic memory<br/>past sessions")]
    SEMANTIC[("Semantic memory<br/>facts and preferences")]
    SUM["Summarizer<br/>compresses old turns"]
    ROUTER{"Retrieve<br/>needed memories"}
    PROMPT["Assembled context"]
    LLM["LLM"]
    UPD["Memory updater<br/>writes new facts"]
    MSG --> WORKING --> ROUTER
    ROUTER -->|Past sessions| EPISODIC
    ROUTER -->|User facts| SEMANTIC
    EPISODIC --> SUM --> PROMPT
    SEMANTIC --> PROMPT
    WORKING --> PROMPT --> LLM --> UPD
    UPD --> EPISODIC
    UPD --> SEMANTIC
    style ROUTER fill:#4f46e5,stroke:#4338ca,color:#fff
    style LLM fill:#f59e0b,stroke:#d97706,color:#1f2937
    style EPISODIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style SEMANTIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
from agents.extensions.sessions import SQLiteSession, EncryptedSession

# Base session — stores data
base_session = SQLiteSession(db_path="./sessions.db")

# Encrypted wrapper — encrypts/decrypts transparently
encryption_key = "your-32-byte-encryption-key-here"  # Must be 32 bytes for AES-256
session = EncryptedSession(
    session=base_session,
    encryption_key=encryption_key,
)

From the agent's perspective, nothing changes. You pass the encrypted session to Runner.run() just like any other session.

Transparent Encryption and Decryption

The encryption is completely transparent to your application code. The agent, runner, and tools never see encrypted data — they work with plain text. Only the storage layer sees ciphertext.

import asyncio
from agents import Agent, Runner
from agents.extensions.sessions import (
    SQLiteSession,
    EncryptedSession,
)

ENCRYPTION_KEY = b"0123456789abcdef0123456789abcdef"  # 32 bytes

base_session = SQLiteSession(db_path="./encrypted_sessions.db")
session = EncryptedSession(session=base_session, encryption_key=ENCRYPTION_KEY)

agent = Agent(
    name="HealthAgent",
    instructions="You are a medical assistant. Handle patient information with care.",
)

async def main():
    sid = "patient-consultation-101"

    # Store sensitive information
    result = await Runner.run(
        agent,
        "Patient John Doe, DOB 1985-03-15, diagnosed with Type 2 diabetes.",
        session=session,
        session_id=sid,
    )
    print(result.final_output)

    # Retrieve it — decrypted transparently
    result = await Runner.run(
        agent,
        "What is the patient's diagnosis?",
        session=session,
        session_id=sid,
    )
    print(result.final_output)  # References Type 2 diabetes

asyncio.run(main())

If you open the SQLite database directly, the stored data is encrypted ciphertext — unreadable without the key.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →

The Encryption Flow

Here is what happens on each operation:

Writing (add_items):

  1. Agent generates response items (plain text)
  2. EncryptedSession serializes each item to JSON
  3. Each JSON blob is encrypted with AES-256-GCM
  4. A unique nonce is generated per item
  5. The ciphertext + nonce are stored in the base session

Reading (get_items):

  1. EncryptedSession retrieves ciphertext from base session
  2. Each item is decrypted using the key and stored nonce
  3. JSON is deserialized back to item objects
  4. Plain text items are returned to the runner

Key Management Strategies

The encryption key is the most critical piece. How you manage it determines your actual security level.

Strategy 1: Environment Variables

Simplest approach — suitable for single-service deployments:

import os
from agents.extensions.sessions import SQLiteSession, EncryptedSession

key = os.environ["AGENT_SESSION_ENCRYPTION_KEY"].encode()
assert len(key) == 32, "Key must be 32 bytes for AES-256"

session = EncryptedSession(
    session=SQLiteSession(db_path="./sessions.db"),
    encryption_key=key,
)

Strategy 2: AWS KMS with Data Key Encryption

For production systems, use an envelope encryption pattern with a cloud KMS:

import boto3
import os
from agents.extensions.sessions import RedisSession, EncryptedSession

def get_data_key() -> bytes:
    """Retrieve or generate a data encryption key from AWS KMS."""
    kms = boto3.client("kms")

    # Check for cached data key
    cached_key = os.environ.get("CACHED_DATA_KEY")
    if cached_key:
        return bytes.fromhex(cached_key)

    # Generate new data key
    response = kms.generate_data_key(
        KeyId="alias/agent-sessions",
        KeySpec="AES_256",
    )

    # Store encrypted version for recovery
    # In production, persist response["CiphertextBlob"] somewhere safe

    return response["Plaintext"]  # 32-byte raw key

session = EncryptedSession(
    session=RedisSession.from_url("redis://redis:6379/0"),
    encryption_key=get_data_key(),
)

Strategy 3: HashiCorp Vault

For organizations using Vault for secrets management:

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

import hvac
from agents.extensions.sessions import SQLAlchemySession, EncryptedSession

def get_key_from_vault() -> bytes:
    client = hvac.Client(url="https://vault.internal:8200")
    client.auth.kubernetes.login(role="agent-service")
    secret = client.secrets.kv.v2.read_secret_version(
        path="agent-sessions/encryption-key"
    )
    return bytes.fromhex(secret["data"]["data"]["key"])

async def create_encrypted_session():
    base = await SQLAlchemySession.from_url(
        "postgresql+asyncpg://user:pass@db:5432/agents",
        create_tables=False,
    )
    return EncryptedSession(
        session=base,
        encryption_key=get_key_from_vault(),
    )

Key Rotation

Key rotation is essential for long-lived systems. The approach depends on your backend, but the general pattern involves re-encrypting existing sessions with a new key.

async def rotate_encryption_key(
    base_session,
    old_key: bytes,
    new_key: bytes,
    session_ids: list[str],
):
    """Re-encrypt all sessions with a new key."""
    old_encrypted = EncryptedSession(session=base_session, encryption_key=old_key)
    new_encrypted = EncryptedSession(session=base_session, encryption_key=new_key)

    for sid in session_ids:
        # Read with old key
        items = await old_encrypted.get_items(sid)

        # Clear old data
        await old_encrypted.clear_session(sid)

        # Write with new key
        await new_encrypted.add_items(sid, items)

    print(f"Rotated {len(session_ids)} sessions to new key")

TTL for Session Expiry

Combine encryption with TTL to ensure sensitive conversations are automatically purged:

from agents.extensions.sessions import RedisSession, EncryptedSession

redis_session = RedisSession.from_url("redis://redis:6379/0")

encrypted_session = EncryptedSession(
    session=redis_session,
    encryption_key=ENCRYPTION_KEY,
)

# After each interaction, refresh the TTL
async def handle_with_ttl(session_id: str, message: str):
    result = await Runner.run(
        agent, message, session=encrypted_session, session_id=session_id
    )

    # Set 24-hour TTL — session auto-deletes if inactive
    await redis_session.client.expire(
        f"session:{session_id}",
        60 * 60 * 24  # 24 hours
    )

    return result.final_output

For SQLAlchemy-backed sessions, implement TTL with a scheduled cleanup job:

from sqlalchemy import text
from datetime import datetime, timedelta

async def cleanup_expired_sessions(engine, max_age_days: int = 30):
    """Delete sessions older than max_age_days."""
    cutoff = datetime.utcnow() - timedelta(days=max_age_days)
    async with engine.begin() as conn:
        result = await conn.execute(
            text("DELETE FROM session_items WHERE created_at < :cutoff"),
            {"cutoff": cutoff},
        )
        print(f"Purged {result.rowcount} expired session items")

HIPAA and SOC2 Compliance Patterns

HIPAA Requirements for Agent Sessions

If your agent handles Protected Health Information (PHI), HIPAA requires:

  1. Encryption at rest: EncryptedSession satisfies this.
  2. Encryption in transit: Use TLS for database connections.
  3. Access controls: Restrict who can access the encryption keys.
  4. Audit logging: Log access to session data.
  5. Data retention policies: Implement TTL-based purging.
import logging
from agents.extensions.sessions import EncryptedSession

logger = logging.getLogger("hipaa_audit")

class AuditedEncryptedSession(EncryptedSession):
    """EncryptedSession with HIPAA audit logging."""

    async def get_items(self, session_id: str, **kwargs):
        logger.info(f"SESSION_READ session_id={session_id} timestamp={datetime.utcnow().isoformat()}")
        return await super().get_items(session_id, **kwargs)

    async def add_items(self, session_id: str, items, **kwargs):
        logger.info(f"SESSION_WRITE session_id={session_id} items={len(items)} timestamp={datetime.utcnow().isoformat()}")
        return await super().add_items(session_id, items, **kwargs)

    async def clear_session(self, session_id: str, **kwargs):
        logger.info(f"SESSION_DELETE session_id={session_id} timestamp={datetime.utcnow().isoformat()}")
        return await super().clear_session(session_id, **kwargs)

SOC2 Requirements

SOC2 Type II compliance focuses on availability, security, processing integrity, confidentiality, and privacy. For agent sessions, the key requirements are:

  • Encryption at rest and in transit — EncryptedSession plus TLS connections
  • Key management — Use a KMS, not hardcoded keys
  • Retention policies — Implement automatic data purging
  • Access logging — Log all reads and writes to session data
  • Incident response — Ability to purge a specific user's sessions immediately
async def purge_user_sessions(user_id: str, session: EncryptedSession):
    """Emergency purge all sessions for a user — for incident response."""
    session_ids = await get_user_session_ids(user_id)  # From your user-session mapping
    for sid in session_ids:
        await session.clear_session(sid)
    logger.critical(f"EMERGENCY_PURGE user_id={user_id} sessions_purged={len(session_ids)}")

Encryption is one layer. True compliance requires encryption, access controls, audit logging, retention policies, and incident response procedures working together.

Sources:

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Infrastructure

HIPAA Pen-Test and Risk Assessment for AI Voice in 2026

The 2024 NPRM proposes mandatory penetration tests every 12 months and vulnerability scans every 6 months. Here is how an AI voice agent should be tested in 2026.

Agentic AI

Safety Evaluation for Agents: Jailbreak, Prompt Injection, and Tool-Misuse Test Suites in 2026

How to build a safety eval pipeline that runs known jailbreak corpora, prompt-injection attacks, and tool-misuse scenarios on every release — and gates merges on it.

Agentic AI

Input and Output Guardrails in the OpenAI Agents SDK: A Production Pattern (2026)

Stop the agent BEFORE it does the wrong thing. How to wire input and output guardrails in the OpenAI Agents SDK with cheap classifiers and an eval suite that proves they work.

Agentic AI

Browser Agents with LangGraph + Playwright: Visual Evaluation Pipelines That Don't Lie

Build a browser agent with LangGraph and Playwright that does multi-step web tasks, then ground-truth its work with visual diffs and DOM-based evaluators.

Agentic AI

OpenAI Computer-Use Agents (CUA) in Production: Build + Evaluate a Real Workflow (2026)

Build a working computer-use agent with the OpenAI Computer Use tool — clicks, types, scrolls a real browser — then evaluate task success on a benchmark suite.

AI Engineering

NeMo Guardrails vs LlamaGuard: Side-by-Side Comparison in 2026

NeMo Guardrails and LlamaGuard solve overlapping problems with different architectures. The trade-offs once you push them past 100 RPS in production agent stacks.