Memory

Adapters

Every adapter implements the same recall/save contract, so they're interchangeable in memoryMiddleware. This page is the full option reference: each adapter's options with an example of each.

Common options

inMemory() and redis() are both client-side rankers built on the same pipeline, so they share these options.

OptionTypeDefaultPurpose
topKnumber6Max hits returned by recall.
minScorenumber0.15Drop hits scoring below this.
kindsArray<MemoryKind>allRestrict recall to these record kinds ('message', 'summary', 'fact', 'preference').
embedder{ embed(text): Promise<number[]> }noneEnable semantic scoring (embeds on both recall and save).
extract(turn, scope) => ExtractedFact[]nonePersist derived facts on save, alongside the raw turn.
render(hits) => stringbuilt-inReplace the prompt renderer.

Every option, 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 in addition 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? }). Return undefined for a no-op. It's where an LLM-based fact extractor plugs in without the adapter taking a hard dependency on any model.

embedder is invoked on the recall path (to embed the query) and again on save (to embed stored text). Without it, scoring is lexical + recency only.

inMemory()

Zero-dependency, Map-backed. Takes only the common options above. Records vanish on restart, so use it for dev, tests, and single-process demos.

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

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

redis()

Plain-Redis adapter. Adds two options to the common options, and requires a client.

OptionTypeDefaultPurpose
redisRedisLike(required)Your Redis client (ioredis, or node-redis via 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,
})

Using node-redis (redis package) instead of ioredis? Its camelCase API doesn't match RedisLike, so wrap it with fromNodeRedis:

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) })

ioredis and redis are both optional peer dependencies. Install whichever you use.

hindsight()

Hosted adapter backed by Hindsight. Owns extraction/ranking server-side and exposes retain/recall/reflect LLM tools through recall. @vectorize-io/hindsight-client is an optional peer, loaded lazily.

OptionTypeDefaultPurpose
userstringscope.userIdDurable user id used in the bank key ({user}__{sessionId}).
baseUrlstringHINDSIGHT_URL / http://localhost:8888Server URL.
budget'low' | 'mid' | 'high''mid'Recall budget.
onToolRetain(receipt) => voidnoneFired when the model calls hindsight_retain.
onToolRecall(query, result) => voidnoneFired when the model calls hindsight_recall.
ts
import { hindsight } from '@tanstack/ai-memory/hindsight'

const memory = hindsight({
  user: 'alice', // bank = alice__{sessionId}
  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()

Hosted adapter backed by a mem0 server, over plain HTTP (no SDK peer). Requires a running mem0 server.

OptionTypeDefaultPurpose
userstringscope.userId / 'demo-user'mem0 user_id.
baseUrlstringMEM0_URL / 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()

Hosted adapter backed by Honcho. recall returns a synthesized dialectic answer over the user's representation (no discrete fragments). @honcho-ai/sdk is an optional peer, loaded lazily.

OptionTypeDefaultPurpose
userstringscope.userId / 'demo-user'User peer id.
baseURLstringHONCHO_URL / http://localhost:8001Server URL.
workspaceIdstringHONCHO_APP_NAME / 'ai-memory'Workspace id.
apiKeystringHONCHO_API_KEY / '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'
})

Where to go next

  • Overview: the recall/save contract and how a turn flows
  • Quickstart: wire an adapter into a real chat() call
  • Operating memory: options, telemetry, devtools events, and failures
  • Custom Adapter: implement recall/save for a backend that isn't shipped