src — session-pruning

Module: src-session-pruning Cohesion: 0.80 Members: 0

src — session-pruning

The session-pruning module provides a robust and configurable system for automatically managing the lifecycle of "prunable items" such as messages, memories, and other session-related data. Its primary goal is to prevent unbounded growth of session data, ensuring efficient resource usage and adherence to defined retention policies.

This module allows developers to define rules based on various conditions (e.g., age, count, size, token usage, type) and specify actions to take when these conditions are met (e.g., delete, archive, summarize, compact).

Core Concepts

At the heart of the session pruning module are several key interfaces and classes that define how pruning is configured and executed.

PrunableItem

The PrunableItem interface represents any piece of data that can be subject to pruning. It includes essential metadata required for evaluation:

interface PrunableItem {
  id: string;
  sessionId: string;
  type: 'message' | 'memory' | 'checkpoint' | 'file';
  createdAt: Date;
  accessedAt?: Date;
  sizeBytes: number;
  tokens?: number;
  metadata?: Record<string, unknown>;
  content?: string; class="hl-cmt">// For summarization/compaction
}

When integrating with this module, your data structures must conform to PrunableItem to be managed by the PruningManager.

Pruning Rules

Pruning logic is encapsulated in PruningRule objects. Each rule consists of:

Pruning Conditions

PruningConditions define the criteria for an item to be considered for pruning. The module supports several built-in condition types:

Pruning Actions

PruningActions specify what happens to an item once a rule's conditions are met:

Pruning Configuration

The PruningConfig interface defines the global settings for the PruningManager:

interface PruningConfig {
  enabled: boolean;
  rules: PruningRule[];
  checkIntervalMs: number; class="hl-cmt">// How often to check if pruning is needed
  minPruneIntervalMs: number; class="hl-cmt">// Minimum time between actual pruning runs
  dryRun: boolean; class="hl-cmt">// If true, items are identified but not modified
  sessionConfigs?: Record<string, SessionPruningConfig>; class="hl-cmt">// Session-specific overrides
}

The DEFAULT_PRUNING_CONFIG provides sensible defaults, including rules for age, count, and token limits.

SessionPruningConfig allows overriding global rules or exempting specific sessions from pruning.

The PruningManager

The PruningManager class is the central orchestrator of the session pruning module. It extends EventEmitter to provide real-time feedback on pruning operations.

Manager Lifecycle

  1. Instantiation: A PruningManager instance is created, optionally with an initial PruningConfig.
    const manager = new PruningManager({ dryRun: true });

  1. Configuration: Rules, session-specific settings, and custom evaluators can be added or modified dynamically using methods like addRule(), setSessionConfig(), and registerEvaluator().
  2. Item Management: PrunableItems are added to the manager using addItem(). The manager maintains an internal collection of these items.
  3. Automatic Pruning: Call manager.start() to begin periodic checks and pruning based on checkIntervalMs.
  4. Manual Pruning: Call manager.prune() to trigger an immediate pruning run.
  5. Stopping: Call manager.stop() to halt automatic pruning.

Configuration Management

The PruningManager provides methods to manage its configuration:

Item Management

The manager acts as a simple in-memory store for PrunableItems:

Pruning Operations

The core pruning logic resides in checkAndPrune() and prune().

checkAndPrune()

This method is called periodically when the manager is start()ed. It first checks:

  1. If pruning is enabled in the configuration.
  2. If minPruneIntervalMs has passed since the last prune.
  3. If any global thresholds (total items, tokens, or size) defined in the rules are exceeded via shouldPrune().

If all checks pass, it triggers an actual prune() operation.

prune()

This is the main entry point for executing a pruning run. It performs the following steps:

graph TD
    A[PruningManager.prune()] --> B{Get Items & Build Context};
    B --> C[buildEvaluationContext()];
    B --> D[findCandidates(items, context)];
    D --> E{For each candidate};
    E -- Session Exempt? --> F[Skip Item];
    E -- Not Exempt --> G[executeAction(candidate)];
    G --> H[Add to prunedItems];
    F --> I[Add to skippedItems];
    H --> E;
    I --> E;
    E -- All candidates processed --> J[calculateStats()];
    J --> K[Emit 'complete' event];
    K --> L[Return PruningResult];

  1. Initialization: Sets up tracking for prunedItems, skippedItems, and errors. Emits a start event.
  2. Item Selection: Gathers all PrunableItems, optionally filtering by sessionId if specified.
  3. Context Building: Calls buildEvaluationContext() to prepare SessionStats and GlobalStats for efficient condition evaluation.
  4. Candidate Identification: findCandidates() iterates through items and rules. For each item, it checks if all conditions of any enabled rule are met using evaluateConditions(). The first matching rule determines the candidate. Candidates are sorted by rule priority.
  5. Action Execution: For each PruningCandidate:

  1. Statistics & Completion: After processing all candidates, calculateStats() compiles a PruningStats summary. A complete event is emitted with the PruningResult.

