Skip to main content

Conversation Operations API

Complete API reference for ACID conversation management (Layer 1a).

Overview​

The Conversation Operations API (cortex.conversations.*) manages immutable conversation threads - the Layer 1a foundation that provides the source of truth for all message history.

Key Characteristics
  • Immutable - Messages never modified, only appended
  • Append-Only - Conversations grow, never shrink (unless deleted)
  • ACID Guarantees - Atomic, consistent, isolated, durable
  • No Retention Limits - Kept forever (unlike Vector with retention)
  • Audit Trail - Complete message history for compliance
  • Referenced - Vector memories link back via conversationRef
  • Multi-Tenant - Automatic tenantId isolation for SaaS platforms

Relationship to Other Layers:

Layer Architecture
Layer 1a: ACID Conversations

THIS API — Immutable source of truth

Layer 2: Vector Memories

References Layer 1a via conversationRef

Layer 3: Facts Store

LLM-extracted facts from conversations

Layer 4: Memory API

Uses L1a + L2 + L3 via cortex.memory.remember()

Conversation Types:

TypeParticipantsUse Case
`user-agent`User + AgentUser chatting with AI agent
`agent-agent`Agent + AgentA2A communication (managed by cortex.a2a helpers)

Why Layer 1a Exists:

  • Complete audit trail (Vector has retention limits)
  • Compliance (legal requirement for message history)
  • Context retrieval (can always get full conversation)
  • Source of truth (Vector is index, this is reality)

Core Operations​

create()​

Create a new conversation.

Signature:

cortex.conversations.create(
input: CreateConversationInput
): Promise<Conversation>
Info

v0.29.0+: Graph sync is now automatic when CORTEX_GRAPH_SYNC=true is set in your environment. The syncToGraph option has been removed from all APIs.

Parameters:

interface CreateConversationInput {
// Memory space (REQUIRED) - top-level for isolation
memorySpaceId: string;

// Type (REQUIRED)
type: "user-agent" | "agent-agent";

// Participants (REQUIRED)
participants: {
userId?: string; // For user-agent conversations (single user)
userIds?: string[]; // For collaborative conversations (multiple users)
agentId?: string; // Agent in the conversation
participantId?: string; // Hive Mode: who created this
memorySpaceIds?: string[]; // For agent-agent (Collaboration Mode)
};

// Optional fields
conversationId?: string; // Auto-generated if not provided
tenantId?: string; // Multi-tenancy: SaaS platform isolation
participantId?: string; // Hive Mode tracking (top-level)
metadata?: Record<string, unknown>;

// Shareable Chats (v0.35.0+)
visibility?: 'private' | 'space' | 'public'; // Default: 'private'
collaborativeSettings?: {
requireApproval: boolean; // Require approval for non-owner messages
ownerUserId?: string; // User who can approve (defaults to userId)
approvedParticipants?: string[]; // Pre-approved participants
};
}

Returns:

interface Conversation {
_id: string; // Convex internal ID
conversationId: string;
memorySpaceId: string; // Memory space this conversation belongs to
type: "user-agent" | "agent-agent";
participants: {
userId?: string;
userIds?: string[]; // Collaborative: multiple users
agentId?: string;
participantId?: string;
memorySpaceIds?: string[];
};

messages: Message[]; // Initially empty
messageCount: number; // Initially 0

metadata?: Record<string, unknown>;

tenantId?: string; // Auto-injected from AuthContext for multi-tenancy
participantId?: string; // Hive Mode tracking

// Shareable Chats (v0.35.0+)
visibility?: 'private' | 'space' | 'public';
collaborativeSettings?: {
requireApproval: boolean;
ownerUserId?: string;
approvedParticipants?: string[];
};

createdAt: number; // Unix timestamp (milliseconds)
updatedAt: number; // Unix timestamp (milliseconds)
}

Side Effects:

  • Creates new conversation entity in Convex
  • Initially contains no messages (append via addMessage())

Example 1: User-agent conversation

const conversation = await cortex.conversations.create({
memorySpaceId: "support-bot-space", // Required - top-level
type: "user-agent",
participants: {
userId: "user-123",
agentId: "support-agent",
},
participantId: "support-agent", // Hive Mode tracking (optional)
metadata: {
channel: "web-chat",
source: "website",
},
});

console.log(conversation.conversationId); // 'conv-abc123'
console.log(conversation.messageCount); // 0 (empty initially)

Example 2: Agent-agent conversation (A2A)

const a2aConvo = await cortex.conversations.create({
memorySpaceId: "shared-workspace", // Required - top-level
type: "agent-agent",
participants: {
memorySpaceIds: ["finance-space", "hr-space"], // Required for agent-agent
},
metadata: {
createdBy: "finance-agent",
purpose: "budget-discussion",
},
});

console.log(a2aConvo.conversationId); // 'a2a-conv-789'

Errors:

  • CortexError('INVALID_TYPE') - Type is not 'user-agent' or 'agent-agent'
  • CortexError('INVALID_PARTICIPANTS') - Participants are malformed
  • CortexError('AGENT_NOT_FOUND') - Agent ID doesn't exist (if registry enabled)
  • CortexError('CONVEX_ERROR') - Database error

