AgentSkillsCN

memory-consolidation

为AI智能体提供类人化的记忆维护机制。整合学习成果,清除无用记忆,应用相关性过滤,并将重要模式提升至核心记忆。每周执行一次,或在项目里程碑完成后运行。触发条件包括:整合记忆、记忆维护、清理记忆、/整合、记忆健康。

SKILL.md
--- frontmatter
name: memory-consolidation
description: "Human-like memory maintenance for AI agents. Consolidates learnings, prunes unused memories, applies relevance filtering, and promotes patterns to core memory. Run weekly or after project milestones. Triggers on: consolidate memory, memory maintenance, clean up memories, /consolidate, memory health."
user-invocable: true
context: fork
version: 1.0.0
model: sonnet
color: "#10b981"
triggers:
  - "/consolidate"
  - "consolidate memory"
  - "memory maintenance"
  - "memory health"
  - "clean up memories"
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Glob
  - mcp__memory__*

Memory Consolidation Skill

A human-inspired memory maintenance system that implements the cognitive processes of consolidation, forgetting, and metacognition for AI agents.

Based on: Towards Human-like Memory for AI Agents


Core Concepts

Memory Hierarchy

code
┌─────────────────────────────────────────────────────────────┐
│                     CORE MEMORY                              │
│  ~/.claude/memory/core-memory.json                          │
│  - Stable preferences, beliefs, patterns                     │
│  - Rarely changes, high confidence                           │
│  - Loaded at session start                                   │
└─────────────────────────────────────────────────────────────┘
                              ▲
                              │ Promotion (high usage + effectiveness)
                              │
┌─────────────────────────────────────────────────────────────┐
│                   LONG-TERM MEMORY                           │
│  Memory MCP (patterns, mistakes, tech-insights)             │
│  - Cross-project learnings                                   │
│  - Subject to decay and consolidation                        │
│  - Conceptual graph with relationships                       │
└─────────────────────────────────────────────────────────────┘
                              ▲
                              │ Sensory Filter (relevance >= 5)
                              │
┌─────────────────────────────────────────────────────────────┐
│                  SHORT-TERM / SESSION                        │
│  progress.md, progress-summary.md, prd.json                 │
│  - Project-specific learnings                                │
│  - Evaluated for long-term storage at session end           │
└─────────────────────────────────────────────────────────────┘

Entry Point Detection

When this skill activates:

ConditionAction
/consolidate invokedRun full consolidation cycle
memory health askedGenerate health report only
Automatic (post-project)Run if 10+ new memories since last

First Action: Load current state:

bash
cat ~/.claude/memory/core-memory.json
ls ~/.claude/memory/archive/ 2>/dev/null | wc -l

Phase 1: Analyze Memory Health

Step 1.1: Load All Memories

javascript
// Query all memories from Memory MCP
const allMemories = await mcp__memory__search_nodes({ query: "" });

// Also query by specific types
const patterns = await mcp__memory__search_nodes({ query: "pattern:" });
const mistakes = await mcp__memory__search_nodes({ query: "mistake:" });
const techInsights = await mcp__memory__search_nodes({ query: "tech-insight:" });
const preferences = await mcp__memory__search_nodes({ query: "preference:" });
const research = await mcp__memory__search_nodes({ query: "research:" });
const competitors = await mcp__memory__search_nodes({ query: "competitor:" });
const designs = await mcp__memory__search_nodes({ query: "design:" });
const stacks = await mcp__memory__search_nodes({ query: "stack:" });

Step 1.2: Calculate Memory Statistics

For each memory, extract or infer:

javascript
function analyzeMemory(entity) {
  const observations = entity.observations || [];

  // Extract metadata from observations (stored as structured strings)
  const discoveredAt = observations.find(o => o.startsWith("Researched:") || o.startsWith("Discovered:"));
  const appliedIn = observations.filter(o => o.startsWith("Applied in"));
  const helpfulCount = appliedIn.filter(o => o.includes("HELPFUL")).length;
  const notHelpfulCount = appliedIn.filter(o => o.includes("NOT HELPFUL")).length;

  return {
    name: entity.name,
    type: entity.entityType,
    observationCount: observations.length,
    discoveredAt: parseDate(discoveredAt),
    useCount: appliedIn.length,
    helpfulCount,
    notHelpfulCount,
    effectiveness: appliedIn.length > 0
      ? helpfulCount / appliedIn.length
      : null,
    ageInDays: daysSince(parseDate(discoveredAt)),
    lastUsed: findLastUsed(appliedIn),
    daysSinceLastUse: daysSince(findLastUsed(appliedIn))
  };
}

