Skip to main content

Mutable Store API

Info

Last Updated: 2026-01-09

Complete API reference for shared mutable data with ACID transaction guarantees.

Overview

The Mutable Store API (Layer 1c) provides methods for storing TRULY SHARED mutable data across ALL memory spaces. This layer has NO memorySpace scoping - it's globally shared just like Layer 1b (Immutable).

Critical Distinction:

  • NO memorySpaceId parameter - This layer is shared across ALL spaces
  • Accessible from any memory space
  • Perfect for: Inventory, config, counters, live shared state

Key Characteristics:

  • TRULY Shared - ALL memory spaces can access
  • NO Isolation - Not scoped to memorySpace (unlike L1a, L2, L3)
  • Mutable - Designed to be updated in-place
  • ACID - Atomic transactions
  • Current-value - No version history (by design)
  • Fast - Optimized for frequent updates
  • Purgeable - Can delete keys
  • Multi-Tenant - Optional tenantId for SaaS isolation (auto-injected from AuthContext)

Comparison to Other Stores:

FeatureConversations (1a)Immutable (1b)Mutable (1c)Vector (2)Facts (3)
ScopingmemorySpaceNO scopingNO scopingmemorySpacememorySpace
PrivacyPrivate to spaceTRULY SharedTRULY SharedPrivate to spacePrivate to space
VersioningN/A (append)AutoNoneAutoAuto
UpdatesAppend onlyNew versionIn-placeNew versionNew version
Use CaseChatsKB, policiesLive dataSearch indexExtracted facts

Core Operations

set()

Set a key to a value (creates or overwrites).

Signature:

cortex.mutable.set(
namespace: string,
key: string,
value: any,
userId?: string,
metadata?: Record<string, unknown>,
options?: SetMutableOptions
): Promise<MutableRecord>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesLogical grouping (e.g., 'inventory', 'config', 'counters')
keystringYesUnique key within namespace
valueanyYesJSON-serializable value
userIdstringNoLink to user (enables GDPR cascade). Must reference existing user.
metadataRecord<string, unknown>NoAdditional metadata to store with the record
optionsSetMutableOptionsNoAdditional options (graph sync is automatic when CORTEX_GRAPH_SYNC=true)

Returns:

interface MutableRecord {
_id: string;
namespace: string;
key: string;
value: any;
userId?: string; // OPTIONAL: User link (GDPR-enabled)
tenantId?: string; // Auto-injected from AuthContext for multi-tenancy
metadata?: Record<string, unknown>;
createdAt: number; // Unix timestamp
updatedAt: number; // Unix timestamp
}

Example 1: System data (no userId)

// Set inventory quantity (system-wide)
const record = await cortex.mutable.set("inventory", "widget-qty", 100);

console.log(record.value); // 100
console.log(record.userId); // undefined (system data)

// Update (overwrites)
await cortex.mutable.set("inventory", "widget-qty", 95); // Now 95

// No version history - previous value (100) is gone!

Example 2: User-specific data (with userId)

// Set user session data (GDPR-enabled)
const session = await cortex.mutable.set(
"user-sessions",
"session-abc123",
{
startedAt: new Date(),
lastActivity: new Date(),
pagesViewed: 5,
},
"user-123", // ← Links to user (enables GDPR cascade)
);

console.log(session.userId); // "user-123"

// When user requests deletion:
await cortex.users.delete("user-123", { cascade: true });
// This session is automatically deleted!

Errors:

  • CortexError('INVALID_NAMESPACE') - Namespace is empty or invalid
  • CortexError('INVALID_KEY') - Key is empty or invalid
  • CortexError('VALUE_TOO_LARGE') - Value exceeds size limit
  • CortexError('USER_NOT_FOUND') - userId doesn't reference existing user
  • CortexError('CONVEX_ERROR') - Database error

get()

Get current value for a key.

Signature:

cortex.mutable.get(
namespace: string,
key: string
): Promise<any | null>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace
keystringYesKey

Returns:

  • any | null - Current value, or null if key doesn't exist

Example:

const qty = await cortex.mutable.get("inventory", "widget-qty");

if (qty !== null) {
console.log(`Current quantity: ${qty}`);

if (qty > 0) {
// Process order
}
} else {
console.log("Product not found");
}

update()

Atomically update a value.

Signature:

cortex.mutable.update(
namespace: string,
key: string,
updater: (current: any) => any
): Promise<MutableRecord>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace
keystringYesKey
updater(current: any) => anyYesFunction that receives current value, returns new value

Returns:

  • MutableRecord - Updated record

Side Effects:

  • ACID: Read, compute, write atomically
  • No race conditions (Convex handles locking)

Example:

// Decrement inventory
await cortex.mutable.update("inventory", "widget-qty", (current) => {
if (current === null || current <= 0) {
throw new Error("Out of stock");
}
return current - 1;
});

// Increment counter
await cortex.mutable.update("counters", "total-refunds", (current) => {
return (current || 0) + 1;
});

// Update object
await cortex.mutable.update("config", "api-settings", (current) => {
return {
...current,
endpoint: "https://api.example.com/v2",
timeout: 30000,
};
});

Errors:

  • CortexError('KEY_NOT_FOUND') - Key doesn't exist
  • CortexError('UPDATE_FAILED') - Updater function threw error
  • CortexError('CONVEX_ERROR') - Database error

increment()

Atomically increment a numeric value (convenience helper).

Signature:

cortex.mutable.increment(
namespace: string,
key: string,
amount?: number
): Promise<MutableRecord>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace
keystringYesKey
amountnumberNo1Amount to increment

Returns:

  • MutableRecord - Updated record with incremented value

Example:

// Initialize counter
await cortex.mutable.set("counters", "page-views", 0);

// Increment by 1 (default)
await cortex.mutable.increment("counters", "page-views");

// Increment by custom amount
await cortex.mutable.increment("counters", "page-views", 10);

const views = await cortex.mutable.get("counters", "page-views");
console.log(`Total views: ${views}`); // 11
Note

This is a convenience wrapper around update(). For more control, use update() directly.