See Also:


get()​

Retrieve a conversation by ID.

Signature:

cortex.conversations.get(
conversationId: string,
options?: GetOptions
): Promise<Conversation | null>

Parameters:

interface GetOptions {
includeMessages?: boolean; // Include message array (default: true)
messageLimit?: number; // Limit messages returned (default: all)
}

Returns:

  • Conversation - Complete conversation with messages
  • null - If conversation doesn't exist

Example:

const conversation = await cortex.conversations.get("conv-123");

if (conversation) {
console.log(`Conversation with ${conversation.participants.userId}`);
console.log(`${conversation.messageCount} total messages`);
console.log(`Last message: ${conversation.lastMessageAt}`);

// Access messages
conversation.messages.forEach((msg) => {
console.log(`${msg.role}: ${msg.content}`);
});
}

Errors:

  • CortexError('INVALID_CONVERSATION_ID') - Conversation ID is invalid

addMessage()​

Add a message to an existing conversation (append-only).

Signature:

cortex.conversations.addMessage(
input: AddMessageInput
): Promise<Conversation>
Info

v0.29.0+: Graph sync is now automatic when CORTEX_GRAPH_SYNC=true is set in your environment. The syncToGraph option has been removed from all APIs.

Parameters:

interface AddMessageInput {
conversationId: string;
message: {
id?: string; // Auto-generated if not provided
role: "user" | "agent" | "system";
content: string;
participantId?: string; // Hive Mode/Collaborative: which participant sent this
metadata?: Record<string, any>;
};
}

Returns:

Returns the updated Conversation object with the new message appended:

interface Conversation {
conversationId: string;
messages: Message[]; // Now includes the new message
messageCount: number; // Incremented
updatedAt: number; // Updated timestamp
// ... other fields
}

interface Message {
id: string;
role: "user" | "agent" | "system";
content: string;
timestamp: number;
participantId?: string;
metadata?: Record<string, unknown>;

// Collaborative conversations (v0.35.0+)
approvalStatus?: 'pending' | 'approved' | 'rejected';
approvedBy?: string; // userId who approved/rejected
approvedAt?: number; // Timestamp of approval
}
Info

For agent-to-agent (A2A) messaging, use the dedicated cortex.a2a.* API which handles both ACID storage and Vector memory automatically.

Side Effects:

  • Appends message to conversation (immutable)
  • Updates conversation updatedAt and lastMessageAt
  • Increments messageCount

Example: Adding messages to a conversation

// User message
const updatedConvo = await cortex.conversations.addMessage({
conversationId: "conv-123",
message: {
role: "user",
content: "What is my account balance?",
},
});

// Get the message ID from the last message
const userMsgId = updatedConvo.messages[updatedConvo.messages.length - 1].id;
console.log(userMsgId); // 'msg-001' - use in conversationRef!

// Agent response
const withResponse = await cortex.conversations.addMessage({
conversationId: "conv-123",
message: {
role: "agent",
content: "Your account balance is $1,234.56",
participantId: "support-agent", // Hive Mode tracking
},
});

const agentMsgId = withResponse.messages[withResponse.messages.length - 1].id;
console.log(agentMsgId); // 'msg-002'

// Now create Vector memory referencing these ACID messages
await cortex.vector.store("support-agent", {
content: "User asked about balance, replied with $1,234.56",
contentType: "summarized",
userId: "user-123",
source: { type: "conversation", userId: "user-123", timestamp: new Date() },
conversationRef: {
conversationId: "conv-123",
messageIds: [userMsgId, agentMsgId], // ← Links to ACID!
},
metadata: { importance: 70, tags: ["balance", "query"] },
});
Tip

For A2A messaging, use cortex.a2a.send() which handles ACID storage and Vector memory automatically.

Errors:

  • CortexError('CONVERSATION_NOT_FOUND') - Conversation doesn't exist
  • CortexError('INVALID_MESSAGE') - Message is malformed
  • CortexError('CONVEX_ERROR') - Database error

See Also:


getHistory()​

Get message thread from a conversation with pagination and filtering.

Signature:

cortex.conversations.getHistory(
conversationId: string,
options?: GetHistoryOptions
): Promise<HistoryResult>

Parameters:

interface GetHistoryOptions {
limit?: number; // Max messages (default: 50)
offset?: number; // Skip N messages (default: 0)
sortOrder?: "asc" | "desc"; // Chronological order (default: 'asc')
since?: number; // Messages after timestamp (milliseconds)
until?: number; // Messages before timestamp (milliseconds)
roles?: ("user" | "agent" | "system")[]; // Filter by role
}

Returns:

interface HistoryResult {
messages: Message[]; // Filtered and paginated messages
total: number; // Total count after filtering
hasMore: boolean; // More messages available
conversationId: string;
}

Example:

// Get last 50 messages
const recent = await cortex.conversations.getHistory("conv-123", {
limit: 50,
sortOrder: "desc", // Most recent first
});

