Skip to main content

Overview

Evolver prevents autonomous agents from overwriting core evolver code and critical workspace files to avoid cascading failures. Location: src/gep/solidify.js:493-535

Why Self-Modification is Dangerous

When an evolver modifies its own source code, it can introduce bugs into:
  1. Prompt generation (src/gep/prompt.js) → malformed prompts → agent confusion
  2. Validation logic (src/gep/solidify.js) → broken safety checks → dangerous evolutions approved
  3. Selector logic (src/gep/selector.js) → wrong genes selected → unintended changes
  4. Rollback logic (src/gep/solidify.js) → failed rollback → data loss
Cascading failure scenario: Location: SKILL.md:65

Protected Skill Directories

These skill directories cannot be modified by default:
// src/gep/solidify.js:493-504
const CRITICAL_PROTECTED_PREFIXES = [
  'skills/feishu-evolver-wrapper/',
  'skills/feishu-common/',
  'skills/feishu-post/',
  'skills/feishu-card/',
  'skills/feishu-doc/',
  'skills/skill-tools/',
  'skills/clawhub/',
  'skills/clawhub-batch-undelete/',
  'skills/git-sync/',
  'skills/evolver/',  // Core evolver
];
Rationale:
  • skills/evolver/: Core evolution engine
  • skills/feishu-evolver-wrapper/: Platform integration layer
  • skills/feishu-common/: Shared Feishu utilities
  • skills/git-sync/: Critical workspace sync
  • skills/clawhub/: Package management
  • skills/skill-tools/: Skill infrastructure
Destroying any of these will crash the entire system.

Protected Workspace Files

These files at the workspace root cannot be modified:
// src/gep/solidify.js:507-520
const CRITICAL_PROTECTED_FILES = [
  'MEMORY.md',           // Agent's long-term memory
  'SOUL.md',             // Agent's personality definition
  'IDENTITY.md',         // Agent identity
  'AGENTS.md',           // Multi-agent coordination
  'USER.md',             // User preferences
  'HEARTBEAT.md',        // Health monitoring
  'RECENT_EVENTS.md',    // Event log
  'TOOLS.md',            // Tool registry
  'TROUBLESHOOTING.md',  // Debugging guide
  'openclaw.json',       // Agent config
  '.env',                // Environment secrets
  'package.json',        // Dependencies
];
Why these are critical:
  • MEMORY.md: Deleting this causes memory loss
  • .env: Contains secrets (API keys, tokens)
  • openclaw.json: Agent configuration; corruption breaks startup
  • package.json: Dependency definitions; corruption breaks npm

Enforcement

During solidify, all changed files are checked against protected paths:
// src/gep/solidify.js:306-319
for (const f of blast.all_changed_files || blast.changed_files || []) {
  if (isCriticalProtectedPath(f)) {
    var norm = normalizeRelPath(f);
    if (allowSelfModify && norm.startsWith('skills/evolver/') && gene && gene.category === 'repair') {
      // Self-modify opt-in: allow repair-only changes to evolver when explicitly enabled
      warnings.push('self_modify_evolver_repair: ' + norm + ' (EVOLVE_ALLOW_SELF_MODIFY=true)');
    } else {
      violations.push('critical_path_modified: ' + norm);
    }
  }
}
Result: If violations are found, solidify fails and rollback is triggered.

isCriticalProtectedPath

Implementation:
// src/gep/solidify.js:522-535
function isCriticalProtectedPath(relPath) {
  const rel = normalizeRelPath(relPath);
  if (!rel) return false;
  
  // Check protected prefixes (skill directories)
  for (const prefix of CRITICAL_PROTECTED_PREFIXES) {
    const p = prefix.replace(/\/+$/, '');
    if (rel === p || rel.startsWith(p + '/')) return true;
  }
  
  // Check protected root files
  for (const f of CRITICAL_PROTECTED_FILES) {
    if (rel === f) return true;
  }
  
  return false;
}
Examples:
isCriticalProtectedPath('skills/evolver/src/evolve.js')  // true
isCriticalProtectedPath('skills/evolver/')               // true
isCriticalProtectedPath('MEMORY.md')                     // true
isCriticalProtectedPath('.env')                          // true
isCriticalProtectedPath('src/custom/code.js')            // false
isCriticalProtectedPath('skills/my-skill/index.js')      // false

