Skip to main content

Immutable Store API

Info

Last Updated: 2026-01-09

Complete API reference for shared immutable data storage with automatic versioning.

Overview

The Immutable Store API (Layer 1b) provides methods for storing TRULY SHARED immutable data across ALL memory spaces. This layer has NO memorySpace scoping - it's the only layer that is globally shared.

Critical Distinction:

  • NO memorySpaceId parameter - This layer is shared across ALL spaces
  • Accessible from any memory space
  • Perfect for: KB articles, policies, org docs, shared knowledge

Key Characteristics:

  • TRULY Shared - ALL memory spaces can access
  • NO Isolation - Not scoped to memorySpace (unlike L1a, L2, L3)
  • Immutable - Can't edit once stored
  • Versioned - Automatic version tracking
  • Append-only - New versions append, old preserved
  • Purgeable - Can delete by policy or manually
  • ACID - All guarantees of Layer 1
  • 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
MutabilityImmutableImmutableMutableMutableImmutable
VersioningN/A (append)AutoNoAutoAuto
Retention7 years20 versionsN/A10 versionsUnlimited
Use CaseChatsKB, policiesLive dataSearch indexExtracted facts

Core Operations

store()

Store immutable data. Creates v1 or increments version if ID exists.

Signature:

cortex.immutable.store(
entry: ImmutableEntry
): Promise<ImmutableRecord>
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 ImmutableEntry {
type: string; // Entity type: 'kb-article', 'policy', 'audit-log', 'feedback'
id: string; // Logical ID (versioned)
data: Record<string, any>; // The actual data
userId?: string; // OPTIONAL: Links to user (enables GDPR cascade)
// tenantId is auto-injected from AuthContext - no need to specify manually
metadata?: {
publishedBy?: string;
tags?: string[];
importance?: number; // 0-100
};
}

userId Field:

  • Optional - Only include if this record belongs to a specific user
  • Validated - Must reference an existing user profile
  • GDPR-enabled - Allows cortex.users.delete(userId, { cascade: true }) to find and delete this record
  • Use cases: User feedback, user-submitted content, user surveys, user audit logs

Examples:

// With userId (user-generated content)
await cortex.immutable.store({
type: "feedback",
id: "feedback-456",
userId: "user-123", // ← Links to user
data: {
rating: 5,
comment: "Great service!",
submittedAt: new Date(),
},
});

// Without userId (system content)
await cortex.immutable.store({
type: "kb-article",
id: "refund-policy",
// No userId - not user-specific
data: {
title: "Refund Policy",
content: "...",
},
});

// Graph sync is automatic when CORTEX_GRAPH_SYNC=true
await cortex.immutable.store({
type: "kb-article",
id: "product-guide",
data: {
title: "Product Guide",
content: "...",
},
});

Returns:

interface ImmutableRecord {
_id: string; // Convex document ID
type: string;
id: string; // Logical ID
version: number; // Version number
data: Record<string, unknown>;
tenantId?: string; // Multi-tenancy: SaaS platform isolation (auto-injected)
userId?: string; // OPTIONAL: User link (GDPR-enabled)
metadata?: {
publishedBy?: string;
tags?: string[];
importance?: number;
[key: string]: unknown;
};
previousVersions: ImmutableVersion[]; // Subject to retention
createdAt: number; // Unix timestamp (ms)
updatedAt: number; // Unix timestamp (ms)
}

interface ImmutableVersion {
version: number;
data: Record<string, unknown>;
metadata?: Record<string, unknown>;
timestamp: number; // Unix timestamp (ms)
}

Side Effects:

  • If ID exists: Creates new version, preserves previous (subject to retention)
  • If ID new: Creates version 1

Example:

// Create v1
const v1 = await cortex.immutable.store({
type: "kb-article",
id: "refund-policy",
data: {
title: "Refund Policy",
content: "Refunds available within 30 days...",
author: "admin@company.com",
},
metadata: {
publishedBy: "admin",
tags: ["policy", "refunds", "customer-service"],
importance: 90,
},
});

console.log(v1.version); // 1
console.log(v1.id); // 'refund-policy'

// Update creates v2 (v1 preserved)
const v2 = await cortex.immutable.store({
type: "kb-article",
id: "refund-policy", // Same ID
data: {
title: "Refund Policy v2",
content: "Refunds available within 60 days...", // Updated
author: "admin@company.com",
},
metadata: {
publishedBy: "admin",
tags: ["policy", "refunds", "customer-service"],
importance: 90,
},
});

console.log(v2.version); // 2
console.log(v2.previousVersions.length); // 1 (contains v1)