// Get messages from specific date range (timestamps in milliseconds)
const october = await cortex.conversations.getHistory("conv-123", {
since: new Date("2025-10-01").getTime(),
until: new Date("2025-10-31").getTime(),
sortOrder: "asc", // Chronological
});

// Get only user messages
const userMessages = await cortex.conversations.getHistory("conv-123", {
roles: ["user"],
limit: 20,
});

// Display conversation - result is an object with messages array
recent.messages.forEach((msg) => {
console.log(`${msg.role}: ${msg.content}`);
});

console.log(`Total: ${recent.total}, Has more: ${recent.hasMore}`);

Errors:

  • CortexError('CONVERSATION_NOT_FOUND') - Conversation doesn't exist
  • CortexError('INVALID_OPTIONS') - Options are malformed

list()​

List conversations with filters, sorting, and pagination.

Signature:

cortex.conversations.list(
filter?: ListConversationsFilter
): Promise<ListConversationsResult>

Parameters:

interface ListConversationsFilter {
// Type filter
type?: "user-agent" | "agent-agent";

// Participant filters
userId?: string;
memorySpaceId?: string;
tenantId?: string; // Multi-tenancy: SaaS platform isolation
participantId?: string; // Hive Mode tracking

// Date filters (timestamps in milliseconds)
createdBefore?: number;
createdAfter?: number;
updatedBefore?: number;
updatedAfter?: number;
lastMessageBefore?: number;
lastMessageAfter?: number;

// Message count filter
messageCount?: number | { min?: number; max?: number };

// Metadata filter
metadata?: Record<string, unknown>;

// Pagination
limit?: number; // Default: 50
offset?: number; // Default: 0

// Sorting
sortBy?: "createdAt" | "updatedAt" | "lastMessageAt" | "messageCount";
sortOrder?: "asc" | "desc"; // Default: "desc"

// Options
includeMessages?: boolean; // Default: true
}

Returns:

interface ListConversationsResult {
conversations: Conversation[];
total: number; // Total matching conversations
limit: number;
offset: number;
hasMore: boolean;
}

Example:

// List all user-agent conversations
const userConvos = await cortex.conversations.list({
type: "user-agent",
sortBy: "lastMessageAt",
sortOrder: "desc",
limit: 50,
});

console.log(`Found ${userConvos.total} conversations`);

// List conversations for specific user
const userHistory = await cortex.conversations.list({
userId: "user-123",
sortBy: "lastMessageAt",
sortOrder: "desc",
});

// List active conversations (recent messages)
const active = await cortex.conversations.list({
lastMessageAfter: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24h
});

// List A2A conversations in a specific memory space
const a2aConvos = await cortex.conversations.list({
type: "agent-agent",
memorySpaceId: "shared-workspace",
});

// Note: For finding A2A conversations between specific agents,
// use findConversation() with memorySpaceIds instead

Errors:

  • CortexError('INVALID_FILTERS') - Filters are malformed
  • CortexError('INVALID_PAGINATION') - Invalid limit/offset

Search conversations by content with filters and search options.

Signature:

cortex.conversations.search(
input: SearchConversationsInput
): Promise<ConversationSearchResult[]>

Parameters:

interface SearchConversationsInput {
query: string; // Search query (required)

filters?: {
type?: "user-agent" | "agent-agent";
userId?: string;
memorySpaceId?: string;
dateRange?: {
start?: number; // Timestamp in milliseconds
end?: number;
};
limit?: number; // Default: 10
};

options?: {
searchIn?: "content" | "metadata" | "both"; // Default: "content"
matchMode?: "contains" | "exact" | "fuzzy"; // Default: "contains"
};
}

Returns:

interface ConversationSearchResult {
conversation: Conversation;
matchedMessages: Message[]; // Messages that matched query
score: number; // Relevance score (0-1)
highlights: string[]; // Matched snippets for display
}

Example:

// Search for conversations about "refund"
const results = await cortex.conversations.search({
query: "refund",
filters: {
type: "user-agent",
userId: "user-123",
limit: 10,
},
});

results.forEach((result) => {
console.log(`Conversation: ${result.conversation.conversationId}`);
console.log(`Matched messages: ${result.matchedMessages.length}`);
result.matchedMessages.forEach((msg) => {
console.log(` ${msg.role}: ${msg.content}`);
});
});

// Search A2A conversations in a memory space
const a2aResults = await cortex.conversations.search({
query: "budget approval",
filters: {
type: "agent-agent",
memorySpaceId: "shared-workspace",
},
});

// Search with options
const fuzzyResults = await cortex.conversations.search({
query: "account balance",
filters: { userId: "user-123" },
options: {
searchIn: "both", // Search content and metadata
matchMode: "fuzzy",
},
});

Errors:

  • CortexError('INVALID_QUERY') - Query is empty
  • CortexError('INVALID_FILTERS') - Filters are malformed

count()​

Count conversations matching filters.

Signature:

cortex.conversations.count(
filters?: ConversationFilters
): Promise<number>

Parameters:

