Your assistant forgets everything the moment a session ends. A user tells it their name this week; next week it asks again. memoryMiddleware fixes that. It gives a chat() run memory that survives across turns and across sessions.
It works in two moves. Before the model runs, it recalls relevant memory from a pluggable adapter and adds it to the system prompt. After the run finishes, it saves the turn. The save is deferred, so it never blocks streaming.
Reach for it when you need recall across turns or sessions. To keep the last few messages of the same request, just pass them in messages. Memory is overkill for that.
Everything lives in @tanstack/ai-memory: the middleware, the adapter contract, and the built-in and vendor adapters.
Want a copy-paste setup? See the Quickstart. Building an adapter for a backend that isn't shipped? See the Custom Adapter guide.
| Need | Use this |
|---|---|
| "Remember what the user told me last week" | Memory middleware with a persistent adapter |
| "Each user has their own context" | Memory middleware with a scoped adapter |
| "Use a hosted memory service (mem0, Honcho, Hindsight)" | The matching vendor adapter |
| Keep the last few turns in the same request | Pass them in messages, skip memory |
A memory adapter has one identifier and two verbs. Extraction, ranking, rendering, and storage are all the adapter's job. The middleware never looks inside a record.
| Member | Purpose |
|---|---|
| id | Stable identifier used in logs and devtools. |
| recall(scope, query) | Return what's relevant to query within scope: a rendered systemPrompt, optional fragments, and optional LLM tools plus toolGuidance. |
| save(scope, turn) | Persist a finished { user, assistant } turn. Extraction happens here. Returns one SaveReceipt per write. |
| inspect(scope)? | Optional. A full snapshot for a devtools panel. |
| listFacts(scope)? | Optional. A flat fact list for a devtools panel. |
// The MemoryAdapter contract, from `@tanstack/ai-memory`:
import type { MemoryAdapter } from '@tanstack/ai-memory'// The MemoryAdapter contract, from `@tanstack/ai-memory`:
import type { MemoryAdapter } from '@tanstack/ai-memory'Built-in adapters, each a tree-shakeable subpath:
import { inMemory } from '@tanstack/ai-memory/in-memory'
import { redis } from '@tanstack/ai-memory/redis'import { inMemory } from '@tanstack/ai-memory/in-memory'
import { redis } from '@tanstack/ai-memory/redis'Vendor adapters:
import { hindsight } from '@tanstack/ai-memory/hindsight'
import { mem0 } from '@tanstack/ai-memory/mem0'
import { honcho } from '@tanstack/ai-memory/honcho'import { hindsight } from '@tanstack/ai-memory/hindsight'
import { mem0 } from '@tanstack/ai-memory/mem0'
import { honcho } from '@tanstack/ai-memory/honcho'See Adapters for every adapter and its options.
To add telemetry, watch memory in devtools, persist without recalling, or handle failures, see Operating memory.
MemoryScope is the isolation boundary. It is session-centric, with an optional durable user id:
// The MemoryScope type, from `@tanstack/ai-memory`:
type MemoryScope = {
sessionId: string
userId?: string
}// The MemoryScope type, from `@tanstack/ai-memory`:
type MemoryScope = {
sessionId: string
userId?: string
}Always derive scope on the server from trusted state. Accepting userId from the request body is how one user reads another user's memory. The function form of scope runs per request and only sees what your server attached to the chat context:
import { memoryMiddleware } from '@tanstack/ai-memory'
import type { MemoryAdapter } from '@tanstack/ai-memory'
declare const adapter: MemoryAdapter
declare function getSession(ctx: unknown): { threadId: string; userId: string }
memoryMiddleware({
adapter,
scope: (ctx) => {
const session = getSession(ctx) // your server-validated session
return { sessionId: session.threadId, userId: session.userId }
},
})import { memoryMiddleware } from '@tanstack/ai-memory'
import type { MemoryAdapter } from '@tanstack/ai-memory'
declare const adapter: MemoryAdapter
declare function getSession(ctx: unknown): { threadId: string; userId: string }
memoryMiddleware({
adapter,
scope: (ctx) => {
const session = getSession(ctx) // your server-validated session
return { sessionId: session.threadId, userId: session.userId }
},
})