Skip to main content

Overview

The memory graph is an append-only, event-sourced causal memory system that records signals, hypotheses, attempts, and outcomes to provide learning and adaptation over time. Location: src/gep/memoryGraph.js

Core Functions

recordSignalSnapshot()

Record current signals as a first-class node. Location: src/gep/memoryGraph.js:314
function recordSignalSnapshot({ signals, observations })
signals
string[]
required
Current signals extracted from logs
observations
object
required
System observations (health, scan time, error count, etc.)
event
MemoryGraphEvent
Created MemoryGraphEvent with kind=signal
Event Structure:
{
  type: 'MemoryGraphEvent',
  kind: 'signal',
  id: 'mge_1678901234567_abc123',
  ts: '2026-03-09T12:34:56.789Z',
  signal: {
    key: 'log_error|errsig_norm:abc123',
    signals: ['log_error', 'errsig:TypeError: undefined'],
    error_signature: 'typeerror undefined <path> <n>',
  },
  observed: {
    agent: 'main',
    system_health: 'Uptime: 12.3h | Node: v18.0.0 | ...',
    recent_error_count: 5,
    scan_ms: 1234,
  }
}

recordHypothesis()

Record hypothesis before execution. Location: src/gep/memoryGraph.js:341
function recordHypothesis({
  signals,
  mutation,
  personality_state,
  selectedGene,
  selector,
  driftEnabled,
  selectedBy,
  capsulesUsed,
  observations,
})
signals
string[]
required
Current signals
mutation
Mutation
required
Mutation object
personality_state
object
required
Personality state with rigor and risk_tolerance
selectedGene
Gene
required
Selected Gene object
selector
object
required
Selector decision metadata
driftEnabled
boolean
required
Whether drift mode is enabled
selectedBy
string
required
Selection source (e.g., selector, memory_graph+selector)
capsulesUsed
string[]
required
Array of capsule IDs used
observations
object
required
System observations
hypothesisId
string
Created hypothesis ID
signalKey
string
Computed signal key
Event Structure:
{
  type: 'MemoryGraphEvent',
  kind: 'hypothesis',
  id: 'mge_1678901234567_def456',
  ts: '2026-03-09T12:34:56.789Z',
  signal: { key: '...', signals: [...], error_signature: '...' },
  hypothesis: {
    id: 'hyp_1678901234567_abc123',
    text: 'Given signal_key=... selecting gene=gene_gep_repair is expected to reduce errors',
    predicted_outcome: { status: null, score: null },
  },
  mutation: { id: 'mut_...', category: 'repair', risk_level: 'low', ... },
  personality: { key: 'rigor0.8_risk0.3', state: { rigor: 0.8, risk_tolerance: 0.3 } },
  gene: { id: 'gene_gep_repair', category: 'repair' },
  action: { drift: false, selected_by: 'selector', selector: {...} },
  capsules: { used: ['capsule_123'] },
}

recordAttempt()

Record action attempt (before outcome is known). Location: src/gep/memoryGraph.js:407
function recordAttempt({
  signals,
  mutation,
  personality_state,
  selectedGene,
  selector,
  driftEnabled,
  selectedBy,
  hypothesisId,
  capsulesUsed,
  observations,
})
Parameters are similar to recordHypothesis() but include hypothesisId to link back.
actionId
string
Created action ID
signalKey
string
Computed signal key
Side Effect: Writes mutable state to memory_graph_state.json for later outcome recording.

recordOutcomeFromState()

Record outcome by closing the last action. Location: src/gep/memoryGraph.js:633
function recordOutcomeFromState({ signals, observations })
signals
string[]
required
Current signals after action
observations
object
required
Current system observations
event
MemoryGraphEvent
Created MemoryGraphEvent with kind=outcome
Outcome Inference: Location: src/gep/memoryGraph.js:493
function inferOutcomeFromSignals({ prevHadError, currentHasError }) {
  if (prevHadError && !currentHasError) {
    return { status: 'success', score: 0.85, note: 'error_cleared' };
  }
  if (prevHadError && currentHasError) {
    return { status: 'failed', score: 0.2, note: 'error_persisted' };
  }
  if (!prevHadError && currentHasError) {
    return { status: 'failed', score: 0.15, note: 'new_error_appeared' };
  }
  return { status: 'success', score: 0.6, note: 'stable_no_error' };
}
Enhanced Outcome Inference: Location: src/gep/memoryGraph.js:535
function inferOutcomeEnhanced({ prevHadError, currentHasError, baselineObserved, currentObserved }) {
  // Try to parse EvolutionEvent from evidence
  const observed = tryParseLastEvolutionEventOutcome(combinedEvidence);
  if (observed) return observed;
  
  // Fallback to signal-based inference
  const base = inferOutcomeFromSignals({ prevHadError, currentHasError });
  let score = base.score;
  
  // Adjust based on error count delta
  const prevErrCount = baselineObserved.recent_error_count;
  const curErrCount = currentObserved.recent_error_count;
  if (prevErrCount != null && curErrCount != null) {
    const delta = prevErrCount - curErrCount;
    score += Math.max(-0.12, Math.min(0.12, delta / 50));
  }
  
  // Adjust based on scan time improvement
  const prevScan = baselineObserved.scan_ms;
  const curScan = currentObserved.scan_ms;
  if (prevScan != null && curScan != null && prevScan > 0) {
    const ratio = (prevScan - curScan) / prevScan;
    score += Math.max(-0.06, Math.min(0.06, ratio));
  }
  
  return { status: base.status, score: clamp01(score), note: `${base.note}|heuristic_delta` };
}

