Use when wiring memoryMiddleware from @tanstack/ai-memory into a chat() call — covers the recall/save adapter contract, scope shape and server-side scope security, the recall-inject / deferred-save lifecycle, choosing an adapter (inMemory, redis, hindsight, mem0, honcho), and devtools events.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Use this when adding server-side memory to a chat() call. Everything lives in
@tanstack/ai-memory. A memory adapter is a single contract with two verbs — recall
and save — and the middleware is thin: it recalls into the system prompt before the
model runs and defers save after the turn finishes.
Do NOT use this just to keep recent messages — that's the messages array on chat().
Memory is for cross-turn / cross-session recall, not within-turn history.
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { memoryMiddleware } from '@tanstack/ai-memory'
import { inMemory } from '@tanstack/ai-memory/in-memory'
const memory = inMemory() // dev/tests only — see the in-memory skill
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
context: { session }, // attached by your auth middleware
middleware: [
memoryMiddleware({
adapter: memory,
// Derive scope server-side from trusted session state.
scope: (ctx) => {
const session = getSession(ctx)
return { threadId: session.threadId, userId: session.userId }
},
}),
],
})memoryMiddleware options: adapter, scope (static or a function of ctx),
role ('recall+save' default, or 'save-only'), and onRecall / onSave telemetry
callbacks.
interface MemoryAdapter {
id: string
recall(scope, query): Promise<RecallResult> // { systemPrompt, fragments?, tools?, toolGuidance? }
save(scope, turn): Promise<Array<SaveReceipt>> // turn = { user, assistant }; extraction lives HERE
inspect?(scope): Promise<MemorySnapshot> // optional (devtools)
listFacts?(scope): Promise<Array<MemoryFact>> // optional (devtools)
}recall decides relevance and renders a systemPrompt; it may also return tools +
toolGuidance to hand the model direct control of memory (hindsight does this).save owns extraction — turning the raw turn into whatever gets persisted.MemoryScope is an alias of the shared Scope type from @tanstack/ai:
{ threadId, userId?, tenantId?, namespace? }. It is the isolation boundary. Never
trust a client-supplied userId/threadId. Resolve scope server-side from
session/auth and pass the validated session through chat({ context: { session } }). If
you accept a thread id from the request body, validate it belongs to the session user
BEFORE using it.
inMemory() / redis() — exact match on threadId + optional userId/tenantId
(namespace ignored). Redis index keys include all three segments.hindsight() — bank {tenant|_}__{user}__{threadId}.mem0() — user_id + run_id (threadId); no tenantId.honcho() — session {tenant|_}__{threadId}; peer tenant-prefixed when set.recall/save and run @tanstack/ai-memory/tests/contract.Memory failures are non-fatal: a throwing recall or save emits memory:error and
the run continues with degraded memory. Streaming is never blocked; a failed save never
fails the turn.
Five events on aiEventClient (from @tanstack/ai-event-client):
memory:retrieve:started / :completed, memory:persist:started / :completed,
memory:error (phase: 'recall' | 'save'). Payloads carry the adapter id and
fragment/receipt counts, not full memory text. Error events include scope only
when it was already resolved; if the resolver threw, scope is omitted.
4ab149f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.