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
- ProvidersUpdated
- 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
Quickstart
Add memoryMiddleware to a working chat route. The route recalls memory before each answer and saves each turn.
Quick setup#
1. Install the package#
npm i @tanstack/ai-memorypnpm add @tanstack/ai-memoryyarn add @tanstack/ai-memorybun add @tanstack/ai-memory2. Create the memory adapter#
Start with inMemory(). It keeps records in a Map in the server process:
// memory.ts
import { inMemory } from '@tanstack/ai-memory/in-memory'
export const memory = inMemory()3. Add the middleware to the server route#
Set the scope from your auth session. The onRecall log lets you see memory work:
// api/chat.ts
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { memoryMiddleware } from '@tanstack/ai-memory'
import { memory } from './memory'
// Replace with your auth. Read the user from the session, never from the body.
function getUserId(_request: Request): string {
return 'demo-user'
}
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const userId = getUserId(request)
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages: params.messages,
threadId: params.threadId,
middleware: [
memoryMiddleware({
adapter: memory,
// In a real app, make sure that this thread belongs to userId.
scope: { threadId: params.threadId, userId },
onRecall: ({ result }) => {
console.log('memory recalled', result.fragments?.length ?? 0, 'items')
},
}),
],
})
return toServerSentEventsResponse(stream)
}4. Use the chat in the client#
Memory runs on the server only. The client is a normal chat client:
import { useState } from 'react'
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
export function Chat() {
const [input, setInput] = useState('')
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-chat',
})
return (
<>
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) =>
part.type === 'text' ? <p key={index}>{part.content}</p> : null,
)}
</div>
))}
<form
onSubmit={(event) => {
event.preventDefault()
void sendMessage(input)
setInput('')
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button type="submit">Send</button>
</form>
</>
)
}For Vue, Solid, Svelte, and the other frameworks, use the client from the Quick Start. Nothing in it changes for memory.
5. Try it#
- Send My favorite language is TypeScript.
- Send Which language do I like?.
The server log shows memory recalled 2 items (or a similar count) for the second message. The first turn was saved and then recalled into the prompt.
How it works#
- Before the model runs, the adapter finds records in the same scope that match the last user message. They go into the system prompt.
- After the stream finishes, the adapter saves the user message and the assistant answer.
- inMemory() scores by word overlap and recency. Records are lost when the process restarts.
Move to production#
Use Redis#
redis() keeps memory across restarts and shares it between server processes. Change only memory.ts. The route stays the same:
// memory.ts
import Redis from 'ioredis'
import { redis } from '@tanstack/ai-memory/redis'
const client = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379')
export const memory = redis({ redis: client })Install ioredis (or redis) yourself. Both are optional peer dependencies. For node-redis and the other options, see redis().
To use a hosted service, export hindsight(), mem0(), or honcho() from memory.ts instead. See Adapters.
Add semantic scoring (optional)#
Word overlap misses a question that uses different words than the stored text. Add an embedder to inMemory() or redis() to also score by meaning:
// memory.ts
import OpenAI from 'openai'
import { inMemory } from '@tanstack/ai-memory/in-memory'
const openai = new OpenAI()
export const memory = inMemory({
embedder: {
async embed(text) {
const result = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
})
const embedding = result.data[0]?.embedding
if (!embedding) throw new Error('embedding request returned no vector')
return embedding
},
},
})The embedder runs on each recall (for the query) and on each save (for the stored text).
Your route now remembers what the user says and can move to Redis with a one-file change. To log memory in production and watch it in devtools, see Operating memory.