Skip to main content

User Profiles

Memory Follows the User

User profiles enable memories to follow users across any tool. Same userId = same memories, whether accessed from Cursor, Claude, custom apps, or any MCP-connected tool. This is the foundation of Hive Mode.


Quick Start

// Create/update a user profile
await cortex.users.update("user-123", {
displayName: "Alex Johnson",
email: "alex@example.com",
preferences: { theme: "dark", language: "en" },
});

// Get user profile (works from any memory space)
const user = await cortex.users.get("user-123");
console.log(user.data.displayName); // "Alex Johnson"

Why User Profiles Exist

Memory Follows the User

The primary purpose of user profiles is cross-tool memory sharing. As long as the same userId is passed to Cortex, memories follow the user regardless of which tool they're using—Cursor, Claude, custom apps, or any MCP-connected tool. This is a core enabler of Hive Mode.

// In Cursor: Store a preference
await cortex.memory.remember({
memorySpaceId: 'user-123-space',
userId: 'user-123', // Same userId
userMessage: 'I prefer TypeScript',
// ...
});

// In Claude Desktop: Recall the same memory
const memories = await cortex.memory.recall({
memorySpaceId: 'user-123-space',
userId: 'user-123', // Same userId = same memories
query: 'programming preferences',
});
// Returns: "User prefers TypeScript" - stored in Cursor, recalled in Claude!
GDPR Cascade Deletion

A critical secondary benefit: One API call removes ALL user data across ALL memory layers for GDPR compliance.

// GDPR "right to be forgotten" - ONE call deletes everything
const result = await cortex.users.delete("user-123", { cascade: true });
// Deletes: profile, conversations, memories, facts, sessions, graph nodes

User Profile Schema

ParameterTypeRequiredDefaultDescription
idstringYesUnique user identifier
tenantIdstringNoMulti-tenancy isolation (auto-injected from AuthContext)
dataRecord<string, unknown>NoFlexible user data - any structure you want
versionnumberNoAuto-incrementing version number
createdAtnumberNoCreation timestamp (Unix ms)
updatedAtnumberNoLast update timestamp (Unix ms)

Suggested Data Structure

await cortex.users.update("user-123", {
displayName: "Alex Johnson",
email: "alex@example.com",
preferences: {
theme: "dark",
language: "en",
timezone: "America/New_York",
},
metadata: {
tier: "pro",
signupDate: Date.now(),
},
});

User Operations

// Create or update user profile
await cortex.users.update("user-123", {
displayName: "Alex Johnson",
email: "alex@example.com",
preferences: {
theme: "dark",
language: "en",
},
metadata: {
tier: "pro",
signupDate: Date.now(),
},
});
Tip

Updates automatically deep-merge with existing data and create a new version.


GDPR Compliance

Right to Erasure

Cortex supports GDPR "right to be forgotten" via cascade deletion with verification.

1

Preview deletion (optional)

const preview = await cortex.users.delete("user-123", {
cascade: true,
dryRun: true, // Preview only
});
console.log(`Would delete ${preview.totalDeleted} records`);
2

Export user data (GDPR requirement)

const exportData = await cortex.users.export({
format: "json",
filters: { displayName: "user-123" },
includeConversations: true,
includeMemories: true,
});
await saveToFile(`gdpr-export-${userId}.json`, exportData);
3

Execute cascade deletion

const result = await cortex.users.delete("user-123", {
cascade: true,
verify: true,
});
4

Automatic cleanup completes

Cortex deletes from ALL stores: User profile, all conversations (Layer 1a), all memories (Layer 2), all facts (Layer 3), all sessions, and graph nodes (if configured).

Deletion Result

{
userId: "user-123",
deletedAt: 1735689600000,
conversationsDeleted: 15,
vectorMemoriesDeleted: 145,
factsDeleted: 89,
totalDeleted: 547,
verification: { complete: true, issues: [] }
}

Automatic User Creation

Auto-Creation on First Memory

User profiles are auto-created when using remember() if they don't exist:

// First call with userId creates the profile automatically
await cortex.memory.remember({
memorySpaceId: "user-123-space",
userId: "user-123", // Auto-creates profile
userName: "Alex", // Used for displayName
// ...
});

Querying Users

// Search by displayName
const users = await cortex.users.search({
displayName: "alex",
limit: 50,
});

// Search recent users
const recent = await cortex.users.search({
createdAfter: Date.now() - 30 * 24 * 60 * 60 * 1000,
sortBy: "createdAt",
sortOrder: "desc",
});

Multi-Tenancy Support

Automatic Tenant Isolation

Use AuthContext for automatic tenant scoping on all user operations.

import { Cortex, createAuthContext } from "@cortexmemory/sdk";

const cortex = new Cortex({
convexUrl: process.env.CONVEX_URL!,
auth: createAuthContext({
tenantId: "customer-acme",
userId: "admin-user",
}),
});

// All operations automatically scoped to tenant
await cortex.users.update("user-123", { displayName: "Alex" });
// tenantId: "customer-acme" auto-injected

const users = await cortex.users.list({ limit: 100 });
// Only returns users for "customer-acme"

await cortex.users.delete("user-123", { cascade: true });
// Only deletes within "customer-acme" tenant

See Isolation Boundaries for complete multi-tenancy documentation.


Common Patterns

Initialize on First Contact

async function ensureUserProfile(userId: string, message: string) {
let user = await cortex.users.get(userId);

if (!user) {
await cortex.users.update(userId, {
displayName: userId,
preferences: {
language: detectLanguage(message),
timezone: "UTC",
},
metadata: {
tier: "free",
signupDate: Date.now(),
},
});
user = await cortex.users.get(userId);
}

return user;
}

Cross-Agent Context

// All agents access the same profile
const user = await cortex.users.get(userId);

// Support agent
const greeting = `Hello ${user.data.displayName}!`;

// Sales agent
if (user.data.metadata.tier === "free") {
offerUpgrade();
}

// Billing agent
sendInvoiceTo(user.data.email);

Profile Validation

import { z } from "zod";

const ProfileSchema = z.object({
displayName: z.string().min(1).max(100),
email: z.string().email().optional(),
preferences: z.object({
theme: z.enum(["light", "dark"]).optional(),
language: z.string().length(2).optional(),
}).optional(),
});

async function safeUpdate(userId: string, data: unknown) {
const validated = ProfileSchema.parse(data);
return await cortex.users.update(userId, validated);
}

Best Practices

Start Minimal
// Only require what you truly need
await cortex.users.update(userId, {
displayName: "Alex", // That's it!
});
// Add more fields over time as you learn about the user
Privacy-First
// Don't store unnecessary PII
await cortex.users.update(userId, {
preferences: {
paymentMethod: "card-ending-1234", // Reference only
},
});
// Never store: SSN, full credit card, passwords
Track Activity
// Update lastSeen on each interaction
await cortex.users.update(userId, {
metadata: { lastSeen: Date.now() },
});

Next Steps