By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
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.
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 for healthcare in your browser — 60 seconds, no signup.
Here is what happens on each operation:
Writing (add_items):
Reading (get_items):
The encryption key is the most critical piece. How you manage it determines your actual security level.
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)
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())
For organizations using Vault for secrets management:
Still reading? Stop comparing — try CallSphere live.
See the healthcare AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
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 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")
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")
If your agent handles Protected Health Information (PHI), HIPAA requires:
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)
compliance focuses on availability, security, processing integrity, confidentiality, and privacy. For agent sessions, the key requirements are:
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:

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Using GPT-Realtime-2 for healthcare voice agents. BAA scope, PHI handling, retention, logging, and why a managed platform usually wins this build.
OpenAI's Frontier platform makes model-native orchestration the default. What that means for agent builders, voice/chat buyers, and the build-vs-buy decision.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
May 2026's biggest agent-architecture shift: planning, tool selection, and self-correction move inside the model. Framework code shrinks. Here is what changes.
A three-way comparison of Gemini Enterprise, Anthropic managed agents and OpenAI Frontier Platform after Cloud Next 2026 — strengths, gaps, buyer fit.
Anthropic's May 2026 push positions Claude as a vertical platform for financial services. The strategic positioning versus OpenAI and Google.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI