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)
ScopingmemorySpace**NO scoping****NO scoping**memorySpacememorySpace
PrivacyPrivate to space**TRULY Shared****TRULY Shared**Private 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.