decrement()

Atomically decrement a numeric value (convenience helper).

Signature:

cortex.mutable.decrement(
namespace: string,
key: string,
amount?: number
): Promise<MutableRecord>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace
keystringYesKey
amountnumberNo1Amount to decrement

Returns:

  • MutableRecord - Updated record with decremented value

Example:

// Initialize inventory
await cortex.mutable.set("inventory", "widget-qty", 100);

// Customer order (decrement)
await cortex.mutable.decrement("inventory", "widget-qty", 5);

const remaining = await cortex.mutable.get("inventory", "widget-qty");
console.log(`Remaining: ${remaining}`); // 95

getRecord()

Get full record with metadata (not just the value).

Signature:

cortex.mutable.getRecord(
namespace: string,
key: string
): Promise<MutableRecord | null>

Returns:

interface MutableRecord {
_id: string;
namespace: string;
key: string;
value: any;
userId?: string;
metadata?: Record<string, unknown>;
createdAt: number; // Unix timestamp
updatedAt: number; // Unix timestamp
}

Example:

// get() returns value only
const value = await cortex.mutable.get("config", "timeout");
console.log(value); // 30

// getRecord() returns full record with metadata
const record = await cortex.mutable.getRecord("config", "timeout");
console.log(record.value); // 30
console.log(record.createdAt); // 1729987200000
console.log(record.updatedAt); // 1729987200000

Use when: You need access to timestamps or metadata.


delete()

Delete a key.

Signature:

cortex.mutable.delete(
namespace: string,
key: string,
options?: DeleteMutableOptions
): Promise<DeleteResult>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace
keystringYesKey
optionsDeleteMutableOptionsNoAdditional options (graph cleanup is automatic when CORTEX_GRAPH_SYNC=true)

Returns:

interface DeleteResult {
deleted: boolean;
namespace: string;
key: string;
}

Example:

// Remove product from inventory
const result = await cortex.mutable.delete("inventory", "discontinued-widget");

console.log(`Deleted: ${result.deleted}`);

// Graph cleanup is automatic when CORTEX_GRAPH_SYNC=true
await cortex.mutable.delete("inventory", "old-item");

transaction()

Execute multiple operations atomically.

Signature:

cortex.mutable.transaction(
operations: TransactionOperation[]
): Promise<TransactionResult>

Parameters:

interface TransactionOperation {
op: "set" | "update" | "delete" | "increment" | "decrement";
namespace: string;
key: string;
value?: any; // Required for "set" and "update" ops
amount?: number; // For "increment"/"decrement" (default: 1)
}

Returns:

interface TransactionResult {
success: boolean;
operationsExecuted: number;
results: unknown[];
}

Example:

// Record sale and update inventory (atomic)
await cortex.mutable.transaction([
{ op: "decrement", namespace: "inventory", key: "widget-qty", amount: 1 },
{ op: "increment", namespace: "counters", key: "total-sales", amount: 1 },
{ op: "set", namespace: "state", key: "last-sale", value: Date.now() },
]);
// All operations succeed together or fail together (ACID)

// Multi-item inventory transfer
await cortex.mutable.transaction([
{ op: "decrement", namespace: "inventory", key: "product-a", amount: 10 },
{ op: "increment", namespace: "inventory", key: "product-b", amount: 10 },
]);

// Complex state update
const result = await cortex.mutable.transaction([
{ op: "set", namespace: "config", key: "api-version", value: "v2" },
{ op: "delete", namespace: "cache", key: "old-api-data" },
{ op: "increment", namespace: "counters", key: "config-changes" },
]);

console.log(result.success); // true
console.log(result.operationsExecuted); // 3

Errors:

  • CortexError('TRANSACTION_FAILED') - Transaction rolled back
  • Key must exist for update, increment, decrement, and delete operations

Querying

list()

List keys in a namespace with filtering, sorting, and pagination.

Signature:

cortex.mutable.list(
filter: ListMutableFilter
): Promise<MutableRecord[]>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace to list
keyPrefixstringNoKeys starting with prefix
tenantIdstringNoMulti-tenancy filter (auto-injected from AuthContext)
userIdstringNoFilter by user
limitnumberNo100Max results
offsetnumberNoPagination offset
updatedAfternumberNoFilter by updatedAt > timestamp
updatedBeforenumberNoFilter by updatedAt < timestamp
sortBy"key" | "updatedAt" | "accessCount"NoSort field
sortOrder"asc" | "desc"No"asc"Sort direction

Example:

// List all inventory items
const items = await cortex.mutable.list({
namespace: "inventory",
limit: 100,
});

items.forEach((item) => {
console.log(`${item.key}: ${item.value}`);
});

// List specific prefix with sorting
const widgets = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "widget-",
sortBy: "updatedAt",
sortOrder: "desc",
});

// List items updated in the last 24 hours
const recentItems = await cortex.mutable.list({
namespace: "inventory",
updatedAfter: Date.now() - 24 * 60 * 60 * 1000,
sortBy: "updatedAt",
sortOrder: "desc",
});

// Pagination example
const page2 = await cortex.mutable.list({
namespace: "inventory",
limit: 20,
offset: 20, // Skip first 20 items
});

count()

Count keys in namespace with optional filters.

Signature:

cortex.mutable.count(
filter: CountMutableFilter
): Promise<number>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace to count
tenantIdstringNoMulti-tenancy filter (auto-injected from AuthContext)
userIdstringNoFilter by user
keyPrefixstringNoKeys starting with prefix
updatedAfternumberNoFilter by updatedAt > timestamp
updatedBeforenumberNoFilter by updatedAt < timestamp

Example:

// Total inventory items
const total = await cortex.mutable.count({ namespace: "inventory" });

// Count items with prefix
const widgetCount = await cortex.mutable.count({
namespace: "inventory",
keyPrefix: "widget-",
});

// Count user-specific items
const userCount = await cortex.mutable.count({
namespace: "user-sessions",
userId: "user-123",
});

