Summary
Milestone M1: turn the M0 decoder into a running Grammatical-Evolution engine by reusing
evoforge. Key reuse (locked D1): GE codons are encoded as evoforge Int genes, so the existing
population / tournament / elitism / gaussian-mutation / ask-tell run GE with no new operator
code. Introduce the Target and Fitness traits so the engine stays DSL-agnostic.
Spec: https://github.com/mnemonik-dev/genetic_algorithms/blob/main/docs/technical-reference/gggp-implementation-spec.md (§3.2, §3.4, §3.5, §4, §11 reuse map)
⭐ Reuse from autonomous-eden/gggp_bundle (reference, not wholesale)
Target/phenotype shape — gggp_bundle/rust/src/gggp/phenotype.rs: the Phenotype trait +
OutputType::{SourceCode(String), Embedding, GlyphMap}. Model our Target::Artifact on
OutputType::SourceCode; drop the embedding/glyph variants.
- Engine cross-check —
gggp_bundle/rust/src/gggp/mod.rs: Gggp (init_population,
calc_fitnesses, sort_population, crossover, mutation) and select_tournament /
SelectionMatrix. Do NOT port Gggp wholesale — D1 says reuse evoforge's engine. Use these
only as a behavior reference / sanity check.
- Their crossover/mutation are node/subtree-aware (CFG-GP style) — that's M3 territory; M1 stays on
evoforge's linear ops over the Int-codon genome.
Scope
src/codon.rs — codon_schema(genome_len, max_codon) -> Vec<evoforge::GeneSpec> (all GeneType::Int, range [0, max_codon]).
src/target.rs — trait Target { type Artifact; type Error; fn grammar(&self)->&Grammar; fn build(&self,&Derivation)->Result<Artifact,Error>; fn repair(&self,&mut Artifact); fn validate(&self,&Artifact)->Result<(),Error>; } + trait Fitness<A> { fn score(&mut self,&A)->f64; } (blanket impl for FnMut(&A)->f64) + Candidate<A>.
src/engine.rs — GrammarEngine<T: Target> wrapping evoforge::Engine over codon_schema:
new(target, GrammarConfig), ask(batch) -> Vec<Candidate<T::Artifact>> (decode → build → repair →
validate; drop/penalize failures), tell(results), run_to_completion(fitness), best(),
snapshot() -> GrammarSnapshot (serde: grammar + codons + best + stats).
GrammarConfig { genome_len, max_codon, map: MapConfig, evo: evoforge::EvolutionConfig, metadata: serde_json::Value }.
- A trivial in-crate
Target (the toy grammar from M0) for tests; a toy Fitness.
Acceptance criteria
Verify
cargo test -p evoforge-grammar
cargo run -p evoforge-grammar --example evolve_toy # now runs a full GE loop
Anti-goals (do NOT)
- Do not implement the Archimedes adapter here (M2) — use the toy
Target only.
- Do not add one-point crossover yet (D2 → M3); reuse evoforge's Uniform.
- Do not add NSGA-II /
Vec<f64> fitness (D5 → v2) — scalar only.
- Do not port
gggp_bundle's Gggp engine wholesale (D1 — reuse evoforge). Do not add
Python/PyO3 (D7). Do not modify evoforge core.
Notes
Depends on M0 (#1). Blocks M2 (#3).
Summary
Milestone M1: turn the M0 decoder into a running Grammatical-Evolution engine by reusing
evoforge. Key reuse (locked D1): GE codons are encoded asevoforgeIntgenes, so the existingpopulation / tournament / elitism / gaussian-mutation /
ask-tellrun GE with no new operatorcode. Introduce the
TargetandFitnesstraits so the engine stays DSL-agnostic.Spec: https://github.com/mnemonik-dev/genetic_algorithms/blob/main/docs/technical-reference/gggp-implementation-spec.md (§3.2, §3.4, §3.5, §4, §11 reuse map)
⭐ Reuse from
autonomous-eden/gggp_bundle(reference, not wholesale)Target/phenotype shape —gggp_bundle/rust/src/gggp/phenotype.rs: thePhenotypetrait +OutputType::{SourceCode(String), Embedding, GlyphMap}. Model ourTarget::ArtifactonOutputType::SourceCode; drop the embedding/glyph variants.gggp_bundle/rust/src/gggp/mod.rs:Gggp(init_population,calc_fitnesses,sort_population,crossover,mutation) andselect_tournament/SelectionMatrix. Do NOT portGggpwholesale — D1 says reuseevoforge's engine. Use theseonly as a behavior reference / sanity check.
evoforge's linear ops over the Int-codon genome.
Scope
src/codon.rs—codon_schema(genome_len, max_codon) -> Vec<evoforge::GeneSpec>(allGeneType::Int, range[0, max_codon]).src/target.rs—trait Target { type Artifact; type Error; fn grammar(&self)->&Grammar; fn build(&self,&Derivation)->Result<Artifact,Error>; fn repair(&self,&mut Artifact); fn validate(&self,&Artifact)->Result<(),Error>; }+trait Fitness<A> { fn score(&mut self,&A)->f64; }(blanket impl forFnMut(&A)->f64) +Candidate<A>.src/engine.rs—GrammarEngine<T: Target>wrappingevoforge::Engineovercodon_schema:new(target, GrammarConfig),ask(batch) -> Vec<Candidate<T::Artifact>>(decode →build→repair→validate; drop/penalize failures),tell(results),run_to_completion(fitness),best(),snapshot() -> GrammarSnapshot(serde: grammar + codons + best + stats).GrammarConfig { genome_len, max_codon, map: MapConfig, evo: evoforge::EvolutionConfig, metadata: serde_json::Value }.Target(the toy grammar from M0) for tests; a toyFitness.Acceptance criteria
cargo test -p evoforge-grammar→0 failed.GrammarEngineruns with identical seed + grammar + config produce byte-identicalsnapshot()JSON (golden fixture committed undertests/fixtures/).Fitness(e.g. "reward outputs containing token X"),best().fitnessstrictly improves from generation 0 to the final generation within a fixed budget
(
population × generations).ask()never returns a candidate whose artifact failsTarget::validate.cargo fmt --check+cargo clippy -- -D warningsclean.Verify
Anti-goals (do NOT)
Targetonly.Vec<f64>fitness (D5 → v2) — scalar only.gggp_bundle'sGggpengine wholesale (D1 — reuse evoforge). Do not addPython/PyO3 (D7). Do not modify
evoforgecore.Notes
Depends on M0 (#1). Blocks M2 (#3).