Persistence (Advanced)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
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#
| 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) => ({ threadId: 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:
| 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'). Carries scope only when it was already resolved; omitted if the resolver failed or never ran. |
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