Memory stores facts from earlier turns and puts them back into the next request. The model then keeps the user's name, stack, and preferences.
memoryMiddleware finds the memory that matters for each new message, adds it to the prompt, and saves each finished turn for later.
npm i @tanstack/ai-memorypnpm add @tanstack/ai-memoryyarn add @tanstack/ai-memorybun add @tanstack/ai-memoryimport { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { memoryMiddleware } from '@tanstack/ai-memory'
import { inMemory } from '@tanstack/ai-memory/in-memory'
const memory = inMemory()
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages: [{ role: 'user', content: 'Hello' }],
threadId: 'demo-thread',
middleware: [
memoryMiddleware({
adapter: memory,
scope: { threadId: 'demo-thread', userId: 'alice' },
}),
],
})That is all the wiring. Memory runs on the server only, so your client code does not change.
The Quickstart puts this in a real server route with a client, and then makes it ready for production.
Each run has two memory steps:
The adapter does the rest: it extracts, ranks, renders, and stores. The middleware never reads a stored record.
If recall or save fails, the chat continues without memory. See Operating memory.
| You need | Use |
|---|---|
| Local development, tests, a demo | inMemory() |
| Memory that survives a restart and is shared across processes | redis() |
| A hosted memory service | hindsight(), mem0(), or honcho() |
| A backend that is not in the list | A custom adapter |
Every adapter has the same recall and save methods, so you can swap one for another and keep the rest of your code. Adapters lists the options for each one.
Tip
Memory is for context from earlier turns or earlier sessions. To keep the last few messages of the current request, send them in messages. You do not need memory for that.
scope tells the adapter which memory to read and write. MemoryScope is the same Scope type that @tanstack/ai uses for persistence:
type MemoryScope = {
threadId: string // required, the same value as ChatMiddlewareContext.threadId
userId?: string
tenantId?: string
namespace?: string // reserved, no adapter uses it yet
}Set the scope on the server, from the session of the signed-in user:
Use the function form of scope to set the scope for each request. It gets the chat context:
import { memoryMiddleware } from '@tanstack/ai-memory'
import { inMemory } from '@tanstack/ai-memory/in-memory'
// Replace with your auth. Return the user from the session, not from the body.
function getUserId(): string {
return 'demo-user'
}
const middleware = memoryMiddleware({
adapter: inMemory(),
scope: (ctx) => ({ threadId: ctx.threadId, userId: getUserId() }),
})The built-in adapters match every scope field exactly. A read with no tenantId does not find a record written with a tenantId. For the fields that each adapter uses, see Which Scope fields each adapter honors.