diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e5a1d46 --- /dev/null +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 66d396c..42595e9 100644 --- a/README.md +++ b/README.md @@ -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** @@ -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 | diff --git a/README.zh-CN.md b/README.zh-CN.md index 30bac17..f42111d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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` | 触发上报所需的最低连续失败次数 | diff --git a/index.js b/index.js index e1bfa95..ff5dc8e 100644 --- a/index.js +++ b/index.js @@ -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') { diff --git a/package.json b/package.json index a767c16..058cb4b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/evolve.js b/src/evolve.js index 5eadfa9..9d59f61 100644 --- a/src/evolve.js +++ b/src/evolve.js @@ -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'); @@ -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).', diff --git a/src/gep/issueReporter.js b/src/gep/issueReporter.js index 2f29a25..c7810fd 100644 --- a/src/gep/issueReporter.js +++ b/src/gep/issueReporter.js @@ -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;