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:
- Prompt generation (
src/gep/prompt.js) → malformed prompts → agent confusion
- Validation logic (
src/gep/solidify.js) → broken safety checks → dangerous evolutions approved
- Selector logic (
src/gep/selector.js) → wrong genes selected → unintended changes
- 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
- Keep EVOLVE_ALLOW_SELF_MODIFY=false in production
- Monitor self-modification warnings in event logs
- Review all repair genes that touch evolver code
- Maintain backups of working evolver versions
- Test self-modifications in isolated environments before production
- Add custom paths to
forbidden_paths in gene constraints if needed