Immutable Store API
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
tenantIdfor SaaS isolation (auto-injected from AuthContext)
Comparison to Other Stores:
| Feature | Conversations (1a) | Immutable (1b) | Mutable (1c) | Vector (2) | Facts (3) |
|---|---|---|---|---|---|
| Scoping | memorySpace | NO scoping | NO scoping | memorySpace | memorySpace |
| Privacy | Private to space | TRULY Shared | TRULY Shared | Private to space | Private to space |
| Mutability | Immutable | Immutable | Mutable | Mutable | Immutable |
| Versioning | N/A (append) | Auto | No | Auto | Auto |
| Retention | 7 years | 20 versions | N/A | 10 versions | Unlimited |
| Use Case | Chats | KB, policies | Live data | Search index | Extracted facts |
Core Operations
store()
Store immutable data. Creates v1 or increments version if ID exists.
Signature:
cortex.immutable.store(
entry: ImmutableEntry
): Promise<ImmutableRecord>
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 invalidImmutableValidationError('INVALID_ID')- ID is empty or invalidImmutableValidationError('INVALID_DATA')- Data is not a valid objectImmutableValidationError('INVALID_METADATA')- Metadata is not a valid objectImmutableValidationError('MISSING_REQUIRED_FIELD')- Required field missing
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 typeid(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 invalidImmutableValidationError('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 typeid(string) - Logical IDversion(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 contextnull- 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 typeid(string) - Logical IDtimestamp(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()
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 invalidImmutableValidationError('INVALID_ID')- ID is empty or invalidError('IMMUTABLE_ENTRY_NOT_FOUND')- Entry doesn't exist (from Convex backend)
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
}
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 typeid(string) - Logical IDkeepLatest(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 invalidImmutableValidationError('INVALID_ID')- ID is empty or invalidImmutableValidationError('INVALID_KEEP_LATEST')- keepLatest must be a positive integer >= 1Error('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:
immutableReffrom Memories (many-to-one)userIdto User (if user-generated content)conversationRefto 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.