Skip to content
Memory

Adapters

Each memory adapter implements recall and save. Swap one for another in memoryMiddleware.

AdapterImportUse it forExtra install
inMemory()@tanstack/ai-memory/in-memoryDevelopment, tests, demosNone
redis()@tanstack/ai-memory/redisProduction on your own Redisioredis or redis
hindsight()@tanstack/ai-memory/hindsightHindsight server, with memory tools for the model@vectorize-io/hindsight-client
mem0()@tanstack/ai-memory/mem0mem0 server, over HTTPNone
honcho()@tanstack/ai-memory/honchoHoncho server@honcho-ai/sdk

All extra installs are optional peer dependencies. Install only the one that your adapter needs.

Common options

inMemory() and redis() rank records in your server process with the same code, so they share these options:

OptionTypeDefaultPurpose
topKnumber6Maximum number of hits that recall returns.
minScorenumber0.15Hits with a lower score are dropped.
kindsArray<MemoryKind>allRecall only these record kinds: 'message', 'summary', 'fact', 'preference'.
embedder{ embed(text): Promise<number[]> }noneAdds semantic scoring. Runs on recall and on save.
extract(turn, scope) => ExtractedFact[]noneSaves derived facts on save, next to the raw turn.
render(hits) => stringbuilt-inReplaces the prompt renderer.

All options in one adapter:

ts
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')}`,
})
  • extract returns ExtractedFact[] ({ text, kind?, importance?, metadata? }), or undefined to save nothing extra. Put an LLM-based fact extractor here. The adapter does not depend on a model.
  • embedder embeds the query on recall and the stored text on save. With no embedder, the score uses word overlap, recency, and importance only.

inMemory()

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.

ts
import { inMemory } from '@tanstack/ai-memory/in-memory'

const memory = inMemory() // all options are optional

redis()

Plain Redis, no vector index. It adds two options to the common options:

OptionTypeDefaultPurpose
redisRedisLike(required)Your Redis client: ioredis, or node-redis through fromNodeRedis.
prefixstring'tanstack-ai:memory'Key namespace.
ts
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:

ts
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()

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.

OptionTypeDefaultPurpose
userstringscope.userId, then 'demo-user'User id in the bank id.
baseUrlstringHINDSIGHT_URL or http://localhost:8888Server URL.
budget'low' | 'mid' | 'high''mid'Recall budget.
onToolRetain(receipt) => voidnoneRuns when the model calls hindsight_retain.
onToolRecall(query, result) => voidnoneRuns when the model calls hindsight_recall.
ts
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()

mem0 does extraction and ranking on its server. The adapter uses plain HTTP, so it needs no SDK. You need a running mem0 server.

OptionTypeDefaultPurpose
userstringscope.userId, then 'demo-user'mem0 user_id.
baseUrlstringMEM0_URL or http://localhost:8000Server URL.
apiKeystringMEM0_ADMIN_API_KEYBearer token.
rerankbooleantrueAsk mem0 to rerank search results.
thresholdnumber0.1Minimum search score.
ts
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()

Honcho stores the turn as messages in a session. recall returns one written answer about the user, built by Honcho, with no separate fragments.

OptionTypeDefaultPurpose
userstringscope.userId, then 'demo-user'User peer id.
baseURLstringHONCHO_URL or http://localhost:8001Server URL.
workspaceIdstringHONCHO_APP_NAME or 'ai-memory'Workspace id.
apiKeystringHONCHO_API_KEY or 'dev-no-auth'API key.
assistantIdstring'assistant'Assistant peer id.
ts
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'
})

Which Scope fields each adapter honors

AdapterthreadIduserIdtenantIdnamespace
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)noignored
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.

Third-party adapters

These adapters are published outside TanStack AI. They have the same recall and save methods, so they work in memoryMiddleware like the ones above.

AdapterPackageBackendRecall
upstashMemory()@upstash/agentkit-tanstack-ai/memoryUpstash 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.

Advanced

Redis keys

redis() writes two kinds of keys:

plaintext
{prefix}:record:{id}                                      -> JSON record
{prefix}:index:{tenantId or _}:{userId or _}:{threadId}   -> Set of record ids
  • Each scope value is escaped, so :, \, and _ in a value cannot make two scopes share a key.
  • A missing optional field becomes _. A write with tenantId and a read without it use different keys. Always read with the same fields that you wrote with.
  • The adapter reads only this index layout. If you wrote memory under a different layout, reindex it or delete it.
  • If a stored record is not valid JSON, recall skips it, logs a warning, and leaves it in Redis.

Vendor ids

  • Hindsight bank id: {tenantId or _}__{user}__{threadId}.
  • mem0 requests send user_id and run_id (the threadId). They do not send tenantId.
  • Honcho session key: {tenantId or _}__{threadId}. The user peer id is {tenantId}__{user} when tenantId is set, and the bare user id when it is not.

How inMemory() and redis() score

  • Each record gets a weighted score: semantic match 0.55, word overlap 0.20, recency 0.15, importance 0.10.
  • Recency halves every 30 days.
  • A raw user or assistant message is saved with importance 0.4. An extracted fact uses the importance that extract returns, or 0.
  • With no embedder, the semantic part is 0.
  • The default renderer quotes each record with JSON.stringify, so stored text with new lines or instructions cannot break out of the memory list.