// Count items updated in the last 24 hours
const recentCount = await cortex.mutable.count({
namespace: "inventory",
updatedAfter: Date.now() - 24 * 60 * 60 * 1000,
});

// Count items not updated in the last week (stale items)
const staleCount = await cortex.mutable.count({
namespace: "cache",
updatedBefore: Date.now() - 7 * 24 * 60 * 60 * 1000,
});

exists()

Check if key exists.

Signature:

cortex.mutable.exists(
namespace: string,
key: string
): Promise<boolean>

Example:

if (await cortex.mutable.exists("inventory", "widget-qty")) {
const qty = await cortex.mutable.get("inventory", "widget-qty");
} else {
// Initialize
await cortex.mutable.set("inventory", "widget-qty", 100);
}

Namespaces

Common Namespaces

inventory - Product quantities

await cortex.mutable.set("inventory", "product-x", 150);
await cortex.mutable.update("inventory", "product-x", (qty) => qty - 1);

config - Live configuration

await cortex.mutable.set(
"config",
"api-endpoint",
"https://api.example.com/v2",
);
await cortex.mutable.set("config", "max-retries", 3);

counters - Shared counters/metrics

await cortex.mutable.update("counters", "total-requests", (n) => (n || 0) + 1);
await cortex.mutable.update("counters", "active-users", (n) => n + 1);

docs - Live documentation references

await cortex.mutable.set("docs", "api-version", "v2.1.0");
await cortex.mutable.set(
"docs",
"changelog-url",
"https://docs.example.com/changelog",
);

state - Agent collaboration state

await cortex.mutable.set("state", "active-campaign-id", "camp-2025-q4");
await cortex.mutable.update("state", "agents-available", (list) => [
...list,
"agent-5",
]);

Complex Data Types

The Mutable Store supports any JSON-serializable value, not just primitives. You can store:

Storing Objects

// Store complex configuration object
await cortex.mutable.set("config", "email-settings", {
smtp: {
host: "smtp.example.com",
port: 587,
secure: true,
},
from: "noreply@example.com",
templates: {
welcome: "templates/welcome.html",
reset: "templates/reset.html",
},
});

// Retrieve and use
const emailConfig = await cortex.mutable.get("config", "email-settings");
sendEmail(emailConfig.smtp, emailConfig.from);

Storing Arrays

// Store product catalog
await cortex.mutable.set("inventory", "featured-products", [
{ id: "prod-1", name: "Widget A", price: 19.99, stock: 150 },
{ id: "prod-2", name: "Widget B", price: 29.99, stock: 75 },
{ id: "prod-3", name: "Widget C", price: 39.99, stock: 200 },
]);

// Atomically update array
await cortex.mutable.update("inventory", "featured-products", (products) => {
return products.map((p) =>
p.id === "prod-1" ? { ...p, stock: p.stock - 1 } : p,
);
});

Storing Nested Structures

// Store complete store configuration
await cortex.mutable.set("stores", "store-15", {
id: "store-15",
name: "Downtown Location",
address: {
street: "123 Main St",
city: "Springfield",
state: "IL",
zip: "62701",
},
hours: {
monday: { open: "09:00", close: "21:00" },
tuesday: { open: "09:00", close: "21:00" },
sunday: { open: "10:00", close: "18:00" },
},
departments: [
{ name: "Produce", manager: "Alice" },
{ name: "Dairy", manager: "Bob" },
{ name: "Bakery", manager: "Carol" },
],
metrics: {
revenue: 125000,
customersToday: 450,
averageTransaction: 67.5,
},
});

Size Considerations

// Good: Reasonable size (< 100KB recommended)
await cortex.mutable.set("config", "app-settings", {
theme: "dark",
language: "en",
notifications: true,
// ... dozens of settings
});

// Caution: Very large objects (> 1MB)
await cortex.mutable.set("cache", "entire-product-catalog", {
// Thousands of products...
// Consider breaking into multiple keys instead
});

// Better: Split large datasets
await cortex.mutable.set("catalog", "page-1", products.slice(0, 100));
await cortex.mutable.set("catalog", "page-2", products.slice(100, 200));
await cortex.mutable.set("catalog", "page-3", products.slice(200, 300));

Hierarchical Data Patterns

For hierarchical/relational data like "Produce" → "Inventory" → "Grocery Store 15" → "Grocery Stores", Cortex provides several patterns:

Use delimiters in keys to create hierarchies:

// Pattern: namespace:key-with:delimiters
await cortex.mutable.set(
"grocery-stores",
"store-15:inventory:produce:apples",
{
quantity: 150,
unit: "lbs",
price: 2.99,
supplier: "Local Farms",
},
);

await cortex.mutable.set(
"grocery-stores",
"store-15:inventory:produce:bananas",
{
quantity: 200,
unit: "lbs",
price: 1.49,
supplier: "Tropical Import Co",
},
);

await cortex.mutable.set("grocery-stores", "store-15:inventory:dairy:milk", {
quantity: 50,
unit: "gallons",
price: 4.99,
supplier: "Dairy Fresh",
});

// Query all produce for store 15
const produce = await cortex.mutable.list({
namespace: "grocery-stores",
keyPrefix: "store-15:inventory:produce:",
});

// Query all inventory for store 15
const allInventory = await cortex.mutable.list({
namespace: "grocery-stores",
keyPrefix: "store-15:inventory:",
});

Benefits:

  • Easy prefix queries
  • Flat structure (no deep nesting)
  • Good performance
  • Easy to add/remove items

Pattern 2: Hierarchical Namespaces

Use delimiters in namespaces for organizational hierarchy:

// Pattern: hierarchical:namespace with flat keys
await cortex.mutable.set(
"grocery-stores:store-15:inventory",
"produce-apples",
{
quantity: 150,
price: 2.99,
},
);

await cortex.mutable.set(
"grocery-stores:store-15:inventory",
"produce-bananas",
{
quantity: 200,
price: 1.49,
},
);

await cortex.mutable.set(
"grocery-stores:store-15:metrics",
"daily-revenue",
12500,
);
await cortex.mutable.set(
"grocery-stores:store-15:metrics",
"customer-count",
450,
);

