# Vercel AI SDK Persistence and Memory | DialogueDB

> Persistent chat history and long-term user memory for the Vercel AI SDK in one client. Semantic search included, no vector database to run.

Canonical: https://dialoguedb.com/integrations/vercel-ai-sdk

Vercel AI SDK Integration

# Persistent memory and message history for the Vercel AI SDK

Persistent chat history and long-term user memory in one API. Semantic search included, no vector database to run.

[Get Your Free API Key](https://app.dialoguedb.com/signup) [See Example Code](https://github.com/dialoguedb/examples/tree/main/vercel-ai-sdk)

Vercel AI SDK app/api/chat/route.ts

```
streamText({ model, messages })
  .toUIMessageStreamResponse({
    onFinish: async ({ messages }) => {
      // ← DialogueDB slots in here
    },
  })
```

save

load

DialogueDB dialogue-db

```
await dialogue.saveMessages(
  toStoredMessages(messages)
)

// history is one call, fed to useChat
await db.getDialogue(id, { namespace })
```

The integration in one call

## From onFinish to a persisted conversation, in one line

Everything an AI app needs to remember users, without a schema, a vector database, or a load-history endpoint to maintain.

With DialogueDB

The conversation database with messages, memory, and semantic search behind one API. A single call inside onFinish persists the full turn.

```
onFinish: async ({ messages }) => {
  await dialogue.saveMessages(
    toStoredMessages(messages)
  )
}
```

With a split stack

A chat store for messages, a memory service for facts, a vector layer for search, and code to keep them aligned. Every piece works on its own; together they multiply the SDKs, auth tokens, and data models to maintain.

```
onFinish: async ({ response }) => {
  // save messages via a chat store SDK
  // send updates to a memory service SDK
  // keep user IDs aligned across both
  // track two data models in app code
  // handle failures in either half
  // pay for two subscriptions
}
```

The memory difference

## Memory that lives alongside every message, not in a second service

Standalone memory tools run beside a chat store and ask you to sync between them. DialogueDB puts messages, memory, and semantic search behind one API and one namespace, so agents pull both from the same place.

### The whole conversation, not just extracted facts

Full message history and structured memory live in the same database. Search past turns by meaning, replay tool calls, or query stored facts, without forcing a choice between remembering everything and remembering meaningfully.

### You decide what gets remembered

No background inference over conversations, no facts extracted for you. Call createMemory with the value you want to persist, so the memory store stays intentional and easy to audit.

### One system, one integration

The same SDK, the same namespace, and the same auth token cover messages, memory, threads, and state. A standalone memory service adds another integration surface and sync logic between two stores.

Memory inside streamText app/api/chat/route.ts

1

Search memories relevant to the latest turn

`db.searchMemories(userMessage, { namespace })`

2

Inject the matches into the system prompt

`system: buildPrompt(memories)`

3

Stream the response with useChat, unchanged

`streamText({ system, messages, onFinish })`

4

Save new context worth remembering

`db.createMemory({ value, namespace })`

The Vercel AI SDK stays stateless. DialogueDB carries the user context between calls without touching the streaming path.

## Install in 3 steps

From npm install to the first persisted turn, in under five minutes.

1

### Install the client

```
npm install dialogue-db
```

2

### Set the API key

```
DIALOGUE_DB_API_KEY=...
```

3

### Wire onFinish

```
await dialogue.saveMessages(
  toStoredMessages(messages)
)
```

[Get Your Free API Key](https://app.dialoguedb.com/signup)

No credit card. Free tier to start. Starter is $29/month when usage grows.

## The reference example

A working Route Handler and client component you can clone and run as-is.

Persist the UI messages from `toUIMessageStreamResponse`'s `onFinish` with a small `toStoredMessages` mapper, then reload them with `validateUIMessages`. The mapper is written to keep structured tool calls and the `tool` role intact so they round-trip unchanged.

1 Streams responses Uses streamText from the Vercel AI SDK. Nothing changes about how tokens reach the client. 2 Saves in onFinish One call on a dialogue instance persists the completed turn. 3 Loads history from the server A server component reads the dialogue and hydrates useChat through its messages prop.

Runs on Edge and serverless too. The client is HTTP-only, with no connection pool and no native dependencies.

[Clone the full repo](https://github.com/dialoguedb/examples/tree/main/vercel-ai-sdk)

app/api/chat/route.ts

Route Handler Client

import { streamText, convertToModelMessages } from "ai"

import { openai } from "@ai-sdk/openai"

import { DialogueDB } from "dialogue-db"

import { toStoredMessages } from "@/lib/persist"

const db \= new DialogueDB ()

export async function POST (req: Request) {

const { messages, dialogueId, userId } \= await req.json ()

const dialogue \= await db.getOrCreateDialogue ({

id: dialogueId,

namespace: userId,

})

// persist the incoming user turn

await dialogue.saveMessages ( toStoredMessages (\[messages\[messages.length - 1\]\]))

const result \= streamText ({

model: openai ( "gpt-4o" ),

messages: await convertToModelMessages (messages),

})

return result.toUIMessageStreamResponse ({

originalMessages: messages,

onFinish: async ({ messages: updated }) => {

// persist the new assistant message(s)

await dialogue.saveMessages ( toStoredMessages (updated.slice (messages.length)))

},

})

}

"use client"

import { useChat } from "@ai-sdk/react"

import { DefaultChatTransport } from "ai"

// initialMessages: prior history loaded server-side from db.getDialogue(id, { namespace }),

// passed into this client component as a prop. dialogue.messages holds the first

// batch (up to 10) from the API; page older turns as the user scrolls back.

const { messages, sendMessage, status } \= useChat ({

messages: initialMessages,

transport: new DefaultChatTransport ({

api: "/api/chat",

body: { dialogueId, userId },

}),

})

## The same client does more

Every capability below runs on the SDK you already installed, shown by what it lets you build.

### Agent handoffs that keep context

Parent-child dialogues let sub-agents pick up mid-conversation without losing the thread.

`dialogue.createThread()`

### Facts and preferences across sessions

Store what an agent should remember about a user, separate from the message history.

`db.createMemory()`

### Search past conversations by meaning

Find every message a user has sent on a topic without relying on shared keywords.

`db.searchMessages()`

### Per-conversation scratchpads

Give each dialogue its own state for agent workflows, session flags, or in-progress context.

`dialogue.saveState()`

### Multi-user isolation without query code

Namespace every operation so users, tenants, and workspaces stay separated at the data layer.

`namespace: "user_abc"`

### Faithful tool call replay

Every tool invocation and result persists inside the message, so replays match reality.

`toStoredMessages(messages)`

## Where DialogueDB fits

Three trade-offs to weigh when adding memory and persistence to a Vercel AI SDK app.

| Approach                           | Persists messages              | User memory across sessions    | Same system              |
| ---------------------------------- | ------------------------------ | ------------------------------ | ------------------------ |
| Standalone memory service          | No, or a second store required | Yes, usually extracted for you | No, two services to run  |
| Framework-bundled persistence      | Yes                            | Rarely, or basic               | Yes but framework-locked |
| Self-managed (Postgres + pgvector) | Design your own                | Design your own                | If you build it that way |
| DialogueDB                         | Yes                            | Yes, you control what's stored | Yes, one API             |

## If you know the AI SDK, you already know DialogueDB

Everything nests under a namespace. The core concepts map one-to-one to what the AI SDK already exposes.

The data model

namespace

scoped to one user or tenant

dialogue

messages, threads, state

indexed for semantic search

memory

facts and preferences, cross-session

indexed for semantic search

| AI SDK                   | DialogueDB                 |
| ------------------------ | -------------------------- |
| Message                  | message                    |
| useChat conversation     | dialogue                   |
| User (from auth)         | namespace                  |
| Tool call                | message with tool metadata |
| User context to remember | memory                     |

What DialogueDB sees

## Only what's needed for conversation storage

Model provider calls run directly from your Route Handler to the LLM. DialogueDB sits alongside that request path, not inside it.

The SDK only receives what you explicitly send (message content, memory values, and the identifiers you use to scope them). Model keys, user tokens, and unrelated app data never pass through, even by accident.

What we receive

- Message content and roles
- Memory values and metadata
- Dialogue and namespace IDs

What we never see

- Model provider credentials
- User authentication tokens
- Data outside the conversation

Two paths from your Route Handler

Route Handler

app/api/chat/route.ts

Your server code decides what goes where.

two independent calls

Model provider

OpenAI, Anthropic, etc.

Streaming call goes directly. DialogueDB never sees the payload or the credentials.

DialogueDB

Message + memory only

Receives what you explicitly send. Nothing implicit, nothing extra.

## Frequently asked questions

Does DialogueDB work with useChat, streamText, and generateText?

Yes, DialogueDB works with useChat, streamText, and generateText. Any Vercel AI SDK handler that exposes an onFinish callback is compatible: save messages there through the dialogue instance, and pass the loaded messages back into the useChat messages prop on the client.

Does DialogueDB extract memories from conversations automatically?

No, DialogueDB does not extract memories from conversations automatically. You decide what to remember by calling createMemory with the value you want to persist, so the memory store stays intentional and easy to audit.

How do I retrieve the right memory when a user starts a new session?

To retrieve the right memory when a user starts a new session, call searchMemories with a natural language query scoped to that user's namespace. Every memory is auto-vectorized at write time, so you can search by meaning. Searching "dietary restrictions" finds a memory stored as "User is allergic to peanuts."

Does DialogueDB work on the Vercel Edge runtime?

Yes, DialogueDB works on the Vercel Edge runtime. The SDK is a thin HTTP client with no connection pool, no long-lived socket, and no native dependencies, so it also runs on Node, Bun, and standard serverless without any special configuration.

What happens to tool calls and tool results in the Vercel AI SDK when messages are saved?

Tool calls and tool results persist as part of the message payload when messages are saved. The full UI message list from onFinish is stored on the dialogue, including tool invocations, results, and reasoning traces, so replay is faithful.

How do I scope conversations and memory to a user, including anonymous sessions?

To scope conversations and memory to a user, pass a namespace on every write and read. Namespaces map naturally to userId from Clerk, Auth.js, or any auth provider. For anonymous sessions, create the dialogue with a session-scoped namespace. When the user signs in, read the messages out of that dialogue and write them into a new dialogue under the user namespace, then delete the anonymous one. Namespaces are set at dialogue creation and cannot be changed later.

Can I migrate to DialogueDB from an existing Postgres messages table?

Yes, you can migrate to DialogueDB from an existing Postgres messages table. Group your existing rows by dialogue ID and call dialogue.saveMessages(rows) for each dialogue with the original message IDs preserved. Namespaces map to your user\_id column, so multi-tenant isolation carries over unchanged. The migration script in the reference repo covers pagination, progress reporting, and resumable retries.

What data does DialogueDB actually see in a Vercel AI SDK integration?

In a Vercel AI SDK integration, DialogueDB sees the message content, memory values, dialogue metadata, and namespaces you pass to the SDK. It never sees your model provider credentials (OpenAI, Anthropic, or others), your user authentication tokens, or data from the rest of your application. Model calls go directly from your Route Handler to the provider.

## One API for messages, memory, and search

Add it to your Vercel AI SDK app in minutes.

[Get Your Free API Key](https://app.dialoguedb.com/signup) [See Example Code](https://github.com/dialoguedb/examples/tree/main/vercel-ai-sdk)