Errors:

  • ImmutableValidationError('INVALID_TYPE') - Type is empty or invalid
  • ImmutableValidationError('INVALID_ID') - ID is empty or invalid
  • ImmutableValidationError('INVALID_DATA') - Data is not a valid object
  • ImmutableValidationError('INVALID_METADATA') - Metadata is not a valid object
  • ImmutableValidationError('MISSING_REQUIRED_FIELD') - Required field missing
Info

Validation errors throw ImmutableValidationError (exported from @cortexso/sdk/immutable). Backend errors are wrapped in standard Error objects.


get()

Get current version of immutable data.

Signature:

cortex.immutable.get(
type: string,
id: string
): Promise<ImmutableRecord | null>

Parameters:

  • type (string) - Entity type
  • id (string) - Logical ID

Returns:

  • ImmutableRecord - Current version with history (subject to retention)
  • null - If doesn't exist

Example:

const article = await cortex.immutable.get("kb-article", "refund-policy");

if (article) {
console.log(`Current version: ${article.version}`);
console.log(`Title: ${article.data.title}`);
console.log(`Content: ${article.data.content}`);

// View version history
article.previousVersions.forEach((v) => {
console.log(`v${v.version} (${v.timestamp}): ${v.data.title}`);
});
}

Errors:

  • ImmutableValidationError('INVALID_TYPE') - Type is empty or invalid
  • ImmutableValidationError('INVALID_ID') - ID is empty or invalid

getVersion()

Get specific version of immutable data.

Signature:

cortex.immutable.getVersion(
type: string,
id: string,
version: number
): Promise<ImmutableVersionExpanded | null>

Parameters:

  • type (string) - Entity type
  • id (string) - Logical ID
  • version (number) - Version number (must be >= 1)

Returns:

interface ImmutableVersionExpanded {
type: string;
id: string;
version: number;
data: Record<string, unknown>;
userId?: string;
metadata?: Record<string, unknown>;
timestamp: number; // Unix timestamp (ms)
createdAt: number; // Unix timestamp (ms)
}
  • ImmutableVersionExpanded - Specific version with full context
  • null - If version doesn't exist or purged by retention

Example:

// Get version 1
const v1 = await cortex.immutable.getVersion("kb-article", "refund-policy", 1);

if (v1) {
console.log(`v1 content: ${v1.data.content}`);
console.log(`v1 created: ${v1.timestamp}`);
} else {
console.log("Version 1 purged by retention policy");
}

getHistory()

Get all versions of immutable data.

Signature:

cortex.immutable.getHistory(
type: string,
id: string
): Promise<ImmutableVersionExpanded[]>

Returns:

  • ImmutableVersionExpanded[] - All versions sorted by version number (subject to retention)
  • Empty array if entry doesn't exist

Example:

const history = await cortex.immutable.getHistory("policy", "max-refund");

console.log(`Policy has ${history.length} versions:`);
history.forEach((v) => {
console.log(`v${v.version} (${v.timestamp}): Value ${v.data.value}`);
console.log(` Changed by: ${v.metadata.publishedBy}`);
console.log(` Reason: ${v.data.reason}`);
});

getAtTimestamp()

Get version that was current at specific time.

Signature:

cortex.immutable.getAtTimestamp(
type: string,
id: string,
timestamp: number | Date
): Promise<ImmutableVersionExpanded | null>

Parameters:

  • type (string) - Entity type
  • id (string) - Logical ID
  • timestamp (number | Date) - Unix timestamp in milliseconds, or Date object

Example:

// What was the refund policy on January 1st? (using Date)
const policy = await cortex.immutable.getAtTimestamp(
"policy",
"max-refund",
new Date("2025-01-01"),
);

// Or using timestamp (ms)
const policy2 = await cortex.immutable.getAtTimestamp(
"policy",
"max-refund",
Date.parse("2025-01-01"),
);

if (policy) {
console.log(`Policy on Jan 1: $${policy.data.value}`);
} else {
console.log("Policy didn't exist yet or version purged");
}

list()

List immutable records with filtering.

Signature:

cortex.immutable.list(
filter?: ListImmutableFilter
): Promise<ImmutableRecord[]>

Parameters:

interface ListImmutableFilter {
type?: string; // Filter by entity type
userId?: string; // Filter by user ID
tenantId?: string; // Multi-tenancy filter (auto-injected from AuthContext if not provided)
limit?: number; // Max records to return (default: 50)
}

Returns:

  • ImmutableRecord[] - Array of immutable records sorted by creation time (newest first)

Example:

// List all KB articles
const articles = await cortex.immutable.list({
type: "kb-article",
limit: 50,
});

