Back to blog
·8 min read·BitAtlas

Persisting Agent Memory in Encrypted Stores

Building secure, private state management for AI agents using client-side encryption and encrypted databases.

agent memoryencrypted storagepersistencestate managementvector storesprivacy

Long-running AI agents need memory. Whether it's context from previous conversations, user preferences, or learned patterns, maintaining state across sessions is critical for building intelligent systems. But storing sensitive agent data—especially when dealing with private user information—requires more than just a database. It requires encryption at rest and in transit, with keys controlled by the client or user, not the service provider.

This post explores how to design secure memory persistence for AI agents using encrypted databases and client-side encryption techniques.

The Challenge: Where Does Agent State Live?

A typical multi-turn agent interaction might produce:

  • Conversation history with user inputs and agent responses
  • Extracted entities from previous interactions
  • User preferences and behavioral patterns
  • Function call results and external data
  • Vector embeddings for semantic search and RAG

All of this is sensitive. Storing it unencrypted on a third-party server violates privacy expectations and exposes you to compliance risk (GDPR, CCPA, etc.). Encrypting before transmission helps, but the server still sees the plaintext at write time.

The solution: encrypt on the client side before transmission, store encrypted blobs, and never give the server decryption keys.

Architecture Patterns

Pattern 1: Client-Encrypted JSON in Blob Storage

The simplest approach: serialize agent memory to JSON, encrypt it on the client, store the ciphertext in a database or object store.

// Client side
const agentMemory = {
  conversationHistory: [...],
  extractedEntities: {...},
  userPreferences: {...}
};

const json = JSON.stringify(agentMemory);
const encrypted = await client.encrypt(json); // Uses client-controlled key
await fetch('/api/agent-memory', {
  method: 'POST',
  body: JSON.stringify({ memoryId: 'user-123', ciphertext: encrypted })
});

Pros:

  • Simple to implement
  • Works with any database (SQL, NoSQL, blob storage)
  • Full end-to-end encryption

Cons:

  • No server-side indexing or search
  • Entire memory must be decrypted to access small subsets
  • Not ideal for large datasets

Pattern 2: Structured Encryption with Field-Level Granularity

For larger or more structured memory, use deterministic encryption for certain fields (so the server can index them) and semantic encryption for sensitive content.

const encryptionScheme = {
  // Deterministic encryption for indexing
  userId: { type: 'deterministic', key: masterKey },
  conversationId: { type: 'deterministic', key: masterKey },
  // Semantic encryption for content (randomized, more secure)
  messages: { type: 'semantic', key: masterKey },
  entities: { type: 'semantic', key: masterKey },
  vectorEmbedding: { type: 'deterministic', key: embedKey }
};

This allows the server to:

  • Query by userId or conversationId without decryption
  • Search by vector similarity (embeddings stay encrypted)
  • Never see plaintext sensitive fields

Pros:

  • Server-side filtering and indexing possible
  • Scales better than Pattern 1
  • Flexible security model

Cons:

  • Deterministic encryption has known attacks (frequency analysis)
  • More complex implementation
  • Requires careful key management

Pattern 3: Encrypted Vector Stores for Semantic Search

For agents using RAG (Retrieval-Augmented Generation), store embeddings encrypted.

# Python example with encrypted vector store
class EncryptedMemoryStore:
    def __init__(self, encryption_key):
        self.cipher = FernetCipher(encryption_key)
        self.vector_store = PineconeIndex()  # or Weaviate, etc.
    
    def add_memory(self, text, embedding):
        encrypted_text = self.cipher.encrypt(text.encode())
        # Store encrypted text + plaintext embedding
        # Server can perform semantic search on embedding
        # Only client can decrypt the retrieved text
        self.vector_store.upsert(
            id=str(uuid4()),
            values=embedding,
            metadata={'ciphertext': encrypted_text}
        )
    
    def search_and_decrypt(self, query_embedding, top_k=5):
        results = self.vector_store.query(query_embedding, top_k=top_k)
        return [
            self.cipher.decrypt(r['metadata']['ciphertext'])
            for r in results
        ]

This enables semantic search without server-side plaintext access.

Key Management for Agents

Who controls the encryption key? There are several models:

User-Controlled Keys:

  • Client derives a key from user password or passphrase
  • Agent can only access memory when user is authenticated
  • Best for privacy, worst for availability

