Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- 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)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
Overview
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.
Quick setup#
1. Install the package#
npm i @tanstack/ai-memorypnpm add @tanstack/ai-memoryyarn add @tanstack/ai-memorybun add @tanstack/ai-memory2. Add the middleware to chat()#
import { 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.
How it works#
Each run has two memory steps:
- Recall. Before the model runs, the middleware sends the last user message to adapter.recall(scope, query). The adapter returns a prompt block, and the middleware adds it to the system prompts. Some adapters also return tools that let the model read and write memory.
- Save. After the stream finishes, the middleware gives the { user, assistant } turn to adapter.save(scope, turn). The save is deferred, so it never blocks the response.
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.
Pick an adapter#
| 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: whose memory is it#
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:
- Read userId and tenantId from your auth session. Never read them from the request body.
- If the client sends threadId, make sure that the thread belongs to the session user before you use it.
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.
Next#
- Quickstart: a full server route, a client, and a production adapter.
- Adapters: every adapter and its options.
- Custom Adapter: write recall and save for your own backend.
- Operating memory: telemetry, devtools, and what happens when memory fails.