// List all records for a specific user
const userRecords = await cortex.immutable.list({
userId: "user-123",
limit: 20,
});

// List all records (up to default limit)
const allRecords = await cortex.immutable.list();

Search immutable data by content.

Signature:

cortex.immutable.search(
input: SearchImmutableInput
): Promise<ImmutableSearchResult[]>

Parameters:

interface SearchImmutableInput {
query: string; // Search query (required)
type?: string; // Filter by entity type
userId?: string; // Filter by user ID
limit?: number; // Max results (default: 10)
}

Returns:

interface ImmutableSearchResult {
entry: ImmutableRecord; // The matching record
score: number; // Relevance score
highlights: string[]; // Matched text snippets
}

Example:

// Search across all KB articles
const results = await cortex.immutable.search({
query: "refund process",
type: "kb-article",
limit: 20,
});

results.forEach((r) => {
console.log(`${r.entry.data.title} (score: ${r.score})`);
console.log(`Highlights: ${r.highlights.join(", ")}`);
});

// Search all immutable data
const allResults = await cortex.immutable.search({
query: "customer policy",
});

count()

Count immutable records.

Signature:

cortex.immutable.count(
filter?: CountImmutableFilter
): Promise<number>

Parameters:

interface CountImmutableFilter {
type?: string; // Filter by entity type
userId?: string; // Filter by user ID
tenantId?: string; // Multi-tenancy filter (auto-injected from AuthContext if not provided)
}

Example:

// Total KB articles
const total = await cortex.immutable.count({ type: "kb-article" });

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

// Count all immutable records
const allCount = await cortex.immutable.count();

purge()

Delete all versions of an immutable record.

Signature:

cortex.immutable.purge(
type: string,
id: string
): Promise<PurgeResult>

Returns:

interface PurgeResult {
deleted: boolean; // Whether deletion was successful
type: string;
id: string;
versionsDeleted: number;
}

Example:

// Delete all versions of an article
const result = await cortex.immutable.purge("kb-article", "old-article");

if (result.deleted) {
console.log(`Purged ${result.versionsDeleted} versions`);
}

Errors:

  • ImmutableValidationError('INVALID_TYPE') - Type is empty or invalid
  • ImmutableValidationError('INVALID_ID') - ID is empty or invalid
  • Error('IMMUTABLE_ENTRY_NOT_FOUND') - Entry doesn't exist (from Convex backend)
Warning

This deletes ALL versions. Vector memories with immutableRef will have broken references.


purgeMany()

Bulk delete immutable records matching a filter.

Signature:

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

Parameters:

interface PurgeManyFilter {
type?: string; // Filter by entity type
userId?: string; // Filter by user ID
}
Info

At least one filter (type or userId) must be provided to prevent accidental deletion of all records.

Returns:

interface PurgeManyResult {
deleted: number; // Number of entries deleted
totalVersionsDeleted: number; // Total versions across all entries
entries: Array<{ type: string; id: string }>; // Deleted entry identifiers
}

Example:

// Delete all audit logs
const result = await cortex.immutable.purgeMany({
type: "audit-log",
});

console.log(
`Deleted ${result.deleted} entries (${result.totalVersionsDeleted} total versions)`,
);

// Delete all records for a user (GDPR)
const userPurge = await cortex.immutable.purgeMany({
userId: "user-123",
});

console.log(`Purged ${userPurge.deleted} user records`);

purgeVersions()

Delete old versions while keeping recent ones (retention enforcement).

Signature:

cortex.immutable.purgeVersions(
type: string,
id: string,
keepLatest: number
): Promise<PurgeVersionsResult>

Parameters:

  • type (string) - Entity type
  • id (string) - Logical ID
  • keepLatest (number) - Number of most recent versions to keep (must be >= 1)

Returns:

interface PurgeVersionsResult {
versionsPurged: number; // Number of versions deleted
versionsRemaining: number; // Number of versions kept
}

Example:

// Keep only last 20 versions
const result = await cortex.immutable.purgeVersions(
"kb-article",
"guide-123",
20,
);

console.log(`Purged ${result.versionsPurged} old versions`);
console.log(`Kept ${result.versionsRemaining} versions`);

// Keep only latest 5 versions
await cortex.immutable.purgeVersions("policy", "refund-policy", 5);

Errors:

  • ImmutableValidationError('INVALID_TYPE') - Type is empty or invalid
  • ImmutableValidationError('INVALID_ID') - ID is empty or invalid
  • ImmutableValidationError('INVALID_KEEP_LATEST') - keepLatest must be a positive integer >= 1
  • Error('IMMUTABLE_ENTRY_NOT_FOUND') - Entry doesn't exist (from Convex backend)