interface CountConversationsFilter {
type?: "user-agent" | "agent-agent";
userId?: string;
tenantId?: string; // Multi-tenancy: SaaS platform isolation
memorySpaceId?: string;
}

Example:

// Total conversations
const total = await cortex.conversations.count();

// Count for specific user
const userCount = await cortex.conversations.count({
userId: "user-123",
});

// Count conversations in a memory space
const spaceCount = await cortex.conversations.count({
memorySpaceId: "user-123-personal",
});

// Count A2A conversations
const a2aCount = await cortex.conversations.count({
type: "agent-agent",
});

export()​

Export conversations to JSON or CSV.

Signature:

cortex.conversations.export(
options: ExportConversationsOptions
): Promise<ExportResult>

Parameters:

interface ExportConversationsOptions {
filters?: {
userId?: string;
participantId?: string; // Hive Mode filter
memorySpaceId?: string;
conversationIds?: string[];
type?: "user-agent" | "agent-agent";
dateRange?: {
start?: number; // Timestamp in milliseconds
end?: number;
};
};
format: "json" | "csv";
includeMetadata?: boolean; // Default: true
}

interface ExportResult {
format: "json" | "csv";
data: string; // The exported data as string
count: number; // Number of conversations exported
exportedAt: number; // Timestamp
}
Note

The following features are planned but not yet implemented:

  • outputPath - Direct file writing (currently returns data string)
  • includeFullMessages - Message content filtering (currently includes all)

Example:

// Export all conversations for a user (GDPR)
const userData = await cortex.conversations.export({
filters: {
userId: "user-123",
},
format: "json",
includeMetadata: true,
});

console.log(`Exported ${userData.count} conversations`);
// userData.data contains the JSON string

// Export conversations from date range
const octoberData = await cortex.conversations.export({
filters: {
dateRange: {
start: new Date("2025-10-01").getTime(),
end: new Date("2025-10-31").getTime(),
},
},
format: "csv",
});

Errors:

  • ConversationValidationError('INVALID_FORMAT') - Format not 'json' or 'csv'

delete()​

Delete a conversation and all its messages.

Warning

This is permanent! Consider using Vector memory deletion instead to preserve ACID audit trail.

Signature:

cortex.conversations.delete(
conversationId: string
): Promise<ConversationDeletionResult>
Info

v0.29.0+: Graph sync is now automatic when CORTEX_GRAPH_SYNC=true is set in your environment. The syncToGraph option has been removed from all APIs.

Returns:

interface ConversationDeletionResult {
deleted: boolean;
conversationId: string;
messagesDeleted: number;
deletedAt: number; // Timestamp in milliseconds
restorable: boolean; // Always false (permanent)
}

Example:

const result = await cortex.conversations.delete("conv-123");

console.log(`Deleted conversation with ${result.messagesDeleted} messages`);
console.log(`Restorable: ${result.restorable}`); // false - permanent!

// WARNING: Vector memories with conversationRef to this conversation
// will have broken references! Usually better to delete Vector only.

Errors:

  • CortexError('CONVERSATION_NOT_FOUND') - Conversation doesn't exist
  • CortexError('DELETION_FAILED') - Delete operation failed

deleteMany()​

Bulk delete conversations matching filters with safety features.

Signature:

cortex.conversations.deleteMany(
filter: {
userId?: string;
memorySpaceId?: string;
type?: "user-agent" | "agent-agent";
},
options?: DeleteManyConversationsOptions
): Promise<DeleteManyConversationsResult>

Parameters:

interface DeleteManyConversationsOptions {
dryRun?: boolean; // Preview what would be deleted
confirmationThreshold?: number; // Safety threshold (default: 10)
}

interface DeleteManyConversationsResult {
deleted: number; // 0 if dryRun
conversationIds: string[];
totalMessagesDeleted: number;
wouldDelete?: number; // Set when dryRun=true
dryRun?: boolean;
}
Info

At least one filter (userId, memorySpaceId, or type) is required. If deletion would exceed the confirmationThreshold, an error is thrown unless using dryRun.

Example:

// Preview deletion (dryRun)
const preview = await cortex.conversations.deleteMany(
{
userId: "user-123",
memorySpaceId: "user-123-personal",
},
{
dryRun: true,
},
);

console.log(`Would delete ${preview.wouldDelete} conversations`);

// Execute deletion
if (preview.wouldDelete && preview.wouldDelete < 100) {
const result = await cortex.conversations.deleteMany({
userId: "user-123",
memorySpaceId: "user-123-personal",
});

console.log(`Deleted ${result.deleted} conversations`);
console.log(`Total messages deleted: ${result.totalMessagesDeleted}`);
}

// Delete all A2A conversations in a memory space
const a2aResult = await cortex.conversations.deleteMany(
{ type: "agent-agent", memorySpaceId: "shared-workspace" },
{ confirmationThreshold: 50 }, // Allow up to 50 without error
);

Advanced Operations​

getMessage()​

Get a specific message by ID.

Signature:

cortex.conversations.getMessage(
conversationId: string,
messageId: string
): Promise<Message | null>

Example:

