tests — session-pruning

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

tests — session-pruning

This document describes the Session Pruning module, located at src/session-pruning/index.js and src/session-pruning/pruning-manager.ts. This module provides a robust and configurable mechanism for automatically managing and reducing the size of session-related data based on defined rules.

The primary goal of this module is to prevent unbounded growth of session data, ensuring efficient resource usage and maintaining system performance by proactively identifying and acting upon items that meet specific criteria (e.g., age, size, type).

Core Concepts

The session pruning module revolves around a few key concepts:

  1. PrunableItem: Any piece of data that can be subject to pruning. It must have an id, sessionId, type, createdAt timestamp, and metrics like sizeBytes and tokens.
  2. PruningRule: Defines the criteria for pruning and the action to take. Rules consist of conditions (what makes an item prunable) and an action (what to do with it). Rules have a priority to determine application order.
  3. PruningManager: The central class responsible for managing items, rules, configuration, and executing the pruning process. It can be configured globally or on a per-session basis.

PruningManager Overview

The PruningManager is the orchestrator of the session pruning process. It maintains a collection of PrunableItems and PruningRules, and periodically (or on demand) evaluates items against these rules.

Instantiation and Lifecycle

You can create a PruningManager instance directly or use the provided singleton accessors.

import { PruningManager, getPruningManager, resetPruningManager } from '../../src/session-pruning/index.js';

class="hl-cmt">// Direct instantiation
const manager = new PruningManager({
  enabled: true,
  rules: [],
  checkIntervalMs: 5000, class="hl-cmt">// Check every 5 seconds
  minPruneIntervalMs: 1000, class="hl-cmt">// Don't prune more often than every 1 second
  dryRun: false,
});

class="hl-cmt">// Singleton access
const singletonManager = getPruningManager();

class="hl-cmt">// Resetting the singleton (primarily for testing)
resetPruningManager();

The manager can be stopped to halt any background pruning checks:

manager.stop();

Architecture Diagram

classDiagram
    direction LR
    class PruningManager {
        +PruningManager(config)
        +addItem(item: PrunableItem)
        +addRule(rule: PruningRule)
        +prune(options?): Promise<PruningResult>
        +on(event, handler)
        +getStats(): PruningStats
        +stop()
        +registerEvaluator(name, fn)
    }
    class PrunableItem {
        +id: string
        +sessionId: string
        +type: string
        +createdAt: Date
        +sizeBytes: number
        +tokens: number
        +content?: string
        +metadata?: object
    }
    class PruningRule {
        +id: string
        +name: string
        +priority: number
        +enabled: boolean
        +conditions: PruningCondition[]
        +action: PruningAction
    }
    class PruningResult {
        +success: boolean
        +prunedItems: PrunedItemInfo[]
        +skippedItems: PrunedItemInfo[]
        +stats: PruningStats
    }

    PruningManager "1" -- "*" PrunableItem : manages
    PruningManager "1" -- "*" PruningRule : applies
    PruningManager "1" -- "1" PruningResult : returns

Configuration

The PruningManager supports both global and session-specific configurations.

Global Configuration

The global configuration dictates the overall behavior of the pruning process.

import { DEFAULT_PRUNING_CONFIG } from &#39;../../src/session-pruning/index.js&#39;;

class="hl-cmt">// Get current config
const config = manager.getConfig();

class="hl-cmt">// Update config
manager.updateConfig({
  enabled: false, class="hl-cmt">// Disable pruning globally
  dryRun: true,   class="hl-cmt">// Perform dry runs only
});

class="hl-cmt">// Default configuration is used if not provided
const defaultManager = new PruningManager();
expect(defaultManager.getConfig().enabled).toBe(DEFAULT_PRUNING_CONFIG.enabled);

Session-Specific Configuration

Individual sessions can have their own pruning settings, such as being exempt from pruning.

