--- title: "Memory for the Vercel AI SDK" description: "The Vercel AI SDK leaves memory persistence to the application. Add temporal memory to an AI SDK agent with one fetch-based tool pair and two endpoints." canonical: https://past.dev/integrations/vercel-ai-sdk last-updated: 2026-10-17 --- # Add memory to the Vercel AI SDK Source: https://past.dev/integrations/vercel-ai-sdk The Vercel AI SDK runs the agent loop and manages the message array within a run. It does not provide native memory persistence: chat history storage is implemented by the application, and the memory guide lists provider-defined tools, memory providers and custom tools as the ways to add more. A custom tool can call past.dev over HTTP for timestamped ingestion and grouped recall documents with dated source excerpts. ## Vercel AI SDK memory scope The [AI SDK](https://ai-sdk.dev/docs/agents) treats an agent as a model calling tools in a loop, with `ToolLoopAgent` managing the loop, the message array and the stopping conditions for one run. Persistence sits with the application: the [message persistence guide](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence) has you implement chat saving and loading against your own database. For memory beyond the transcript, the [memory guide](https://ai-sdk.dev/docs/agents/memory) states that the SDK provides no native memory persistence and documents three approaches: provider-defined tools, external memory providers and custom tools. The recipe below is the custom-tool path. - `useChat` manages client-side message state; storing and reloading chats is application code in your route handlers. - `runtimeContext` carries shared state through one agent loop, for `prepareStep` and lifecycle callbacks. It is run-scoped state rather than durable memory. - A saved transcript stays unstructured: nothing in it resolves entities, dates facts or marks which value is current. ## The integration: one tool pair, two endpoints Define a [tool](https://ai-sdk.dev/docs/foundations/tools) per endpoint: a `description`, an `inputSchema`, a `contextSchema` and an `execute` function that calls `fetch`. The wire format is small: `content` plus `timestamp` on ingest, `query` on recall ([API reference](/docs/memory-api/api-reference)). ```typescript import { tool } from "ai"; import { z } from "zod"; const BASE = "https://api.past.dev/api/v1"; const headers = { Authorization: `Bearer ${process.env.PAST_API_KEY}`, "Content-Type": "application/json", }; // The signed-in user. The route handler sets it; the model never sees it. const contextSchema = z.object({ identity: z.string() }); export const rememberFact = tool({ description: "Store timestamped source text, readable by the whole project, authored by the current user.", inputSchema: z.object({ text: z.string(), happenedAt: z.string().describe("ISO 8601 event time"), }), contextSchema, execute: async ({ text, happenedAt }, { context }) => { const r = await fetch(`${BASE}/ingest`, { method: "POST", headers, body: JSON.stringify({ content: text, timestamp: happenedAt, identity: context.identity }), }); return r.json(); }, }); export const recallMemory = tool({ description: "Return ranked documents with source excerpts for the current user.", inputSchema: z.object({ question: z.string() }), contextSchema, execute: async ({ question }, { context }) => { const r = await fetch(`${BASE}/recall`, { method: "POST", headers, body: JSON.stringify({ query: question, identity: context.identity }), }); return r.json(); }, }); ``` Pass both through `tools`, and the signed-in user through `toolsContext`, keyed by tool name. The SDK validates each entry against the tool's `contextSchema` and passes it to `execute` as `context` ([runtime and tool context](https://ai-sdk.dev/docs/ai-sdk-core/runtime-and-tool-context)). `streamText` and a `ToolLoopAgent` take the same options. ```typescript import { generateText } from "ai"; import { recallMemory, rememberFact } from "./tools/past"; const user = { identity: session.userId }; // the signed-in user, from your auth const { text } = await generateText({ model, // any AI SDK model tools: { rememberFact, recallMemory }, toolsContext: { rememberFact: user, recallMemory: user }, prompt: "What did we decide about the Q4 budget?", }); ``` The model decides when to store and when to recall from the tool descriptions. Keep your existing chat persistence for resuming conversations; route durable facts through the API. > **Identity** > > The application sets the identity. The model never chooses it. Recall returns what the identity in the request may read, so take the identity from the signed-in user in code, and keep it out of every value that the model fills. ## Or connect the MCP server `createMCPClient` from `@ai-sdk/mcp` connects with the `http` transport, and `tools()` returns the server's tools for `generateText` or `streamText` ([MCP tools](https://ai-sdk.dev/docs/ai-sdk-core/mcp-tools)). Close the client when the response is complete. The project's end-user MCP server then gives the agent `recall`, `answer` and `who_am_i`, plus `remember` while the project's write toggle is on, with no tool code to write. 1. Turn on the project's end-user MCP server on **Build › MCP server** in the console. 2. Mint an access link there, or with the account server's `create_mcp_access_link` tool. The link has the form `https://api.past.dev/mcp//link/`. It is one URL that is one identity, and the server answers it with no sign-in step. 3. Keep the link in a secret store, such as the `PAST_MCP_URL` environment variable that the code below reads. The secret in its path is the credential. ```typescript import { createMCPClient } from "@ai-sdk/mcp"; import { generateText } from "ai"; const past = await createMCPClient({ transport: { type: "http", url: process.env.PAST_MCP_URL! }, }); try { const { text } = await generateText({ model, // any AI SDK model tools: await past.tools(), prompt: "What did we decide about the Q4 budget?", }); console.log(text); } finally { await past.close(); } ``` Every call through the link runs as its identity, with that identity's audiences. Mint one link per install, so that a revoked link stops one agent only. When the agent serves several people, mint one link per end user, and connect each request with the link of the user who makes it. Never share one link between users. `remember` writes to the identity's own private audience, and only that identity recalls it. Send data that the whole project must recall through the ingest call. The [end-user server documentation](/docs/mcp/serving-your-own-users) describes the tools, the links and revocation. ## Routing on recall results Recall returns ranked documents with dates and source excerpts. The application decides whether those documents support an answer, contain a disagreement, or are insufficient; HTTP failures are handled separately. In an AI SDK app the ranked documents arrive as the tool result. A lifecycle callback or route handler can apply a sufficiency policy, send disagreements to a person with both source excerpts, or trigger a clarifying question instead of a guess. ## When to use which layer | Requirement | Chat persistence you build | past.dev via tool | | --- | --- | --- | | Resume a conversation in useChat | Yes | No | | Facts shared across chats, users and apps | Only by rereading transcripts | One recall call | | Current value after a change | The model rereads the transcript | Supersession with both values queryable | | Backfilled history on original dates | No | Timestamped ingestion | | Recall result | Provider-specific | Grouped documents with dates and source excerpts | The [quickstart](/docs/memory-api/quickstart) shows how to ingest, wait until ingestion completes, and recall. The [benchmarks](/benchmarks) document how recall is measured, and [choosing a memory system](/guides/choose-memory-system) compares the wider field of options. ## Frequently asked questions ### Does the Vercel AI SDK have built-in memory? No. The AI SDK documents memory as something the application adds through provider-defined tools, an external memory provider, or custom tools. Chat history persistence is also implemented by the application. ### Do I need a database to give an AI SDK agent memory? You need storage somewhere. A transcript store you build covers resuming conversations, and a memory API can add timestamped source ingestion and grouped recall documents without adding an agent runtime. ### Does this work with useChat and streamText? Yes. The tools execute inside your route handler wherever generateText or streamText runs, and useChat on the client is unchanged. ## Related - [Memory for the OpenAI Agents SDK](https://past.dev/integrations/openai-agents-sdk) - [Memory for LangChain](https://past.dev/integrations/langchain) - [Memory for n8n](https://past.dev/integrations/n8n) - [Memory for Mastra](https://past.dev/integrations/mastra) - [Cross-session memory](https://past.dev/glossary/cross-session-memory) - [Quickstart](https://past.dev/docs/memory-api/quickstart)