Step 1.3: Generate Health Report

markdown
## Memory Health Report

**Generated:** [timestamp]
**Last Consolidation:** [date or "Never"]

### Summary Statistics

| Metric | Value |
|--------|-------|
| Total memories | X |
| Patterns | Y |
| Mistakes | Z |
| Tech insights | A |
| Research cache | B |
| Avg age (days) | C |
| Avg effectiveness | D% |

### Health Indicators

| Indicator | Status | Details |
|-----------|--------|---------|
| Memory count | OK/WARN | X memories (threshold: 200) |
| Stale memories | OK/WARN | Y unused >90 days |
| Low effectiveness | OK/WARN | Z memories <30% effective |
| Duplicates | OK/WARN | A potential duplicates |
| Orphaned | OK/WARN | B memories with no relations |

### Recommendations

1. [Recommendation based on findings]
2. [Another recommendation]

Phase 2: Sensory Filter (Relevance Scoring)

This logic is used by other skills BEFORE saving to memory. Document here for reference.

Relevance Score Calculation

javascript
/**
 * Calculate relevance score for a potential memory.
 * Only save to long-term memory if score >= threshold (default: 5)
 */
function calculateRelevance(learning, existingMemories, coreMemory) {
  let score = 0;
  const reasons = [];

  // 1. NOVELTY CHECK (-∞ if duplicate)
  const similar = findSimilarMemories(learning, existingMemories);
  if (similar.length > 0 && similar[0].similarity > 0.85) {
    return { score: 0, reasons: ["Duplicate of existing memory"], save: false };
  }
  if (similar.length > 0 && similar[0].similarity > 0.6) {
    score -= 2;
    reasons.push("Similar memory exists (-2)");
  }

  // 2. GENERALITY - Applies to multiple projects/frameworks?
  if (learning.appliesTo && learning.appliesTo.length > 2) {
    score += 3;
    reasons.push("Broadly applicable (+3)");
  } else if (learning.appliesTo && learning.appliesTo.length > 1) {
    score += 2;
    reasons.push("Applies to multiple contexts (+2)");
  }

  // 3. SEVERITY - How important is this?
  const severityScores = {
    'critical': 5,
    'high': 3,
    'medium': 2,
    'low': 1
  };
  if (learning.severity) {
    score += severityScores[learning.severity] || 1;
    reasons.push(`Severity: ${learning.severity} (+${severityScores[learning.severity]})`);
  }

  // 4. SOURCE - How was this learned?
  if (learning.source === 'mistake' || learning.source === 'failure') {
    score += 2;
    reasons.push("Learned from failure (+2)");
  }
  if (learning.source === 'explicit_user_feedback') {
    score += 3;
    reasons.push("User explicitly shared (+3)");
  }

  // 5. ALIGNMENT - Matches core memory patterns?
  if (alignsWithCoreMemory(learning, coreMemory)) {
    score += 1;
    reasons.push("Aligns with existing patterns (+1)");
  }

  // 6. FREQUENCY POTENTIAL - Will encounter often?
  if (learning.frequencyHint === 'common') {
    score += 2;
    reasons.push("Common scenario (+2)");
  }

  const threshold = coreMemory.memoryConfig?.relevanceThreshold || 5;

  return {
    score,
    reasons,
    save: score >= threshold,
    threshold
  };
}

/**
 * Find similar existing memories using text overlap
 */
function findSimilarMemories(learning, existingMemories) {
  const learningText = [
    learning.name,
    learning.summary,
    ...(learning.observations || [])
  ].join(' ').toLowerCase();

  return existingMemories
    .map(memory => {
      const memoryText = [
        memory.name,
        ...(memory.observations || [])
      ].join(' ').toLowerCase();

      return {
        memory,
        similarity: calculateTextSimilarity(learningText, memoryText)
      };
    })
    .filter(m => m.similarity > 0.3)
    .sort((a, b) => b.similarity - a.similarity);
}