class="hl-cmt">// Exempt &#39;session-1&#39; from pruning
manager.setSessionConfig(&#39;session-1&#39;, { exempt: true });

class="hl-cmt">// Remove session-specific config
manager.removeSessionConfig(&#39;session-1&#39;);

Item Management

The PruningManager acts as a repository for PrunableItems.

import { type PrunableItem } from &#39;../../src/session-pruning/index.js&#39;;

class="hl-cmt">// Helper to create a test item
function createItem(overrides: Partial<PrunableItem> = {}): PrunableItem {
  return {
    id: `item-${Math.random().toString(36).slice(2)}`,
    sessionId: &#39;session-1&#39;,
    type: &#39;message&#39;,
    createdAt: new Date(),
    sizeBytes: 100,
    tokens: 50,
    ...overrides,
  };
}

const item1 = createItem({ sessionId: &#39;session-1&#39; });
const item2 = createItem({ sessionId: &#39;session-2&#39; });

class="hl-cmt">// Add items
manager.addItem(item1);
manager.addItem(item2);

class="hl-cmt">// Retrieve items
const retrievedItem = manager.getItem(item1.id);
const allItems = manager.getAllItems();
const session1Items = manager.getSessionItems(&#39;session-1&#39;);

class="hl-cmt">// Remove items
manager.removeItem(item1.id); class="hl-cmt">// Removes a specific item
manager.clearItems();        class="hl-cmt">// Removes all items

Items that are archived by a rule are moved to a separate collection:

const archivedItems = manager.getArchivedItems();

Pruning Rules

Pruning rules define what to prune and how. Each rule has an id, name, priority, enabled status, conditions, and an action.

import { type PruningRule } from &#39;../../src/session-pruning/index.js&#39;;

const myRule: PruningRule = {
  id: &#39;my-custom-rule&#39;,
  name: &#39;Delete old messages&#39;,
  priority: 100, class="hl-cmt">// Higher priority rules are evaluated first
  enabled: true,
  conditions: [
    { type: &#39;age&#39;, maxAgeMs: 24 * 60 * 60 * 1000 }, class="hl-cmt">// Older than 24 hours
    { type: &#39;type&#39;, messageTypes: [&#39;message&#39;], include: true }, class="hl-cmt">// Only messages
  ],
  action: { type: &#39;delete&#39; },
};

class="hl-cmt">// Add a rule
manager.addRule(myRule);

class="hl-cmt">// Update a rule (by ID)
manager.addRule({ ...myRule, priority: 200 });

class="hl-cmt">// Enable/disable a rule
manager.setRuleEnabled(&#39;my-custom-rule&#39;, false);

class="hl-cmt">// Remove a rule
manager.removeRule(&#39;my-custom-rule&#39;);

Pruning Conditions

Conditions determine if an item should be pruned. Multiple conditions in a rule are typically combined with an AND logic.

    { type: &#39;age&#39;, maxAgeMs: 1000 * 60 * 60 * 24 } class="hl-cmt">// Older than 24 hours
    { type: &#39;size&#39;, maxBytes: 1024 * 1024 } class="hl-cmt">// Larger than 1MB
    { type: &#39;tokens&#39;, maxTokens: 500 } class="hl-cmt">// More than 500 tokens
    { type: &#39;type&#39;, messageTypes: [&#39;checkpoint&#39;, &#39;system&#39;], include: true }
    { type: &#39;type&#39;, messageTypes: [&#39;user_message&#39;], include: false }
    class="hl-cmt">// Register a custom evaluator
    manager.registerEvaluator(&#39;isImportant&#39;, (item: PrunableItem) => {
      return item.metadata?.important === true;
    });

    class="hl-cmt">// Use it in a rule
    const customRule: PruningRule = {
      id: &#39;custom-important-rule&#39;,
      name: &#39;Prune important items&#39;,
      priority: 10,
      enabled: true,
      conditions: [{ type: &#39;custom&#39;, fn: &#39;isImportant&#39; }],
      action: { type: &#39;delete&#39; },
    };
    manager.addRule(customRule);

Pruning Actions

Actions define what happens to an item once it meets a rule's conditions.

    { type: &#39;delete&#39; }
    { type: &#39;archive&#39;, destination: &#39;long-term-storage&#39; }
    { type: &#39;summarize&#39;, targetTokens: 50 }
    { type: &#39;compact&#39;, ratio: 0.5 } class="hl-cmt">// Reduce size/tokens by 50%

Rule Priority

Rules are applied in order of their priority (higher number first). If an item is pruned by a higher-priority rule, lower-priority rules for that item are skipped.

const lowPriorityRule: PruningRule = { /* ... action: archive */ priority: 10 };
const highPriorityRule: PruningRule = { /* ... action: delete */ priority: 100 };

manager.addRule(lowPriorityRule);
manager.addRule(highPriorityRule);

class="hl-cmt">// If an item matches both, the &#39;delete&#39; action (high priority) will be applied.

Executing Pruning

Pruning can be triggered manually or automatically by the manager's internal timer (if checkIntervalMs is set and enabled is true).

import { type PruningResult } from &#39;../../src/session-pruning/index.js&#39;;

class="hl-cmt">// Trigger pruning manually
const result: PruningResult = await manager.prune();

if (result.success) {
  console.log(`Pruned ${result.prunedItems.length} items.`);
  console.log(`Freed ${result.stats.freedBytes} bytes and ${result.stats.freedTokens} tokens.`);
} else {
  console.error(&#39;Pruning failed:&#39;, result.error);
}

Dry Run Mode

When dryRun is enabled in the configuration, the prune() method will identify items that would be pruned but will not modify the actual item store. The PruningResult will still contain prunedItems with a reason indicating it was a dry run.

manager.updateConfig({ dryRun: true });
const result = await manager.prune();
class="hl-cmt">// result.prunedItems will contain items, but manager.getAllItems() will be unchanged.

Session Exemption

Sessions can be marked as exempt from pruning via setSessionConfig. Items belonging to exempt sessions will be skipped unless prune() is called with force: true.

manager.setSessionConfig(&#39;session-1&#39;, { exempt: true });

class="hl-cmt">// Items in &#39;session-1&#39; will be skipped
await manager.prune();

class="hl-cmt">// Items in &#39;session-1&#39; will be pruned despite exemption
await manager.prune({ force: true });

Events

The PruningManager extends EventEmitter and emits various events during the pruning process, allowing external components to react to its lifecycle.

manager.on(&#39;start&#39;, () => {
  console.log(&#39;Pruning process started.&#39;);
});

manager.on(&#39;progress&#39;, (progress: { scanned: number; total: number }) => {
  console.log(`Pruning progress: ${progress.scanned}/${progress.total} items scanned.`);
});

manager.on(&#39;item-pruned&#39;, (itemInfo: PruningResult[&#39;prunedItems&#39;][0]) => {
  console.log(`Item ${itemInfo.item.id} pruned by rule ${itemInfo.ruleId} with action ${itemInfo.action}.`);
});

manager.on(&#39;complete&#39;, (result: PruningResult) => {
  console.log(&#39;Pruning process complete.&#39;, result);
});

Statistics

The manager provides real-time statistics about the items it manages and detailed statistics about each pruning run.

Global Statistics

const stats = manager.getStats();
console.log(&#39;Global Stats:&#39;, {
  totalItems: stats.globalStats.totalItems,
  totalBytes: stats.globalStats.totalBytes,
  totalTokens: stats.globalStats.totalTokens,
  totalSessions: stats.globalStats.totalSessions,
});

Pruning Run Statistics

The PruningResult object returned by prune() includes statistics specific to that run.

const result = await manager.prune();
console.log(&#39;Pruning Run Stats:&#39;, {
  scannedCount: result.stats.scannedCount,
  prunedCount: result.stats.prunedCount,
  skippedCount: result.stats.skippedCount,
  freedBytes: result.stats.freedBytes,
  freedTokens: result.stats.freedTokens,
  durationMs: result.stats.durationMs,
});