const msg = await cortex.conversations.getMessage("conv-123", "msg-001");

console.log(msg.content);
console.log(msg.timestamp);

getMessagesByIds()​

Get multiple messages by their IDs (batch retrieval).

Signature:

cortex.conversations.getMessagesByIds(
conversationId: string,
messageIds: string[]
): Promise<Message[]>

Example:

// Get specific messages referenced by Vector memory
const memory = await cortex.memory.get("agent-1", "mem-123");

if (memory.conversationRef) {
const sourceMessages = await cortex.conversations.getMessagesByIds(
memory.conversationRef.conversationId,
memory.conversationRef.messageIds,
);

console.log("Original messages that informed this memory:");
sourceMessages.forEach((msg) => console.log(msg.content));
}

findConversation()​

Find an existing conversation by type and participants.

Signature:

cortex.conversations.findConversation(
params: FindConversationParams
): Promise<Conversation | null>

Parameters:

interface FindConversationParams {
memorySpaceId: string; // Required
type: "user-agent" | "agent-agent";
userId?: string; // Required for user-agent
memorySpaceIds?: string[]; // Required for agent-agent (min 2)
}

Example:

// Find existing user-agent conversation
const existing = await cortex.conversations.findConversation({
memorySpaceId: 'support-bot-space',
type: 'user-agent',
userId: 'user-123',
});

if (existing) {
// Reuse existing conversation
await cortex.conversations.addMessage({
conversationId: existing.conversationId,
message: { role: 'user', content: 'Hello again!' }
});
} else {
// Create new
const newConvo = await cortex.conversations.create({ ... });
}

// Find A2A conversation (Collaboration Mode)
const a2a = await cortex.conversations.findConversation({
memorySpaceId: 'shared-workspace',
type: 'agent-agent',
memorySpaceIds: ['finance-space', 'hr-space'],
});

getOrCreate()​

Get existing conversation or create if doesn't exist.

Signature:

cortex.conversations.getOrCreate(
params: ConversationInput
): Promise<Conversation>

Example:

// Always get a conversation (creates if needed)
const conversation = await cortex.conversations.getOrCreate({
memorySpaceId: "support-bot-space",
type: "user-agent",
participants: {
userId: "user-123",
agentId: "support-agent",
},
});

// Use immediately
await cortex.conversations.addMessage({
conversationId: conversation.conversationId,
message: {
role: "user",
content: "Hello!",
},
});

Shareable Chats​

Shareable Chats enable conversation sharing, visibility control, snapshots, and collaborative multi-user conversations.

Visibility​

Control who can access conversations with visibility levels.

Visibility Levels​

LevelDescriptionAccess
`private`Owner only (default)Only the conversation owner
`space`Memory space membersAnyone with access to the memory space
`public`AnyoneAccessible to all users

create() with visibility​

const conversation = await cortex.conversations.create({
memorySpaceId: 'support-space',
type: 'user-agent',
participants: {
userId: 'user-123',
agentId: 'support-agent',
},
visibility: 'space', // 'private' | 'space' | 'public'
});

console.log(conversation.visibility); // 'space'

checkAccess()​

Check if a user has access to a conversation.

Signature:

cortex.conversations.checkAccess(
input: CheckAccessInput
): Promise<CheckAccessResult>

Parameters:

interface CheckAccessInput {
conversationId: string;
userId: string;
memorySpaceId?: string; // For space visibility checks
}

Returns:

interface CheckAccessResult {
canView: boolean;
canEdit: boolean;
reason: 'OWNER' | 'PUBLIC_VISIBILITY' | 'SPACE_MEMBER' |
'PRIVATE_VISIBILITY' | 'NOT_IN_MEMORY_SPACE' | 'CONVERSATION_NOT_FOUND';
visibility: 'private' | 'space' | 'public' | null;
}

Example:

const access = await cortex.conversations.checkAccess({
conversationId: 'conv-123',
userId: 'user-456',
memorySpaceId: 'shared-space',
});

if (access.canView) {
console.log(`Access granted: ${access.reason}`);
} else {
console.log(`Access denied: ${access.reason}`);
}

setVisibility()​

Change a conversation's visibility level.

Signature:

cortex.conversations.setVisibility(
input: SetVisibilityInput
): Promise<Conversation>

Example:

// Make a private conversation public
const updated = await cortex.conversations.setVisibility({
conversationId: 'conv-123',
visibility: 'public',
});

console.log(updated.visibility); // 'public'

Sharing Grants​

Create granular sharing permissions with time limits, view limits, and redaction options.

share()​

Create a sharing grant for a conversation.

Signature:

cortex.conversations.share(
input: CreateShareInput
): Promise<CreateShareResult>

Parameters:

interface CreateShareInput {
conversationId: string;
grantType: 'user' | 'space' | 'link' | 'domain';
grantedTo?: string; // Required for 'user', 'space', 'domain'
permissions?: {
canView?: boolean; // Default: true
canViewFacts?: boolean; // Default: false
canViewMemories?: boolean;
canContinue?: boolean; // Continue the conversation
canFork?: boolean; // Create a copy
canExport?: boolean; // Export data
};
expiresAt?: number; // Timestamp in milliseconds
maxViews?: number; // Maximum view count
redactBefore?: number; // Hide messages before timestamp
redactSensitive?: boolean;
}