Integration Point

Other skills should call this before saving:

javascript
// In autonomous-dev, cto, cpo-ai-skill, etc.
async function saveToMemoryWithFilter(learning) {
  const coreMemory = JSON.parse(
    await readFile('~/.claude/memory/core-memory.json')
  );
  const existingMemories = await mcp__memory__search_nodes({ query: "" });

  const relevance = calculateRelevance(learning, existingMemories.entities, coreMemory);

  if (!relevance.save) {
    console.log(`Skipping memory save: score ${relevance.score} < ${relevance.threshold}`);
    console.log(`Reasons: ${relevance.reasons.join(', ')}`);
    return { saved: false, relevance };
  }

  // Add metadata observations for tracking
  const enrichedLearning = {
    ...learning,
    observations: [
      ...(learning.observations || []),
      `Discovered: ${new Date().toISOString().split('T')[0]}`,
      `Relevance score: ${relevance.score}`,
      `Source: ${learning.source || 'implementation'}`
    ]
  };

  await mcp__memory__create_entities({ entities: [enrichedLearning] });

  return { saved: true, relevance };
}

Phase 3: Memory Consolidation

Step 3.1: Identify Consolidation Candidates

Merge similar patterns:

javascript
function findMergeCandidates(memories) {
  const candidates = [];

  for (let i = 0; i < memories.length; i++) {
    for (let j = i + 1; j < memories.length; j++) {
      const similarity = calculateSimilarity(memories[i], memories[j]);

      if (similarity > 0.7 && sameEntityType(memories[i], memories[j])) {
        candidates.push({
          memory1: memories[i],
          memory2: memories[j],
          similarity,
          recommendation: similarity > 0.85 ? 'merge' : 'review'
        });
      }
    }
  }

  return candidates.sort((a, b) => b.similarity - a.similarity);
}

Step 3.2: Merge Memories

javascript
async function mergeMemories(memory1, memory2) {
  // Combine observations, deduplicating
  const combinedObservations = [
    ...new Set([
      ...memory1.observations,
      ...memory2.observations
    ])
  ];

  // Keep the more descriptive name
  const mergedName = memory1.name.length > memory2.name.length
    ? memory1.name
    : memory2.name;

  // Calculate combined stats
  const useCount1 = extractUseCount(memory1);
  const useCount2 = extractUseCount(memory2);

  // Add merge note
  combinedObservations.push(
    `Merged from: ${memory1.name}, ${memory2.name} on ${new Date().toISOString().split('T')[0]}`
  );

  // Delete old memories
  await mcp__memory__delete_entities({
    entityNames: [memory1.name, memory2.name]
  });

  // Create merged memory
  await mcp__memory__create_entities({
    entities: [{
      name: mergedName,
      entityType: memory1.entityType,
      observations: combinedObservations
    }]
  });

  return { merged: mergedName, from: [memory1.name, memory2.name] };
}

Step 3.3: Promote to Core Memory

Patterns with high usage and effectiveness should become stable preferences:

javascript
async function promoteToCore(memory, coreMemory) {
  const stats = analyzeMemory(memory);

  // Promotion criteria
  const shouldPromote =
    stats.useCount >= 15 &&
    stats.effectiveness >= 0.85 &&
    stats.ageInDays >= 30; // Must be proven over time

  if (!shouldPromote) return false;

  // Determine where in core memory this belongs
  if (memory.entityType === 'pattern') {
    coreMemory.stablePatterns[memory.name.replace('pattern:', '')] =
      extractSummary(memory);
  } else if (memory.entityType === 'preference') {
    // Parse and add to preferences
    const prefKey = memory.name.replace('preference:', '');
    coreMemory.preferences[prefKey] = extractPreferenceValue(memory);
  }

  // Mark as promoted in observations
  await mcp__memory__add_observations({
    observations: [{
      entityName: memory.name,
      contents: [`Promoted to core memory: ${new Date().toISOString().split('T')[0]}`]
    }]
  });

  // Update core memory file
  coreMemory.lastUpdated = new Date().toISOString().split('T')[0];
  await writeFile('~/.claude/memory/core-memory.json', JSON.stringify(coreMemory, null, 2));

  return true;
}