Condition Evaluation

The conditionEvaluators map holds functions that implement the logic for each PruningCondition type. When evaluateConditions() is called, it retrieves the appropriate evaluator based on condition.type and executes it.

Developers can extend this by registering custom evaluators using registerEvaluator(). This allows for highly specific pruning logic without modifying the core module.

manager.registerEvaluator(&#39;myCustomCondition&#39;, (item, condition, context) => {
  if (condition.type !== &#39;custom&#39; || condition.fn !== &#39;myCustomCondition&#39;) return false;
  class="hl-cmt">// Example: Prune if item metadata has a specific flag
  return item.metadata?.[&#39;doNotKeep&#39;] === true;
});

class="hl-cmt">// Then, define a rule:
manager.addRule({
  id: &#39;custom-flag-prune&#39;,
  name: &#39;Prune items with custom flag&#39;,
  priority: 50,
  enabled: true,
  conditions: [{ type: &#39;custom&#39;, fn: &#39;myCustomCondition&#39; }],
  action: { type: &#39;delete&#39; },
});

Statistics

The getStats() method provides a snapshot of the current configuration, global statistics (GlobalStats), and per-session statistics (SessionStats). This is useful for monitoring the state of managed items and understanding pruning triggers.

Singleton Access

The module provides singleton access to the PruningManager for convenience:

Events

The PruningManager extends EventEmitter and emits several events during its operation, allowing external components to react to pruning activities:

Integration Points

To integrate the session-pruning module:

  1. Initialize: Get the PruningManager instance using getPruningManager().
  2. Configure: Define your PruningRules and PruningConfig. Add rules using manager.addRule().
  3. Add Items: Whenever a new PrunableItem (e.g., a new message, memory entry) is created, add it to the manager using manager.addItem().
  4. Start Automatic Pruning: Call manager.start() to enable continuous monitoring and pruning.
  5. Listen to Events: Subscribe to relevant events (e.g., item-pruned, error, complete) to handle pruned items, log issues, or update UI.
import { getPruningManager, PruningManager, PrunableItem } from &#39;./session-pruning/index.js&#39;;

const manager: PruningManager = getPruningManager({
  enabled: true,
  checkIntervalMs: 30 * 1000, class="hl-cmt">// Check every 30 seconds
  rules: [
    {
      id: &#39;my-app-rule&#39;,
      name: &#39;Delete old messages&#39;,
      priority: 100,
      enabled: true,
      conditions: [{ type: &#39;age&#39;, maxAgeMs: 7 * 24 * 60 * 60 * 1000 }], class="hl-cmt">// 7 days
      action: { type: &#39;delete&#39; },
    },
  ],
});

manager.on(&#39;item-pruned&#39;, (prunedItem) => {
  console.log(`Item ${prunedItem.item.id} was ${prunedItem.action} because: ${prunedItem.reason}`);
  class="hl-cmt">// Here you might update your database or UI
});

manager.on(&#39;error&#39;, (error) => {
  console.error(&#39;Pruning error:&#39;, error.error, error.item?.id);
});

manager.on(&#39;complete&#39;, (result) => {
  console.log(`Pruning run completed. Pruned: ${result.stats.prunedCount}, Skipped: ${result.stats.skippedCount}`);
});

class="hl-cmt">// Start automatic pruning
manager.start();

class="hl-cmt">// Example: Add an item
const message: PrunableItem = {
  id: &#39;msg-123&#39;,
  sessionId: &#39;session-abc&#39;,
  type: &#39;message&#39;,
  createdAt: new Date(),
  sizeBytes: 100,
  tokens: 20,
  content: &#39;Hello, this is a message.&#39;,
};
manager.addItem(message);

class="hl-cmt">// You can also trigger a manual prune
class="hl-cmt">// manager.prune({ force: true });