Skip to main content

Getting Started with Cortex Memory for Vercel AI SDK

Info
Last Updated: 2026-01-08

Complete guide to adding persistent memory to your Next.js application using Cortex Memory SDK v0.21.0+.

Prerequisites

  • Node.js 18+
  • Next.js 14+ (App Router)
  • Convex account (free tier available)
  • OpenAI API key (or other LLM provider)

The fastest way to understand Cortex Memory is with our interactive quickstart demo. This is a complete Next.js app with:

  • Real-time layer flow visualization
  • Live memory orchestration
  • Multi-tenant memory space switching
  • Streaming with progressive fact extraction
  • Authentication system
  • Data preview panels
Clone and install
$ 

cd packages/vercel-ai-provider/quickstart npm install

Configure environment
$ 

cp .env.local.example .env.local

Edit .env.local with your Convex URL and OpenAI key.

Deploy Convex schema (required)
$ 

npm run convex:dev

Start the demo
$ 

npm run dev

Open http://localhost:3000 and:

  1. Send a message like "Hi, I'm Alex and I work at Acme Corp as an engineer"
  2. Watch the Layer Flow Diagram show data flowing through all 7 Cortex layers in real-time
  3. Ask a question like "What do you remember about me?"
  4. Refresh the page and see memory persists across sessions
  5. Try fact updates - say "I actually prefer purple" after mentioning "I like blue" to see belief revision

View Quickstart Documentation

Option 2: Add to Existing Project

Step 1: Install Dependencies

Terminal
$ 

npm install @cortexmemory/vercel-ai-provider @cortexmemory/sdk ai convex npm install @ai-sdk/openai

You can also use @ai-sdk/anthropic, @ai-sdk/google, or other providers.

Step 2: Set Up Convex Backend

Terminal
$ 

npm create cortex-memories

Follow the wizard to set up Convex with Cortex backend automatically.

Step 2.5: Deploy Convex Schema

After setting up Convex, deploy the schema:

Terminal
$ 

npm run convex:dev

This initializes the Convex backend and is required before running your app.

Step 3: Configure Environment Variables

Create .env.local:

# Required
CONVEX_URL=https://your-deployment.convex.cloud
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
OPENAI_API_KEY=sk-your-key-here

# Optional: Memory Space
MEMORY_SPACE_ID=my-chatbot

# Optional: Enable fact extraction (SDK v0.18.0+)
CORTEX_FACT_EXTRACTION=true
CORTEX_FACT_EXTRACTION_MODEL=gpt-4o-mini

# Optional: Enable graph sync (SDK v0.19.0+)
# CORTEX_GRAPH_SYNC=true
# NEO4J_URI=bolt://localhost:7687
# NEO4J_USERNAME=neo4j
# NEO4J_PASSWORD=your-password

Step 4: Create Memory-Enabled Chat API

// app/api/chat/route.ts
import { createCortexMemoryAsync } from "@cortexmemory/vercel-ai-provider";
import { openai, createOpenAI } from "@ai-sdk/openai";
import {
streamText,
embed,
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
} from "ai";