Returns:

interface CreateShareResult {
shareId: string;
expiresAt?: number;
share: ConversationShare;
}

Examples:

// Create a link share (anyone with link can view)
const linkShare = await cortex.conversations.share({
conversationId: 'conv-123',
grantType: 'link',
permissions: { canView: true, canFork: true },
expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
});

console.log(linkShare.shareId); // 'share-abc123'

// Share with specific user
const userShare = await cortex.conversations.share({
conversationId: 'conv-123',
grantType: 'user',
grantedTo: 'user-456',
permissions: { canView: true, canContinue: true },
});

// Share with view limit
const limitedShare = await cortex.conversations.share({
conversationId: 'conv-123',
grantType: 'link',
maxViews: 10, // Expires after 10 views
});

revokeShare()​

Revoke a sharing grant.

const result = await cortex.conversations.revokeShare('share-abc123');

console.log(result.revoked); // true
console.log(result.share.status); // 'revoked'

listShares()​

List all sharing grants for a conversation.

// List all shares
const shares = await cortex.conversations.listShares('conv-123');

// Filter by status
const activeShares = await cortex.conversations.listShares('conv-123', {
status: 'active',
});

shares.forEach(share => {
console.log(`${share.shareId}: ${share.grantType} - ${share.status}`);
});

getShare()​

Get a share by ID with validity check.

const share = await cortex.conversations.getShare('share-abc123');

if (share?.isValid) {
console.log('Share is valid');
console.log(`Views: ${share.viewCount}/${share.maxViews || '∞'}`);
}

checkShareAccess()​

Check if a user has access via sharing grants.

const access = await cortex.conversations.checkShareAccess({
conversationId: 'conv-123',
userId: 'user-456',
});

if (access.hasAccess) {
console.log('Access via share:', access.shareId);
console.log('Permissions:', access.permissions);
} else {
console.log('No access:', access.reason);
}

URL Utilities​

Build and parse shareable URLs.

import { buildShareUrl, extractShareId } from '@cortexmemory/sdk';

// Build URL
const url = buildShareUrl('share-abc123', {
baseUrl: 'https://myapp.com/shared',
});
// → 'https://myapp.com/shared/share-abc123'

// Query-style URL
const queryUrl = buildShareUrl('share-abc123', {
baseUrl: 'https://myapp.com/view',
style: 'query',
paramName: 'id',
});
// → 'https://myapp.com/view?id=share-abc123'

// Extract share ID from URL
const shareId = extractShareId('https://myapp.com/shared/share-abc123');
// → 'share-abc123'

Conversation Snapshots​

Create immutable snapshots of conversations with optional PII redaction.

snapshot()​

Create a snapshot of a conversation.

Signature:

cortex.conversations.snapshot(
input: CreateSnapshotInput
): Promise<CreateSnapshotResult>

Parameters:

interface CreateSnapshotInput {
conversationId: string;
redactPII?: boolean; // Redact emails, phones, SSN, etc.
redactBefore?: number; // Exclude messages before timestamp
customRedactions?: Array<{
pattern: string; // Regex or string to match
replacement: string; // Replacement text
}>;
}

Examples:

// Basic snapshot
const result = await cortex.conversations.snapshot({
conversationId: 'conv-123',
});

console.log(result.snapshotId); // 'snap-abc123'
console.log(result.snapshot.messageCount); // 15

// Snapshot with PII redaction
const redacted = await cortex.conversations.snapshot({
conversationId: 'conv-123',
redactPII: true, // Redacts emails, phone numbers, SSN, IPs, etc.
});

// Message: "Contact me at john@example.com"
// Becomes: "Contact me at [EMAIL]"

// Custom redactions
const custom = await cortex.conversations.snapshot({
conversationId: 'conv-123',
redactPII: true,
customRedactions: [
{ pattern: 'ACME Corp', replacement: '[COMPANY]' },
{ pattern: 'Project Alpha', replacement: '[PROJECT]' },
],
});
PII Redaction

When redactPII: true, the following patterns are automatically redacted:

  • Email addresses → [EMAIL]
  • Phone numbers → [PHONE]
  • Social Security Numbers → [SSN]
  • Credit card numbers → [CREDIT_CARD]
  • IP addresses → [IP_ADDRESS]

getSnapshot()​

Retrieve a snapshot by ID.

const snapshot = await cortex.conversations.getSnapshot('snap-abc123');

if (snapshot) {
console.log(`Messages: ${snapshot.messageCount}`);
console.log(`Created: ${new Date(snapshot.createdAt)}`);
console.log(`PII Redacted: ${snapshot.redaction.piiRedacted}`);
}

listSnapshots()​

List snapshots for a conversation.

const snapshots = await cortex.conversations.listSnapshots('conv-123');

snapshots.forEach(snap => {
console.log(`${snap.snapshotId}: ${snap.messageCount} messages`);
});

