Skip to content
Memory

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

shell
npm i @tanstack/ai-memory

2. Create the memory adapter

Start with inMemory(). It keeps records in a Map in the server process:

ts
// 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:

ts
// 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:

tsx
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

  1. Send My favorite language is TypeScript.
  2. 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:

ts
// 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:

ts
// 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.