EVOLVE_ALLOW_SELF_MODIFY

NOT recommended for production. Only enable for controlled experiments.

When enabled

EVOLVE_ALLOW_SELF_MODIFY=true
Evolver can modify skills/evolver/ for repair genes only:
if (allowSelfModify && norm.startsWith('skills/evolver/') && gene && gene.category === 'repair') {
  warnings.push('self_modify_evolver_repair: ' + norm + ' (EVOLVE_ALLOW_SELF_MODIFY=true)');
} else {
  violations.push('critical_path_modified: ' + norm);
}
Location: src/gep/solidify.js:308-315

Constraints

  • Only repair genes: Innovate/optimize genes still blocked
  • Warning logged: Every self-modification is logged for audit
  • Still requires validation: Gene validation commands must pass

Destructive Change Detection

Evolver detects when critical files are deleted or emptied:
// src/gep/solidify.js:537-567
function detectDestructiveChanges({ repoRoot, changedFiles, baselineUntracked }) {
  const violations = [];
  const baselineSet = new Set((Array.isArray(baselineUntracked) ? baselineUntracked : []).map(normalizeRelPath));

  for (const rel of changedFiles) {
    const norm = normalizeRelPath(rel);
    if (!norm) continue;
    if (!isCriticalProtectedPath(norm)) continue;

    const abs = path.join(repoRoot, norm);
    
    // If a critical file existed before but is now missing/empty, that is destructive.
    if (!baselineSet.has(norm)) {
      if (!fs.existsSync(abs)) {
        violations.push(`CRITICAL_FILE_DELETED: ${norm}`);
      } else {
        const stat = fs.statSync(abs);
        if (stat.isFile() && stat.size === 0) {
          violations.push(`CRITICAL_FILE_EMPTIED: ${norm}`);
        }
      }
    }
  }
  return violations;
}
Example violations:
CRITICAL_FILE_DELETED: MEMORY.md
CRITICAL_FILE_EMPTIED: openclaw.json
CRITICAL_FILE_DELETED: skills/evolver/src/evolve.js
Location: src/gep/solidify.js:1077-1088

Rollback Protection