deleteSnapshot()​

Delete a snapshot.

const result = await cortex.conversations.deleteSnapshot('snap-abc123');
console.log(result.deleted); // true

Collaborative Conversations​

Enable multi-user conversations with message approval workflows.

Creating Collaborative Conversations​

// Multiple participants
const collab = await cortex.conversations.create({
memorySpaceId: 'team-space',
type: 'user-agent',
participants: {
userIds: ['user-1', 'user-2', 'user-3'], // Multiple users
agentId: 'team-assistant',
},
collaborativeSettings: {
requireApproval: true, // Messages from non-owners need approval
ownerUserId: 'user-1', // The owner who can approve
approvedParticipants: ['user-2'], // Pre-approved participants
},
});

Message Approval Workflow​

When requireApproval: true, messages from non-owner, non-approved participants are marked as pending:

// Owner's message - auto-approved
await cortex.conversations.addMessage({
conversationId: collab.conversationId,
message: {
role: 'user',
content: 'Team update',
participantId: 'user-1', // Owner
},
});
// → approvalStatus: 'approved'

// Non-approved participant's message - pending
await cortex.conversations.addMessage({
conversationId: collab.conversationId,
message: {
role: 'user',
content: 'My input',
participantId: 'user-3', // Not in approvedParticipants
},
});
// → approvalStatus: 'pending'

approveMessage()​

Approve a pending message.

const approved = await cortex.conversations.approveMessage({
conversationId: collab.conversationId,
messageId: 'msg-456',
});

const msg = approved.messages.find(m => m.id === 'msg-456');
console.log(msg.approvalStatus); // 'approved'
console.log(msg.approvedBy); // 'user-1'
console.log(msg.approvedAt); // timestamp

rejectMessage()​

Reject a pending message.

const rejected = await cortex.conversations.rejectMessage({
conversationId: collab.conversationId,
messageId: 'msg-789',
});

const msg = rejected.messages.find(m => m.id === 'msg-789');
console.log(msg.approvalStatus); // 'rejected'
Backward Compatibility

Conversations without collaborativeSettings work exactly as before. The approval workflow only activates when collaborativeSettings.requireApproval is true.


GDPR and Deletion​

Conversations Support userId​

Conversations with userId are targets for GDPR cascade deletion:

// Create user conversation (GDPR-enabled)
const convo = await cortex.conversations.create({
memorySpaceId: "user-123-personal",
type: "user-agent",
participants: {
userId: "user-123", // ← GDPR link
agentId: "support-agent",
},
participantId: "support-agent", // Hive Mode (top-level)
});

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

console.log(`Conversations deleted: ${result.conversationsDeleted}`);
console.log(`Messages deleted: ${result.conversationMessagesDeleted}`);

Preservation Options:

// Delete Vector memories but preserve ACID audit trail
await cortex.users.delete("user-123", {
cascade: true,
deleteFromConversations: false, // ← Preserve Layer 1a
deleteFromVector: true, // Delete Layer 2
});

// Conversations remain for compliance/audit
const preserved = await cortex.conversations.list({ userId: "user-123" });
console.log(`${preserved.total} conversations preserved for audit`);

Best Practices​

1. Create Conversations Early​

// Good: Create at start of session
const conversation = await cortex.conversations.getOrCreate({
memorySpaceId: "user-123-personal",
type: "user-agent",
participants: { userId: "user-123", agentId: "support-agent" },
});

// Then use throughout session
await cortex.conversations.addMessage({
conversationId: conversation.conversationId,
message: { role: "user", content: "Hello!" },
});
// Always link Vector memories to their ACID source
const updatedConvo = await cortex.conversations.addMessage({
conversationId: 'conv-123',
message: {
role: 'user',
content: 'The password is Blue',
},
});

// Get the message ID from the last message
const msg = updatedConvo.messages[updatedConvo.messages.length - 1];

await cortex.vector.store('agent-1', {
content: 'The password is Blue',
conversationRef: {
conversationId: 'conv-123',
messageIds: [msg.id], // ← Critical for audit trail!
},
...
});

// Or use cortex.memory.remember() which does this automatically

3. Preserve for Audit​

// Don't delete ACID conversations unless legally required
// Instead, delete Vector memories (searchable index)

// Good: Delete Vector, keep ACID
await cortex.memory.delete("agent-1", "mem-123");
// Vector deleted, ACID preserved

// Caution: Delete ACID (breaks audit trail)
await cortex.conversations.delete("conv-123");
// Permanent, unrestorable - use with care

4. Use Pagination for Large Conversations​

// Good: Paginate large conversations
const page1 = await cortex.conversations.getHistory("conv-123", {
limit: 50,
offset: 0,
sortOrder: "desc",
});

const page2 = await cortex.conversations.getHistory("conv-123", {
limit: 50,
offset: 50,
sortOrder: "desc",
});

// Avoid: Don't load huge conversations at once
const all = await cortex.conversations.get("conv-with-10k-messages");
// Could be slow!

5. Metadata for Organization​

