Memory is wired into your chat() call. Now you want to see whether it actually recalls anything, get that activity into your own logs, and know that a slow or broken store won't take down the chat. This page covers the middleware's options and how to observe and operate it.
New to memory? Start with the Overview and Quickstart first.
| Option | Type | Default | Purpose |
|---|---|---|---|
| adapter | MemoryAdapter | (required) | The backend to recall from and save to. |
| scope | MemoryScope | (ctx) => MemoryScope | (required) | Isolation scope, static or derived per request. |
| role | 'recall+save' | 'save-only' | 'recall+save' | 'save-only' persists turns without recalling or injecting. |
| onRecall | ({ scope, query, result }) => void | none | App telemetry after each recall. |
| onSave | ({ scope, turn, receipts }) => void | none | App telemetry after each deferred save. |
Every option in one place:
import { memoryMiddleware } from '@tanstack/ai-memory'
import { inMemory } from '@tanstack/ai-memory/in-memory'
const mw = memoryMiddleware({
adapter: inMemory(),
// Function form derives scope per request. `ctx.threadId` is the stable
// per-conversation id; add `userId` from your server-validated session.
scope: (ctx) => ({ sessionId: ctx.threadId }),
role: 'recall+save', // or 'save-only' to persist without injecting
onRecall: ({ query, result }) => {
console.log('recalled', result.fragments?.length ?? 0, 'hits for', query)
},
onSave: ({ receipts }) => {
console.log('saved', receipts.filter((r) => r.ok).length, 'records')
},
})import { memoryMiddleware } from '@tanstack/ai-memory'
import { inMemory } from '@tanstack/ai-memory/in-memory'
const mw = memoryMiddleware({
adapter: inMemory(),
// Function form derives scope per request. `ctx.threadId` is the stable
// per-conversation id; add `userId` from your server-validated session.
scope: (ctx) => ({ sessionId: ctx.threadId }),
role: 'recall+save', // or 'save-only' to persist without injecting
onRecall: ({ query, result }) => {
console.log('recalled', result.fragments?.length ?? 0, 'hits for', query)
},
onSave: ({ receipts }) => {
console.log('saved', receipts.filter((r) => r.ok).length, 'records')
},
})By default the middleware both recalls and saves (role: 'recall+save'). Set role: 'save-only' to persist each turn without reading memory back or injecting anything into the prompt. Use it to build up a user's history before you turn recall on, or on routes where you want to record turns but not shape the current answer.
The devtools events below are for watching memory during development. For telemetry that ships with your app, use the onRecall and onSave callbacks shown above. onRecall fires after each recall with the query and result, so you can count hits. onSave fires after each deferred save with the write receipts, so you can count writes. Send those to your metrics client instead of console.log.
The AI DevTools has a Memory tab for any chat wired with memoryMiddleware. It shows each turn's recall (query, fragment count, characters injected, recall duration) and, for adapters that implement inspect/listFacts (the built-in inMemory() and redis() do), the current stored records and facts. See the Memory Inspector for what it renders and how server-side memory reaches the panel.
Under the hood the middleware emits these events on aiEventClient (from @tanstack/ai-event-client). The panel reads them, and you can subscribe directly:
| Event | When |
|---|---|
| memory:retrieve:started | Recall begins |
| memory:retrieve:completed | Recall returns (fragment count, whether tools were injected) |
| memory:persist:started | A deferred save begins |
| memory:persist:completed | A save completes (receipt count) |
| memory:error | A recall or save threw (phase: 'recall' or 'save') |
A memory failure never breaks a chat run. A throwing recall or save emits memory:error and the run continues with degraded memory: recall returns nothing, and a failed save is dropped. Streaming is never blocked, and a failed save never fails the turn. This means a flaky store degrades the experience instead of taking down the chat.