@cortexmemory/sdk
Version:
AI agent memory SDK built on Convex - ACID storage, vector search, and conversation management
507 lines (436 loc) • 19.6 kB
text/typescript
/**
* Cortex SDK - Convex Schema
*
* Layer 1: ACID Stores
* - conversations (Layer 1a) - Immutable conversation history (memorySpace-scoped)
* - immutable (Layer 1b) - Versioned immutable data (TRULY shared, NO memorySpace)
* - mutable (Layer 1c) - Live operational data (TRULY shared, NO memorySpace)
*
* Layer 2: Vector Index
* - memories - Searchable knowledge with embeddings (memorySpace-scoped)
*
* Layer 3: Facts Store
* - facts - LLM-extracted facts (memorySpace-scoped, versioned)
*
* Layer 4: Convenience APIs (SDK only, no schema)
*
* Coordination:
* - contexts - Hierarchical context chains (memorySpace-scoped, cross-space support)
* - memorySpaces - Memory space registry (Hive/Collaboration modes)
* - agents - DEPRECATED: Use memorySpaces instead
*/
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Layer 1a: Conversations (ACID, Immutable)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
conversations: defineTable({
// Identity
conversationId: v.string(), // Unique ID (e.g., "conv-abc123")
// Memory Space (NEW - fundamental isolation boundary)
memorySpaceId: v.string(), // Which memory space owns this conversation
participantId: v.optional(v.string()), // Hive Mode: which participant created this
// Type: user-agent (user ↔ participant) or agent-agent (space ↔ space)
type: v.union(v.literal("user-agent"), v.literal("agent-agent")),
// Participants (based on type)
participants: v.object({
// user-agent conversations
userId: v.optional(v.string()),
participantId: v.optional(v.string()), // Hive Mode tracking
// agent-agent conversations (Collaboration Mode - cross-space)
memorySpaceIds: v.optional(v.array(v.string())), // Both spaces involved
}),
// Messages (append-only, immutable)
messages: v.array(
v.object({
id: v.string(), // Message ID
role: v.union(
v.literal("user"),
v.literal("agent"),
v.literal("system"),
),
content: v.string(),
timestamp: v.number(),
// Optional fields
participantId: v.optional(v.string()), // Which participant sent this (Hive Mode)
metadata: v.optional(v.any()), // Flexible metadata
}),
),
// Statistics
messageCount: v.number(),
// Metadata (flexible)
metadata: v.optional(v.any()),
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_conversationId", ["conversationId"]) // Unique lookup
.index("by_memorySpace", ["memorySpaceId"]) // NEW: Memory space's conversations
.index("by_type", ["type"]) // List by type
.index("by_user", ["participants.userId"]) // User's conversations
.index("by_memorySpace_user", ["memorySpaceId", "participants.userId"]) // NEW: Space + user
.index("by_created", ["createdAt"]), // Chronological ordering
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Layer 1b: Immutable Store (ACID, Versioned, Shared)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
immutable: defineTable({
// Identity (composite key: type + id)
type: v.string(), // Entity type: 'kb-article', 'policy', 'audit-log', 'feedback', 'user'
id: v.string(), // Type-specific logical ID
// Data (flexible, immutable once stored)
data: v.any(),
// GDPR support (optional)
userId: v.optional(v.string()), // Links to user for cascade deletion
// Versioning
version: v.number(), // Current version number (starts at 1)
previousVersions: v.array(
v.object({
version: v.number(),
data: v.any(),
timestamp: v.number(),
metadata: v.optional(v.any()),
}),
),
// Metadata (flexible)
metadata: v.optional(
v.object({
publishedBy: v.optional(v.string()),
tags: v.optional(v.array(v.string())),
importance: v.optional(v.number()),
}),
),
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_type_id", ["type", "id"]) // Unique lookup
.index("by_type", ["type"]) // List by type
.index("by_userId", ["userId"]) // GDPR cascade
.index("by_created", ["createdAt"]), // Chronological
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Layer 1c: Mutable Store (ACID, No Versioning, Shared)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
mutable: defineTable({
// Composite key: namespace + key
namespace: v.string(), // Logical grouping: 'inventory', 'config', 'counters', etc.
key: v.string(), // Unique key within namespace
// Value (flexible, mutable)
value: v.any(),
// GDPR support (optional)
userId: v.optional(v.string()), // Links to user for cascade deletion
// Metadata (optional)
metadata: v.optional(v.any()),
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
// Access tracking
accessCount: v.number(),
lastAccessed: v.optional(v.number()),
})
.index("by_namespace_key", ["namespace", "key"]) // Unique lookup
.index("by_namespace", ["namespace"]) // List by namespace
.index("by_userId", ["userId"]) // GDPR cascade
.index("by_updated", ["updatedAt"]), // Recent changes
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Layer 2: Vector Memory (Searchable, memorySpace-scoped, Versioned)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
memories: defineTable({
// Identity
memoryId: v.string(), // Unique ID for this memory
memorySpaceId: v.string(), // NEW: Memory space isolation (was agentId)
participantId: v.optional(v.string()), // NEW: Hive Mode participant tracking
// Content
content: v.string(),
contentType: v.union(
v.literal("raw"),
v.literal("summarized"),
v.literal("fact"), // NEW: For facts indexed in vector layer
),
embedding: v.optional(v.array(v.float64())), // Optional for keyword-only
// Source (flattened for indexing performance)
sourceType: v.union(
v.literal("conversation"),
v.literal("system"),
v.literal("tool"),
v.literal("a2a"),
v.literal("fact-extraction"), // NEW: For facts
),
sourceUserId: v.optional(v.string()),
sourceUserName: v.optional(v.string()),
sourceTimestamp: v.number(),
// GDPR support
userId: v.optional(v.string()), // For cascade deletion
// References to Layer 1 (mutually exclusive, all optional)
conversationRef: v.optional(
v.object({
conversationId: v.string(),
messageIds: v.array(v.string()),
}),
),
immutableRef: v.optional(
v.object({
type: v.string(),
id: v.string(),
version: v.optional(v.number()),
}),
),
mutableRef: v.optional(
v.object({
namespace: v.string(),
key: v.string(),
snapshotValue: v.any(),
snapshotAt: v.number(),
}),
),
// NEW: Reference to Layer 3 fact
factsRef: v.optional(
v.object({
factId: v.string(),
version: v.optional(v.number()),
}),
),
// Metadata
importance: v.number(), // 0-100 (flattened for filtering)
tags: v.array(v.string()), // Flattened for filtering
// Versioning (like immutable)
version: v.number(),
previousVersions: v.array(
v.object({
version: v.number(),
content: v.string(),
embedding: v.optional(v.array(v.float64())),
timestamp: v.number(),
}),
),
// Timestamps & Access
createdAt: v.number(),
updatedAt: v.number(),
lastAccessed: v.optional(v.number()),
accessCount: v.number(),
})
.index("by_memorySpace", ["memorySpaceId"]) // NEW: Memory space's memories
.index("by_memoryId", ["memoryId"]) // Unique lookup
.index("by_userId", ["userId"]) // GDPR cascade
.index("by_memorySpace_created", ["memorySpaceId", "createdAt"]) // NEW: Chronological
.index("by_memorySpace_userId", ["memorySpaceId", "userId"]) // NEW: Space + user
.index("by_participantId", ["participantId"]) // NEW: Hive Mode tracking
.searchIndex("by_content", {
searchField: "content",
filterFields: ["memorySpaceId", "sourceType", "userId", "participantId"], // Updated filters
})
.vectorIndex("by_embedding", {
vectorField: "embedding",
dimensions: 1536, // Default: OpenAI text-embedding-3-small
filterFields: ["memorySpaceId", "userId", "participantId"], // Updated: memorySpace isolation
}),
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Layer 3: Facts Store (NEW - memorySpace-scoped, Versioned)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
facts: defineTable({
// Identity
factId: v.string(), // Unique ID for this fact
memorySpaceId: v.string(), // Memory space isolation
participantId: v.optional(v.string()), // Hive Mode: which participant extracted this
// Fact content
fact: v.string(), // The fact statement
factType: v.union(
v.literal("preference"),
v.literal("identity"),
v.literal("knowledge"),
v.literal("relationship"),
v.literal("event"),
v.literal("custom"),
),
// Triple structure (subject-predicate-object)
subject: v.optional(v.string()), // Primary entity (e.g., "user-123")
predicate: v.optional(v.string()), // Relationship (e.g., "prefers", "works_at")
object: v.optional(v.string()), // Secondary entity (e.g., "dark mode")
// Quality & Source
confidence: v.number(), // 0-100: extraction confidence
sourceType: v.union(
v.literal("conversation"),
v.literal("system"),
v.literal("tool"),
v.literal("manual"),
v.literal("a2a"),
),
sourceRef: v.optional(
v.object({
conversationId: v.optional(v.string()),
messageIds: v.optional(v.array(v.string())),
memoryId: v.optional(v.string()),
}),
),
// Metadata & Tags
metadata: v.optional(v.any()),
tags: v.array(v.string()),
// Temporal validity
validFrom: v.optional(v.number()),
validUntil: v.optional(v.number()),
// Versioning (creates immutable chain)
version: v.number(),
supersededBy: v.optional(v.string()), // factId of newer version
supersedes: v.optional(v.string()), // factId this replaces
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_factId", ["factId"]) // Unique lookup
.index("by_memorySpace", ["memorySpaceId"]) // Memory space's facts
.index("by_memorySpace_subject", ["memorySpaceId", "subject"]) // Entity-centric queries
.index("by_participantId", ["participantId"]) // Hive Mode tracking
.searchIndex("by_content", {
searchField: "fact",
filterFields: ["memorySpaceId", "factType"],
}),
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Memory Spaces Registry (Hive/Collaboration Mode Management)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
memorySpaces: defineTable({
// Identity
memorySpaceId: v.string(), // Unique memory space ID
name: v.optional(v.string()), // Human-readable name
type: v.union(
v.literal("personal"),
v.literal("team"),
v.literal("project"),
v.literal("custom"),
),
// Participants (for Hive Mode)
participants: v.array(
v.object({
id: v.string(), // Participant ID (e.g., 'cursor', 'claude', 'my-bot')
type: v.string(), // 'ai-tool', 'human', 'ai-agent', 'system'
joinedAt: v.number(),
}),
),
// Metadata (flexible)
metadata: v.any(),
// Status
status: v.union(v.literal("active"), v.literal("archived")),
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_memorySpaceId", ["memorySpaceId"]) // Unique lookup
.index("by_status", ["status"]) // Filter active/archived
.index("by_type", ["type"]) // Filter by type
.index("by_created", ["createdAt"]), // Chronological
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Contexts (Hierarchical Coordination, memorySpace-scoped with cross-space support)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
contexts: defineTable({
// Identity
contextId: v.string(), // Unique ID
memorySpaceId: v.string(), // NEW: Which memory space owns this context
// Purpose
purpose: v.string(), // What this context is for
// Hierarchy
parentId: v.optional(v.string()), // Parent context (can be cross-space)
rootId: v.optional(v.string()), // Root context
depth: v.number(), // 0 for root, increments with depth
childIds: v.array(v.string()), // Child contexts
// Status
status: v.union(
v.literal("active"),
v.literal("completed"),
v.literal("cancelled"),
v.literal("blocked"),
),
// Source conversation (optional)
conversationRef: v.optional(
v.object({
conversationId: v.string(),
messageIds: v.optional(v.array(v.string())),
}),
),
// User association (GDPR)
userId: v.optional(v.string()),
// Participants (for tracking)
participants: v.array(v.string()), // Memory spaces or participants involved
// Cross-space access control
grantedAccess: v.optional(
v.array(
v.object({
memorySpaceId: v.string(), // Which space has access
scope: v.string(), // 'read-only', 'context-only', etc.
grantedAt: v.number(),
}),
),
),
// Data (flexible)
data: v.optional(v.any()),
// Metadata
metadata: v.optional(v.any()),
// Timestamps
createdAt: v.number(),
updatedAt: v.number(),
completedAt: v.optional(v.number()),
})
.index("by_contextId", ["contextId"]) // Unique lookup
.index("by_memorySpace", ["memorySpaceId"]) // NEW: Space's contexts
.index("by_parentId", ["parentId"]) // Child lookup
.index("by_rootId", ["rootId"]) // All contexts in tree
.index("by_status", ["status"]) // Filter by status
.index("by_memorySpace_status", ["memorySpaceId", "status"]) // NEW
.index("by_userId", ["userId"]) // GDPR cascade
.index("by_created", ["createdAt"]), // Chronological
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Agents Registry (Optional Metadata Layer)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
agents: defineTable({
// Identity
agentId: v.string(), // Unique agent identifier
// Metadata
name: v.string(), // Display name
description: v.optional(v.string()), // What this agent does
metadata: v.optional(v.any()), // Flexible metadata (team, capabilities, etc.)
// Configuration
config: v.optional(v.any()), // Agent-specific configuration
// Status
status: v.union(
v.literal("active"),
v.literal("inactive"),
v.literal("archived"),
),
// Timestamps
registeredAt: v.number(),
updatedAt: v.number(),
lastActive: v.optional(v.number()), // Last time agent created data
})
.index("by_agentId", ["agentId"]) // Unique lookup
.index("by_status", ["status"]) // Filter by status
.index("by_registered", ["registeredAt"]), // Chronological ordering
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Graph Sync Queue (Real-time Graph Database Synchronization)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
graphSyncQueue: defineTable({
// Entity identification
table: v.string(), // "memories", "facts", "contexts", "conversations", etc.
entityId: v.string(), // Cortex entity ID
// Operation type
operation: v.union(
v.literal("insert"),
v.literal("update"),
v.literal("delete"),
),
// Entity data (full object for sync)
entity: v.optional(v.any()), // Null for deletes
// Sync status
synced: v.boolean(),
syncedAt: v.optional(v.number()),
// Retry tracking
failedAttempts: v.optional(v.number()),
lastError: v.optional(v.string()),
// Priority (for ordering)
priority: v.optional(v.string()), // "high", "normal", "low"
// Timestamps
createdAt: v.number(),
})
.index("by_synced", ["synced"]) // Get unsynced items (reactive query!)
.index("by_table", ["table"]) // Filter by entity type
.index("by_table_entity", ["table", "entityId"]) // Unique lookup
.index("by_priority", ["priority", "synced"]) // Priority-based processing
.index("by_created", ["createdAt"]), // Chronological
});