- MCP Server Tools
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type Generation
- MCP Client Input
- MCP Server Tools: Advanced
- MCP Events
- MCP AppsUpdated
- Serve Tools over HTTP
- Serve Resources and Prompts
- Ask for Input
- Draft Text from a Tool
- MCP Server TasksUpdated
- MCP Server Auth
- MCP Server Sessions
- MCP Server on stdio
Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clientsUpdated
- 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 toolsUpdated
- 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 APIUpdated
- 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
Adapters
Each memory adapter implements recall and save. Swap one for another in memoryMiddleware. memoryMiddleware calls recall(scope, query, { signal }) with the abort signal of the chat run. The built-in adapters pass it to their requests when the backend client can take it.
| 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.
Common options#
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')}`,
})- 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. On recall, embed(text, { signal }) gets the abort signal of the chat run. Pass it to your embedding request, so a stopped run does not wait for it.
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.
import { inMemory } from '@tanstack/ai-memory/in-memory'
const memory = inMemory() // all options are optionalredis()#
Plain 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()#
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()#
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()#
Honcho stores the turn as messages in a session. recall returns one written answer about the user, built by Honcho, with no separate fragments. When the chat run aborts, recall stops waiting and returns an empty prompt. The Honcho SDK takes no abort signal, so the Honcho request continues on the server.
| 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'
})Which Scope fields each adapter honors#
| 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.
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.
| 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.
Advanced#
Redis keys#
redis() writes two kinds of keys:
{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.