All files / lib/memory long_term_memory.ts

0% Statements 0/69
0% Branches 0/1
0% Functions 0/1
0% Lines 0/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                                                                                                                                                                                       
/**
 * Phase 6.1 — LongTermMemory
 * - Append snapshots (from Phase 5 via toSnapshot/createSnapshot)
 * - Compute rolling metrics: stability, adaptationRate, evolutionScore
 * - Persist to JSON (storage/phase6_long_term_memory.json)
 *
 * Also acts as the backing store for the alias "MemoryKernel" (see memory_kernel.ts).
 */
 
import fs from 'node:fs';
import { Snapshot, LongTermState, LongTermMetrics } from '../../types/core';
import { Phase6Config } from '../../config/phase6.config';
 
/** ---------- load / save ---------- */
function loadState(): LongTermState {
  if (!fs.existsSync(Phase6Config.storagePath)) {
    return { snapshots: [], metrics: null };
  }
  try {
    const parsed = JSON.parse(fs.readFileSync(Phase6Config.storagePath, 'utf-8'));
    return {
      snapshots: Array.isArray(parsed?.snapshots) ? parsed.snapshots : [],
      metrics: parsed?.metrics ?? null,
    };
  } catch {
    return { snapshots: [], metrics: null };
  }
}
 
function saveState(state: LongTermState) {
  fs.mkdirSync('./storage', { recursive: true });
  fs.writeFileSync(Phase6Config.storagePath, JSON.stringify(state, null, 2));
}
 
/** ---------- metrics ---------- */
function computeMetrics(snapshots: Snapshot[]): LongTermMetrics {
  const win = Phase6Config.metricsWindow;
  const recent = snapshots.slice(-win);
 
  // stability = variance of all outcome scores in the recent window (lower is better)
  const scores = recent.flatMap((s) => s.outcomes.map((o) => o.score));
  const mean = avg(scores);
  const variance = avg(scores.map((x) => (x - mean) ** 2));
  const stability = variance || 0;
 
  // adaptationRate = mean(delta of average snapshot score) across the recent window
  const deltas: number[] = [];
  for (let i = 1; i < recent.length; i++) {
    const prev = snapshotMean(recent[i - 1]);
    const curr = snapshotMean(recent[i]);
    deltas.push(curr - prev);
  }
  const adaptationRate = avg(deltas) || 0;
 
  // evolutionScore = improvement discounted by instability
  const evolutionScore = Math.max(0, adaptationRate - stability);
 
  return { stability, adaptationRate, evolutionScore };
}
 
function snapshotMean(s: Snapshot) {
  const vals = s.outcomes.map((o) => o.score);
  return avg(vals);
}
const avg = (arr: number[]) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0);
 
/** ---------- public API ---------- */
export const LongTermMemory = {
  /**
   * Append a snapshot, trim to rolling window, recompute metrics, persist.
   */
  appendSnapshot(snapshot: Snapshot) {
    const state = loadState();
    state.snapshots.push(snapshot);
 
    if (state.snapshots.length > Phase6Config.maxSnapshots) {
      state.snapshots = state.snapshots.slice(-Phase6Config.maxSnapshots);
    }
 
    state.metrics = computeMetrics(state.snapshots);
    saveState(state);
    return state;
  },
 
  /**
   * Load in-memory state (snapshots + metrics) from disk.
   */
  getState(): LongTermState {
    return loadState();
  },
};