Memory

Operating

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.

memoryMiddleware options

OptionTypeDefaultPurpose
adapterMemoryAdapter(required)The backend to recall from and save to.
scopeMemoryScope | (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 }) => voidnoneApp telemetry after each recall.
onSave({ scope, turn, receipts }) => voidnoneApp telemetry after each deferred save.

Every option in one place:

ts
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')
  },
})

Persist without recalling

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.

Telemetry with onRecall and onSave

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.

Watch it in devtools

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:

EventWhen
memory:retrieve:startedRecall begins
memory:retrieve:completedRecall returns (fragment count, whether tools were injected)
memory:persist:startedA deferred save begins
memory:persist:completedA save completes (receipt count)
memory:errorA recall or save threw (phase: 'recall' or 'save')

Failures are non-fatal

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.

Where to go next

  • Overview: the recall/save contract and how a turn flows
  • Adapters: every adapter's options, with an example of each
  • Custom Adapter: implement recall/save for a backend that isn't shipped