Spaces:
Runtime error
Runtime error
| import { db } from '../config/gun.js'; | |
| import { gunSafe } from '../utils/gunUtils.js'; | |
| import { sandbox } from './IsolateSandbox.js'; | |
| /** | |
| * Gene Definitions — structured genome for P2PCLAW protocol optimization | |
| * Each gene is a continuous [0,1] parameter governing network behavior. | |
| */ | |
| export const GENE_DEFS = [ | |
| { key: 'research_depth', min: 0, max: 1, optimum: 0.65, label: 'Research Depth', desc: 'Depth vs. breadth of topic investigation per agent' }, | |
| { key: 'validation_strictness', min: 0, max: 1, optimum: 0.70, label: 'Validation Strictness', desc: 'Rigor of peer review applied to submitted papers' }, | |
| { key: 'publication_rate', min: 0, max: 1, optimum: 0.40, label: 'Publication Rate', desc: 'Frequency of publishing findings (higher = more spam risk)' }, | |
| { key: 'consensus_threshold', min: 0, max: 1, optimum: 0.68, label: 'Consensus Threshold', desc: 'Minimum agreement ratio required to promote a paper' }, | |
| { key: 'collaboration_weight', min: 0, max: 1, optimum: 0.55, label: 'Collaboration Weight', desc: 'Tendency to collaborate vs. isolated solo research' }, | |
| { key: 'exploration_rate', min: 0, max: 1, optimum: 0.38, label: 'Exploration Rate', desc: 'Explore new topics vs. exploit established research areas' }, | |
| { key: 'fault_tolerance', min: 0, max: 1, optimum: 0.80, label: 'Fault Tolerance', desc: 'Network resilience to agent failures and adversarial nodes' }, | |
| { key: 'convergence_speed', min: 0, max: 1, optimum: 0.45, label: 'Convergence Speed', desc: 'Speed of consensus convergence (too fast = premature, too slow = stagnation)' }, | |
| ]; | |
| /** | |
| * GeneticService — Full Evolutionary Engine | |
| * | |
| * Implements: | |
| * - Real genetic algorithm (selection, crossover, mutation, elitism) | |
| * - Multi-objective fitness function based on network optimization theory | |
| * - Lineage tracking (parent IDs per offspring) | |
| * - Population diversity metric (avg pairwise gene distance) | |
| * - Persistence via Gun.js | |
| * - Code mutation sandbox (legacy IsolateSandbox integration) | |
| */ | |
| export class GeneticService { | |
| constructor() { | |
| this.population = []; // current live population | |
| this.generation = 0; | |
| this.populationSize = 12; | |
| this.mutationRate = 0.12; | |
| this.eliteCount = 2; // elitism: always carry top N | |
| this._historyBuf = []; // [{generation, best, avg, diversity}] | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Gene helpers | |
| // ───────────────────────────────────────────────────────────────── | |
| _randGene(def) { | |
| return +(Math.random() * (def.max - def.min) + def.min).toFixed(4); | |
| } | |
| _randomGenome(overrides = {}) { | |
| const genes = {}; | |
| for (const def of GENE_DEFS) { | |
| genes[def.key] = overrides[def.key] !== undefined ? overrides[def.key] : this._randGene(def); | |
| } | |
| return genes; | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Fitness function — multi-objective, range [0, 1] | |
| // ───────────────────────────────────────────────────────────────── | |
| evaluateFitness(genes) { | |
| // 1. Network efficiency: research_depth ≈ 0.65, exploration_rate ≈ 0.38 | |
| const netEff = Math.max(0, | |
| 1 - Math.abs(genes.research_depth - 0.65) * 1.4 | |
| - 0.6 * Math.abs(genes.exploration_rate - 0.38) | |
| ); | |
| // 2. Quality gate: validation_strictness × (1 - publication_rate × 0.4) | |
| // High strictness + moderate rate = good. Too loose + too fast = spam. | |
| const qualityGate = Math.min(1, | |
| genes.validation_strictness * (1 - genes.publication_rate * 0.45) * 1.25 | |
| ); | |
| // 3. Consensus health: threshold sweet-spot around 0.68 | |
| const consensusScore = genes.consensus_threshold >= 0.5 | |
| ? Math.max(0, 1 - Math.abs(genes.consensus_threshold - 0.68) * 2.5) | |
| : genes.consensus_threshold * 0.6; | |
| // 4. Collaboration balance: neither isolated (→0) nor echo chamber (→1) | |
| const collabScore = Math.max(0, 1 - Math.abs(genes.collaboration_weight - 0.55) * 2.2); | |
| // 5. Fault tolerance: monotone reward, strongly penalise < 0.5 | |
| const resilienceScore = genes.fault_tolerance >= 0.5 | |
| ? genes.fault_tolerance | |
| : genes.fault_tolerance * 0.4; | |
| // 6. Convergence speed: sweet-spot at 0.45 | |
| const convergenceScore = Math.max(0, 1 - Math.abs(genes.convergence_speed - 0.45) * 2.4); | |
| // Weighted aggregate | |
| const raw = | |
| netEff * 0.22 + | |
| qualityGate * 0.22 + | |
| consensusScore * 0.16 + | |
| collabScore * 0.14 + | |
| resilienceScore * 0.14 + | |
| convergenceScore * 0.12; | |
| return Math.max(0, Math.min(1, raw)); | |
| } | |
| _fitnessComponents(genes) { | |
| const fc = { | |
| network_efficiency: Math.max(0, 1 - Math.abs(genes.research_depth - 0.65)*1.4 - 0.6*Math.abs(genes.exploration_rate - 0.38)), | |
| quality_gate: Math.min(1, genes.validation_strictness * (1 - genes.publication_rate * 0.45) * 1.25), | |
| consensus_health: genes.consensus_threshold >= 0.5 ? Math.max(0, 1 - Math.abs(genes.consensus_threshold - 0.68)*2.5) : genes.consensus_threshold*0.6, | |
| collaboration_balance: Math.max(0, 1 - Math.abs(genes.collaboration_weight - 0.55)*2.2), | |
| resilience: genes.fault_tolerance >= 0.5 ? genes.fault_tolerance : genes.fault_tolerance*0.4, | |
| convergence_score: Math.max(0, 1 - Math.abs(genes.convergence_speed - 0.45)*2.4), | |
| }; | |
| return Object.fromEntries(Object.entries(fc).map(([k, v]) => [k, +v.toFixed(4)])); | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Genetic operators | |
| // ───────────────────────────────────────────────────────────────── | |
| /** Tournament selection — picks best of k random candidates */ | |
| _tournamentSelect(pop, k = 3) { | |
| const candidates = []; | |
| for (let i = 0; i < k; i++) candidates.push(pop[Math.floor(Math.random() * pop.length)]); | |
| return candidates.reduce((best, c) => (c.fitness > best.fitness ? c : best)); | |
| } | |
| /** Uniform crossover — each gene inherited independently with 50% probability */ | |
| _crossover(parentA, parentB) { | |
| const childGenes = {}; | |
| for (const def of GENE_DEFS) { | |
| childGenes[def.key] = Math.random() < 0.5 ? parentA.genes[def.key] : parentB.genes[def.key]; | |
| } | |
| return childGenes; | |
| } | |
| /** Gaussian mutation — perturbs each gene with probability `rate` */ | |
| _mutate(genes, rate = this.mutationRate) { | |
| const mutated = { ...genes }; | |
| for (const def of GENE_DEFS) { | |
| if (Math.random() < rate) { | |
| const sigma = (def.max - def.min) * 0.10; | |
| // Box-Muller approximation | |
| const delta = (Math.random() + Math.random() - 1) * sigma; | |
| mutated[def.key] = +(Math.max(def.min, Math.min(def.max, genes[def.key] + delta))).toFixed(4); | |
| } | |
| } | |
| return mutated; | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Population management | |
| // ───────────────────────────────────────────────────────────────── | |
| /** Seed a fresh random population (resets generation counter) */ | |
| seedPopulation(size = this.populationSize) { | |
| this.population = []; | |
| this.generation = 0; | |
| this._historyBuf = []; | |
| this.populationSize = size; | |
| for (let i = 0; i < size; i++) { | |
| const genes = this._randomGenome(); | |
| const fitness = this.evaluateFitness(genes); | |
| const genome = this._buildGenome(`genome-g0-${i}`, 0, [], genes, fitness, 'EVALUATED'); | |
| this.population.push(genome); | |
| db.get('genetic_population').get(genome.id).put(gunSafe(genome)); | |
| } | |
| const stats = this.getStats(); | |
| this._historyBuf.push(stats); | |
| db.get('genetic_stats').put(gunSafe({ ...stats, timestamp: Date.now() })); | |
| db.get('genetic_history').get(`g0`).put(gunSafe(stats)); | |
| return this.population; | |
| } | |
| /** Evolve one full generation (selection → crossover → mutation → elitism) */ | |
| evolveGeneration() { | |
| if (this.population.length < 2) throw new Error('Population too small — seed first (minimum 2)'); | |
| const sorted = [...this.population].sort((a, b) => b.fitness - a.fitness); | |
| const nextGen = []; | |
| // Elitism: carry over top N unchanged | |
| for (let i = 0; i < this.eliteCount && i < sorted.length; i++) { | |
| nextGen.push({ ...sorted[i], status: 'ELITE' }); | |
| } | |
| // Generate offspring via tournament → crossover → mutation | |
| while (nextGen.length < this.populationSize) { | |
| const pa = this._tournamentSelect(sorted, 3); | |
| const pb = this._tournamentSelect(sorted, 3); | |
| let childGenes = this._crossover(pa, pb); | |
| childGenes = this._mutate(childGenes); | |
| const fitness = this.evaluateFitness(childGenes); | |
| const idx = nextGen.length; | |
| const child = this._buildGenome( | |
| `genome-g${this.generation + 1}-${idx}`, | |
| this.generation + 1, | |
| [pa.id, pb.id], | |
| childGenes, | |
| fitness, | |
| 'EVALUATED' | |
| ); | |
| nextGen.push(child); | |
| } | |
| this.generation++; | |
| this.population = nextGen; | |
| // Persist to Gun | |
| for (const g of nextGen) { | |
| db.get('genetic_population').get(g.id).put(gunSafe(g)); | |
| } | |
| const stats = this.getStats(); | |
| this._historyBuf.push(stats); | |
| db.get('genetic_stats').put(gunSafe({ ...stats, timestamp: Date.now() })); | |
| db.get('genetic_history').get(`g${this.generation}`).put(gunSafe(stats)); | |
| return { generation: this.generation, population: nextGen, stats, history: this._historyBuf }; | |
| } | |
| /** Manual crossover of two specific genomes by ID */ | |
| crossoverById(idA, idB) { | |
| const pa = this.population.find(g => g.id === idA); | |
| const pb = this.population.find(g => g.id === idB); | |
| if (!pa) throw new Error(`Genome ${idA} not found`); | |
| if (!pb) throw new Error(`Genome ${idB} not found`); | |
| let childGenes = this._crossover(pa, pb); | |
| childGenes = this._mutate(childGenes, 0.05); // light mutation for manual cross | |
| const fitness = this.evaluateFitness(childGenes); | |
| const child = this._buildGenome( | |
| `genome-cross-${Date.now().toString(36)}`, | |
| Math.max(pa.generation, pb.generation) + 1, | |
| [pa.id, pb.id], | |
| childGenes, | |
| fitness, | |
| 'MANUAL_CROSS' | |
| ); | |
| this.population.push(child); | |
| db.get('genetic_population').get(child.id).put(gunSafe(child)); | |
| return child; | |
| } | |
| _buildGenome(id, generation, parent_ids, genes, fitness, status) { | |
| return { | |
| id, | |
| generation, | |
| parent_ids, | |
| genes, | |
| fitness: +fitness.toFixed(4), | |
| fitness_components: this._fitnessComponents(genes), | |
| status, | |
| born_at: Date.now(), | |
| }; | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Stats & population access | |
| // ───────────────────────────────────────────────────────────────── | |
| getStats() { | |
| if (this.population.length === 0) { | |
| return { generation: this.generation, size: 0, best: 0, avg: 0, diversity: 0, elites: [] }; | |
| } | |
| const fits = this.population.map(g => g.fitness); | |
| const best = Math.max(...fits); | |
| const avg = fits.reduce((a, b) => a + b, 0) / fits.length; | |
| // Average pairwise Euclidean gene distance (normalised) | |
| let divSum = 0, pairs = 0; | |
| for (let i = 0; i < this.population.length; i++) { | |
| for (let j = i + 1; j < this.population.length; j++) { | |
| let dist = 0; | |
| const ga = this.population[i].genes; | |
| const gb = this.population[j].genes; | |
| for (const def of GENE_DEFS) dist += Math.abs(ga[def.key] - gb[def.key]); | |
| divSum += dist / GENE_DEFS.length; | |
| pairs++; | |
| } | |
| } | |
| const diversity = pairs > 0 ? divSum / pairs : 0; | |
| return { | |
| generation: this.generation, | |
| size: this.population.length, | |
| best: +best.toFixed(4), | |
| avg: +avg.toFixed(4), | |
| diversity: +diversity.toFixed(4), | |
| elites: this.population.filter(g => g.status === 'ELITE').map(g => ({ id: g.id, fitness: g.fitness })), | |
| }; | |
| } | |
| getHistory() { return this._historyBuf; } | |
| async getPopulation() { | |
| if (this.population.length > 0) return this.population; | |
| // Fallback: load from Gun (e.g. after server restart) | |
| return new Promise((resolve) => { | |
| const pop = []; | |
| db.get('genetic_population').map().once((data) => { | |
| if (data && data.id && data.genes) pop.push(data); | |
| }); | |
| setTimeout(() => { | |
| this.population = pop.sort((a, b) => (b.fitness || 0) - (a.fitness || 0)); | |
| resolve(this.population); | |
| }, 1500); | |
| }); | |
| } | |
| // ───────────────────────────────────────────────────────────────── | |
| // Legacy: code mutation sandbox (unchanged interface) | |
| // ───────────────────────────────────────────────────────────────── | |
| async submitProposal(agentId, { title, description, code, logicType = 'protocol' }) { | |
| const proposalId = `mutation-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; | |
| const proposal = { | |
| id: proposalId, author: agentId, title, description, code, logicType, | |
| status: 'PENDING_SANDBOX', consensusWeight: 0, timestamp: Date.now(), results: null, | |
| }; | |
| db.get('genetic_tree').get(proposalId).put(gunSafe(proposal)); | |
| this.runSandboxCheck(proposalId, code); | |
| return proposalId; | |
| } | |
| async runSandboxCheck(proposalId, code) { | |
| const result = await sandbox.execute(code, { memory: '64m', cpus: '0.2', timeout: 5000 }); | |
| const status = result.success ? 'SANDBOX_PASSED' : 'SANDBOX_FAILED'; | |
| db.get('genetic_tree').get(proposalId).put(gunSafe({ | |
| status, | |
| results: { | |
| success: result.success, | |
| exitCode: result.exitCode, | |
| stdout: (result.stdout || '').slice(0, 500), | |
| stderr: (result.stderr || '').slice(0, 300), | |
| }, | |
| })); | |
| console.log(`[GENETIC] Proposal ${proposalId} → ${status}`); | |
| } | |
| async getGeneticTree() { | |
| return new Promise((resolve) => { | |
| const tree = []; | |
| db.get('genetic_tree').map().once((data) => { if (data && data.title) tree.push(data); }); | |
| setTimeout(() => resolve(tree), 1500); | |
| }); | |
| } | |
| } | |
| export const geneticService = new GeneticService(); | |