// Query entire namespace
const storeInventory = await cortex.mutable.list({
namespace: "grocery-stores:store-15:inventory",
});
const storeMetrics = await cortex.mutable.list({
namespace: "grocery-stores:store-15:metrics",
});

Benefits:

  • Logical namespace organization
  • Clear separation of concerns
  • Easy to list all keys in a category

Store hierarchy as nested object in value:

// Store entire store hierarchy in one key
await cortex.mutable.set("grocery-stores", "store-15", {
id: "store-15",
name: "Downtown Location",
inventory: {
produce: {
apples: { quantity: 150, price: 2.99, unit: "lbs" },
bananas: { quantity: 200, price: 1.49, unit: "lbs" },
oranges: { quantity: 100, price: 3.49, unit: "lbs" },
},
dairy: {
milk: { quantity: 50, price: 4.99, unit: "gallons" },
cheese: { quantity: 75, price: 6.99, unit: "lbs" },
},
bakery: {
bread: { quantity: 100, price: 3.99, unit: "loaves" },
donuts: { quantity: 200, price: 1.99, unit: "each" },
},
},
metrics: {
revenue: 12500,
customers: 450,
avgTransaction: 27.78,
},
});

// Update specific nested value atomically
await cortex.mutable.update("grocery-stores", "store-15", (store) => ({
...store,
inventory: {
...store.inventory,
produce: {
...store.inventory.produce,
apples: {
...store.inventory.produce.apples,
quantity: store.inventory.produce.apples.quantity - 10,
},
},
},
}));

// Or use helper function
async function updateNestedInventory(
storeId: string,
department: string,
item: string,
updates: Partial<any>,
) {
await cortex.mutable.update("grocery-stores", storeId, (store) => ({
...store,
inventory: {
...store.inventory,
[department]: {
...store.inventory[department],
[item]: {
...store.inventory[department][item],
...updates,
},
},
},
}));
}

await updateNestedInventory("store-15", "produce", "apples", { quantity: 140 });

Benefits:

  • Single atomic update for related data
  • Natural data representation
  • Smaller transaction scope
  • Easy to retrieve entire hierarchy

Trade-offs:

  • Large objects can be slow to update
  • Need careful atomic updates
  • Harder to query subsets

Combine approaches for optimal performance:

// Store metadata in nested object
await cortex.mutable.set("grocery-stores", "store-15-meta", {
id: "store-15",
name: "Downtown Location",
address: { street: "123 Main St", city: "Springfield" },
departments: ["produce", "dairy", "bakery"],
});

// Store inventory items individually with hierarchical keys
await cortex.mutable.set("inventory", "store-15:produce:apples", {
quantity: 150,
price: 2.99,
unit: "lbs",
});

await cortex.mutable.set("inventory", "store-15:produce:bananas", {
quantity: 200,
price: 1.49,
unit: "lbs",
});

// Store aggregated metrics separately
await cortex.mutable.set("metrics", "store-15-daily", {
revenue: 12500,
customers: 450,
date: new Date().toISOString(),
});

Benefits:

  • Optimal for large datasets
  • Fast queries and updates
  • Good separation of concerns
  • Scalable architecture

Helper Functions for Hierarchical Data

// Parse hierarchical keys
function parseKey(key: string): string[] {
return key.split(":");
}

function buildKey(...parts: string[]): string {
return parts.join(":");
}

// Example usage
const key = buildKey("store-15", "inventory", "produce", "apples");
await cortex.mutable.set("grocery-stores", key, { quantity: 150 });

const parts = parseKey(key); // ["store-15", "inventory", "produce", "apples"]
console.log(`Store: ${parts[0]}, Department: ${parts[2]}, Item: ${parts[3]}`);

// Query helpers
async function listByPrefix(namespace: string, ...prefixParts: string[]) {
const prefix = buildKey(...prefixParts);
return await cortex.mutable.list({
namespace,
keyPrefix: prefix,
});
}

// Get all produce for store 15
const produce = await listByPrefix(
"grocery-stores",
"store-15",
"inventory",
"produce",
);

// Get all inventory for store 15
const inventory = await listByPrefix("grocery-stores", "store-15", "inventory");

Choosing the Right Pattern

PatternBest ForQuery PerformanceUpdate PerformanceComplexity
Hierarchical KeysPrefix queries, flat relationshipsExcellentExcellentLow
Hierarchical NamespacesOrganizational structureGoodExcellentLow
Nested Object ValuesRelated data, small datasetsModerateModerateMedium
HybridLarge systems, mixed accessExcellentExcellentMedium

Recommendations:

  • Inventory systems: Hierarchical Keys or Hybrid
  • Configuration: Nested Object Values
  • Metrics/Analytics: Hierarchical Keys
  • Multi-tenant data: Hierarchical Namespaces
  • Small datasets (< 100 items): Nested Object Values
  • Large datasets (> 1000 items): Hierarchical Keys + Hybrid

Complete Real-World Example: Grocery Store Chain

Here's a comprehensive example using the Hybrid pattern for a multi-store inventory system:

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Setup: Initialize grocery store chain
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Store chain metadata (nested object - small, related data)
await cortex.mutable.set("chain", "metadata", {
name: "Fresh Foods Chain",
totalStores: 25,
headquarters: "Springfield, IL",
storeIds: ["store-1", "store-15", "store-23"],
});

// Store individual store configs (nested object per store)
await cortex.mutable.set("stores", "store-15", {
id: "store-15",
name: "Downtown Location",
address: "123 Main St, Springfield",
manager: "Alice Johnson",
departments: ["produce", "dairy", "bakery", "meat"],
openHours: { open: "09:00", close: "21:00" },
});

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Inventory: Hierarchical keys (scalable, fast queries)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Set initial inventory (hierarchical keys: store:dept:item)
await cortex.mutable.set("inventory", "store-15:produce:apples", {
quantity: 150,
unit: "lbs",
price: 2.99,
supplier: "Local Farms",
lastRestocked: new Date(),
});

