Guides / Frameworks

Vercel AI SDK

For apps and agents built with the Vercel AI SDK (ai, version 7). @recall-memory/ai-sdk gives any model Recall memory in one line: a middleware that adds what Recall knows about the user to each request and stores every turn, and three tools the model can call to search memory, ask it a question and save a fact.

What you get

ExportWhat it does
recallMiddleware(options)Language model middleware for wrapLanguageModel. Before each call it adds what Recall knows about the user, biased towards their latest message, as a system message. After the final step it stores the user's message and the reply in the session.
recallTools(options)Tools for tools: searchMemory (facts and messages that match), askMemory (a question about the user, answered from memory) and rememberFact (save one fact).
recallContext(options)The same memory block as text, for system, instructions or a prepareCall.
saveTurn(options, turn)Stores one exchange by hand, for example from onEnd, when you do not let the middleware store turns.

Every one of them takes the same options: userId (the person, as a Recall peer), sessionId (the conversation) and optionally recall (a client; by default one is made from RECALL_API_KEY) and assistantId.

Build it

Install the package and the SDK (npm install @recall-memory/ai-sdk @recall-memory/sdk; ai and zod you already have) and set RECALL_API_KEY. Pick the ids: the user is a peer keyed by your own stable user id, and each conversation is a session. Facts from your app's settings can go in directly.

import { recallMiddleware, recallTools } from "@recall-memory/ai-sdk";
import { Recall } from "@recall-memory/sdk";
import { generateText, isStepCount, ToolLoopAgent, wrapLanguageModel } from "ai";
import { z } from "zod";

const recall = new Recall({ workspaceId: "my-app" });

// Who the memory is about (your own stable user id) and the conversation it belongs to.
const userId = "user-8412";
const sessionId = "chat-20261013-a91f";
// A fact from your app's settings, stored as given and searchable at once.
await recall.peer(userId).conclusions.create("Is vegetarian.");

Memory for every request: the middleware. Wrap the model you already use. The middleware reads Recall before the call (a context call, not a question, so it is fast) and stores the turn after the final step, so a tool loop is stored once, with its answer. If Recall cannot be reached, the call goes on without memory and onError is told.

// `model` is any AI SDK language model. The middleware adds what Recall knows about the user to
// the request, then stores the user's message and the reply in the session.
const { text: dinner } = await generateText({
  model: wrapLanguageModel({
    model,
    middleware: recallMiddleware({ recall, userId, sessionId }),
  }),
  system: "You are a helpful assistant.",
  prompt: "My parents visit on Saturday. Can you plan a dinner for four?",
});

The same works with streamText: the turn is stored when the stream ends. wrapLanguageModel takes a model instance, such as one from a provider package or gateway(...), not a model id string.

Let the model use memory: the tools. With the tools the model looks things up when it needs them and saves what the user shares, without you deciding when. Give it room for a tool step or two with stopWhen.

// Tools let the model look things up (searchMemory, askMemory) and save what the user shares
// (rememberFact) on its own.
const { text: answer } = await generateText({
  model,
  tools: recallTools({ recall, userId }),
  stopWhen: isStepCount(5),
  prompt: "What do I eat, again?",
});

Agents. A ToolLoopAgent is usually defined once, while the user and the conversation change per call. Pass them as call options and add memory in prepareCall.

// An agent defined once, with memory per call: who and which conversation come in as call options.
const agent = new ToolLoopAgent({
  model,
  instructions: "You are a helpful assistant.",
  callOptionsSchema: z.object({ userId: z.string(), sessionId: z.string() }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    model: wrapLanguageModel({ model, middleware: recallMiddleware({ recall, ...options }) }),
    tools: recallTools({ recall, userId: options.userId }),
  }),
});
const { text: reply } = await agent.generate({
  prompt: "Remember that my sister Ana is coming too.",
  options: { userId, sessionId },
});

Settings that matter

OptionUse
userIdYour own stable user id, not an email address. Create the middleware and tools per request, inside your route handler, so every request reads and writes the right person's memory.
sessionIdOne per conversation. The middleware stores turns only when it has one; reading memory does not need it.
assistantIdThe assistant's peer (default assistant). It is created with observeMe: false: Recall reads its replies as context but learns nothing about it.
context: false / store: falseMiddleware that only stores turns, or only reads memory.
maxConclusionsMost facts in the memory block. Fewer means a shorter prompt.
reasoningLevel (tools)The reasoning level for askMemory. See Reasoning levels and models.

Cost and speed

The middleware's read is a context call: it is included in the price and takes under a second. Storing a turn is one request. Recall learns from the user's messages in the background, and that is what you pay for. askMemory is a question, a model call of a few seconds, so the model should reach for searchMemory first. Prices are on the pricing page and worked examples in Cost and speed.

Privacy and forgetting

Everything the user writes through a wrapped model is stored and learned from. Tell users in your privacy notice, keep secrets such as passwords and card numbers out of messages, and do not wrap the model for calls that should leave no trace. To forget a user, delete their peer (recall.peer(userId).delete()) and their sessions; see Privacy, consent and forgetting.

Pitfalls

Next: LangChain.js · Mastra