l patterns. This code introduces a Genome structure, a PhyloTree for lineage tracking, and mechanisms for noise injection and narrative context.
// Core Types
interface Genome {
id: string;
seed: number;
metadata: Record<string, unknown>;
}
interface Phenotype {
genomeId: string;
visualData: Uint8Array; // Encoded image data
semanticTags: string[];
fitnessScore: number;
}
interface LineageNode {
genomeId: string;
parentId: string | null;
generation: number;
timestamp: number;
}
// Phylogenetic Tracker
class PhyloTree {
private nodes: Map<string, LineageNode> = new Map();
private roots: Set<string> = new Set();
addNode(genomeId: string, parentId: string | null, generation: number): void {
const node: LineageNode = { genomeId, parentId, generation, timestamp: Date.now() };
this.nodes.set(genomeId, node);
if (!parentId) this.roots.add(genomeId);
}
getComplexityScore(): number {
// Simplified metric: ratio of unique branches to total nodes
const branchCount = Array.from(this.nodes.values())
.filter(n => n.parentId !== null).length;
return branchCount / this.nodes.size;
}
}
// Narrative Memory for Momentum
class NarrativeMemory {
private history: string[] = [];
private maxContext: number;
constructor(maxContext: number = 5) {
this.maxContext = maxContext;
}
recordAction(action: string): void {
this.history.push(action);
if (this.history.length > this.maxContext) {
this.history.shift();
}
}
getPromptContext(): string {
return `Evolutionary trajectory: ${this.history.join(' -> ')}. ` +
`Maintain momentum by exploring variations of recent successes.`;
}
}
// VLM Agent with Behavioral Diversity
class VLMAgent {
private persona: string;
private temperature: number;
constructor(persona: string, temperature: number) {
this.persona = persona;
this.temperature = temperature;
}
async generateMutation(
parentPhenotype: Phenotype,
narrativeContext: string
): Promise<Phenotype> {
// In production, this calls the VLM API with structured prompt
const prompt = `
System: You are a creative evolution agent. Persona: ${this.persona}.
User: Mutate the following phenotype based on this context:
Context: ${narrativeContext}
Parent Tags: ${parentPhenotype.semanticTags.join(', ')}
Constraint: Introduce novel semantic elements while preserving core structure.
`;
// Mock generation for illustration
return {
genomeId: crypto.randomUUID(),
visualData: new Uint8Array(),
semanticTags: ['novel', 'variant', this.persona],
fitnessScore: 0
};
}
}
// Evolutionary Orchestrator
class EvolutionaryOrchestrator {
private phyloTree: PhyloTree;
private memory: NarrativeMemory;
private agents: VLMAgent[];
private noiseFactor: number;
constructor(config: { noiseFactor: number; agentPersonas: string[] }) {
this.phyloTree = new PhyloTree();
this.memory = new NarrativeMemory();
this.noiseFactor = config.noiseFactor;
this.agents = config.agentPersonas.map(
p => new VLMAgent(p, 0.8 + Math.random() * 0.4) // Diversity in temperature
);
}
async evolveStep(
population: Phenotype[],
targetSize: number
): Promise<Phenotype[]> {
const nextGen: Phenotype[] = [];
const narrativeContext = this.memory.getPromptContext();
// Selection with Exploratory Noise
const selected = this.selectWithNoise(population, targetSize);
for (const parent of selected) {
// Assign diverse agents to mutations
const agent = this.agents[Math.floor(Math.random() * this.agents.length)];
const child = await agent.generateMutation(parent, narrativeContext);
// Track lineage
this.phyloTree.addNode(child.genomeId, parent.genomeId, parent.metadata.generation + 1);
// Record action for narrative momentum
this.memory.recordAction(`Selected ${parent.semanticTags[0]} -> Mutated via ${agent.persona}`);
nextGen.push(child);
}
return nextGen;
}
private selectWithNoise(population: Phenotype[], count: number): Phenotype[] {
// Add Gaussian noise to fitness scores to prevent deterministic convergence
const scored = population.map(p => ({
...p,
noisyScore: p.fitnessScore + (Math.random() * 2 - 1) * this.noiseFactor
}));
return scored
.sort((a, b) => b.noisyScore - a.noisyScore)
.slice(0, count);
}
}
Architectural Decisions and Rationale
-
Phylogenetic Tracking:
- Why: Open-endedness requires measuring the structure of the search space, not just the quality of outputs. The
PhyloTree enables calculation of complexity metrics and allows the system to detect when diversity is dropping.
- Implementation: Every mutation records its parent. This graph structure supports backtracking and lineage-based diversity penalties.
-
Exploratory Noise Injection:
- Why: Deterministic selection drives the population to local optima. Adding noise to fitness scores during selection allows sub-optimal but diverse candidates to survive, preserving genetic variety.
- Implementation: The
selectWithNoise method perturbs scores. The noiseFactor is a hyperparameter that balances exploration vs. exploitation.
-
Behavioral Diversity via Agent Personas:
- Why: A single VLM agent tends to apply consistent biases. Using multiple agents with different personas and temperatures forces the system to explore different semantic directions.
- Implementation: The
agents array holds instances with distinct system prompts and temperature settings. Random assignment ensures varied mutation strategies.
-
Narrative Memory:
- Why: VLMs lack inherent memory of past actions. Without context, each generation is independent, leading to "amnesia" where successful trajectories are abandoned. Narrative momentum provides continuity.
- Implementation:
NarrativeMemory maintains a sliding window of recent actions. This context is injected into prompts to guide the VLM toward coherent exploration rather than random drift.
Pitfall Guide
Building VLM-driven evolutionary systems introduces unique failure modes. The following pitfalls are derived from production experience with open-ended search architectures.
| Pitfall | Explanation | Fix |
|---|
| Deterministic Selection Trap | Using strict top-k selection causes rapid mode collapse. The population converges to a single solution within few generations. | Implement noise injection in the selection step. Use tournament selection with stochasticity or softmax sampling based on fitness. |
| Narrative Drift | The narrative memory accumulates irrelevant history, confusing the VLM and degrading output quality. | Use a sliding window for memory. Implement summarization logic to compress history into high-level trends rather than raw logs. |
| Phylogenetic Blindness | Failing to track lineage prevents measurement of complexity. Engineers may optimize for fitness while unknowingly reducing diversity. | Always maintain a lineage graph. Monitor complexity metrics alongside fitness. Alert when branch ratio drops below threshold. |
| Agent Homogenization | Over time, diverse agents may converge to similar behaviors due to similar training data or prompt drift. | Periodically refresh agent personas. Use adversarial diversity loss to penalize agents that produce similar outputs. |
| Context Window Saturation | Injecting full history into prompts exceeds context limits, causing truncation and loss of critical information. | Use vector retrieval for relevant history. Summarize past generations. Limit narrative context to the most recent N steps. |
| Metric Gaming | The system optimizes for the proxy metric (e.g., semantic tags) rather than true novelty, producing artifacts that score well but lack value. | Use multi-objective evaluation. Combine automated metrics with periodic human-in-the-loop validation. Rotate evaluation criteria. |
| Noise Overload | Excessive noise in selection leads to random walk behavior, destroying any accumulated progress. | Calibrate noise factor dynamically. Reduce noise as the system stabilizes or increase noise when diversity metrics drop. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High Novelty Requirement | Multi-Agent + Narrative Memory + High Noise | Maximizes exploration and sustains diversity across semantic space. | High Compute (Multiple VLM calls per step) |
| Fast Iteration / Low Latency | Single Agent + Moderate Noise + Short Memory | Reduces overhead while maintaining basic exploration. Suitable for rapid prototyping. | Low Compute |
| Stable Refinement | Deterministic Selection + Low Noise | Focuses on optimizing existing solutions. Use when novelty is less critical than quality. | Low Compute |
| Resource Constrained | Single Agent + Vector Memory + Diversity Penalty | Balances exploration with cost. Vector memory reduces context overhead. | Medium Compute |
Configuration Template
Use this template to bootstrap your evolutionary orchestrator. Adjust parameters based on your domain and compute budget.
evolutionary_config:
population_size: 20
generations: 100
selection:
method: "noisy_tournament"
noise_factor: 0.3
tournament_size: 3
agents:
- persona: "Abstract Explorer"
temperature: 0.9
weight: 0.4
- persona: "Structural Refiner"
temperature: 0.7
weight: 0.3
- persona: "Semantic Innovator"
temperature: 0.85
weight: 0.3
memory:
type: "narrative_sliding_window"
max_context: 5
summary_interval: 10
metrics:
track_phylogeny: true
novelty_threshold: 0.6
complexity_alert: 0.2
Quick Start Guide
-
Initialize Orchestrator:
Create an instance of EvolutionaryOrchestrator with your configuration. Ensure you define agent personas and noise parameters.
const orchestrator = new EvolutionaryOrchestrator({
noiseFactor: 0.3,
agentPersonas: ["Explorer", "Refiner", "Innovator"]
});
-
Seed Population:
Generate an initial population of phenotypes. This can be done via random seeds or by querying the VLM with diverse prompts.
const initialPopulation = await generateSeedPopulation(10);
-
Run Evolution Loop:
Execute the evolution steps. Monitor metrics after each step to detect convergence or degradation.
let currentPop = initialPopulation;
for (let gen = 0; gen < 50; gen++) {
currentPop = await orchestrator.evolveStep(currentPop, 10);
logMetrics(orchestrator.getMetrics());
}
-
Inspect Results:
Analyze the phylogenetic tree and output phenotypes. Use complexity scores to validate open-endedness.
const complexity = orchestrator.phyloTree.getComplexityScore();
console.log(`Final Phylogenetic Complexity: ${complexity}`);
This architecture provides a robust foundation for engineering open-ended discovery systems. By prioritizing lineage tracking, controlled noise, behavioral diversity, and narrative momentum, you can transform VLMs from static generators into dynamic agents of creative exploration.