await cortex.mutable.set("inventory", "store-15:produce:bananas", {
quantity: 200,
unit: "lbs",
price: 1.49,
supplier: "Tropical Import",
});

await cortex.mutable.set("inventory", "store-15:dairy:milk", {
quantity: 50,
unit: "gallons",
price: 4.99,
supplier: "Dairy Fresh",
});

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Operations: Customer purchases (atomic updates)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Customer buys 10 lbs of apples
await cortex.mutable.update("inventory", "store-15:produce:apples", (item) => {
if (item.quantity < 10) {
throw new Error("Insufficient stock");
}
return {
...item,
quantity: item.quantity - 10,
};
});

// Multi-item purchase (ACID transaction with decrement operations)
// Note: transaction() uses array-based operations, not callback functions
await cortex.mutable.transaction([
// Buy apples, bananas, and milk (using decrement)
{
op: "decrement",
namespace: "inventory",
key: "store-15:produce:apples",
amount: 5,
},
{
op: "decrement",
namespace: "inventory",
key: "store-15:produce:bananas",
amount: 3,
},
{
op: "decrement",
namespace: "inventory",
key: "store-15:dairy:milk",
amount: 2,
},
// Increment sales counter
{
op: "increment",
namespace: "metrics",
key: "store-15-daily-sales",
amount: 1,
},
]);
// All operations succeed together or fail together (ACID)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Queries: Get inventory by hierarchy level
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Get all produce for store 15
const produce = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "store-15:produce:",
});

console.log(`Store 15 produce items: ${produce.length}`);
produce.forEach((item) => {
console.log(
`- ${item.key.split(":")[2]}: ${item.value.quantity} ${item.value.unit}`,
);
});

// Get ALL inventory for store 15
const allInventory = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "store-15:",
});

// Get specific item across all stores
const allApples = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "store-", // Gets all stores
});
const applesOnly = allApples.filter((r) => r.key.endsWith(":apples"));

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Aggregation: Chain-wide reports
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Get total inventory across all stores
async function getChainwideInventory(department?: string) {
const allStores = await cortex.mutable.get("chain", "metadata");

const inventory: Record<string, number> = {};

for (const storeId of allStores.storeIds) {
const prefix = department ? `${storeId}:${department}:` : `${storeId}:`;

const items = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: prefix,
});

items.forEach((record) => {
const itemName = record.key.split(":").pop()!;
inventory[itemName] = (inventory[itemName] || 0) + record.value.quantity;
});
}

return inventory;
}

// Total produce across all stores
const chainProduce = await getChainwideInventory("produce");
console.log("Chain-wide produce:", chainProduce);
// { apples: 3750, bananas: 5000, oranges: 2100 }

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Restocking: Bulk updates
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// Restock all produce for store 15 (using transaction for atomicity)
// Note: For simple numeric inventory, use increment operations
async function restockDepartment(
storeId: string,
department: string,
items: Record<string, number>,
) {
// Build array of increment operations
const operations = Object.entries(items).map(([itemName, quantity]) => ({
op: "increment" as const,
namespace: "inventory",
key: `${storeId}:${department}:${itemName}`,
amount: quantity,
}));

// Execute all increments atomically
await cortex.mutable.transaction(operations);
}

await restockDepartment("store-15", "produce", {
apples: 100,
bananas: 150,
oranges: 75,
});

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Low Stock Alerts
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

async function checkLowStock(storeId: string, threshold: number = 20) {
const inventory = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: `${storeId}:`,
});

const lowStock = inventory.filter((item) => item.value.quantity < threshold);

if (lowStock.length > 0) {
console.log(`[ALERT] Low stock for ${storeId}:`);
lowStock.forEach((item) => {
const [store, dept, product] = item.key.split(":");
console.log(
` - ${dept}/${product}: ${item.value.quantity} ${item.value.unit} remaining`,
);
});
}

return lowStock;
}

await checkLowStock("store-15", 25);

This example demonstrates:

  • Complex objects as values (store metadata, inventory items)
  • Hierarchical keys for scalable queries (store:dept:item)
  • Nested objects for related configuration
  • Atomic transactions across multiple keys
  • Prefix-based queries for hierarchy traversal
  • Aggregation across hierarchies
  • Real-world business logic (purchases, restocking, alerts)

Best Practices

1. Use Descriptive Namespaces

// Good namespaces
"inventory";
"config-production";
"counters-analytics";
"docs-api";

// Avoid: Generic namespaces
"data";
"stuff";
"temp";

2. Atomic Operations Only

// Avoid: Race condition
const current = await cortex.mutable.get("inventory", "widget-qty");
await cortex.mutable.set("inventory", "widget-qty", current - 1);
// Another agent could modify between get and set!

// Recommended: Atomic update
await cortex.mutable.update("inventory", "widget-qty", (qty) => qty - 1);
// ACID guarantees no race condition

3. Initialize Before Use

// Safe initialization
async function getOrInitialize(
namespace: string,
key: string,
defaultValue: any,
) {
const value = await cortex.mutable.get(namespace, key);
if (value === null) {
await cortex.mutable.set(namespace, key, defaultValue);
return defaultValue;
}
return value;
}

const qty = await getOrInitialize("inventory", "new-product", 0);

4. Use Transactions for Multi-Key Operations

// Recommended: Atomic multi-key update (array-based API)
await cortex.mutable.transaction([
{ op: "decrement", namespace: "inventory", key: "product-a", amount: 1 },
{ op: "increment", namespace: "counters", key: "total-sales", amount: 1 },
]);
// Both or neither - ACID guarantees

// Avoid: Non-atomic (could fail partway)
await cortex.mutable.decrement("inventory", "product-a", 1);
await cortex.mutable.increment("counters", "total-sales", 1);
// Second could fail, leaving inconsistent state

5. Choose Appropriate Data Structure

Use Complex Objects When:

// Related data that changes together
await cortex.mutable.set("stores", "store-15", {
name: "Downtown",
manager: "Alice",
address: { street: "123 Main", city: "Springfield" },
// All fields updated as a unit
});