Phase 4: Selective Forgetting

Step 4.1: Identify Stale Memories

javascript
function identifyStaleMemories(memories, coreMemory) {
  const config = coreMemory.memoryConfig || {
    decayThresholdDays: 90,
    minUsesToRetain: 3
  };

  return memories.filter(memory => {
    const stats = analyzeMemory(memory);

    // Never forget critical memories
    if (memory.observations?.some(o => o.includes('critical: true'))) {
      return false;
    }

    // Never forget recently used
    if (stats.daysSinceLastUse < 30) {
      return false;
    }

    // Forget if: old AND rarely used
    const isOld = stats.daysSinceLastUse > config.decayThresholdDays;
    const isRarelyUsed = stats.useCount < config.minUsesToRetain;
    const isIneffective = stats.effectiveness !== null && stats.effectiveness < 0.3;

    return isOld && (isRarelyUsed || isIneffective);
  });
}

Step 4.2: Archive Before Forgetting

javascript
async function archiveMemories(memories) {
  const archiveDir = '~/.claude/memory/archive';
  const archiveFile = `${archiveDir}/${new Date().toISOString().split('T')[0]}-forgotten.json`;

  // Read existing archive or create new
  let archive = [];
  try {
    archive = JSON.parse(await readFile(archiveFile));
  } catch {
    // File doesn't exist yet
  }

  // Add memories to archive
  archive.push(...memories.map(m => ({
    ...m,
    forgottenAt: new Date().toISOString(),
    reason: 'decay'
  })));

  await writeFile(archiveFile, JSON.stringify(archive, null, 2));

  return archiveFile;
}

Step 4.3: Delete Stale Memories

javascript
async function forgetMemories(memories) {
  // Archive first (safety net)
  const archiveFile = await archiveMemories(memories);

  // Delete from Memory MCP
  const names = memories.map(m => m.name);

  for (const name of names) {
    await mcp__memory__delete_entities({ entityNames: [name] });
  }

  return {
    forgotten: names.length,
    archived: archiveFile
  };
}

Phase 5: Enhanced Graph Structure

Step 5.1: Add Relationships

When creating memories, establish relationships:

javascript
async function createMemoryWithRelations(memory, relatedMemories) {
  // Create the memory
  await mcp__memory__create_entities({ entities: [memory] });

  // Create relationships
  const relations = [];

  for (const related of relatedMemories) {
    relations.push({
      from: memory.name,
      relationType: related.relation, // 'related_to', 'supersedes', 'applies_to', 'derived_from'
      to: related.name
    });
  }

  if (relations.length > 0) {
    await mcp__memory__create_relations({ relations });
  }

  return { memory, relations };
}

Step 5.2: Standard Relationship Types

RelationMeaningExample
related_toConceptually similarpattern:early-returns → pattern:guard-clauses
supersedesReplaces older memorypattern:v2 → pattern:v1
applies_toWorks with technologypattern:rls → tech-insight:supabase
derived_fromBased on anothermistake:auth-bug → pattern:auth-validation
competes_inCompetitor in marketcompetitor:linear → research:task-management
part_ofComponent of largerdesign:button-styles → design:dashboard-patterns

Step 5.3: Find Orphaned Memories

javascript
async function findOrphanedMemories(memories) {
  const allRelations = await mcp__memory__open_nodes({
    names: memories.map(m => m.name)
  });

  const connectedNames = new Set();
  for (const node of allRelations.nodes || []) {
    if (node.relations?.length > 0) {
      connectedNames.add(node.name);
      node.relations.forEach(r => {
        connectedNames.add(r.to);
        connectedNames.add(r.from);
      });
    }
  }

  return memories.filter(m => !connectedNames.has(m.name));
}

Phase 6: Generate Consolidation Report

Final Report Template

markdown
# Memory Consolidation Report

**Date:** [timestamp]
**Previous Consolidation:** [date]
**Duration:** [X minutes]

---

## Summary