getAdvice()

Get memory graph recommendations for gene selection. Location: src/gep/memoryGraph.js:225
function getAdvice({ signals, genes, driftEnabled })
signals
string[]
required
Current signals
genes
Gene[]
required
Available genes
driftEnabled
boolean
default:false
Whether drift mode is enabled
currentSignalKey
string
Computed signal key for current signals
preferredGeneId
string
Recommended gene ID (highest expected success)
bannedGeneIds
Set<string>
Set of gene IDs to avoid (low-efficiency paths)
explanation
string[]
Human-readable explanation of advice
Edge Aggregation: Location: src/gep/memoryGraph.js:163
function aggregateEdges(events) {
  const map = new Map();
  for (const ev of events) {
    if (ev.type !== 'MemoryGraphEvent' || ev.kind !== 'outcome') continue;
    
    const signalKey = ev.signal.key;
    const geneId = ev.gene.id;
    const k = `${signalKey}::${geneId}`;
    
    const cur = map.get(k) || { signalKey, geneId, success: 0, fail: 0 };
    const status = ev.outcome.status;
    if (status === 'success') cur.success += 1;
    else if (status === 'failed') cur.fail += 1;
    
    map.set(k, cur);
  }
  return map;
}
Expected Success Calculation: Location: src/gep/memoryGraph.js:214
function edgeExpectedSuccess(edge, opts) {
  const succ = Number(edge.success) || 0;
  const fail = Number(edge.fail) || 0;
  const total = succ + fail;
  
  // Laplace smoothing: avoid 0/1 extremes
  const p = (succ + 1) / (total + 2);
  
  // Time-based decay
  const halfLifeDays = opts.half_life_days || 30;
  const w = decayWeight(edge.last_ts, halfLifeDays);
  
  return { p, w, total, value: p * w };
}
Decay Weight: Location: src/gep/memoryGraph.js:152
function decayWeight(updatedAtIso, halfLifeDays) {
  const ageDays = (Date.now() - Date.parse(updatedAtIso)) / (1000 * 60 * 60 * 24);
  if (ageDays <= 0) return 1;
  // Exponential half-life decay: weight = 0.5^(age/hl)
  return Math.pow(0.5, ageDays / halfLifeDays);
}

Signal Key Computation

Location: src/gep/memoryGraph.js:62
function computeSignalKey(signals) {
  const list = normalizeSignalsForMatching(signals);
  const uniq = Array.from(new Set(list.filter(Boolean))).sort();
  return uniq.join('|') || '(none)';
}

function normalizeSignalsForMatching(signals) {
  const out = [];
  for (const s of signals) {
    const str = String(s || '').trim();
    if (!str) continue;
    
    // Normalize error signatures
    if (str.startsWith('errsig:')) {
      const norm = normalizeErrorSignature(str.slice('errsig:'.length));
      if (norm) out.push(`errsig_norm:${stableHash(norm)}`);
      continue;
    }
    out.push(str);
  }
  return out;
}
Error Signature Normalization: Location: src/gep/memoryGraph.js:27
function normalizeErrorSignature(text) {
  return String(text || '')
    .toLowerCase()
    .replace(/[a-z]:\\[^ \n\r\t]+/gi, '<path>') // Windows paths
    .replace(/\/[^ \n\r\t]+/g, '<path>') // Unix paths
    .replace(/\b0x[0-9a-f]+\b/gi, '<hex>') // Hex values
    .replace(/\b\d+\b/g, '<n>') // Numbers
    .replace(/\s+/g, ' ')
    .slice(0, 220);
}

Memory Advice Scoring

Location: src/gep/memoryGraph.js:254
for (const ck of candidateKeys) {
  for (const g of genes) {
    const k = `${ck.key}::${g.id}`;
    const edge = edges.get(k);
    const cur = byGene.get(g.id) || { geneId: g.id, best: 0, attempts: 0, prior: 0 };
    
    if (edge) {
      const ex = edgeExpectedSuccess(edge, { half_life_days: 30 });
      const weighted = ex.value * ck.sim; // Weighted by signal similarity
      if (weighted > cur.best) cur.best = weighted;
      cur.attempts = Math.max(cur.attempts, ex.total);
    }
    
    // Gene->Outcome prior (independent of signal)
    const gEdge = geneOutcomes.get(String(g.id));
    if (gEdge) {
      const gx = edgeExpectedSuccess(gEdge, { half_life_days: 45 });
      cur.prior = Math.max(cur.prior, gx.value);
    }
    
    byGene.set(g.id, cur);
  }
}

// Combined score: edge score + prior stabilizer
for (const [geneId, info] of byGene.entries()) {
  const combined = info.best > 0 ? info.best + info.prior * 0.12 : info.prior * 0.4;
  scoredGeneIds.push({ geneId, score: combined, attempts: info.attempts });
  
  // Low-efficiency path suppression
  if (!driftEnabled && info.attempts >= 2 && info.best < 0.18) {
    bannedGeneIds.add(geneId);
  }
}

Example Usage

const { recordSignalSnapshot } = require('./gep/memoryGraph');

const event = recordSignalSnapshot({
  signals: ['log_error', 'errsig:TypeError: undefined'],
  observations: {
    agent: 'main',
    system_health: 'Uptime: 12.3h',
    recent_error_count: 5,
  },
});

console.log('Signal recorded:', event.id);

Build docs developers (and LLMs) love