// Small datasets (< 100 items)
await cortex.mutable.set("config", "app-settings", {
theme: "dark",
language: "en",
features: { chat: true, voice: false },
});

// Configuration that's read together
await cortex.mutable.set("email", "settings", {
smtp: { host: "...", port: 587 },
templates: { welcome: "...", reset: "..." },
});

Use Hierarchical Keys When:

// Large datasets (> 100 items)
await cortex.mutable.set("inventory", "store-15:produce:apples", {...});
await cortex.mutable.set("inventory", "store-15:produce:bananas", {...});
// Can have thousands of products

// Need prefix queries
const produce = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "store-15:produce:",
});

// Independent updates (no related data)
await cortex.mutable.update("inventory", "store-15:dairy:milk", ...);
// Doesn't affect produce or other items

// Different access patterns
const apples = await cortex.mutable.get("inventory", "store-15:produce:apples");
// Fast direct access to single item

Decision Matrix:

ScenarioRecommended PatternWhy
Store metadata (< 50 fields)Complex ObjectRelated data, atomic updates
Product catalog (1000s of items)Hierarchical KeysScalable, independent updates
App configurationComplex ObjectRead/updated together
Multi-tenant inventoryHierarchical KeysPer-tenant prefix queries
Workflow stateComplex ObjectState transitions are atomic
Metrics/countersHierarchical KeysIndependent increments
Small lists (< 100 items)Complex Object (array)Simple, atomic
Large lists (> 1000 items)Hierarchical KeysScalable queries

6. Use TypeScript for Type Safety

Define interfaces for complex objects:

// Define your data structures
interface InventoryItem {
quantity: number;
unit: string;
price: number;
supplier: string;
sku: string;
lastRestocked: Date;
}

interface StoreConfig {
id: string;
name: string;
manager: string;
address: {
street: string;
city: string;
state: string;
zip: string;
};
departments: string[];
openHours: {
open: string;
close: string;
};
}

// Type-safe operations
async function setInventoryItem(
storeId: string,
department: string,
item: string,
data: InventoryItem,
): Promise<void> {
const key = `${storeId}:${department}:${item}`;
await cortex.mutable.set("inventory", key, data);
}

async function getInventoryItem(
storeId: string,
department: string,
item: string,
): Promise<InventoryItem | null> {
const key = `${storeId}:${department}:${item}`;
return await cortex.mutable.get("inventory", key);
}

// Usage is type-safe
await setInventoryItem("store-15", "produce", "apples", {
quantity: 150,
unit: "lbs",
price: 2.99,
supplier: "Local Farms",
sku: "PROD-APL-001",
lastRestocked: new Date(),
});

const apples = await getInventoryItem("store-15", "produce", "apples");
if (apples) {
console.log(apples.quantity); // TypeScript knows this is a number
// apples.invalidField // TypeScript error!
}

Integration with Vector Layer

Mutable data CAN be referenced by Vector memories (as snapshots):

// 1. Store mutable data
await cortex.mutable.set(
"config",
"api-endpoint",
"https://api.example.com/v2",
);

// 2. Create Vector memory referencing it (snapshot)
await cortex.vector.store("config-agent", {
content: "API endpoint changed to v2",
contentType: "raw",
source: { type: "system", timestamp: new Date() },
mutableRef: {
namespace: "config",
key: "api-endpoint",
snapshotValue: "https://api.example.com/v2", // Value at time of indexing
snapshotAt: new Date(),
},
metadata: {
importance: 80,
tags: ["config", "api"],
},
});

// 3. Later, mutable value changes
await cortex.mutable.set(
"config",
"api-endpoint",
"https://api.example.com/v3",
);

// Vector memory still has snapshot of v2
// Current value is v3 (from mutable store)

Note: Vector's mutableRef is a snapshot, not live. Use for audit/history, not current values.


Purging

purge()

Delete a single key. Alias for delete() for API consistency.

Signature:

cortex.mutable.purge(
namespace: string,
key: string
): Promise<PurgeResult>

Returns:

interface PurgeResult {
deleted: boolean;
namespace: string;
key: string;
}

Example:

const result = await cortex.mutable.purge("inventory", "discontinued-product");
console.log(`Deleted: ${result.deleted}`);

purgeNamespace()

Delete entire namespace with optional dry-run mode.

Signature:

cortex.mutable.purgeNamespace(
namespace: string,
options?: PurgeNamespaceOptions
): Promise<PurgeNamespaceResult>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace to purge
options.dryRunbooleanNofalsePreview what would be deleted without deleting
options.tenantIdstringNoMulti-tenancy filter (auto-injected from AuthContext)

Returns:

interface PurgeNamespaceResult {
deleted: number; // Count of keys deleted (or would be deleted in dryRun mode)
namespace: string;
keys?: string[]; // List of keys (only in dryRun mode)
dryRun: boolean;
}

Example:

// Preview what would be deleted (dryRun mode)
const preview = await cortex.mutable.purgeNamespace("test-data", {
dryRun: true,
});
console.log(
`Would delete ${preview.deleted} keys: ${preview.keys?.join(", ")}`,
);

// Actually delete
const result = await cortex.mutable.purgeNamespace("test-data");
console.log(`Deleted ${result.deleted} keys from ${result.namespace}`);

purgeMany()

Delete keys matching filters with date-based cleanup support.

Signature:

cortex.mutable.purgeMany(
filter: PurgeManyFilter
): Promise<PurgeManyResult>

Parameters:

ParameterTypeRequiredDefaultDescription
namespacestringYesNamespace to purge from
keyPrefixstringNoKeys starting with prefix
userIdstringNoFilter by user
updatedBeforenumberNoDelete keys updated before this timestamp
tenantIdstringNoMulti-tenancy filter (auto-injected from AuthContext)

Returns:

interface PurgeManyResult {
deleted: number; // Count of keys deleted
namespace: string;
keys: string[]; // List of deleted keys
}

Example:

// Delete keys with prefix
const result = await cortex.mutable.purgeMany({
namespace: "cache",
keyPrefix: "temp-",
});
console.log(`Deleted ${result.deleted} keys: ${result.keys.join(", ")}`);

