AuthContext is SDK request metadata, not a verified Convex identity or an authorization policy. Tenant propagation varies by operation. Validate the authenticated identity and tenant on your trusted server, authorize every read and mutation there, and keep administrative APIs behind that boundary. Do not expose a privileged Convex deployment directly to untrusted clients or rely on IDs and client-side filters as access controls.
Isolation Boundaries
Understanding Cortex's multi-layered isolation model for security, privacy, and compliance.
Overview
Cortex provides four layers of isolation that work together to ensure data security, enable multi-tenancy, and maintain privacy boundaries:
tenantId • Multi-tenant SaaS separation • Auto-injected via AuthContext
memorySpaceId • Fundamental data boundary • Hive or Collaboration Mode
userId • GDPR compliance • Enables cascade deletion
participantId • Hive Mode attribution • Audit trail
Each layer serves a distinct purpose and can be used independently or combined.
1. Tenant Isolation (Multi-Tenant SaaS)
Tenant metadata helps scope SaaS data. Complete isolation requires authorization in the application backend.
const cortex = new Cortex({
convexUrl: process.env.CONVEX_URL!,
auth: createAuthContext({
tenantId: "customer-acme", // Tenant metadata; server authorization is still required
userId: "user-123",
}),
});
// Supported APIs use tenantId as a default scope
await cortex.memory.remember({
memorySpaceId: "user-123-personal",
// tenantId: "customer-acme" ← Auto-injected!
userMessage: "Hello",
agentResponse: "Hi!",
userId: "user-123",
userName: "Alice",
});
- Tenant A cannot query Tenant B's data
- Tenant A cannot access Tenant B's memory spaces
- GDPR deletion respects tenant boundaries
- Complete isolation at database level
2. Memory Space Isolation
Core data isolation boundary for organizing memories, conversations, and facts.
| Hive Mode | Collaboration Mode | |
|---|---|---|
| Spaces | One shared space | Separate spaces per agent |
| Data Access | All participants see all data | Agents only see their own |
| Cross-Access | Automatic | Via A2A or Context Chains |
| Use Case | Personal AI tools | Enterprise workflows |
All AI tools share user's personal space:
const memorySpaceId = "user-123-personal";
// Cursor stores
await cortex.memory.remember({
memorySpaceId,
participantId: "cursor",
userMessage: "I prefer TypeScript",
agentResponse: "Noted!",
userId: "user-123",
userName: "Alice",
});
// Claude reads same space — sees Cursor's memories
const memories = await cortex.memory.search(memorySpaceId, "preferences");
Each agent has separate space:
// Finance agent stores in its own space
await cortex.memory.remember({
memorySpaceId: "finance-agent-space",
userMessage: "Approve budget",
agentResponse: "Approved",
userId: "user-123",
userName: "CFO",
});
// Communication via A2A (dual-write to both spaces)
await cortex.a2a.send({
from: "finance-agent",
to: "hr-agent",
message: "Budget approved",
});
3. User Data Isolation (GDPR)
Link data to users via userId to enable cascade deletion across ALL layers with a single API call.
// Link data to user
await cortex.memory.remember({
memorySpaceId: "support-bot-space",
userId: "user-123", // ← GDPR link
userMessage: "My account issue",
agentResponse: "Let me help",
userName: "Alice",
});
// One API call deletes from ALL layers
const result = await cortex.users.delete("user-123", {
cascade: true,
verify: true,
});
Deleted from: Conversations • Memories • Facts • Sessions • User Profile
4. Participant Tracking
participantId tracks which tool stored data. It does NOT isolate data — all participants can still see all memories in the space.
// Track which tool stored what
await cortex.memory.remember({
memorySpaceId: "shared-space",
participantId: "cursor", // Attribution
userMessage: "User message",
agentResponse: "Response",
userId: "user-123",
userName: "Alice",
});
// Filter by participant for debugging
const cursorMemories = await cortex.memory.list({
memorySpaceId: "shared-space",
participantId: "cursor",
});
Isolation Matrix
How each data type is scoped by isolation layer:
| Data Type | tenantId | memorySpaceId | userId | participantId |
|---|---|---|---|---|
| Conversations | ✅ | ✅ | Optional | Optional |
| Vector Memories | ✅ | ✅ | Optional | Optional |
| Facts | ✅ | ✅ | Optional | Optional |
| User Profiles | ✅ | Shared | N/A | N/A |
| Immutable Store | ✅ | Shared | Optional | N/A |
| Mutable Store | ✅ | Shared | Optional | N/A |
Best Practices
const cortex = new Cortex({
auth: createAuthContext({
tenantId: req.tenant.id, // REQUIRED for multi-tenant
userId: req.user.id,
}),
});
await cortex.memory.remember({
memorySpaceId: "bot-space",
userId: "user-123", // Required for cascade deletion
// ...
});
await cortex.memory.remember({
memorySpaceId: "shared-space",
participantId: "my-bot", // Track attribution
// ...
});