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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## 2.0.0

- Stabilized the runtime and solidify pipeline by tightening baseline tracking, runtime-only diff handling, and canonical status convergence.
- Added structured exploration context propagation so downstream reports can display exploration role, persona, target profile, and objective.
- Clarified the boundary between core evolver execution and wrapper/reporting layers for coordinated 2.x releases.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ node index.js

The **Capability Evolver** inspects runtime history, extracts signals, selects a Gene/Capsule, and emits a strict GEP protocol prompt to guide safe evolution.

## What's New In 2.0.0

- **Stabilized runtime + solidify loop**: false-positive blast radius and empty-cycle failures were reduced by tightening baseline tracking and runtime-only diff handling.
- **Canonical reporting convergence**: wrapper summaries, status artifacts, and Unified Evolution Report now align on the same run outcome instead of drifting apart.
- **Structured exploration context**: exploration role/persona, target profile, and objective can now be carried as structured fields and surfaced in downstream reports.
- **Clearer core-wrapper boundary**: evolver is responsible for emitting structured exploration context, while wrappers focus on reporting and orchestration.

## Who This Is For / Not For

**For**
Expand Down Expand Up @@ -244,7 +251,7 @@ When the evolver detects persistent failures (failure loop or recurring errors w
| Variable | Default | Description |
|----------|---------|-------------|
| `EVOLVER_AUTO_ISSUE` | `true` | Enable/disable auto issue reporting |
| `EVOLVER_ISSUE_REPO` | `autogame-17/capability-evolver` | Target GitHub repository (owner/repo) |
| `EVOLVER_ISSUE_REPO` | `autogame-17/evolver` | Target GitHub repository (owner/repo) |
| `EVOLVER_ISSUE_COOLDOWN_MS` | `86400000` (24h) | Cooldown period for the same error signature |
| `EVOLVER_ISSUE_MIN_STREAK` | `5` | Minimum consecutive failure streak to trigger |

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ MAJOR.MINOR.PATCH
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `EVOLVER_AUTO_ISSUE` | `true` | 是否启用自动 issue 上报 |
| `EVOLVER_ISSUE_REPO` | `autogame-17/capability-evolver` | 目标 GitHub 仓库(owner/repo) |
| `EVOLVER_ISSUE_REPO` | `autogame-17/evolver` | 目标 GitHub 仓库(owner/repo) |
| `EVOLVER_ISSUE_COOLDOWN_MS` | `86400000`(24 小时) | 同类错误签名的冷却期 |
| `EVOLVER_ISSUE_MIN_STREAK` | `5` | 触发上报所需的最低连续失败次数 |

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ async function main() {
// Post-run hint
console.log('\n' + '=======================================================');
console.log('Capability evolver finished. If you use this project, consider starring the upstream repository.');
console.log('Upstream: https://github.com/autogame-17/capability-evolver');
console.log('Upstream: https://github.com/autogame-17/evolver');
console.log('=======================================================\n');

} else if (command === 'solidify') {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@evomap/evolver",
"version": "1.31.0",
"version": "2.0.0",
"description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
"main": "index.js",
"bin": {
Expand Down
215 changes: 207 additions & 8 deletions src/evolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,185 @@ try {
console.warn('[Evolver] Failed to create MEMORY_DIR (may cause downstream errors):', e && e.message || e);
}

function readJsonIfExists(filePath, fallback) {
try {
if (!filePath || !fs.existsSync(filePath)) return fallback;
const raw = fs.readFileSync(filePath, 'utf8');
if (!String(raw || '').trim()) return fallback;
return JSON.parse(raw);
} catch (_) {
return fallback;
}
}

function writeJsonAtomic(filePath, value) {
if (!filePath) return;
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
const tmpPath = `${filePath}.tmp.${process.pid}`;
fs.writeFileSync(tmpPath, JSON.stringify(value, null, 2) + '\n', 'utf8');
fs.renameSync(tmpPath, filePath);
}

function firstNonEmptyValue(values) {
for (const value of Array.isArray(values) ? values : [values]) {
const text = String(value || '').replace(/\s+/g, ' ').trim();
if (text) return text;
}
return '';
}

function clipInlineText(value, maxLen) {
const text = String(value || '').replace(/\s+/g, ' ').trim();
if (!text) return '';
const limit = Number.isFinite(Number(maxLen)) ? Number(maxLen) : 180;
return text.length <= limit ? text : `${text.slice(0, Math.max(0, limit - 3))}...`;
}

function cleanPromptLine(value) {
return String(value || '')
.replace(/\*\*/g, '')
.replace(/`/g, '')
.replace(/\s+/g, ' ')
.trim();
}

function readFirstEnv(names, maxLen) {
for (const name of Array.isArray(names) ? names : [names]) {
const text = clipInlineText(process.env[name], maxLen);
if (text) return text;
}
return '';
}

function findPromptValue(promptText, patterns) {
const lines = String(promptText || '').split('\n');
for (const rawLine of lines) {
const line = cleanPromptLine(rawLine);
if (!line) continue;
for (const pattern of patterns) {
const match = line.match(pattern);
if (match && match[1]) return clipInlineText(match[1], 180);
}
}
return '';
}

function buildExplorationContextFromPrompt(promptText, promptMeta, promptPath) {
const role = firstNonEmptyValue([
readFirstEnv(['EVOLVER_EXPLORATION_ROLE', 'EVOLVE_EXPLORATION_ROLE'], 140),
findPromptValue(promptText, [
/Identity Updated:\s*Confirmed role as\s+(.+?)(?:[.!?]|$)/i,
/\brole(?: is| as|:)\s+(.+?)(?:[.!?]|$)/i,
]),
]);
const persona = firstNonEmptyValue([
readFirstEnv(['EVOLVER_EXPLORATION_PERSONA', 'EVOLVE_EXPLORATION_PERSONA'], 140),
findPromptValue(promptText, [
/\bpersona(?: is|:)\s+(.+?)(?:[.!?]|$)/i,
/\bstyle persona(?: is|:)\s+(.+?)(?:[.!?]|$)/i,
]),
]);
const targetProfileId = readFirstEnv(['EVOLVER_TARGET_PROFILE_ID', 'EVOLVE_TARGET_PROFILE_ID'], 120);
const targetProfileTitle = readFirstEnv(['EVOLVER_TARGET_PROFILE_TITLE', 'EVOLVE_TARGET_PROFILE_TITLE'], 160);
const objective = firstNonEmptyValue([
readFirstEnv([
'EVOLVER_EXPLORATION_OBJECTIVE',
'EVOLVE_EXPLORATION_OBJECTIVE',
'EVOLVER_TARGET',
'EVOLVE_TARGET',
], 220),
]);
const direction = firstNonEmptyValue([
readFirstEnv(['EVOLVER_EXPLORATION_DIRECTION', 'EVOLVE_EXPLORATION_DIRECTION'], 220),
objective,
]);
const agent = firstNonEmptyValue([
readFirstEnv(['EVOLVER_EXPLORATION_AGENT', 'EVOLVE_EXPLORATION_AGENT'], 80),
promptMeta && promptMeta.agent ? String(promptMeta.agent) : '',
AGENT_NAME,
]);
return {
role: role || null,
persona: persona || null,
agent: agent || null,
target_profile_id: targetProfileId || null,
target_profile_title: targetProfileTitle || null,
exploration_objective: objective || null,
direction: direction || null,
prompt_path: promptPath || null,
};
}

function hasExplorationContext(context) {
const value = context && typeof context === 'object' ? context : {};
return [
value.role,
value.persona,
value.agent,
value.target_profile_id,
value.target_profile_title,
value.exploration_objective,
value.direction,
value.prompt_path,
].some(item => String(item || '').trim());
}

function mergeExplorationContextIntoLastRun(runId, extras) {
if (!runId || !extras || typeof extras !== 'object') return;
try {
const state = readStateForSolidify();
const lastRun = state && state.last_run ? state.last_run : null;
if (!lastRun || String(lastRun.run_id || '') !== String(runId)) return;
const nextContext = Object.assign({}, lastRun.exploration_context || {}, extras.exploration_context || {});
lastRun.prompt_path = extras.prompt_path || lastRun.prompt_path || null;
lastRun.prompt_meta = extras.prompt_meta || lastRun.prompt_meta || null;
lastRun.exploration_context = nextContext;
lastRun.exploration_role = nextContext.role || null;
lastRun.exploration_persona = nextContext.persona || null;
lastRun.exploration_agent = nextContext.agent || null;
lastRun.target_profile_id = nextContext.target_profile_id || lastRun.target_profile_id || null;
lastRun.target_profile_title = nextContext.target_profile_title || lastRun.target_profile_title || null;
lastRun.exploration_objective = nextContext.exploration_objective || lastRun.exploration_objective || null;
lastRun.exploration_direction = nextContext.direction || lastRun.exploration_direction || null;
writeStateForSolidify(state);
} catch (e) {
console.warn('[ExplorationContext] Failed to merge context into solidify state:', e && e.message || e);
}
}

function writeExplorationStatusContext(runInfo, explorationContext) {
if (!hasExplorationContext(explorationContext)) return null;
try {
const statusPath = path.join(getEvolutionDir(), 'exploration_status.json');
const existing = readJsonIfExists(statusPath, {}) || {};
const next = Object.assign({}, existing, {
timestamp: existing.timestamp || (runInfo && runInfo.created_at ? runInfo.created_at : new Date().toISOString()),
status: existing.status || 'active',
run_id: runInfo && runInfo.run_id ? runInfo.run_id : (existing.run_id || null),
cycle_id: runInfo && runInfo.cycle_id ? runInfo.cycle_id : (existing.cycle_id || null),
role: explorationContext.role || existing.role || null,
persona: explorationContext.persona || existing.persona || null,
agent: explorationContext.agent || existing.agent || null,
target_profile_id: explorationContext.target_profile_id || existing.target_profile_id || null,
target_profile_title: explorationContext.target_profile_title || existing.target_profile_title || null,
exploration_objective: explorationContext.exploration_objective || existing.exploration_objective || null,
direction: explorationContext.direction || existing.direction || null,
prompt_path: explorationContext.prompt_path || existing.prompt_path || null,
context_source: 'evolver_prompt_context',
context_updated_at: new Date().toISOString(),
});
if (!next.message) {
next.message = 'Exploration context captured from the latest evolver prompt.';
}
writeJsonAtomic(statusPath, next);
return next;
} catch (e) {
console.warn('[ExplorationContext] Failed to update exploration status:', e && e.message || e);
return null;
}
}

function formatSessionLog(jsonlContent) {
const result = [];
const lines = jsonlContent.split('\n');
Expand Down Expand Up @@ -1773,24 +1952,44 @@ ${mutationDirective}
if (st && st.last_run && st.last_run.run_id) runId = String(st.last_run.run_id);
} catch (e) {}
let artifact = null;
const promptMeta = {
agent: AGENT_NAME,
drift_enabled: IS_RANDOM_DRIFT,
review_mode: IS_REVIEW_MODE,
dry_run: IS_DRY_RUN,
mutation_id: mutation && mutation.id ? mutation.id : null,
personality_key: personalitySelection && personalitySelection.personality_key ? personalitySelection.personality_key : null,
};
try {
artifact = writePromptArtifact({
memoryDir: getEvolutionDir(),
cycleId,
runId,
prompt,
meta: {
agent: AGENT_NAME,
drift_enabled: IS_RANDOM_DRIFT,
review_mode: IS_REVIEW_MODE,
dry_run: IS_DRY_RUN,
mutation_id: mutation && mutation.id ? mutation.id : null,
personality_key: personalitySelection && personalitySelection.personality_key ? personalitySelection.personality_key : null,
},
meta: promptMeta,
});
} catch (e) {
artifact = null;
}
try {
const explorationContext = buildExplorationContextFromPrompt(
prompt,
promptMeta,
artifact && artifact.promptPath ? artifact.promptPath : null
);
mergeExplorationContextIntoLastRun(runId, {
prompt_path: artifact && artifact.promptPath ? artifact.promptPath : null,
prompt_meta: promptMeta,
exploration_context: explorationContext,
});
writeExplorationStatusContext({
run_id: runId,
cycle_id: cycleId,
created_at: new Date().toISOString(),
}, explorationContext);
} catch (e) {
console.warn('[ExplorationContext] Failed to persist prompt context:', e && e.message || e);
}

const executorTask = [
'You are the executor (the Hand).',
Expand Down
2 changes: 1 addition & 1 deletion src/gep/issueReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { redactString } = require('./sanitize');
const { getNodeId } = require('./a2aProtocol');

const STATE_FILE_NAME = 'issue_reporter_state.json';
const DEFAULT_REPO = 'autogame-17/capability-evolver';
const DEFAULT_REPO = 'autogame-17/evolver';
const DEFAULT_COOLDOWN_MS = 24 * 60 * 60 * 1000;
const DEFAULT_MIN_STREAK = 5;
const MAX_LOG_CHARS = 2000;
Expand Down