// Create OpenAI client for embeddings
const openaiClient = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function POST(req: Request) {
const { messages, conversationId: providedConversationId } = await req.json();

// Generate conversation ID if not provided (new chat)
const conversationId =
providedConversationId ||
`conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

// Normalize messages for AI SDK v6 compatibility
// This ensures messages have the `parts` array format expected by convertToModelMessages
const normalizedMessages = messages.map((msg: any) => {
const role = msg.role === "agent" ? "assistant" : msg.role;
const parts = msg.parts || (msg.content ? [{ type: "text", text: msg.content }] : []);
return { ...msg, role, parts };
});

// Convert UIMessage[] to ModelMessage[] for streamText
const modelMessagesResult = convertToModelMessages(normalizedMessages);
const modelMessages =
modelMessagesResult instanceof Promise
? await modelMessagesResult
: modelMessagesResult;

// Create memory-augmented model with async initialization
// Use createCortexMemoryAsync for graph memory support
const cortexMemory = await createCortexMemoryAsync({
convexUrl: process.env.CONVEX_URL!,
memorySpaceId: process.env.MEMORY_SPACE_ID || "default-chat",

// User identification
userId: "demo-user", // Replace with real user ID
userName: "User",

// REQUIRED in SDK v0.17.0+
agentId: "my-assistant",
agentName: "My AI Assistant",

// Conversation ID for chat history isolation
conversationId,

// Embedding provider (required for semantic deduplication)
embeddingProvider: {
generate: async (text: string) => {
const result = await embed({
model: openaiClient.embedding("text-embedding-3-small"),
value: text,
});
return result.embedding;
},
},

// Optional: Enable enhanced features
enableFactExtraction: process.env.CORTEX_FACT_EXTRACTION === "true",
enableGraphMemory: process.env.CORTEX_GRAPH_SYNC === "true",
});

// Stream response with memory integration
const result = await streamText({
model: cortexMemory(openai("gpt-4o-mini")),
messages: modelMessages,
system: `You are a helpful assistant with long-term memory.
You remember everything users tell you and can recall it naturally.`,
});

// Use createUIMessageStreamResponse for advanced streaming features
return createUIMessageStreamResponse({
stream: createUIMessageStream({
execute: async ({ writer }) => {
writer.merge(result.toUIMessageStream());
},
}),
});
}

Step 5: Create Chat UI

// app/page.tsx
'use client';
import { useChat } from 'ai/react';

export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();

return (
<div className="p-4 max-w-2xl mx-auto">
<h1 className="text-2xl mb-4">Chat with Memory</h1>

<div className="space-y-4 mb-4">
{messages.map(m => (
<div
key={m.id}
className={`p-3 rounded ${
m.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'
}`}
>
<strong>{m.role === 'user' ? 'You' : 'Assistant'}:</strong> {m.content}
</div>
))}

{isLoading && (
<div className="p-3 rounded bg-gray-100 animate-pulse">
Thinking...
</div>
)}
</div>

<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="flex-1 p-2 border rounded"
disabled={isLoading}
/>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
disabled={isLoading}
>
Send
</button>
</form>
</div>
);
}

Step 6: Test Memory

  1. Start your app:
Terminal
$ 

npm run dev

  1. Open http://localhost:3000

  2. Test memory persistence:

    • Say: "Hi, my name is Alice and I work at Acme Corp"
    • Agent: "Nice to meet you, Alice! Acme Corp sounds great."
    • Refresh the page
    • Ask: "What do you know about me?"
    • Agent: "I remember you're Alice and you work at Acme Corp!" ✨

Configuration Options

Required Options

OptionTypeDescription
convexUrlstringYour Convex deployment URL
memorySpaceIdstringNamespace for memory isolation
userIdstringUser identifier
agentIdstringAgent identifier (required in v0.17.0+)
conversationIdstringConversation identifier for chat isolation
embeddingProviderobjectEmbedding provider for semantic deduplication (required for belief revision)

Optional Options

OptionTypeDefaultDescription
userNamestring"User"User display name
agentNamestringagentIdAgent display name
enableFactExtractionbooleanfalseEnable LLM fact extraction
enableGraphMemorybooleanfalseSync to graph database
memorySearchLimitnumber20Max memories to retrieve
minMemoryRelevancenumber0.7Min similarity score (0-1)
debugbooleanfalseEnable debug logging
Info

Use createCortexMemoryAsync (with await) instead of createCortexMemory when you need graph memory support or want automatic configuration from environment variables.

Dynamic User Resolution

For authentication systems:

import { auth } from "@clerk/nextjs";
import { createCortexMemoryAsync } from "@cortexmemory/vercel-ai-provider";

export async function POST(req: Request) {
const { userId } = await auth();

if (!userId) {
return new Response("Unauthorized", { status: 401 });
}

const cortexMemory = await createCortexMemoryAsync({
convexUrl: process.env.CONVEX_URL!,
memorySpaceId: "app",
userId,
userName: "User",
agentId: "app-assistant",
conversationId: `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
});

// ... use cortexMemory
}

Multi-Tenant Setup

Different memory space per team/tenant:

import { createCortexMemoryAsync } from "@cortexmemory/vercel-ai-provider";
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

export async function POST(req: Request) {
const { teamId, messages, conversationId } = await req.json();

const cortexMemory = await createCortexMemoryAsync({
convexUrl: process.env.CONVEX_URL!,
memorySpaceId: `team-${teamId}`, // Isolated per team
userId: currentUser.id,
userName: currentUser.name,
agentId: "team-assistant",
conversationId: conversationId || `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
});

const result = await streamText({
model: cortexMemory(openai("gpt-4o-mini")),
messages,
});

return result.toDataStreamResponse();
}

Deploy to Vercel

Terminal
$ 

vercel

Set environment variables in Vercel dashboard:

  • CONVEX_URL
  • NEXT_PUBLIC_CONVEX_URL
  • OPENAI_API_KEY
  • MEMORY_SPACE_ID (optional)
  • CORTEX_FACT_EXTRACTION (optional)
  • CORTEX_GRAPH_SYNC (optional)

Troubleshooting

"agentId is required"

Add agentId to your configuration (required in SDK v0.17.0+):

const cortexMemory = await createCortexMemoryAsync({
// ... other config
agentId: "my-assistant", // Add this!
});

AI SDK v6 Message Format

If you encounter issues with message formatting, ensure you normalize messages for AI SDK v6 compatibility:

// Normalize messages to ensure they have the `parts` array format
const normalizedMessages = messages.map((msg: any) => {
const role = msg.role === "agent" ? "assistant" : msg.role;
const parts = msg.parts || (msg.content ? [{ type: "text", text: msg.content }] : []);
return { ...msg, role, parts };
});

// Convert to ModelMessage[] format
const modelMessages = await convertToModelMessages(normalizedMessages);

Memory not persisting

  1. Check that Convex is running: npx convex dev
  2. Verify CONVEX_URL is set correctly
  3. Check browser console for errors

Search returns no results

  1. Make sure you've had at least one conversation first
  2. If using semantic search, set up embeddingProvider
  3. Check that memorySpaceId matches between requests

See the Troubleshooting Guide for more solutions.

Next Steps