Evolving Lightning's next routing algorithm
We pointed an LLM-driven evolutionary search at lnd's real pathfinding stack, inside a simulator that hides channel liquidity the way the network does. Tuning its parameters found nothing. Replacing the algorithm found a lot.
mainnet, vs lnd's 19.8 2.3
vs lnd's 0.694 0.791
from a 300-line seed 1,525
stock lnd, zero losses 14 / 14
What the search found
Every number below is held out. The closing check ran on a real mainnet graph snapshot — lnd's home turf, and a topology the evolved routers had never seen.
Table view
| router | mainnet | hard sealed test | out-of-distribution | combined |
|---|---|---|---|---|
| lnd production stackDijkstra + mission control | 0.694 | 0.309 | 0.357 | 0.333 |
| hand-written seed~300 lines, cheapest path + blacklist | 0.762 | 0.530 | 0.487 | 0.509 |
| hb1evolved, 872 lines | 0.790 | 0.586 | 0.545 | 0.565 |
| mx_c3evolved, 1,525 lines | 0.791 | 0.583 | 0.581 | 0.582 |
The headline is not the size of the win, it is where the win came from. A 400-evaluation search over lnd's own pathfinding knobs — which probability estimator, how much a failed attempt should virtually cost, the floor on acceptable route probability — produced no setting that beat the shipped defaults on validation. The defaults are locally robust; the headroom is not in the knobs.
Meanwhile a deliberately naive 300-line router with a different paradigm beat lnd's full production stack by 39% on the objective, winning or tying every example. Handing that seed to the optimizer, and letting it rewrite the whole algorithm rather than adjust its settings, produced routers that beat both. What they invented — liquidity intervals instead of decaying penalties — is the interesting part.
How the search works
A candidate is a whole routing algorithm in Go. Every proposal is compiled into a simulator and made to actually pay.
The simulator runs lnd's real code
routing/sim_graph.go is an in-memory Lightning network with
hidden per-channel balances and per-direction policies: base and
proportional fees, CLTV deltas, min and max HTLC. It implements lnd's own
Graph interface, so lnd's production pathfinding and mission
control run against it unmodified — which is what makes the baseline
honest.
Forwarding enforces real BOLT semantics. An attempt that asks too little
fee gets FeeInsufficient; one that asks for more than a hop
holds gets TemporaryChannelFailure, the liquidity miss that
all of routing is really about. Balances move on success and unwind on
failure. Topologies are synthetic (line, grid, hub-and-spoke, small-world,
scale-free) or loaded from a mainnet describegraph snapshot,
and hidden balances follow a half, uniform, or bimodal model — the
last one hugging one end of each channel, which is both realistic and the
hard case.
It is fast enough to be an inner loop: ten payments on a 200-node network in about 0.3 seconds including process startup, so thousands of evaluations are cheap.
A candidate is an algorithm, not a config
The evolvable unit is a Go file behind a paradigm-free interface: gossip
view, local balances, and per-attempt feedback in; a route out. GEPA
rewrites that file, go build -overlay compiles the rewrite into
the simulator, and the evaluator scores it on a corpus split into train,
validation and sealed test. Nothing in the contract mentions Dijkstra,
mission control, or probability estimators. Those become background
knowledge the reflection prompt may draw on, keep, reshape, or discard.
the sandbox held
An independent audit found that the graph session handed candidates the concrete simulator graph, type-assertable back into a read of hidden balances or an outright rewrite of ground-truth liquidity — a perfect-score reward hack using no banned identifiers. It was sealed the same day, and every in-flight candidate was checked against it: zero hits. No result on this site was produced through that hole.
The scenarios it is scored against
— scenario files, — payments, split —. Validation picks the incumbent; the test split stays sealed until a champion is declared.
Run telemetry (latest completed optimizer run)
Telemetry from the optimizer, exported straight out of the GEPA run directory. code_drift1 has since finished all 400 evaluations, and so have exp-010's three splitting arms — all of them beaten by the champions (the verdict). exp-010b's two atomic-arena arms have since closed the same way — the champion's fifth hold, against an arena built to charge its probe ladder honestly (the atomic verdict) — and so has exp-012, which found no hot-cache regime anywhere but did find the one change this work argues for upstream (cold cache, hot load). Nothing is live now. The telemetry below is still exp-018's gepa arm, the engine adjudication, which gave three optimizers the same seed, corpus and eval budget and found that only gepa produced a router at all (the adjudication). Two optimizer runs have finished since, both overnight and both closed: exp-022 bred a router against a channel that lies about which hop failed (the lying channel), and exp-024 gave the runner-up engine ten times the evaluations to see whether the band was ever budget starvation (the ceiling arm). In between, exp-021 turned the findings into a flag-gated diff against lnd's own stack, where one mechanism is now PR-ready and the other is a measured null (the distillation patch). The most recent run of all is exp-025, the first bred in a world where money is real, and its winner is the first router here ever to read the fee budget it is handed (evolution learns to count). Two runs have closed since: the compose world, every economic knob plus the lying channel at once, which returned its seed unchanged, and exp-028, its first pre-registered escape, which seeded the same world from the fee-budget specialist and reproduced the give-up attractor from a second lineage (the rule). The 800-evaluation arm, code_full3, is live and carries what is left of the compose question.
where the program stands
No optimizer run is live. Champions are unchanged at hb1 and mx_c3, nine challengers deep, and the arms still open are about changing the environment rather than changing the search (§15 is why).
(Update, 30 July: the frontier is unchanged and the ledger still reads nine challengers. Champions hb1 and mx_c3, specialists atomic1 where holds contend and econ2 where money is scarce. What has changed is where the work points: the integration branch now measures at 14 of 14 tiers interval-solid over stock lnd with zero losses, margins intact under the production default fee limit (§19), and the stated target is shipping it in the next lnd major release. One optimizer run is live, the 800-evaluation compose arm code_full3.)
(Update, 31 July: code_full3 has closed, and with it the evolution track's search for new machinery here. 800 evaluations returned the hand seed to seven digits, which fails the second of the two pre-registered escapes and makes the compose world closed to this recipe at any seed and any practical budget (§23). Nothing is live; no run holds the router. The frontier now belongs to the ship track. The release candidate is interval-router at eb4fc3e62, rebased as b489649f6 on the fork, and it has been validated twice more since: champion-grade to the third decimal on a liquidity family nobody here authored (§21), and carrying a fix for the one interaction §19 could not explain, where shifted blame fabricates an alibi for the guilty channel (§22). Champions unchanged, ledger still nine challengers deep. Next is offline replay on a real node's payment history, which is the last escape from "simulator-shaped" this program has left.)
exp-023 and exp-025, economic realism, both closed. All five stages are merged and flag-gated — min and max HTLC pressure, inbound fees, fees as a first-class cost, concurrent payments, latency — each proven byte-identical with its flag off. The 1,920-run sweep says the champions' edge is informational rather than a pricing edge (the priced world), and the evolution run that followed produced econ2, the fee-budget specialist and challenger number nine (evolution learns to count). The compose-world run (all five economic knobs plus the lying channel) is live as code_full1; its five knobs are live at once. (It has since closed, returning its seed unchanged, and so has the first of its two escapes (§20); the second is live now.)
The interval-router branch. Separately from the simulator, the champions' paradigm is being ported into lnd proper: fourteen commits on interval-router, unchanged through both economic cycles, that put a liquidity interval per directed channel behind routerrpc.router=interval, off by default, with the stock stack untouched when the flag is unset. It still has not been benchmarked in this simulator. Nothing on this site is a measurement of that branch, and no number here should be read as one — though the hybrid it builds, evolved beliefs on top of lnd's own fee-aware pricing, is exactly what §17 says to build. (That benchmark has since run, and this paragraph is the one thing on the page it retires: exp-027 put the branch inside lnd's real payment lifecycle and measured it over fourteen tiers, where it scores 0.788 on mainnet against stock lnd's 0.694 and leads stock lnd on all fourteen with zero losses (§19). The hybrid §17 asked for is built, measured, and the thing we intend to ship.)
code_drift1 was the first run in a moving world. Its
predecessor, code_gen2, showed that more evaluations of a
static environment buy nothing — three independently bred routers landed
inside a band 0.014 wide (the
paradigm ceiling). So this
run changed the environment instead of the budget: a virtual clock and
background senders moving hidden liquidity between our payments, and the
question of whether time-awareness re-evolves once knowledge genuinely goes
stale. It did — and it still lost. The winner stamps every
belief and halves its confidence every 35 virtual minutes, and the time-less
champions beat it on all four held-out tiers, drift included. The mechanism,
the numbers and the caveats are on the
drift page.
The definitive write-up — the paradigm result, the champion scores, an anatomy of the evolved algorithms against lnd's, the ceiling three separate lineages ran into, what happens when a router is handed knowledge it did not pay for, the one patch that came out of all this and the theory that died beside it (the distillation patch), what a second optimizer does with ten times the budget (the ceiling arm), what a router bred against a channel that lies buys its robustness with (the lying channel), what happens to all of it once the world has prices (the priced world and evolution learns to count), what happens when the whole evolved belief system is put inside lnd's own payment lifecycle and measured there (the integration benchmark), what happens to the whole ordering on a balance sheet nobody here drew (the foreign balance sheet), the one unexplained interaction on the release candidate's record and the trust boundary that closed it (misattribution manufactures innocence), where the evolution track finally ran out of room (the compose world is closed), the corrections we have had to make to our own record, and the process lessons from thirty-one experiments.