// Add useful metadata
await cortex.conversations.create({
memorySpaceId: "user-123-personal",
type: "user-agent",
participants: { userId: "user-123", agentId: "support-agent" },
metadata: {
channel: "web-chat",
source: "website",
campaign: "q4-promotion",
tags: ["support", "billing"],
},
});

// Query by metadata
const campaignConvos = await cortex.conversations.list({
metadata: { campaign: "q4-promotion" },
});

Integration Patterns​

Pattern 1: With cortex.memory.remember()​

// Layer 3 handles ACID + Vector automatically
const result = await cortex.memory.remember({
participantId: "support-agent", // Hive Mode
conversationId: "conv-123",
userMessage: "I need help",
agentResponse: "How can I assist you?",
userId: "user-123",
userName: "Alex",
});

// Behind the scenes:
// 1. Adds 2 messages to ACID conversation
// 2. Creates 2 Vector memories with conversationRef
console.log("ACID messages:", result.conversation.messageIds);
console.log(
"Vector memories:",
result.memories.map((m) => m.id),
);

Pattern 2: Manual Layer 1 + Layer 2​

// Step 1: Store in ACID (Layer 1a)
const userConvo = await cortex.conversations.addMessage({
conversationId: "conv-123",
message: {
role: "user",
content: "What is my balance?",
},
});
const userMsgId = userConvo.messages[userConvo.messages.length - 1].id;

const agentConvo = await cortex.conversations.addMessage({
conversationId: "conv-123",
message: {
role: "agent",
content: "$1,234.56",
participantId: "support-agent", // Hive Mode
},
});
const agentMsgId = agentConvo.messages[agentConvo.messages.length - 1].id;

// Step 2: Index in Vector (Layer 2)
await cortex.vector.store("user-123-personal", {
content: "User asked about balance: $1,234.56",
contentType: "summarized",
source: { type: "conversation" },
conversationRef: {
conversationId: "conv-123",
messageIds: [userMsgId, agentMsgId],
},
metadata: { importance: 70, tags: [] },
});

Pattern 3: A2A with cortex.a2a helpers​

// A2A helpers manage ACID conversations automatically
await cortex.a2a.send({
from: "finance-agent",
to: "hr-agent",
message: "What is the budget?",
});

// Behind the scenes:
// 1. Creates/finds agent-agent conversation in ACID
// 2. Adds message to ACID
// 3. Stores in both agents' Vector memories with conversationRef

Graph-Lite Capabilities​

Conversations serve as central graph nodes connecting users, agents, contexts, and memories:

Conversation as Graph Hub:

  • Connects user to agent (via participants)
  • Referenced by memories (via conversationRef)
  • Referenced by facts (via conversationRef)
  • Referenced by contexts (via conversationRef)

Graph Patterns:

// User → Conversations (1-hop)
const convos = await cortex.conversations.list({ userId: 'user-123' });

// Conversation → Contexts (reverse lookup)
const contexts = await cortex.contexts.search({
'conversationRef.conversationId': 'conv-456'
});

// Conversation → Memories (reverse lookup)
const memories = await cortex.memory.search('agent-1', '*', {
'conversationRef.conversationId': 'conv-456'
});

// Complete graph from conversation
{
conversation: 'conv-456',
user: 'user-123',
agent: 'agent-1',
triggeredWorkflows: contexts,
generatedMemories: memories
}

Performance: Conversations are efficiently indexed by userId and memorySpaceId. Finding related entities within a memory space typically takes 20-80ms.

Learn more: Graph Capabilities


Error Reference​

SDK Validation Errors (ConversationValidationError)​

Error CodeDescriptionCause
`MISSING_REQUIRED_FIELD`Required field missingField like conversationId is empty
`INVALID_TYPE`Type is invalidNot 'user-agent' or 'agent-agent'
`INVALID_ROLE`Message role invalidNot 'user', 'agent', or 'system'
`INVALID_ID_FORMAT`ID format invalidContains invalid chars or too long
`INVALID_FORMAT`Export format invalidNot 'json' or 'csv'
`INVALID_SORT_ORDER`Sort order invalidNot 'asc' or 'desc'
`EMPTY_STRING`String is emptySearch query is empty
`INVALID_RANGE`Numeric value out of rangelimit < 1 or > 1000, offset < 0
`EMPTY_ARRAY`Array is emptyRequired array has no elements
`INVALID_ARRAY_LENGTH`Array length wrongmemorySpaceIds has < 2 elements
`INVALID_DATE_RANGE`Date range invalidStart date >= end date
`INVALID_PARTICIPANTS`Participants malformedMissing required participant fields
`DUPLICATE_VALUES`Array has duplicatesmemorySpaceIds contains duplicates

Backend Errors (Convex)​

Error CodeDescriptionCause
`CONVERSATION_NOT_FOUND`Conversation doesn't existInvalid conversationId
`CONVERSATION_ALREADY_EXISTS`Conversation already existsDuplicate conversationId
`DELETE_MANY_THRESHOLD_EXCEEDED`Too many to deleteUse dryRun or increase threshold

Next Steps​


Questions? Ask in GitHub Discussions.