Version Management

Automatic Version Creation

// First store - creates v1
const v1 = await cortex.immutable.store({
type: "kb-article",
id: "guide-1",
data: { content: "Version 1" },
});

// Same ID - creates v2 automatically
const v2 = await cortex.immutable.store({
type: "kb-article",
id: "guide-1", // Same ID
data: { content: "Version 2" }, // Updated content
});

// Get current (v2) with history
const current = await cortex.immutable.get("kb-article", "guide-1");
console.log(current.version); // 2
console.log(current.previousVersions[0].version); // 1

Common Types

KB Articles

await cortex.immutable.store({
type: "kb-article",
id: "how-to-refund",
data: {
title: "How to Process Refunds",
content: "1. Verify eligibility\n2. Process in system...",
category: "customer-service",
author: "training-team",
},
metadata: {
publishedBy: "admin",
tags: ["refunds", "how-to", "training"],
importance: 85,
},
});

Policies

await cortex.immutable.store({
type: "policy",
id: "max-refund-amount",
data: {
value: 5000,
currency: "USD",
effectiveDate: new Date(),
approvedBy: "ceo-agent",
reason: "Increased from $2000 for customer satisfaction",
},
metadata: {
publishedBy: "ceo-agent",
tags: ["policy", "financial", "refunds"],
importance: 100,
},
});

Audit Logs

await cortex.immutable.store({
type: "audit-log",
id: `audit-${Date.now()}`, // Unique ID per log entry
data: {
action: "REFUND_APPROVED",
agentId: "finance-agent",
userId: "user-123",
amount: 500,
reason: "Defective product",
timestamp: new Date(),
},
metadata: {
importance: 95,
tags: ["audit", "refund", "financial"],
},
});

Agent Reasoning

await cortex.immutable.store({
type: "agent-reasoning",
id: `reasoning-${agentId}-${Date.now()}`,
data: {
agentId: "support-agent",
userId: "user-123",
situation: "User seems frustrated",
reasoning: "Detected negative sentiment in last 3 messages",
decision: "Switch to empathetic tone",
confidence: 0.87,
},
metadata: {
importance: 70,
tags: ["reasoning", "sentiment", "tone-adjustment"],
},
});

Integration with Vector Layer

Indexing Immutable Data

// 1. Store in immutable (Layer 1b)
const article = await cortex.immutable.store({
type: "kb-article",
id: "refund-guide",
data: { title: "Refund Guide", content: "..." },
});

// 2. Index in Vector for searchability (Layer 2)
await cortex.vector.store("kb-agent", {
content: `${article.data.title}: ${article.data.content}`,
contentType: "raw", // or 'summarized'
embedding: await embed(article.data.content),
source: { type: "system", timestamp: new Date() },
immutableRef: {
// Link to immutable store
type: article.type,
id: article.id,
version: article.version,
},
metadata: {
importance: 85,
tags: ["kb", "refunds"],
},
});

// 3. All agents can search
const results = await cortex.memory.search("support-agent", "refund policy");

// 4. Retrieve full article via immutableRef
if (results[0].immutableRef) {
const fullArticle = await cortex.immutable.get(
results[0].immutableRef.type,
results[0].immutableRef.id,
);
console.log("Full article:", fullArticle.data);
}

Best Practices

1. Use Meaningful Types

// Good types
"kb-article";
"policy-refund";
"audit-log-financial";
"agent-reasoning";

// Avoid: Generic types
"data";
"item";
"thing";

2. Version-Aware IDs

// Good: Logical IDs that can be versioned
"refund-policy"; // Same ID, multiple versions
"guide-refunds";
"config-max-users";

// Avoid: Timestamped IDs (defeats versioning)
"refund-policy-2025-10-24"; // Each is separate, no version tracking

3. Store Complete Data

// Good: Self-contained
await cortex.immutable.store({
type: "kb-article",
id: "guide-1",
data: {
title: "...",
content: "...", // Full content
author: "...",
publishedDate: new Date(),
category: "...",
},
});

// Avoid: Partial data requiring joins
await cortex.immutable.store({
type: "kb-article",
id: "guide-1",
data: {
authorId: "user-123", // Requires lookup elsewhere
},
});

4. Index Important Data in Vector

// Store in immutable for source of truth
const policy = await cortex.immutable.store({
type: "policy",
id: "refund-max",
data: { value: 5000 },
});

