Redis for Fast Short-Term Agent State
Use Redis as a shared, low-latency store for conversation history and scratchpad fields so any worker can continue a session after the previous request finished.
Search across all documentation pages
Use Redis as a shared, low-latency store for conversation history and scratchpad fields so any worker can continue a session after the previous request finished.
Serialize a session document (messages + scratchpad + optional tool cache metadata) under a tenant-scoped key, set a TTL, and load-modify-save on each turn with optimistic concurrency or a lock when concurrent writers exist.
agent:stm:{tenant_id}:{user_id}:{session_id} (adjust separators to your house style).from __future__ import annotations
import json
import os
import time
from typing import Any
import redis # pip install redis
r = redis.Redis.from_url(os.environ.get("REDIS_URL", "redis://localhost:6379/0"), decode_responses=True)
SESSION_TTL_SEC = 60 * 60 * 24 # 24h; tune per product
def session_key(tenant_id: str, user_id: str, session_id: str) -> str:
return f"agent:stm:{tenant_id}:{user_id}:{session_id}"
def new_session() -> dict[str, Any]:
return {
"v": 1,
"messages": [
{"role": "system", "content": "You are a brief, careful assistant."},
],
"scratchpad": {},
"updated_at": time.time(),
}
def load_session(tenant_id: str, user_id: str, session_id: str) -> dict[str, Any]:
raw = r.get(session_key(tenant_id, user_id, session_id))
if not raw:
return new_session()
return json.loads(raw)
def save_session(tenant_id: str, user_id: str, session_id: str, session: dict[str, Any]) -> None:
session["updated_at"] = time.time()
key = session_key(tenant_id, user_id, session_id)
r.set(key, json.dumps(session), ex=SESSION_TTL_SEC)
def append_turn(tenant_id: str, user_id: str, session_id: str, user_text: str, assistant_text: str) -> dict:
sess = load_session(tenant_id, user_id, session_id)
sess["messages"].append({"role": "user", "content": user_text})
sess["messages"].append({"role": "assistant", "content": assistant_text})
# Optional: call trim_to_budget(sess["messages"]) before save
save_session(tenant_id, user_id, session_id, sess)
return sessFor concurrent turns on the same session, wrap the read-modify-write in a Redis lock (SET key NX EX) or store a version and use WATCH/MULTI so two writers do not clobber each other.
Agent turns are chatty: every user message may hit a different app instance.
In-process dicts break under horizontal scale and deploys.
Redis gives sub-millisecond reads for multi-kilobyte session blobs and first-class TTL so memory reclaims itself when users leave.
| Store in Redis session | Prefer elsewhere |
|---|---|
| Recent messages (bounded) | Multi-megabyte tool payloads (object storage + pointer) |
| Scratchpad JSON | Long-term user profile (primary DB) |
| Tool cache keys + small values | Secrets (vault / KMS) |
| Session version / lock fields | Full observability traces (log pipeline) |
Keys must include tenant and user (or an equivalent authz scope), not only a client-supplied session uuid.
If the client can guess session ids, missing scope checks become cross-tenant reads.
Authorize that the authenticated principal owns (tenant, user, session) before GET/SET.
Refresh TTL on activity so live chats do not expire mid-conversation.
Do not use infinite TTL for chat state unless you have a separate purge job.
Compliance may require shorter retention than product convenience wants - document the choice.
Cap serialized session size (for example 256 KB-1 MB) and trim history before save.
Oversized values hurt Redis memory and request latency.
If you need more history, keep cold transcript in Postgres/S3 and only hot-tail in Redis.
| Pattern | Use when |
|---|---|
| Last-write-wins SET | Low concurrency, simple bots |
| Version field + check | Detect lost updates; retry |
| Distributed lock per session | Multi-step tool runs that must not interleave |
| Streams / lists for messages | Append-only logs with separate scratchpad hash |
LangGraph and similar runtimes may offer Redis checkpointers; still apply the same keying, TTL, and size rules underneath.
session:{uuid} without tenant scope is a multi-tenant footgun.decode_responses and binary. Be consistent; tool payloads with bytes need base64 or external storage.| Approach | Pros | Cons |
|---|---|---|
| Redis JSON blob (this recipe) | Simple, fast, TTL-native | Large values need discipline |
| Redis Hash fields | Partial updates to scratchpad | Awkward for message arrays |
| Postgres session row | Strong durability, SQL audit | Higher latency unless pooled carefully |
| Framework checkpoint (LangGraph etc.) | Integrated with graph state | Still need ops, TTL, isolation |
| Sticky in-memory + sticky LB | Zero external dep | Fragile deploys and scale-out |
For short-term session continuity, yes if you accept possible loss on failure modes you configure (AOF/RDB settings). For legal-grade transcripts, also write to a durable store.
Only compact results or pointers. Large HTML and PDFs belong in object storage with a reference in the session.
DELETE the session key (and any related cache keys). Expose this as a product action when regulations or UX require it.
Yes. Keep keys that must be multi-key transactional on the same hash tag if you use multi-key ops; prefer single-key session documents to stay simple.
Fine for many agents; watch request overhead and payload size limits. The key layout and TTL rules do not change.
Redis session state dies with TTL. Long-term facts should be extracted into a DB or vector store with their own lifecycle.
Not required for a prototype. Introduce Redis when you add a second worker, rolling deploys, or multi-instance hosting.
Prefer not relying on allkeys-lru for correctness; use TTLs. If you must, understand that LRU can drop live sessions under memory pressure.
Related: Short-Term Memory Basics
Related: Session Isolation
Related: Scratchpad Patterns
Related: Managing Conversation History Within a Context Budget
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026