- Per-tenant key isolation—different encryption keys for different customers
- KMS integration—AWS KMS, Google Cloud KMS, or HashiCorp Vault for key management, rotation, and audit logging
Configure custom encryption
How it works
- Configure the encryption module path in
langgraph.json - Define an encryption context handler that derives values such as a tenant ID from the authenticated user
- Define your encryption module with handlers for blob and JSON encryption
- Agent Server calls your handlers before storing and after retrieving data
Configuration
Add your encryption module tolanggraph.json:
If you’re migrating from basic encryption, keep
LANGGRAPH_AES_KEY configured. Custom encryption handles new writes while existing AES-encrypted data remains readable.Define encryption context
Use@encryption.context to derive encryption context from the authenticated user:
ctx.metadata for every encryption operation in the request and is stored in plaintext so Agent Server can restore it during decryption. Include only non-secret identifiers and routing metadata, never keys, tokens, or credentials.
Defining your encryption module
Blob encryption (checkpoints)
Blob handlers encrypt checkpoint data—the serialized state from graph execution. Here’s a simplified example using per-tenant keys with Fernet (a symmetric encryption scheme from thecryptography library):
ctx.metadata dictionary comes from the context handler, so each encryption handler can select the correct key.
JSON encryption (metadata)
JSON handlers encrypt structured data like thread metadata, assistant context, and run kwargs. Unlike blob encryption, you choose which fields to encrypt—keeping some unencrypted for search and filtering.JSON encryption considerations
Migration consideration: Use a recognizable prefix or format in encrypted values so your decryptor can detect and skip unencrypted data. This allows you to encrypt additional fields in the future without re-encrypting existing records. The example above uses this pattern.
Performance consideration: Per-key encryption means one encryption call per field. If your encryption involves round-trips to an external service (e.g., KMS), this can significantly impact latency. Consider caching data keys locally or using envelope encryption where you encrypt a local data key with KMS and use it for multiple fields.
tenant_id, owner) must remain unchanged and unencrypted, as must fields used for search and filtering. Agent Server rejects writes if a custom encryptor changes an authorization-filter field. Additionally, some system-managed fields will never be encrypted:
- Resource identifiers (
thread_id,run_id,assistant_id,graph_id,checkpoint_id,task_id) - Named LangGraph system fields (
langgraph_version,langgraph_api_version,langgraph_plan,langgraph_host,langgraph_api_url,langgraph_request_id,langgraph_auth_user,langgraph_auth_user_id,langgraph_auth_permissions) - Required checkpoint metadata (
source,step,parents,run_attempt) - Named internal fields used for scheduling and orchestration, including
__after_seconds__and__request_start_time_ms__ - Run-level execution limits (
max_concurrency,recursion_limit) specified in a run’sconfig - Thread TTL updates (
ttl) specified in a run’sconfig.configurable
What gets encrypted
JSON handlers (@encryption.encrypt.json / @encryption.decrypt.json) are applied recursively to fields including:
thread.metadata,thread.valuesassistant.metadata,assistant.contextrun.metadata,run.kwargscron.metadata,cron.payloadstore.value
@encryption.encrypt.blob / @encryption.decrypt.blob) are applied to checkpoint blobs (graph execution state).
Rotate custom encryption keys lazily
Lazy re-encryption requires Agent Server version
0.14.0 or later, Python SDK version langgraph-sdk>=0.4.3, the PostgreSQL checkpointer, PREFER_GRPC_CHECKPOINTER=true, and LANGGRAPH_STORE_BACKEND=grpc.DecryptResult from a blob or JSON decrypt handler when the handler decrypts data with an old key. Its plaintext value is returned to the caller, and its replacement value is new ciphertext for Agent Server to persist.
For example, replace the blob decrypt handler above with one that tries the current tenant key first, then returns replacement ciphertext when a legacy key succeeds:
DecryptResult[dict] in the same way. The replacement must have the same shape as a normal result from the corresponding encrypt handler.
Agent Server writes the replacement with a compare-and-swap operation, so it does not overwrite a concurrent update. Re-encryption is best effort: a read still succeeds if the stored value changes before writeback or if writeback fails.
Monitor lazy re-encryption
Agent Server emits the following INFO-tier internal metrics for replacement writebacks:
Each metric includes
reencryption_table and reencryption_column attributes. Use them to find surfaces that still read old ciphertext and to isolate writeback failures.
CAS skips produce warning logs with Re-encryption writeback skipped due to concurrent update. Failures produce error logs with Re-encryption writeback failed. Both include the table and column. Successful writebacks do not produce logs.
For Prometheus, set EXPOSE_INTERNAL_METRICS_PROMETHEUS=true and scrape the Agent Server /metrics endpoint. Internal metrics are also available through the documented Datadog export.
These counters measure writeback outcomes, not the amount of old ciphertext remaining. A period without new writebacks does not prove that every record has been read and rotated. Keep old keys available until your retention policy or a separate exhaustive migration confirms that no unread data requires them.
Envelope encryption with AWS Encryption SDK
For production deployments on AWS, use the AWS Encryption SDK with AWS KMS, or an equivalent within your cloud provider. This approach:- Handles envelope encryption automatically (no manual key packing)
- Provides key rotation and audit logging
- Binds ciphertext to encryption context (tenant isolation)
- Caches data keys locally to avoid repeated KMS calls, latency and rate limits
Complete example
encryption_context is cryptographically bound to the ciphertext via KMS—decryption fails if the context doesn’t match. The context is embedded in the ciphertext, so decrypt handlers don’t need to reference ctx.metadata.
Key rotation
KMS rotates backing material for the same key ID automatically. Old encrypted data keys remain decryptable, so existing data does not need re-encryption.Related
Connect these docs to your agent of choice via MCP for real-time answers.

