- Working memory: the running conversation for the current session, stored in a single Redis key with a TTL so it expires on its own.
- Long-term memory: durable facts about the user (preferences, events, decisions) stored as JSON documents and recalled with Redis Search full-text queries.
This tutorial uses OpenAI for the chat and fact-extraction calls, but the memory
layer itself is model-agnostic, so swap in any LLM you like.
Prerequisites
- An Upstash Redis database (the REST URL and token).
- An OpenAI API key.
- TypeScript
- Python
Step 1: Create the long-term memory index
Long-term memories are JSON documents stored under thememory: prefix. We index
the text field for full-text recall, and keep userId and kind as exact-match
keywords so we can scope a search to a single user. createdAt is a sortable
number we can use to favor recent memories.
Create the index once (e.g. in a setup script), not on every request.
- TypeScript
- Python
Step 2: Working (short-term) memory
Working memory is just the recent message history for a session. We store it as a single JSON value with a one-hour TTL and cap it to the last 20 messages so the prompt stays small. When the session goes quiet, Redis expires the key for us.- TypeScript
- Python
Step 3: Recall relevant memories
To answer well, the agent needs the long-term facts that relate to the current message. We run a full-text query against thememories index, scoped to the
user with the userId keyword. Redis Search ranks matches by relevance, so we
take the top few.
- TypeScript
- Python
Step 4: Remember new facts
After each exchange we ask the model to pull out durable facts, the things worth remembering across sessions, not small talk. Each fact becomes a JSON document under thememory: prefix, so the index picks it up automatically.
Because full-text search gives us a cheap similarity check, we deduplicate
before writing: if a very similar memory already exists for this user, we skip it.
- TypeScript
- Python
Step 5: The chat loop
Now we wire it together. Each turn: recall relevant memories, build a prompt from those plus the working memory, call the model, persist the updated history, and remember new facts.- TypeScript
- Python
Try it
Run two sessions for the same user. Even after the first session’s working memory expires, the facts learned there are recalled in the second:- TypeScript
- Python
Redis Search indexes writes asynchronously: a
JSON.SET returns before the
document is searchable. For a deterministic demo or test, call waitIndexing() /
wait_indexing() to block until pending updates are applied. In a real app the
next user turn normally arrives later than the indexing window, so an explicit
wait isn’t needed.How it fits together
- Working memory lives under
chat:{sessionId}with a TTL: fast to read, self-expiring, scoped to one conversation. - Long-term memory lives under
memory:{userId}:{id}and is searchable across sessions through thememoriesindex. - Recall uses full-text relevance to surface the facts that matter for the current message; remember extracts and deduplicates new ones.
Next steps
- Add a
kindsuch as"preference"vs"event"and filter recall by it. - Boost recent memories with a score function.
- Summarize older working-memory messages instead of dropping them.
- Stream the reply to a chat UI and animate it smoothly. See Smooth Text Streaming in AI SDK v5.
- Learn more about what Redis Search can do in the Search docs.