During rollback, evolver never deletes files inside protected directories:
// src/gep/solidify.js:700-723
for (const rel of toDelete) {
  const safeRel = String(rel || '').replace(/\\/g, '/').replace(/^\.\//+/, '');
  if (!safeRel) continue;
  
  // CRITICAL: Never delete files inside protected skill directories during rollback.
  if (isCriticalProtectedPath(safeRel)) {
    skipped.push(safeRel);
    continue;
  }
  
  // Delete file
  fs.unlinkSync(abs);
  deleted.push(safeRel);
}

if (skipped.length > 0) {
  console.log(`[Rollback] Skipped ${skipped.length} critical protected file(s): ${skipped.slice(0, 5).join(', ')}`);
}
Rationale: Even if an evolution accidentally creates a file inside skills/evolver/, rollback should not delete it (could be a pre-existing backup).

Empty Directory Cleanup

After rollback, evolver removes empty directories to prevent ghost skill directories:
// src/gep/solidify.js:724-756
var dirsToCheck = new Set();
for (var di = 0; di < deleted.length; di++) {
  var dir = path.dirname(deleted[di]);
  while (dir && dir !== '.' && dir !== '/') {
    var normalized = dir.replace(/\\/g, '/');
    if (!normalized.includes('/')) break;
    dirsToCheck.add(dir);
    dir = path.dirname(dir);
  }
}

// Sort deepest first to ensure children are removed before parents
var sortedDirs = Array.from(dirsToCheck).sort(function (a, b) { return b.length - a.length; });
var removedDirs = [];
for (var si = 0; si < sortedDirs.length; si++) {
  if (isCriticalProtectedPath(sortedDirs[si] + '/')) continue;
  var dirAbs = path.join(repoRoot, sortedDirs[si]);
  try {
    var entries = fs.readdirSync(dirAbs);
    if (entries.length === 0) {
      fs.rmdirSync(dirAbs);
      removedDirs.push(sortedDirs[si]);
    }
  } catch (e) { /* ignore */ }
}
Safety: Never removes:
  • Top-level structural directories (skills/, src/)
  • Critical protected directories
  • Non-empty directories

Forbidden Paths in Genes

Genes can declare forbidden_paths constraints:
{
  "type": "Gene",
  "id": "gene_example",
  "constraints": {
    "max_files": 10,
    "forbidden_paths": [
      ".git",
      "node_modules",
      "skills/evolver",
      "skills/feishu-evolver-wrapper"
    ]
  }
}
During solidify, these are checked:
// src/gep/solidify.js:298-301
const forbidden = Array.isArray(constraints.forbidden_paths) ? constraints.forbidden_paths : [];
for (const f of blast.all_changed_files || blast.changed_files || []) {
  if (isForbiddenPath(f, forbidden)) violations.push(`forbidden_path touched: ${f}`);
}

Auto-Generated Gene Protection

When evolver creates an auto-generated gene, it includes default forbidden paths:
// src/gep/solidify.js:923-958
function buildAutoGene({ signals, intent }) {
  const gene = {
    type: 'Gene',
    id: `gene_auto_${stableHash(signalKey)}`,
    category: intent || inferCategoryFromSignals(signals),
    constraints: {
      max_files: 12,
      forbidden_paths: [
        '.git', 'node_modules',
        'skills/feishu-evolver-wrapper', 'skills/feishu-common',
        'skills/feishu-post', 'skills/feishu-card', 'skills/feishu-doc',
        'skills/skill-tools', 'skills/clawhub', 'skills/clawhub-batch-undelete',
        'skills/git-sync',
      ],
    },
  };
  return gene;
}
Note: skills/evolver is NOT in the default forbidden list (to allow opt-in self-modification).

Safe vs. Unsafe Examples

Safe Evolution

{
  "blast_radius": {
    "files": 3,
    "changed_files": [
      "src/custom/feature.js",
      "src/custom/test.js",
      "README.md"
    ]
  }
}
Result: ✅ All files are in non-protected paths.

Unsafe Evolution (Blocked)

{
  "blast_radius": {
    "files": 2,
    "changed_files": [
      "skills/evolver/src/evolve.js",
      "MEMORY.md"
    ]
  }
}
Result: ❌ Solidify fails with:
constraint: critical_path_modified: skills/evolver/src/evolve.js
constraint: critical_path_modified: MEMORY.md

Self-Modify Opt-In (Warning)

EVOLVE_ALLOW_SELF_MODIFY=true node index.js
Gene:
{
  "category": "repair",
  "validation": ["npm test"]
}
Blast radius:
{
  "changed_files": [
    "skills/evolver/src/gep/selector.js"
  ]
}
Result: ⚠️ Warning logged:
self_modify_evolver_repair: skills/evolver/src/gep/selector.js (EVOLVE_ALLOW_SELF_MODIFY=true)
Evolution proceeds if validation passes.

Monitoring Self-Modification

To detect unauthorized self-modification attempts:
# Check recent events for self-modification warnings
grep 'self_modify_evolver' memory/evolution/events.jsonl

# Check for protected path violations
grep 'critical_path_modified' memory/evolution/events.jsonl

# Audit blast radius for evolver changes
jq -r 'select(.blast_radius.changed_files[] | contains("skills/evolver"))' memory/evolution/events.jsonl

Recovery from Self-Modification Bugs

Symptom

Evolver crashes on every cycle after a self-modification.

Diagnosis

# Check last few events
tail -5 memory/evolution/events.jsonl | jq .

# Find self-modification event
grep 'self_modify_evolver' memory/evolution/events.jsonl | tail -1

Recovery

# 1. Disable evolver loop
pm2 stop evolver

# 2. Roll back to last known-good commit
git log --oneline -10
git reset --hard <last-good-commit>

# 3. Disable self-modification
echo 'EVOLVE_ALLOW_SELF_MODIFY=false' >> .env

# 4. Restart
pm2 start evolver

Best Practices

  1. Keep EVOLVE_ALLOW_SELF_MODIFY=false in production
  2. Monitor self-modification warnings in event logs
  3. Review all repair genes that touch evolver code
  4. Maintain backups of working evolver versions
  5. Test self-modifications in isolated environments before production
  6. Add custom paths to forbidden_paths in gene constraints if needed

Build docs developers (and LLMs) love