// Delete all keys for a specific user
await cortex.mutable.purgeMany({
namespace: "user-sessions",
userId: "user-123",
});

// Delete old keys (not updated in 30 days)
await cortex.mutable.purgeMany({
namespace: "cache",
updatedBefore: Date.now() - 30 * 24 * 60 * 60 * 1000,
});

// Combine filters: delete old cache entries for a user
await cortex.mutable.purgeMany({
namespace: "user-cache",
userId: "user-123",
updatedBefore: Date.now() - 14 * 24 * 60 * 60 * 1000,
});

Use Cases

Use Case 1: Live Inventory (Complex Hierarchical Data)

// Initialize inventory with complex objects and hierarchical keys
await cortex.mutable.set("inventory", "store-15:produce:apples", {
quantity: 150,
unit: "lbs",
price: 2.99,
supplier: "Local Farms",
sku: "PROD-APL-001",
perishable: true,
expiryDate: new Date("2025-10-30"),
});

// Customer orders 10 lbs (atomic decrement with complex object)
await cortex.mutable.update("inventory", "store-15:produce:apples", (item) => {
if (item.quantity < 10) throw new Error("Out of stock");

return {
...item,
quantity: item.quantity - 10,
lastSold: new Date(),
};
});

// Check availability with full details
const apples = await cortex.mutable.get("inventory", "store-15:produce:apples");
console.log(`${apples.quantity} ${apples.unit} available at $${apples.price}`);

// Restock (atomic increment + update metadata)
await cortex.mutable.update("inventory", "store-15:produce:apples", (item) => ({
...item,
quantity: item.quantity + 50,
lastRestocked: new Date(),
supplier: "Local Farms", // Confirm supplier
}));

// Query all produce for this store
const allProduce = await cortex.mutable.list({
namespace: "inventory",
keyPrefix: "store-15:produce:",
});

console.log(`Store 15 has ${allProduce.length} produce items`);

Use Case 2: Live Configuration (Complex Nested Objects)

// Set application configuration as nested object
await cortex.mutable.set("config", "app-settings", {
api: {
endpoint: "https://api.example.com/v1",
timeout: 30000,
retries: 3,
rateLimits: {
perMinute: 60,
perHour: 1000,
},
},
features: {
enableChat: true,
enableVoice: false,
maxConcurrentChats: 5,
enableFileUpload: true,
},
notifications: {
email: true,
sms: false,
push: true,
channels: ["email", "push"],
},
security: {
sessionTimeout: 3600,
requireMFA: false,
allowedOrigins: ["https://app.example.com"],
},
});

// Agents check config (type-safe access)
const config = await cortex.mutable.get("config", "app-settings");
if (
config.features.enableChat &&
activeChats < config.features.maxConcurrentChats
) {
// Accept new chat
}

// Update specific nested value (atomic)
await cortex.mutable.update("config", "app-settings", (cfg) => ({
...cfg,
features: {
...cfg.features,
maxConcurrentChats: 10, // Immediate effect for all agents
},
}));

// Or update API endpoint
await cortex.mutable.update("config", "app-settings", (cfg) => ({
...cfg,
api: {
...cfg.api,
endpoint: "https://api.example.com/v2",
timeout: 60000, // Also increase timeout
},
}));

Use Case 3: Shared Counters

// Initialize counters
await cortex.mutable.set("counters", "total-requests", 0);
await cortex.mutable.set("counters", "successful-requests", 0);
await cortex.mutable.set("counters", "failed-requests", 0);

// Each agent increments (atomic)
await cortex.mutable.update("counters", "total-requests", (n) => n + 1);

if (requestSucceeded) {
await cortex.mutable.update("counters", "successful-requests", (n) => n + 1);
} else {
await cortex.mutable.update("counters", "failed-requests", (n) => n + 1);
}

// Get stats
const total = await cortex.mutable.get("counters", "total-requests");
const success = await cortex.mutable.get("counters", "successful-requests");
console.log(`Success rate: ${((success / total) * 100).toFixed(1)}%`);

Use Case 4: Agent Collaboration State (Complex Workflow)

// Track active workflow with complex state
await cortex.mutable.set("workflows", "refund-request-12345", {
id: "refund-request-12345",
type: "refund",
status: "in-progress",
createdAt: new Date(),
customer: {
id: "user-123",
name: "Alex Johnson",
email: "alex@example.com",
},
order: {
orderId: "ORD-98765",
amount: 299.99,
items: ["Widget A", "Widget B"],
purchaseDate: new Date("2025-10-15"),
},
workflow: {
currentStep: "manager-approval",
completedSteps: ["initiated", "customer-verified"],
pendingSteps: ["manager-approval", "payment-processing", "complete"],
},
agents: {
initiator: "support-agent-1",
currentAssignee: "manager-agent",
history: [
{
agentId: "support-agent-1",
action: "initiated",
timestamp: new Date(),
},
{ agentId: "support-agent-1", action: "verified", timestamp: new Date() },
],
},
notes: ["Customer reports defective product", "Original packaging intact"],
});

// Manager approves (atomic state transition)
await cortex.mutable.update(
"workflows",
"refund-request-12345",
(workflow) => ({
...workflow,
status: "approved",
workflow: {
...workflow.workflow,
currentStep: "payment-processing",
completedSteps: [...workflow.workflow.completedSteps, "manager-approval"],
pendingSteps: workflow.workflow.pendingSteps.filter(
(s) => s !== "manager-approval",
),
},
agents: {
...workflow.agents,
currentAssignee: "finance-agent",
history: [
...workflow.agents.history,
{ agentId: "manager-agent", action: "approved", timestamp: new Date() },
],
},
approvalDetails: {
approvedBy: "manager-agent",
approvedAt: new Date(),
amount: 299.99,
},
}),
);

