Each memory adapter implements recall and save. Swap one for another in memoryMiddleware.
| Adapter | Import | Use it for | Extra install |
|---|---|---|---|
| inMemory() | @tanstack/ai-memory/in-memory | Development, tests, demos | None |
| redis() | @tanstack/ai-memory/redis | Production on your own Redis | ioredis or redis |
| hindsight() | @tanstack/ai-memory/hindsight | Hindsight server, with memory tools for the model | @vectorize-io/hindsight-client |
| mem0() | @tanstack/ai-memory/mem0 | mem0 server, over HTTP | None |
| honcho() | @tanstack/ai-memory/honcho | Honcho server | @honcho-ai/sdk |
All extra installs are optional peer dependencies. Install only the one that your adapter needs.
inMemory() and redis() rank records in your server process with the same code, so they share these options:
| Option | Type | Default | Purpose |
|---|---|---|---|
| topK | number | 6 | Maximum number of hits that recall returns. |
| minScore | number | 0.15 | Hits with a lower score are dropped. |
| kinds | Array<MemoryKind> | all | Recall only these record kinds: 'message', 'summary', 'fact', 'preference'. |
| embedder | { embed(text): Promise<number[]> } | none | Adds semantic scoring. Runs on recall and on save. |
| extract | (turn, scope) => ExtractedFact[] | none | Saves derived facts on save, next to the raw turn. |
| render | (hits) => string | built-in | Replaces the prompt renderer. |
All options in one adapter:
import { inMemory } from '@tanstack/ai-memory/in-memory'
// `embedText` stands in for your embedding client (OpenAI, Cohere, a local model).
declare function embedText(text: string): Promise<Array<number>>
const memory = inMemory({
topK: 8, // return up to 8 hits
minScore: 0.2, // ignore weak matches
kinds: ['message', 'fact', 'preference'], // skip summaries
embedder: { embed: embedText }, // semantic + lexical scoring
extract: (turn) => [
// store a derived fact next to the raw turn
{ text: `User said: ${turn.user}`, kind: 'fact', importance: 0.8 },
],
render: (hits) =>
// custom prompt block instead of the default renderer
`What I remember:\n${hits.map((h) => `- ${h.record.text}`).join('\n')}`,
})Records live in a Map and are lost when the process restarts. Each server process has its own Map. It takes only the common options.
import { inMemory } from '@tanstack/ai-memory/in-memory'
const memory = inMemory() // all options are optionalPlain Redis, no vector index. It adds two options to the common options:
| Option | Type | Default | Purpose |
|---|---|---|---|
| redis | RedisLike | (required) | Your Redis client: ioredis, or node-redis through fromNodeRedis. |
| prefix | string | 'tanstack-ai:memory' | Key namespace. |
import Redis from 'ioredis'
import { redis } from '@tanstack/ai-memory/redis'
const memory = redis({
redis: new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379'), // required
prefix: 'myapp:memory', // key namespace
topK: 8, // common options apply here too
minScore: 0.2,
})If you use node-redis (the redis package), wrap the client with fromNodeRedis. Its camelCase methods do not match RedisLike:
import { createClient } from 'redis'
import { redis, fromNodeRedis } from '@tanstack/ai-memory/redis'
const client = createClient({ url: process.env.REDIS_URL })
await client.connect()
const memory = redis({ redis: fromNodeRedis(client) })redis() loads all records of a scope and ranks them in your process. This suits up to about 10,000 records per scope. For the key layout, see Redis keys.
Hindsight does extraction and ranking on its server. recall also gives the model three tools: hindsight_retain, hindsight_recall, and hindsight_reflect. The client package loads only when the adapter first runs.
| Option | Type | Default | Purpose |
|---|---|---|---|
| user | string | scope.userId, then 'demo-user' | User id in the bank id. |
| baseUrl | string | HINDSIGHT_URL or http://localhost:8888 | Server URL. |
| budget | 'low' | 'mid' | 'high' | 'mid' | Recall budget. |
| onToolRetain | (receipt) => void | none | Runs when the model calls hindsight_retain. |
| onToolRecall | (query, result) => void | none | Runs when the model calls hindsight_recall. |
import { hindsight } from '@tanstack/ai-memory/hindsight'
const memory = hindsight({
user: 'alice',
baseUrl: 'https://hindsight.internal', // default: HINDSIGHT_URL
budget: 'high', // deeper recall
onToolRetain: (receipt) => console.log('model retained', receipt.ok),
onToolRecall: (query, result) =>
console.log('model recalled', query, result.fragments?.length),
})mem0 does extraction and ranking on its server. The adapter uses plain HTTP, so it needs no SDK. You need a running mem0 server.
| Option | Type | Default | Purpose |
|---|---|---|---|
| user | string | scope.userId, then 'demo-user' | mem0 user_id. |
| baseUrl | string | MEM0_URL or http://localhost:8000 | Server URL. |
| apiKey | string | MEM0_ADMIN_API_KEY | Bearer token. |
| rerank | boolean | true | Ask mem0 to rerank search results. |
| threshold | number | 0.1 | Minimum search score. |
import { mem0 } from '@tanstack/ai-memory/mem0'
const memory = mem0({
user: 'alice', // mem0 user_id
baseUrl: 'https://mem0.internal', // default: MEM0_URL
apiKey: process.env.MEM0_ADMIN_API_KEY, // bearer token
rerank: true, // rerank results
threshold: 0.2, // stricter score floor
})Honcho stores the turn as messages in a session. recall returns one written answer about the user, built by Honcho, with no separate fragments.
| Option | Type | Default | Purpose |
|---|---|---|---|
| user | string | scope.userId, then 'demo-user' | User peer id. |
| baseURL | string | HONCHO_URL or http://localhost:8001 | Server URL. |
| workspaceId | string | HONCHO_APP_NAME or 'ai-memory' | Workspace id. |
| apiKey | string | HONCHO_API_KEY or 'dev-no-auth' | API key. |
| assistantId | string | 'assistant' | Assistant peer id. |
import { honcho } from '@tanstack/ai-memory/honcho'
const memory = honcho({
user: 'alice', // user peer
baseURL: 'https://honcho.internal', // default: HONCHO_URL
workspaceId: 'my-app', // default: HONCHO_APP_NAME
apiKey: process.env.HONCHO_API_KEY, // default: 'dev-no-auth'
assistantId: 'support-bot', // default: 'assistant'
})| Adapter | threadId | userId | tenantId | namespace |
|---|---|---|---|---|
| inMemory() | yes (exact) | yes (exact) | yes (exact) | ignored |
| redis() | yes (key segment) | yes (key segment) | yes (key segment) | ignored |
| hindsight() | yes (bank id) | yes (bank id or user option) | yes (bank prefix, _ when unset) | ignored |
| mem0() | yes (run_id) | yes (user_id or user option) | no | ignored |
| honcho() | yes (session key) | yes (peer id or user option) | yes (session and peer prefix) | ignored |
An optional field matches exactly. A read with no tenantId does not match a record written with one. mem0 does not model tenants, so for multi-tenant isolation, put the tenant into the user option.
These adapters are published outside TanStack AI. They have the same recall and save methods, so they work in memoryMiddleware like the ones above.
| Adapter | Package | Backend | Recall |
|---|---|---|---|
| upstashMemory() | @upstash/agentkit-tanstack-ai/memory | Upstash Redis (HTTP client) | BM25 full-text query in Redis Search |
upstashMemory() passes runMemoryAdapterContract from @tanstack/ai-memory/testkit. It ranks inside Redis, so it does not load records into your process. It honors namespace as well as tenantId, userId, and threadId.
redis() writes two kinds of keys:
{prefix}:record:{id} -> JSON record
{prefix}:index:{tenantId or _}:{userId or _}:{threadId} -> Set of record ids