Domain: Retention — Function Reference

Lifecycle governance for memory, RAG (Retrieval-Augmented Generation), and audit data. Manages retention policies for two isolated domains: memory and rag. Policies are persisted atomically in data/state/retention-policies.json. Provides rules evaluation (eligible vs. protected) and archive manifest creation. Does NOT directly delete records — callers use the eligible list.

MCP Tools Exposed

Exposed through unified controller. Key tool names: retention_configure, retention_get, retention_apply, retention_archive, retention_policies.

Tool Name Description Key Inputs Returns
retention_configure Set retention policy for a domain domain: memory\|rag, config: Partial<Policy> { success, domain, policy, updated_at }
retention_get Get current policy for a domain domain: memory\|rag RetentionPolicy object
retention_apply Evaluate resources against policy domain, resources[], options? { success, eligible[], protected[], counts }
retention_archive Archive resources to cold storage domain, resources[], options? { success, archive_id, manifest_path, resource_count, total_size_bytes }
retention_policies Get all policies (legacy) none { version, updated_at, memory, rag }

Core Functions

configureRetentionPolicy(domain, config)

Purpose: Merge partial config onto existing policy with bounds clamping, then save. Parameters: domain: "memory"|"rag", config: Partial<RetentionPolicy> Returns: { success, domain, policy, updated_at } Side effects: Writes atomically to data/state/retention-policies.json. Notes for rewrite: Enforces INV-RET-1 (atomic write), INV-RET-4 (bounds), INV-RET-8 (ISO timestamp).

getRetentionPolicy(domain)

Purpose: Read the current retention policy for a domain (no mutation). Parameters: domain: "memory"|"rag" Returns: RetentionPolicy object.

applyRetentionRules(domain, resources, options)

Purpose: Classify resources into eligible (can be deleted) vs. protected. Does NOT delete anything. Parameters: domain: "memory"|"rag", resources: Resource[], options?: object Returns: { success, domain, cutoff_date, policy_applied, eligible[], protected[], counts: { eligible, protected, total } } Algorithm: See filtering logic below.

getRetentionPolicies()

Purpose: Return both policies together. DEPRECATED — use getRetentionPolicy(domain). Returns: { version, updated_at, memory, rag }

setRetentionPolicy(domain, patch)

Purpose: Alias for configureRetentionPolicy. DEPRECATED.

resolveGcParamsWithPolicy(domain, rawArgs, validatedArgs)

Purpose: Merge explicit GC call arguments with policy defaults. Explicit args win over policy defaults. Parameters: domain, rawArgs: object (raw tool args), validatedArgs: object (validated) Returns: GcParams — all GC parameters resolved to final values. Notes for rewrite: Used by GC tools to apply policy-based defaults when tool caller omits parameters.

Database Operations

None. All persistence is via JSON files in data/state/ and data/archive/.

Key Algorithms

Atomic Write (atomicWriteFileSync)

tmpPath = filePath + ".tmp-" + pid + "-" + timestamp
writeFileSync(tmpPath, data)
renameSync(tmpPath, filePath)    // atomic
on error: unlinkSync(tmpPath), rethrow

Policy Normalization (normalizePolicy)

older_than_days: clamp(value, 1, 3650, fallback)    // INV-RET-4
source_types: filter to { context, action, thought }, dedup, fallback if empty  // INV-RET-3
keep_session_ids: dedup string array
keep_context_ids: dedup positive integer array
limit: clamp(value, 1, 20000, fallback)              // INV-RET-4
vacuum: boolean, fallback if non-boolean
auto_gc: boolean, fallback if non-boolean

Retention Rules Evaluation (applyRetentionRules)

cutoffDate = now - older_than_days days
keepSessionIds = Set(policy.keep_session_ids)
keepContextIds = Set(policy.keep_context_ids)

for each resource:
  if resource.session_id in keepSessionIds:      protected (reason: protected_session)
  elif resource.context_id in keepContextIds:    protected (reason: protected_context)
  elif resource.source_type not in ALLOWED_SOURCE_TYPES: protected (reason: invalid_source_type)
  elif resource.created_at > cutoffDate:         protected (reason: within_retention_period)
  else: eligible

limitedEligible = eligible.slice(0, policy.limit)

RetentionPolicy Schema

{
  older_than_days: number (1-3650, default 30)
  source_types: string[] (subset of ["context","action","thought"])
  keep_session_ids: string[]
  keep_context_ids: number[]
  limit: number (1-20000, default 5000)
  vacuum: boolean (default false)
  auto_gc: boolean (default false)
}

8 Core Invariants

  1. INV-RET-1: All policy updates are atomic (write-to-temp-then-rename)
  2. INV-RET-2: memory and rag policies are isolated — changes to one never affect the other
  3. INV-RET-3: Only {context, action, thought} are valid source types; invalid types filtered at normalization
  4. INV-RET-4: older_than_days clamped to [1, 3650]; limit clamped to [1, 20000]
  5. INV-RET-5: Resources in keep_session_ids or keep_context_ids are never eligible
  6. INV-RET-6: DEFAULT_POLICY is Object.freeze()d — modifications always create new objects
  7. INV-RET-7: State includes version field for migration support
  8. INV-RET-8: updated_at is always ISO 8601, set on every mutation

Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.