| Action | Count |
|--------|-------|
| Memories analyzed | X |
| Merged | Y |
| Promoted to core | Z |
| Forgotten (archived) | A |
| Relationships created | B |
| Orphans identified | C |

---

## Merges Performed

| Original Memories | Merged Into | Similarity |
|-------------------|-------------|------------|
| pattern:a, pattern:b | pattern:combined | 87% |

---

## Promoted to Core Memory

| Memory | Use Count | Effectiveness | Promoted As |
|--------|-----------|---------------|-------------|
| pattern:early-returns | 23 | 95% | stablePatterns.earlyReturns |

---

## Forgotten (Archived)

| Memory | Age (days) | Last Used | Use Count | Reason |
|--------|------------|-----------|-----------|--------|
| mistake:old-api | 180 | 150 days ago | 1 | Decay threshold |

**Archive location:** ~/.claude/memory/archive/2026-01-27-forgotten.json

---

## Orphaned Memories (No Relations)

Consider adding relationships or reviewing:

1. tech-insight:random-thing - No connections, consider relating to patterns
2. competitor:old-company - Market research may be stale

---

## Health After Consolidation

| Metric | Before | After | Change |
|--------|--------|-------|--------|
| Total memories | 150 | 142 | -8 |
| Avg effectiveness | 68% | 72% | +4% |
| Orphaned | 12 | 8 | -4 |
| Stale (>90d unused) | 15 | 0 | -15 |

---

## Recommendations

1. **Run `/consolidate` again in 7 days** - Memory count healthy
2. **Review orphaned memories** - 8 memories have no relationships
3. **Consider archiving research:** - 3 market research entries >60 days old

---

## Next Consolidation Triggers

- Memory count exceeds 200
- 7 days since last consolidation
- Major project completion

Quick Commands

Consolidation

CommandAction
/consolidateFull consolidation cycle
/consolidate --dry-runPreview changes without applying
/consolidate --healthHealth report only
/consolidate --forget-onlyOnly run forgetting phase
/consolidate --merge-onlyOnly run merge phase

Reflection (Metacognition)

CommandAction
/reflectDeep reflection cycle (what worked, what didn't)
/reflect --quickQuick reflection on current session
/reflect --beliefsValidate core beliefs against evidence
/reflect --memoriesShow memory effectiveness rankings

Attention

CommandAction
/attentionShow current attention weights
/attention --focus [topic]Manually set focus topic
/attention --resetReset attention weights

Automation Hooks

Add to ~/.claude/hooks/post-project.sh:

bash
#!/bin/bash
# Run consolidation after project completion

# Check if consolidation needed
MEMORY_COUNT=$(claude --print "How many memories in Memory MCP?" 2>/dev/null | grep -o '[0-9]*')
LAST_CONSOLIDATION=$(jq -r '.lastConsolidation' ~/.claude/memory/core-memory.json)

if [ "$MEMORY_COUNT" -gt 200 ] || [ "$LAST_CONSOLIDATION" = "null" ]; then
  echo "Running memory consolidation..."
  claude "/consolidate"
fi

Integration with Other Skills

autonomous-dev

Before saving memories, call sensory filter:

javascript
// In Step 3.4 after story completion
const relevance = calculateRelevance(learning, existingMemories, coreMemory);
if (relevance.save) {
  await mcp__memory__create_entities({ entities: [learning] });
}

cpo-ai-skill

Research caching uses same relevance logic:

javascript
// In Phase 1.4 after research completes
const research = { name: 'research:...', entityType: 'market-research', ... };
const { saved } = await saveToMemoryWithFilter(research);

cto

Architecture decisions saved with relationships:

javascript
// After generating ADR
await createMemoryWithRelations(
  { name: 'architecture:decision-name', ... },
  [{ name: 'tech-insight:related-tech', relation: 'applies_to' }]
);


Reference Documents

DocumentPurpose
memory-utils.mdReusable functions for memory operations
attention-system.mdShort-term attention and focus tracking
metacognition.mdSelf-reflection and effectiveness tracking

Version History

  • 1.1.0 (2026-01-27): Added attention system and metacognition
  • 1.0.0 (2026-01-27): Initial release with full consolidation, forgetting, and graph support