// Index in Vector for searchability
await cortex.vector.store("policy-agent", {
content: `Maximum refund amount is $${policy.data.value}`,
contentType: "raw",
embedding: await embed("maximum refund amount"),
immutableRef: { type: "policy", id: "refund-max", version: policy.version },
metadata: { importance: 90, tags: ["policy", "refunds"] },
});

Retention & Purging

Manual Purging

Use these methods to manage storage and enforce retention policies:

// Purge specific entity (all versions)
await cortex.immutable.purge("kb-article", "outdated-guide");

// Purge by type or user
await cortex.immutable.purgeMany({
type: "agent-reasoning",
});

// Clean up old versions (keep latest 20)
await cortex.immutable.purgeVersions("kb-article", "guide-123", 20);

Governance Integration

For automated retention enforcement, use the Governance API:

// See Governance Policies API for multi-layer retention rules
await cortex.governance.enforce({
layers: ["immutable"],
rules: ["retention"],
});

Use Cases

Use Case 1: Knowledge Base

// Publish article
await cortex.immutable.store({
type: "kb-article",
id: "troubleshooting-login",
data: {
title: "Troubleshooting Login Issues",
content: "...",
sections: ["Check credentials", "Clear cache", "Contact support"],
},
});

// Update article (creates v2)
await cortex.immutable.store({
type: "kb-article",
id: "troubleshooting-login",
data: {
title: "Troubleshooting Login Issues",
content: "... updated with new section ...",
sections: [
"Check credentials",
"Clear cache",
"Reset 2FA",
"Contact support",
],
},
});

// Agents access current version
const guide = await cortex.immutable.get("kb-article", "troubleshooting-login");

Use Case 2: Policy Management

// Set initial policy
await cortex.immutable.store({
type: "policy",
id: "refund-window",
data: {
days: 30,
effectiveDate: new Date("2025-01-01"),
approvedBy: "board",
},
});

// Update policy (v2)
await cortex.immutable.store({
type: "policy",
id: "refund-window",
data: {
days: 60, // Extended
effectiveDate: new Date("2025-06-01"),
approvedBy: "board",
reason: "Customer satisfaction initiative",
},
});

// Agents check policy
const policy = await cortex.immutable.get("policy", "refund-window");
if (daysSincePurchase <= policy.data.days) {
// Approve refund
}

Use Case 3: Audit Trail

// Log every significant action
async function logAction(action: string, agentId: string, data: any) {
await cortex.immutable.store({
type: "audit-log",
id: `${action}-${Date.now()}`, // Unique per action
data: {
action,
agentId,
...data,
timestamp: Date.now(),
},
metadata: {
importance: 95,
tags: ["audit", action.toLowerCase()],
},
});
}

// Query audit trail by type
const auditLogs = await cortex.immutable.list({
type: "audit-log",
limit: 100,
});

// Search audit logs
const searchResults = await cortex.immutable.search({
query: "finance-agent",
type: "audit-log",
limit: 50,
});

Graph-Lite Capabilities

Immutable records can be graph nodes (especially Facts and KB Articles):

Immutable Record as Graph Node:

  • Shared across all agents (unlike memories which are agent-private)
  • Referenced by vector memories via immutableRef

Edges:

  • immutableRef from Memories (many-to-one)
  • userId to User (if user-generated content)
  • conversationRef to Conversation (if derived from conversation)

Graph Pattern - Facts:

// Fact → User (via userId)
const userFacts = await cortex.immutable.list({
type: "fact",
userId: "user-123",
});

// Fact → Conversation (via conversationRef in data)
const fact = await cortex.immutable.get("fact", "fact-abc123");
if (fact?.data.conversationRef) {
const sourceConvo = await cortex.conversations.get(
fact.data.conversationRef.conversationId,
);
}

// Complete graph:
// Fact-abc123
// ├──[userId]──────────> User-123
// ├──[conversationRef]──> Conversation-456
// └──[referenced by]<──── Memory-xyz (via immutableRef)

Use Case: Facts with entity relationships can be synced to graph databases for knowledge graph construction.

Learn more: Graph Database Integration


Summary

Immutable Store provides:

  • Shared knowledge across all agents
  • Automatic versioning (no data loss)
  • Temporal queries (what was true when)
  • ACID guarantees
  • Configurable retention
  • Full audit trail

Use for:

  • Knowledge base articles
  • Policies and rules
  • Audit logs
  • Agent reasoning traces
  • Static reference data
  • Anything that should be append-only with versions

Don't use for:

  • Private conversations (use cortex.conversations.*)
  • Live/frequently changing data (use cortex.mutable.*)
  • Searchable memories (use cortex.vector.* referencing immutable)

Next Steps


Questions? Ask in GitHub Discussions.