// Finance agent processes payment
await cortex.mutable.update("workflows", "refund-request-12345", (workflow) => {
if (workflow.status !== "approved") {
throw new Error("Workflow not approved");
}

return {
...workflow,
status: "completed",
workflow: {
...workflow.workflow,
currentStep: "complete",
completedSteps: [
...workflow.workflow.completedSteps,
"payment-processing",
],
pendingSteps: [],
},
completedAt: new Date(),
};
});

Performance

Optimized for Frequent Updates

// Mutable store is designed for high-frequency updates
for (let i = 0; i < 1000; i++) {
await cortex.mutable.update("counters", "page-views", (n) => n + 1);
}
// Fast! No versioning overhead

Caching Recommendations

// For frequently-read, rarely-updated values
class MutableCache {
private cache = new Map<string, { value: any; cachedAt: Date }>();
private ttl = 60000; // 60 second cache

async get(namespace: string, key: string) {
const cacheKey = `${namespace}:${key}`;
const cached = this.cache.get(cacheKey);

if (cached && Date.now() - cached.cachedAt.getTime() < this.ttl) {
return cached.value;
}

const value = await cortex.mutable.get(namespace, key);
this.cache.set(cacheKey, { value, cachedAt: new Date() });
return value;
}
}

Limitations

No Version History

// Version 1
await cortex.mutable.set("config", "timeout", 30);

// Version 2 (v1 is GONE)
await cortex.mutable.set("config", "timeout", 60);

// Can't retrieve v1 - it's overwritten!
const current = await cortex.mutable.get("config", "timeout");
console.log(current); // 60 only

// If you need history, use immutable store instead!

Backup Strategy

// Snapshot mutable data for backups
async function backupMutableNamespace(namespace: string) {
const all = await cortex.mutable.list({ namespace });

const backup = {
namespace,
timestamp: new Date(),
data: all.reduce(
(acc, record) => {
acc[record.key] = record.value;
return acc;
},
{} as Record<string, any>,
),
};

// Store backup (could use immutable store or external storage)
await cortex.immutable.store({
type: "mutable-backup",
id: `${namespace}-backup-${Date.now()}`,
data: backup,
});

return backup;
}

// Restore from backup
async function restoreMutableNamespace(backupId: string) {
const backup = await cortex.immutable.get("mutable-backup", backupId);

for (const [key, value] of Object.entries(backup.data.data)) {
await cortex.mutable.set(backup.data.namespace, key, value);
}
}

Graph-Lite Capabilities

Mutable records can participate in the graph structure via userId:

Mutable Record as Graph Node:

  • Represents live/current state data
  • Can be linked to users for GDPR compliance

Edges:

  • userId to User (optional, for user-specific data)
  • mutableRef from Memories (snapshots of mutable state)

Graph Pattern:

// User → Mutable Records (via userId)
const userSessions = await cortex.mutable.list({
namespace: 'user-sessions',
userId: 'user-123',
});

const userPreferences = await cortex.mutable.get('user-preferences', 'user-123-prefs');

// Memory → Mutable (via mutableRef - snapshot)
await cortex.vector.store('agent-1', {
content: 'Config changed to value X',
mutableRef: {
namespace: 'config',
key: 'api-endpoint',
snapshotValue: 'https://api.example.com/v2',
snapshotAt: new Date()
}
});

// Graph:
User-123
└──[userId]──> Mutable: user-sessions/session-abc
└──[userId]──> Mutable: user-cache/cache-xyz

Memory-def
└──[mutableRef]──> Mutable: config/api-endpoint (snapshot at time T)
Note

Mutable data changes frequently, so mutableRef stores snapshots (value at time of reference), not live links.

Info

GDPR: Mutable records with userId are included in cascade deletion traversal.

Learn more: Graph Capabilities


Error Handling

MutableValidationError

The Mutable API exports MutableValidationError for client-side validation errors. This error is thrown before any network requests when input validation fails.

Import:

import { MutableValidationError } from "@cortex/sdk/mutable";
// or from main export:
import { MutableValidationError } from "@cortex/sdk";

Error Structure:

class MutableValidationError extends Error {
name: "MutableValidationError";
code: string; // Error code for programmatic handling
field?: string; // Which field failed validation
}

Example:

import { MutableValidationError } from "@cortex/sdk";

try {
await cortex.mutable.set("", "key", "value"); // Invalid namespace
} catch (error) {
if (error instanceof MutableValidationError) {
console.log(`Validation failed: ${error.message}`);
console.log(`Error code: ${error.code}`); // "MISSING_NAMESPACE"
console.log(`Field: ${error.field}`); // "namespace"
}
}

Common Error Codes:

CodeDescription
MISSING_NAMESPACENamespace is required but was empty or undefined
INVALID_NAMESPACENamespace format is invalid (must be alphanumeric, hyphens, underscores, dots, colons)
NAMESPACE_TOO_LONGNamespace exceeds 100 characters
MISSING_KEYKey is required but was empty or undefined
INVALID_KEYKey format is invalid
KEY_TOO_LONGKey exceeds 255 characters
MISSING_VALUEValue is required for set operations
VALUE_TOO_LARGEValue exceeds 1MB size limit
INVALID_USER_IDuserId format is invalid
INVALID_LIMIT_RANGElimit must be between 0 and 1000
INVALID_UPDATER_TYPEUpdater must be a function
MISSING_OPERATIONSTransaction requires operations array
EMPTY_OPERATIONS_ARRAYTransaction operations array cannot be empty
INVALID_OPERATION_TYPETransaction operation must be set, update, delete, increment, or decrement

Summary

Mutable Store provides:

  • Shared live data across agents
  • ACID transaction guarantees
  • Current-value semantics (fast)
  • No version overhead
  • Atomic updates (no race conditions)

Use for:

  • Real-time inventory
  • Live configuration
  • Shared counters/metrics
  • Agent collaboration state
  • Frequently-changing reference data

Don't use for:

  • Data needing version history (use cortex.immutable.*)
  • Private conversations (use cortex.conversations.*)
  • Searchable knowledge (use cortex.vector.* + immutable)
  • Audit trails (use cortex.immutable.* for versioning)

Next Steps


Questions? Ask in GitHub Discussions.