GA in the Trenches — The Pitfalls That Are Actually in the Code
Disclaimer up front: this article is not a narrative built on run logs. The previous 24.6 cited a whole set of "Scenario 6/7/8" runs (lineage improvement 100% vs 3.7%, Best 89.47 vs 62.50, and so on). Those logs came from a directory that doesn't exist in this repo —
examples/autonomous-evolution/— and I cannot reproduce or vouch for them. So this version is about engineering lessons that are really in the code, each one a fix or decision with evidence in a source comment or commit. Numbers only when verifiable; otherwise I say "not landed / to be verified" out loud.
1. First: about the article's data
A widely-quoted claim:
"Wired is GA's exploration bottleneck. Scenario 6 = 89.47 crushes Scenario 7 = 62.50."
Reading the source this time, the facts are:
examples/autonomous-evolution/does not exist (ls examples/shows only10-ga-full-evolutionand a19-ga-candidate-e2e).- Those Best scores, generation trajectories, lineage-improvement rates, prompt-mutation ratios,
deterministic_score=85.9 → llm_score=70etc., have no in-repo value to cite as truth. Either they're from a historical experiment that never landed, or they're invented — either way they shouldn't be quoted as fact.
So this article no longer retells that narrative. The verifiable GA engineering lessons live in code comments. Here they are, chosen by "is there source evidence."
2. Lesson 1: don't keep methods in an interface that nobody calls
The package doc of internal/runtime/evolution/genome/genome.go (a 2026-07 change):
CrossoverandFitnesswere removed from the coreGenomeinterface because they had zero production callers; they're now optional (CrossoverGenome/FitnessGenome).
A classic trench-truth: an interface should look like the real call surface, not the idealized one in a paper. Every uncalled method misleads implementers — you'd think subsystems all crossover and all score, when at runtime only Mutate/Snapshot are hard requirements. Decide whether an extension point stays by grep-ing for callers, not by intuition.
This lines up with internal/runtime/ares_evolution/genome/crossover.go, where Crossover and Fitness are likewise optional capabilities.
3. Lesson 2: a "retired" name is still there to mislead people
internal/runtime/evolution/genome/workflow_genome.go:
// SchedulerGenomeName is RETIRED (fusion plan Section B1, 2026-08-22): the
// scheduler dimension no longer participates in genome mutation/diff.
// Kept only as a historical identifier for legacy persisted patches.
SchedulerGenomeName = "scheduler"
The scheduler dimension is out of GA mutation/diff, but the constant stays because it may appear in persisted historical patches. Deleting a "useless-looking" identifier would make old data mismatch on deserialization/query. Lesson: before renaming/retiring, confirm no historical data references it; if you keep it, write down why in a comment.
4. Lesson 3: metadata almost silently vanished on node replace (C4)
In workflow_genome.go, mutateReplaceNode carries an important comment and fix (C4):
// C4: preserve Metadata through replace – previously the new step was
// built without Metadata, which meant a metadata-only gene mutation
// produced ZERO patches ... evolution could never select for a metadata variant.
Previously ReplaceNode built the new step without copying Metadata. Consequence: mutateSetMetadata (which changes only metadata) produced a child whose diff-engine view was topologically identical to the parent → zero patches → evolution could never select a metadata variant. The fix is copying mdCopy into Metadata.
A textbook "initialization" bug — not a wrong architecture decision, a field that wasn't carried over. The old 24.6's "Wired is the bottleneck" was the extra-large version of this same class of bug — just here I can pin the root cause to real code.
5. Lesson 3b: the mutateSetMetadata operator that made it possible
The same file has mutateSetMetadata(), which exists precisely to make metadata mutations possible, flipping a random node's budget between "" → "10" → "20" → "10":
const budgetKey = "budget"
switch md[budgetKey] {
case "": md[budgetKey] = "10"
case "10": md[budgetKey] = "20"
default: md[budgetKey] = "10"
}
g.dag.SetNodeMetadata(step.ID, md)
The comment states the motivation: "Without this operator, evolution could never explore the metadata dimension." Lesson: to make a dimension evolvable, changing the differ isn't enough — the genome also needs an operator that can produce a child differing only in that dimension, or that exploration path is always empty.
6. Lesson 4: use Replace, not Register (#25)
In candidate_pipeline.go, when the same TargetRole is released a second time:
// Replace, not Register (#25): the registry has no unregister path, so a
// second verified candidate for the same TargetRole must be able to
// supersede the first — Register would fail with "already registered".
if err := p.registry.Replace(target, p.executor); err != nil { ... }
A plain lesson: when the underlying interface has no unregister, "idempotent overwrite" must use Replace rather than a Register that errors, or the second identical candidate fails in the exact same way forever. It's a "read the registry's implementation before you wire it" integration detail.
7. Lesson 5: soft defaults can mask each other (a952206e)
The DefaultPolicy() comment in internal/runtime/evolution/coordinator/coordinator.go documents a real fix:
Previously a
fallthroughafter settingAutoApplyThresholdmeant every patch went to apply, rendering the threshold dead. With explicit logic, it actually became:Priority>=8auto-applies; GA fitness>=70auto-applies;<30rejected;[30,70)delayed for review.
The real constants:
AutoApplyThreshold: 8,
MaxPatchesPerMinute: 4,
MinFitnessThreshold: 30.0,
ApplyFitnessThreshold: 70.0,
Lesson: a single fallthrough can neutralize a safety valve. No matter how many knobs you write, you need tests proving they're reached by the branches that use them — the code comment candidly records this pitfall.
8. Lesson 6: when no executor exists — DAGPatchExecutor as a fallback
internal/fabric/task/workflow/engine/dag_patcher.go: DAGPatchExecutor is the patch registry's fallback. Previously a workflow patch died on "no executor registered for target <nodeID>"; now the fallback mutates the live *MutableDAG (the same object runtime.GetAgentDAG returns), closing the cross-graph loop (Step 7.1).
// It is wired as the patch registry's fallback so a workflow patch no longer
// dies on "no executor registered for target <nodeID>"; instead it mutates the
// real live DAG. This is the closure the cross-graph fix unlocks.
Snapshot/Restore implement patch.Restorable, so deployment rollback restores the DAG in place (the *MutableDAG pointer stays stable; the runtime manager, WorkflowGenome, and other executors all observe the restored graph).
9. Putting these together: the real "GA in the trenches"
These lessons converge on a plain, true methodology:
- Suspect initialization / registration / wiring before you suspect architecture. C4's Metadata, the pipeline's Replace, the coordinator's fallthrough, the DAGPatchExecutor fallback — all wiring details, none an algorithmic error.
- Delete cautiously, annotate why you keep.
SchedulerGenomeNamesurvives for historical data; the comment says why. - For a dimension to be evolvable, you need an operator that produces a purely-different child.
mutateSetMetadataonly works becausemutateReplaceNodepreserves Metadata. - Interfaces and configs must live off real evidence. Count callers for interface methods, verify config branches with tests, and measure performance with
go test -bench(see 24.3).
10. Honest boundaries (important)
- The previous 24.6's whole run-data story (three Scenarios, Best scores, lineage-improvement rates, prompt-mutation ratios, LLM final-check
85.9→70) andexamples/autonomous-evolution/run.log→ do not exist in the repo, are not reproducible, and this version no longer cites them. - If you need real evolution trajectories: run
go run ./examples/10-ga-full-evolution(genuinely present in the repo), or use the 24.3go test -benchfor selection operators; when you cite, state the machine and flags. - This version only references facts verifiable in source comments / commits.
Appendix
[A] No invented performance or run data in this version.
[B] Evidence you can read yourself: internal/runtime/evolution/genome/genome.go (interface slimming), internal/runtime/evolution/genome/workflow_genome.go (SchedulerGenomeName, C4, mutateSetMetadata), internal/runtime/evolution/candidate_pipeline.go (#25), internal/runtime/evolution/coordinator/coordinator.go (DefaultPolicy + fallthrough), internal/fabric/task/workflow/engine/dag_patcher.go (DAGPatchExecutor fallback).