Application-Controlled Keys (with User Isolation):

  • Server holds keys but uses separate key per user
  • Keys derivable from username + salt, not stored plaintext
  • Reasonable compromise

Ephemeral Keys:

  • Keys issued per session, short-lived
  • Agent can access memory during active session, not after
  • Good for zero-trust architectures

Hierarchical Keys:

  • Master key in HSM or KMS
  • Per-user keys encrypted with master key
  • Allows key rotation without re-encrypting data

Implementation Considerations

1. Encryption Library Choice

Use battle-tested libraries:

  • JavaScript: TweetNaCl.js, libsodium.js, or crypto-js
  • Python: cryptography, nacl, or PyCryptodome
  • Go: crypto/aes, golang.org/x/crypto

Avoid custom crypto. Use AEAD (Authenticated Encryption with Associated Data) like AES-256-GCM to prevent tampering.

2. Size and Performance

Encrypted blobs are typically less than 20% larger than plaintext. For a conversation with 10,000 tokens, expect around 50KB encrypted.

Decryption on the client takes microseconds for typical memory sizes. Latency is dominated by network, not crypto.

3. Data Format

Store metadata alongside ciphertext:

{
  "version": 1,
  "algorithm": "aes-256-gcm",
  "nonce": "base64-encoded-nonce",
  "ciphertext": "base64-encoded-ciphertext",
  "tag": "base64-encoded-auth-tag"
}

This allows algorithm upgrades without breaking old data.

4. Backup and Recovery

Encrypted memory is only useful if you can decrypt it:

  • Store keys in a password manager
  • Use key derivation functions (PBKDF2, Argon2) for password-based keys
  • Maintain encrypted backups separately
  • Test recovery regularly

Compliance and Privacy

Using client-side encryption for agent memory provides:

  • GDPR: Encrypted data at rest; key not held by service provider
  • CCPA: User can request, decrypt, and export their data
  • HIPAA: End-to-end encryption satisfies encryption requirements
  • EU adequacy: Stronger data protection posture for cross-border transfers

Document your encryption model clearly. Compliance teams will ask:

  • Where are keys stored?
  • Who has access?
  • What's the key rotation policy?
  • How is old data handled?

Example: Building an Encrypted Agent Memory Service

from cryptography.fernet import Fernet
from datetime import datetime, timedelta
import json

class EncryptedAgentMemory:
    def __init__(self, user_key: str):
        self.cipher = Fernet(user_key)
    
    def store_memory(self, memory_id: str, data: dict) -> str:
        serialized = json.dumps({
            'data': data,
            'timestamp': datetime.utcnow().isoformat()
        })
        encrypted = self.cipher.encrypt(serialized.encode())
        return encrypted.decode()
    
    def retrieve_memory(self, encrypted_blob: str) -> dict:
        decrypted = self.cipher.decrypt(encrypted_blob.encode())
        payload = json.loads(decrypted)
        
        # Check timestamp (implement TTL if needed)
        ts = datetime.fromisoformat(payload['timestamp'])
        age = datetime.utcnow() - ts
        
        if age > timedelta(days=90):
            raise ValueError('Memory expired')
        
        return payload['data']

Tradeoffs

Encrypted memory isn't free:

AspectCost
ComplexityHigh—key management, client-side crypto
PerformanceLow—encryption/decryption is fast
Server searchHigh—can't search encrypted data easily
User experienceMedium—keys must be managed securely
ComplianceLow—encryption simplifies regulatory compliance

Choose the pattern that matches your requirements: full blob encryption for simplicity, structured encryption for server-side filtering, or encrypted vector stores for semantic search.

Next Steps

  1. Evaluate your memory requirements: How much state? What's sensitive? Who needs server-side access?
  2. Pick a key management model: User-controlled, application-controlled, or ephemeral?
  3. Implement incrementally: Start with JSON blobs, migrate to structured encryption if needed.
  4. Test key rotation: Ensure you can rotate keys without losing data.
  5. Document compliance: Write down your encryption model for audits.

Encrypted agent memory is the foundation of privacy-respecting AI systems. Done right, it's also faster and simpler than ad-hoc access control.

Encrypt your agent's data today

BitAtlas gives your AI agents AES-256-GCM encrypted storage with zero-knowledge guarantees. Free tier, no credit card required.