Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,13 @@ function readJsonSafe(p) {
}

function rejectPendingRun(statePath) {
try {
const { getRepoRoot } = require('./src/gep/paths');
const { execSync } = require('child_process');
const repoRoot = getRepoRoot();

execSync('git checkout -- .', { cwd: repoRoot, encoding: 'utf8', timeout: 30000 });
execSync('git clean -fd', { cwd: repoRoot, encoding: 'utf8', timeout: 30000 });
} catch (e) {
console.warn('[Loop] Pending run rollback failed: ' + (e.message || e));
}

try {
const state = readJsonSafe(statePath);
if (state && state.last_run && state.last_run.run_id) {
state.last_solidify = {
run_id: state.last_run.run_id,
rejected: true,
reason: 'loop_bridge_disabled_autoreject',
reason: 'loop_bridge_disabled_autoreject_no_rollback',
timestamp: new Date().toISOString(),
};
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
Expand Down Expand Up @@ -184,7 +173,7 @@ async function main() {
if (isPendingSolidify(stAfterRun)) {
const cleared = rejectPendingRun(solidifyStatePath);
if (cleared) {
console.warn('[Loop] Auto-rejected pending run because bridge is disabled in loop mode.');
console.warn('[Loop] Auto-rejected pending run because bridge is disabled in loop mode (state only, no rollback).');
}
}
}
Expand Down Expand Up @@ -284,16 +273,21 @@ async function main() {

if (res && res.ok && !dryRun) {
try {
const { shouldDistill, prepareDistillation } = require('./src/gep/skillDistiller');
const { shouldDistill, prepareDistillation, autoDistill } = require('./src/gep/skillDistiller');
if (shouldDistill()) {
const dr = prepareDistillation();
if (dr && dr.ok && dr.promptPath) {
console.log('\n[DISTILL_REQUEST]');
console.log('Distillation prompt ready. Read the prompt file, process it with your LLM,');
console.log('save the LLM response to a file, then run:');
console.log(' node index.js distill --response-file=<path_to_llm_response>');
console.log('Prompt file: ' + dr.promptPath);
console.log('[/DISTILL_REQUEST]');
const auto = autoDistill();
if (auto && auto.ok && auto.gene) {
console.log('[Distiller] Auto-distilled gene: ' + auto.gene.id);
} else {
const dr = prepareDistillation();
if (dr && dr.ok && dr.promptPath) {
console.log('\n[DISTILL_REQUEST]');
console.log('Distillation prompt ready. Read the prompt file, process it with your LLM,');
console.log('save the LLM response to a file, then run:');
console.log(' node index.js distill --response-file=<path_to_llm_response>');
console.log('Prompt file: ' + dr.promptPath);
console.log('[/DISTILL_REQUEST]');
}
}
}
} catch (e) {
Expand Down Expand Up @@ -528,3 +522,10 @@ async function main() {
if (require.main === module) {
main();
}

module.exports = {
main,
readJsonSafe,
rejectPendingRun,
isPendingSolidify,
};
95 changes: 87 additions & 8 deletions src/evolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const { getEvolutionDir } = require('./gep/paths');
const { shouldReflect, buildReflectionContext, recordReflection } = require('./gep/reflection');
const { loadNarrativeSummary } = require('./gep/narrativeMemory');
const { maybeReportIssue } = require('./gep/issueReporter');
const { resolveStrategy } = require('./gep/strategy');
const { expandSignals } = require('./gep/learningSignals');

const REPO_ROOT = getRepoRoot();

Expand Down Expand Up @@ -347,6 +349,73 @@ function getMutationDirective(logContent) {
`;
}

function computeAdaptiveStrategyPolicy(opts) {
const recentEvents = Array.isArray(opts && opts.recentEvents) ? opts.recentEvents : [];
const selectedGene = opts && opts.selectedGene ? opts.selectedGene : null;
const signals = Array.isArray(opts && opts.signals) ? opts.signals : [];
const baseStrategy = resolveStrategy({ signals: signals });

const tail = recentEvents.slice(-8);
let repairStreak = 0;
for (let i = tail.length - 1; i >= 0; i--) {
if (tail[i] && tail[i].intent === 'repair') repairStreak++;
else break;
}
let failureStreak = 0;
for (let i = tail.length - 1; i >= 0; i--) {
if (tail[i] && tail[i].outcome && tail[i].outcome.status === 'failed') failureStreak++;
else break;
}

const antiPatterns = selectedGene && Array.isArray(selectedGene.anti_patterns) ? selectedGene.anti_patterns.slice(-5) : [];
const learningHistory = selectedGene && Array.isArray(selectedGene.learning_history) ? selectedGene.learning_history.slice(-6) : [];
const signalTags = new Set(expandSignals(signals, ''));
const overlappingAntiPatterns = antiPatterns.filter(function (ap) {
return ap && Array.isArray(ap.learning_signals) && ap.learning_signals.some(function (tag) {
return signalTags.has(String(tag));
});
});
const hardFailures = overlappingAntiPatterns.filter(function (ap) { return ap && ap.mode === 'hard'; }).length;
const softFailures = overlappingAntiPatterns.filter(function (ap) { return ap && ap.mode !== 'hard'; }).length;
const recentSuccesses = learningHistory.filter(function (x) { return x && x.outcome === 'success'; }).length;

const stagnation = signals.includes('stable_success_plateau') ||
signals.includes('evolution_saturation') ||
signals.includes('empty_cycle_loop_detected') ||
failureStreak >= 3 ||
repairStreak >= 3;

const forceInnovate = stagnation && !signals.includes('log_error');
const highRiskGene = hardFailures >= 1 || (softFailures >= 2 && recentSuccesses === 0);
const cautiousExecution = highRiskGene || failureStreak >= 2;

let blastRadiusMaxFiles = selectedGene && selectedGene.constraints && Number.isFinite(Number(selectedGene.constraints.max_files))
? Number(selectedGene.constraints.max_files)
: 12;
if (cautiousExecution) blastRadiusMaxFiles = Math.max(2, Math.min(blastRadiusMaxFiles, 6));
else if (forceInnovate) blastRadiusMaxFiles = Math.max(3, Math.min(blastRadiusMaxFiles, 10));

const directives = [];
directives.push('Base strategy: ' + baseStrategy.label + ' (' + baseStrategy.description + ')');
if (forceInnovate) directives.push('Force strategy shift: prefer innovate over repeating repair/optimize.');
if (highRiskGene) directives.push('Selected gene is high risk for current signals; keep blast radius narrow and prefer smallest viable change.');
if (failureStreak >= 2) directives.push('Recent failure streak detected; avoid repeating recent failed approach.');
directives.push('Target max files for this cycle: ' + blastRadiusMaxFiles + '.');

return {
name: baseStrategy.name,
label: baseStrategy.label,
description: baseStrategy.description,
forceInnovate: forceInnovate,
cautiousExecution: cautiousExecution,
highRiskGene: highRiskGene,
repairStreak: repairStreak,
failureStreak: failureStreak,
blastRadiusMaxFiles: blastRadiusMaxFiles,
directives: directives,
};
}

const STATE_FILE = path.join(getEvolutionDir(), 'evolution_state.json');
const DORMANT_HYPOTHESIS_FILE = path.join(getEvolutionDir(), 'dormant_hypothesis.json');
var DORMANT_TTL_MS = 3600 * 1000;
Expand Down Expand Up @@ -1178,6 +1247,7 @@ async function run() {
const newCandidates = extractCapabilityCandidates({
recentSessionTranscript: recentMasterLog,
signals,
recentFailedCapsules: readRecentFailedCapsules(50),
});
for (const c of newCandidates) {
try {
Expand Down Expand Up @@ -1321,6 +1391,11 @@ async function run() {
? capsuleCandidates.map(c => (c && c.id ? String(c.id) : null)).filter(Boolean)
: [];
const selectedCapsuleId = capsulesUsed.length ? capsulesUsed[0] : null;
const strategyPolicy = computeAdaptiveStrategyPolicy({
recentEvents,
selectedGene,
signals,
});

// Personality selection (natural selection + small mutation when triggered).
// This state is persisted in MEMORY_DIR and is treated as an evolution control surface (not role-play).
Expand Down Expand Up @@ -1351,9 +1426,9 @@ async function run() {
tailAvgScore >= 0.7;
const forceInnovation =
String(process.env.FORCE_INNOVATION || process.env.EVOLVE_FORCE_INNOVATION || '').toLowerCase() === 'true';
const mutationInnovateMode = !!IS_RANDOM_DRIFT || !!innovationPressure || !!forceInnovation;
const mutationInnovateMode = !!IS_RANDOM_DRIFT || !!innovationPressure || !!forceInnovation || !!strategyPolicy.forceInnovate;
const mutationSignals = innovationPressure ? [...(Array.isArray(signals) ? signals : []), 'stable_success_plateau'] : signals;
const mutationSignalsEffective = forceInnovation
const mutationSignalsEffective = (forceInnovation || strategyPolicy.forceInnovate)
? [...(Array.isArray(mutationSignals) ? mutationSignals : []), 'force_innovation']
: mutationSignals;

Expand Down Expand Up @@ -1454,10 +1529,13 @@ async function run() {
console.warn('[SolidifyState] Failed to read git HEAD:', e && e.message || e);
}

const maxFiles =
selectedGene && selectedGene.constraints && Number.isFinite(Number(selectedGene.constraints.max_files))
? Number(selectedGene.constraints.max_files)
: 12;
const maxFiles = strategyPolicy && Number.isFinite(Number(strategyPolicy.blastRadiusMaxFiles))
? Number(strategyPolicy.blastRadiusMaxFiles)
: (
selectedGene && selectedGene.constraints && Number.isFinite(Number(selectedGene.constraints.max_files))
? Number(selectedGene.constraints.max_files)
: 12
);
const blastRadiusEstimate = {
files: Number.isFinite(maxFiles) && maxFiles > 0 ? maxFiles : 0,
lines: Number.isFinite(maxFiles) && maxFiles > 0 ? Math.round(maxFiles * 80) : 0,
Expand Down Expand Up @@ -1491,6 +1569,7 @@ async function run() {
baseline_untracked: baselineUntracked,
baseline_git_head: baselineHead,
blast_radius_estimate: blastRadiusEstimate,
strategy_policy: strategyPolicy,
active_task_id: activeTask ? (activeTask.id || activeTask.task_id || null) : null,
active_task_title: activeTask ? (activeTask.title || null) : null,
worker_assignment_id: activeTask ? (activeTask._worker_assignment_id || null) : null,
Expand Down Expand Up @@ -1628,6 +1707,7 @@ ${mutationDirective}
capabilityCandidatesPreview,
externalCandidatesPreview,
hubMatchedBlock,
strategyPolicy,
failedCapsules: recentFailedCapsules,
hubLessons,
});
Expand Down Expand Up @@ -1716,5 +1796,4 @@ ${mutationDirective}
}
}

module.exports = { run };

module.exports = { run, computeAdaptiveStrategyPolicy };
71 changes: 66 additions & 5 deletions src/gep/candidates.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { expandSignals } = require('./learningSignals');

function stableHash(input) {
// Deterministic lightweight hash (not cryptographic).
const s = String(input || '');
Expand Down Expand Up @@ -56,13 +58,15 @@ function buildFiveQuestionsShape({ title, signals, evidence }) {
};
}

function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
function extractCapabilityCandidates({ recentSessionTranscript, signals, recentFailedCapsules }) {
const candidates = [];
const signalList = Array.isArray(signals) ? signals : [];
const expandedTags = expandSignals(signalList, recentSessionTranscript);
const toolCalls = extractToolCalls(recentSessionTranscript);
const freq = countFreq(toolCalls);

for (const [tool, count] of freq.entries()) {
if (count < 2) continue;
if (count < 3) continue;
const title = `Repeated tool usage: ${tool}`;
const evidence = `Observed ${count} occurrences of tool call marker for ${tool}.`;
const shape = buildFiveQuestionsShape({ title, signals, evidence });
Expand All @@ -72,13 +76,13 @@ function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
title,
source: 'transcript',
created_at: new Date().toISOString(),
signals: Array.isArray(signals) ? signals : [],
signals: signalList,
tags: expandedTags,
shape,
});
}

// Signals-as-candidates: capture recurring pain points as reusable capability shapes.
const signalList = Array.isArray(signals) ? signals : [];
const signalCandidates = [
// Defensive signals
{ signal: 'log_error', title: 'Repair recurring runtime errors' },
Expand All @@ -105,10 +109,67 @@ function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
source: 'signals',
created_at: new Date().toISOString(),
signals: signalList,
tags: expandedTags,
shape,
});
}

var failedCapsules = Array.isArray(recentFailedCapsules) ? recentFailedCapsules : [];
var groups = {};
var problemPriority = [
'problem:performance',
'problem:protocol',
'problem:reliability',
'problem:stagnation',
'problem:capability',
];
for (var i = 0; i < failedCapsules.length; i++) {
var fc = failedCapsules[i];
if (!fc || fc.outcome && fc.outcome.status === 'success') continue;
var reason = String(fc.failure_reason || '').trim();
var failureTags = expandSignals((fc.trigger || []).concat(signalList), reason).filter(function (t) {
return t.indexOf('problem:') === 0 || t.indexOf('risk:') === 0 || t.indexOf('area:') === 0 || t.indexOf('action:') === 0;
});
if (failureTags.length === 0) continue;
var dominantProblem = null;
for (var p = 0; p < problemPriority.length; p++) {
if (failureTags.indexOf(problemPriority[p]) !== -1) {
dominantProblem = problemPriority[p];
break;
}
}
var groupingTags = dominantProblem
? [dominantProblem]
: failureTags.filter(function (tag) { return tag.indexOf('area:') === 0 || tag.indexOf('risk:') === 0; }).slice(0, 1);
var key = groupingTags.join('|');
if (!groups[key]) groups[key] = { count: 0, tags: failureTags, reasons: [], gene: fc.gene || null };
groups[key].count += 1;
if (reason) groups[key].reasons.push(reason);
}

Object.keys(groups).forEach(function (key) {
var group = groups[key];
if (!group || group.count < 2) return;
var title = 'Learn from recurring failed evolution paths';
if (group.tags.indexOf('problem:performance') !== -1) title = 'Resolve recurring performance regressions';
else if (group.tags.indexOf('problem:protocol') !== -1) title = 'Prevent recurring protocol and validation regressions';
else if (group.tags.indexOf('problem:reliability') !== -1) title = 'Repair recurring reliability failures';
else if (group.tags.indexOf('problem:stagnation') !== -1) title = 'Break repeated stagnation loops with a new strategy';
else if (group.tags.indexOf('area:orchestration') !== -1) title = 'Stabilize task and orchestration behavior';
var evidence = 'Observed ' + group.count + ' recent failed evolutions with similar learning tags. ' +
(group.reasons[0] ? 'Latest reason: ' + clip(group.reasons[0], 180) : '');
candidates.push({
type: 'CapabilityCandidate',
id: 'cand_' + stableHash('failed:' + key),
title: title,
source: 'failed_capsules',
created_at: new Date().toISOString(),
signals: signalList,
tags: group.tags,
shape: buildFiveQuestionsShape({ title: title, signals: signalList, evidence: evidence }),
});
});

// Dedup by id
const seen = new Set();
return candidates.filter(c => {
Expand Down Expand Up @@ -138,5 +199,5 @@ function renderCandidatesPreview(candidates, maxChars = 1400) {
module.exports = {
extractCapabilityCandidates,
renderCandidatesPreview,
expandSignals,
};

Loading