lnd/simulation/command-center/index.html
Olaoluwa Osuntokun a5f295409e simulation/command-center: v66, the boundary and the balance sheet
In this commit, we add findings sections 21 through 23: the foreign
balance sheet (exp-029, the ordering survives on balances nobody fit
to and the circularity caveat measures out to zero), the
misattribution mechanism (exp-030, only settlements prove innocence,
the quarantine keeps), and the closing of the compose world
(exp-031, both escapes failed, the evolution track at its measured
boundary). The index now names the release-candidate pair and states
the two-track split: evolution found the paradigm, engineering is
shipping it.
2026-07-31 00:14:16 -07:00

659 lines
33 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Evolving Lightning's next routing algorithm — lnd × GEPA</title>
<meta name="description" content="An LLM-driven evolutionary search over Lightning routing algorithms, run against lnd's real pathfinding stack in an in-process payment simulator. What it found, and how." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300..600;1,6..72,300..500&family=Spline+Sans+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="site-header">
<div class="shell">
<a class="wordmark" href="index.html">lnd<span class="x">×</span>GEPA</a>
<nav class="site-nav">
<a href="index.html" aria-current="page">overview</a>
<a href="findings.html">findings</a>
<a href="drift.html">drift</a>
<a href="#run">live run</a>
</nav>
</div>
</header>
<main id="main">
<!-- ================= MASTHEAD ================= -->
<div class="shell masthead">
<div class="eyebrow">
Research log<span class="sep">/</span>Lightning Network pathfinding<span class="sep">/</span>July 2026
</div>
<h1>Evolving Lightning's <em>next</em> routing algorithm</h1>
<div class="masthead-grid">
<p class="standfirst">
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. <strong>Tuning its parameters found nothing. Replacing the algorithm
found a lot.</strong>
</p>
<div class="glance">
<div class="g hi">
<span class="l">attempts per payment<br>mainnet, vs lnd's 19.8</span>
<span class="v">2.3</span>
</div>
<div class="g">
<span class="l">objective, mainnet<br>vs lnd's 0.694</span>
<span class="v">0.791</span>
</div>
<div class="g">
<span class="l">evolved lines of Go<br>from a 300-line seed</span>
<span class="v">1,525</span>
</div>
<div class="g hi">
<span class="l">tiers the integrated branch leads<br>stock lnd, zero losses</span>
<span class="v">14 / 14</span>
</div>
<div class="cap">best evolved routers (mx_c3/hb1) against lnd's production stack; the last row is the interval-router branch measured inside lnd's own payment lifecycle (exp-027)</div>
</div>
</div>
<div class="byline">
<span>method <b>GEPA reflective evolution</b></span>
<span>reflection LM <b>gpt-5.6-sol</b></span>
<span>evaluator <b>cmd/routesim</b></span>
<span>experiments <b>exp-001 … exp-031</b></span>
<span>champions <b>hb1 + mx_c3</b>, unchanged since exp-007 · specialists atomic1 + econ2</span>
<span>shipping <b>interval-lnd, 14/14 over stock lnd</b> · release candidate <b>eb4fc3e62</b>, rebased <b>b489649f6</b> on the fork</span>
<span>tracks <b>evolution at its measured boundary</b> (exp-031) · <b>the ship track owns the frontier</b> · replay on real payment history next week</span>
</div>
</div>
<!-- ================= 01 · FINDINGS ================= -->
<section id="result">
<div class="shell">
<div class="sec-head">
<div class="sec-no">01</div>
<h2>What the search found</h2>
<p class="sec-sub">
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.
</p>
</div>
<div class="keyrow wide">
<div class="key hi">
<span class="kn">8.6<span class="u">×</span></span>
<div class="kl">
fewer HTLC attempts per payment than lnd on the real mainnet graph, at
the same success rate, on a perfect failure channel
</div>
<div class="kf">2.3 vs 19.8 attempts · exp-009 · ratio retired by exp-019: under realistic attribution degradation the edge converts to success</div>
</div>
<div class="key">
<span class="kn">6<span class="u">of 6</span></span>
<div class="kl">
held-out tiers the evolved routers lead: sealed synthetic test,
out-of-distribution topologies, mainnet snapshot, drifting liquidity,
splitting pressure, and the atomic arena
</div>
<div class="kf">nine challengers have since failed to displace them — though the frontier is now three regimes deep: hb1/mx_c3 where information wins, atomic1 where holds contend, econ2 where money is scarce</div>
</div>
<div class="key">
<span class="kn">0</span>
<div class="kl">
parameter settings, out of 400 evaluations of estimator and cost
knobs, that beat the lnd defaults
</div>
<div class="kf">exp-002 · the negative result that redirected the project</div>
</div>
</div>
<figure>
<div class="fig-head">
<span class="fig-t">Attempts per payment on a mainnet snapshot</span>
<span class="fig-n">Fig. 1 · lower is better</span>
</div>
<div class="plot resp" id="fig-attempts"></div>
<figcaption>
12,161 nodes, 39,659 channels, 100 payments from the network's
highest-degree node. Success rates are close — lnd 0.790, seed 0.820,
evolved 0.810 — so the whole difference lands in <b>how much probing it
takes to get there</b>. This is the tier lnd should win: its defaults were
tuned for this graph, while the evolved routers were bred entirely on
synthetic topologies. The attempt ratio is a perfect-channel figure;
exp-019 retired it, and under a degraded failure channel the same edge
shows up as success instead
(<a class="link" href="findings.html#attribution">the ladder</a>).
</figcaption>
</figure>
<figure>
<div class="fig-head">
<span class="fig-t">Composite objective, by router and held-out tier</span>
<span class="fig-n">Fig. 2 · higher is better</span>
</div>
<div class="plot resp" id="fig-champions"></div>
<div class="legend">
<span class="item"><i style="background:#a83f22"></i> evolved by GEPA</span>
<span class="item"><i style="background:#8a8175"></i> baseline (lnd, or hand-written)</span>
</div>
<figcaption>
Objective is <span class="mono">success 0.01·min(extra attempts, 15)
0.00002·min(fee ppm, 5000)</span>. The two evolved routers lead on all
three tiers. On synthetic corpora the gap is mostly success rate; on the
real graph it is almost entirely efficiency. Reruns are bit-identical
(stdev 0.00000 over five repeats), and both champions were audited clean
of simulator exploits.
</figcaption>
<details class="tableview">
<summary>Table view</summary>
<div class="tw">
<table class="data">
<caption>Composite objective. Combined is the mean of the two synthetic held-out sets.</caption>
<thead>
<tr>
<th>router</th>
<th class="num">mainnet</th>
<th class="num">hard sealed test</th>
<th class="num">out-of-distribution</th>
<th class="num">combined</th>
</tr>
</thead>
<tbody>
<tr>
<td>lnd production stack<span class="sub">Dijkstra + mission control</span></td>
<td class="num" data-l="mainnet">0.694</td><td class="num" data-l="hard sealed test">0.309</td>
<td class="num" data-l="out-of-distribution">0.357</td><td class="num" data-l="combined">0.333</td>
</tr>
<tr>
<td>hand-written seed<span class="sub">~300 lines, cheapest path + blacklist</span></td>
<td class="num" data-l="mainnet">0.762</td><td class="num" data-l="hard sealed test">0.530</td>
<td class="num" data-l="out-of-distribution">0.487</td><td class="num" data-l="combined">0.509</td>
</tr>
<tr>
<td>hb1<span class="sub">evolved, 872 lines</span></td>
<td class="num" data-l="mainnet">0.790</td><td class="num" data-l="hard sealed test">0.586</td>
<td class="num" data-l="out-of-distribution">0.545</td><td class="num" data-l="combined">0.565</td>
</tr>
<tr class="best">
<td>mx_c3<span class="sub">evolved, 1,525 lines</span></td>
<td class="num" data-l="mainnet">0.791</td><td class="num" data-l="hard sealed test">0.583</td>
<td class="num" data-l="out-of-distribution">0.581</td><td class="num" data-l="combined">0.582</td>
</tr>
</tbody>
</table>
</div>
</details>
</figure>
<div class="prose">
<p>
The headline is not the size of the win, it is <em>where the win came
from</em>. 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.
</p>
<p>
Meanwhile a deliberately naive 300-line router with a <em>different
paradigm</em> 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.
</p>
<p>
<a class="link" href="findings.html">Read the full findings →</a>
</p>
</div>
</div>
</section>
<!-- ================= 02 · METHOD ================= -->
<section id="method">
<div class="shell">
<div class="sec-head">
<div class="sec-no">02</div>
<h2>How the search works</h2>
<p class="sec-sub">
A candidate is a whole routing algorithm in Go. Every proposal is
compiled into a simulator and made to actually pay.
</p>
</div>
<figure>
<div class="fig-head">
<span class="fig-t">One turn of the evolutionary loop</span>
<span class="fig-n">Fig. 3</span>
</div>
<div class="schema">
<svg viewBox="0 0 1000 252" role="img" aria-label="The GEPA loop: propose, compile, simulate, score, reflect, then back to propose.">
<!-- stage boxes -->
<g>
<rect x="1" y="26" width="170" height="92" fill="#fbfaf6" stroke="#cbc4b2" stroke-width="1"/>
<text class="s-num" x="14" y="46">01</text>
<text class="s-title" x="14" y="66" font-size="14">propose</text>
<text class="s-body" x="14" y="86">the reflection LM</text>
<text class="s-body" x="14" y="102">rewrites the router</text>
</g>
<path d="M 179 72 h 20 m -6 -5 l 6 5 l -6 5" fill="none" stroke="#b3ab97" stroke-width="1.2"/>
<g>
<rect x="207" y="26" width="170" height="92" fill="#fbfaf6" stroke="#cbc4b2" stroke-width="1"/>
<text class="s-num" x="220" y="46">02</text>
<text class="s-title" x="220" y="66" font-size="14">compile</text>
<text class="s-body" x="220" y="86">go build overlay drops</text>
<text class="s-body" x="220" y="102">it into the simulator</text>
</g>
<path d="M 385 72 h 20 m -6 -5 l 6 5 l -6 5" fill="none" stroke="#b3ab97" stroke-width="1.2"/>
<g>
<rect x="413" y="26" width="170" height="92" fill="#fbfaf6" stroke="#cbc4b2" stroke-width="1"/>
<text class="s-num" x="426" y="46">03</text>
<text class="s-title" x="426" y="66" font-size="14">simulate</text>
<text class="s-body" x="426" y="86">it pays, against hidden</text>
<text class="s-body" x="426" y="102">channel liquidity</text>
</g>
<path d="M 591 72 h 20 m -6 -5 l 6 5 l -6 5" fill="none" stroke="#b3ab97" stroke-width="1.2"/>
<g>
<rect x="619" y="26" width="170" height="92" fill="#fbfaf6" stroke="#cbc4b2" stroke-width="1"/>
<text class="s-num" x="632" y="46">04</text>
<text class="s-title" x="632" y="66" font-size="14">score</text>
<text class="s-body" x="632" y="86">success, minus attempt</text>
<text class="s-body" x="632" y="102">and fee penalties</text>
</g>
<path d="M 797 72 h 20 m -6 -5 l 6 5 l -6 5" fill="none" stroke="#b3ab97" stroke-width="1.2"/>
<g>
<rect x="825" y="26" width="170" height="92" fill="#fbfaf6" stroke="#cbc4b2" stroke-width="1"/>
<text class="s-num" x="838" y="46">05</text>
<text class="s-title" x="838" y="66" font-size="14">reflect</text>
<text class="s-body" x="838" y="86">keep it only if it holds</text>
<text class="s-body" x="838" y="102">up across the splits</text>
</g>
<!-- feedback return path -->
<path d="M 910 118 V 176 H 86 V 124 m -5 6 l 5 -6 l 5 6"
fill="none" stroke="#a83f22" stroke-width="1.4"/>
<text class="s-label-strong" x="498" y="199" text-anchor="middle" fill="#a83f22">
per-attempt failure traces are the feedback
</text>
<text class="s-body" x="498" y="220" text-anchor="middle">
which hop died, at what amount, with which BOLT error — so the next mutation is informed, not random
</text>
</svg>
</div>
<div class="scrollhint">scroll the diagram sideways →</div>
<figcaption>
The optimizer never sees hidden balances. A candidate gets the public
gossip graph, its own channel balances, and the errors its attempts
provoke — the same information a real Lightning sender has.
</figcaption>
</figure>
<div class="prose">
<h3>The simulator runs lnd's real code</h3>
<p>
<code>routing/sim_graph.go</code> 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
<code>Graph</code> interface, so lnd's production pathfinding and mission
control run against it unmodified — which is what makes the baseline
honest.
</p>
<p>
Forwarding enforces real BOLT semantics. An attempt that asks too little
fee gets <code>FeeInsufficient</code>; one that asks for more than a hop
holds gets <code>TemporaryChannelFailure</code>, 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 <code>describegraph</code> snapshot,
and hidden balances follow a half, uniform, or <em>bimodal</em> model — the
last one hugging one end of each channel, which is both realistic and the
hard case.
</p>
<p>
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.
</p>
<h3>A candidate is an algorithm, not a config</h3>
<p>
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, <code>go build -overlay</code> 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.
</p>
<div class="note">
<h4>the sandbox held</h4>
<p>
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.
</p>
</div>
</div>
</div>
</section>
<!-- ================= 03 · CORPUS ================= -->
<section id="corpus">
<div class="shell">
<div class="sec-head">
<div class="sec-no">03</div>
<h2>The scenarios it is scored against</h2>
<p class="sec-sub">
<span class="mono" id="c-files"></span> scenario files,
<span class="mono" id="c-scen"></span> payments,
split <span class="mono" id="c-split"></span>. Validation picks the
incumbent; the test split stays sealed until a champion is declared.
</p>
</div>
<div class="figgrid">
<figure>
<div class="fig-head">
<span class="fig-t">Topology</span>
<span class="fig-n">files</span>
</div>
<div class="bars" id="bars-topology"></div>
</figure>
<figure>
<div class="fig-head">
<span class="fig-t">Hidden liquidity model</span>
<span class="fig-n">files</span>
</div>
<div class="bars" id="bars-liquidity"></div>
<figcaption>
Bimodal is the hard regime: funds parked at one end of each channel,
so capacity tells you almost nothing about what will actually pass.
</figcaption>
</figure>
<figure>
<div class="fig-head">
<span class="fig-t">Payment amount</span>
<span class="fig-n">scenarios, satoshis</span>
</div>
<div class="bars" id="bars-amount"></div>
</figure>
<figure>
<div class="fig-head">
<span class="fig-t">Shard budget</span>
<span class="fig-n">scenarios, max parts</span>
</div>
<div class="bars" id="bars-parts"></div>
</figure>
</div>
</div>
</section>
<!-- ================= 04 · LIVE RUN ================= -->
<section id="run">
<div class="shell">
<div class="sec-head">
<div class="sec-no">04</div>
<h2>Run telemetry (latest completed optimizer run)</h2>
<p class="sec-sub">
Telemetry from the optimizer, exported straight out of the GEPA run
directory. <span class="mono">code_drift1</span> has since finished all 400
evaluations, and so have exp-010's three splitting arms — all of them beaten
by the champions
(<a class="link" href="findings.html#splitting">the verdict</a>). 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
(<a class="link" href="findings.html#atomic">the atomic verdict</a>) — and so
has exp-012, which found no hot-cache regime anywhere but did find the one
change this work argues for upstream
(<a class="link" href="findings.html#coldcache">cold cache, hot load</a>).
Nothing is live now. The telemetry below is still
<span class="mono">exp-018</span>'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
(<a class="link" href="findings.html#omni">the adjudication</a>). Two
optimizer runs have finished since, both overnight and both closed:
<span class="mono">exp-022</span> bred a router against a channel that lies
about which hop failed
(<a class="link" href="findings.html#lying">the lying channel</a>), and
<span class="mono">exp-024</span> gave the runner-up engine ten times the
evaluations to see whether the band was ever budget starvation
(<a class="link" href="findings.html#ceilingarm">the ceiling arm</a>). 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
(<a class="link" href="findings.html#distillation">the distillation patch</a>).
The most recent run of all is <span class="mono">exp-025</span>, 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
(<a class="link" href="findings.html#econevo">evolution learns to count</a>).
Two runs have closed since: the compose world, every economic knob plus the
lying channel at once, which returned its seed unchanged, and
<span class="mono">exp-028</span>, 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
(<a class="link" href="findings.html#compose">the rule</a>). The 800-evaluation
arm, <span class="mono">code_full3</span>, is live and carries what is left of
the compose question.
</p>
</div>
<div class="note">
<h4>where the program stands</h4>
<p>
<strong>No optimizer run is live.</strong> 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
(<a class="link" href="findings.html#ceilingarm">§15</a> is why).
</p>
<p>
<em>(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
<strong>14 of 14 tiers interval-solid over stock lnd with zero losses</strong>,
margins intact under the production default fee limit
(<a class="link" href="findings.html#integration">§19</a>), and the stated
target is shipping it in the next lnd major release. One optimizer run is
live, the 800-evaluation compose arm
<span class="mono">code_full3</span>.)</em>
</p>
<p>
<em>(Update, 31 July: <span class="mono">code_full3</span> 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
(<a class="link" href="findings.html#closed">§23</a>). Nothing is live; no run
holds the router. The frontier now belongs to the ship track. The release
candidate is <span class="mono">interval-router</span> at
<span class="mono">eb4fc3e62</span>, rebased as
<span class="mono">b489649f6</span> on the fork, and it has been validated
twice more since: champion-grade to the third decimal on a liquidity family
nobody here authored
(<a class="link" href="findings.html#foreign">§21</a>), and carrying a fix for
the one interaction §19 could not explain, where shifted blame fabricates an
alibi for the guilty channel
(<a class="link" href="findings.html#innocence">§22</a>). 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.)</em>
</p>
<p>
<strong>exp-023 and exp-025, economic realism, both closed.</strong> 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
(<a class="link" href="findings.html#econ">the priced world</a>), and the
evolution run that followed produced econ2, the fee-budget specialist and
challenger number nine
(<a class="link" href="findings.html#econevo">evolution learns to count</a>).
The compose-world run (all five economic knobs plus the lying channel) is live as code_full1; its five knobs are
live at once. <em>(It has since closed, returning its seed unchanged, and so
has the first of its two escapes
(<a class="link" href="findings.html#compose">§20</a>); the second is live
now.)</em>
</p>
<p>
<strong>The interval-router branch.</strong> Separately from the simulator,
the champions' paradigm is being ported into lnd proper: fourteen commits on
<span class="mono">interval-router</span>, unchanged through both economic
cycles, that put a liquidity interval per directed channel behind
<span class="mono">routerrpc.router=interval</span>, off by default, with
the stock stack untouched when the flag is unset. <strong>It still has not
been benchmarked in this simulator.</strong> 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 <a class="link" href="findings.html#econ">§17</a> says to
build. <em>(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
(<a class="link" href="findings.html#integration">§19</a>). The hybrid
§17 asked for is built, measured, and the thing we intend to
ship.)</em>
</p>
</div>
<div class="runbar">
<span><span class="dot"></span>run <b id="rb-run"></b></span>
<span>reflection <b id="rb-lm"></b></span>
<span>mode <b id="rb-mode"></b></span>
<span>budget <b>150 evals</b></span>
</div>
<div class="statline wide" id="run-stats"></div>
<div class="prose" style="margin-bottom:34px">
<p>
<code>code_drift1</code> was the first run in a moving world. Its
predecessor, <code>code_gen2</code>, showed that more evaluations of a
static environment buy nothing — three independently bred routers landed
inside a band 0.014 wide (the
<a class="link" href="findings.html#ceiling">paradigm ceiling</a>). 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. <strong>It did — and it still lost.</strong> 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
<a class="link" href="drift.html">drift page</a>.
</p>
</div>
<figure class="wide" style="margin-top:0">
<div class="fig-head">
<span class="fig-t">Score over iterations</span>
<span class="fig-n">Fig. 4 · minibatch objective</span>
</div>
<div class="plot" id="score-chart"></div>
<div class="scrollhint">scroll the chart sideways →</div>
<div class="legend">
<span class="item"><i class="line" style="background:#a83f22"></i> best kept</span>
<span class="item"><i class="line" style="background:#2f6ea8"></i> proposed</span>
<span class="item"><i class="dash"></i> seed baseline</span>
</div>
<figcaption>
These are per-minibatch scores on the training split, not held-out
numbers — they are how GEPA decides what to keep, and they run
optimistically high. Every comparison in section 01 comes from separate
held-out runs instead.
</figcaption>
</figure>
<figure class="wide">
<div class="fig-head">
<span class="fig-t">Candidate lineage</span>
<span class="fig-n">Fig. 5 · select a candidate</span>
</div>
<div class="plot dag" id="lineage-dag"></div>
<figcaption>
Each node is one compiled, scored router. Accepted candidates stay on the
frontier and can be mutated further; rejects are dead ends. The scores are
per-minibatch and each is measured on a different subset, so a rejected
candidate can carry a higher number than the incumbent without being
better.
</figcaption>
</figure>
<div class="panels">
<div class="panel">
<div class="fig-head">
<span class="fig-t">Candidate detail</span>
<span class="fig-n" id="detail-sub"></span>
</div>
<div id="cand-detail"></div>
</div>
<div class="panel">
<div class="fig-head">
<span class="fig-t">What the mutation changed</span>
<span class="fig-n">line diff</span>
</div>
<div class="selects">
<label>base <select id="diff-a"></select></label>
<span></span>
<label>candidate <select id="diff-b"></select></label>
</div>
<pre class="diff" id="diff-view"></pre>
</div>
</div>
</div>
</section>
<div class="shell">
<div class="readnext">
<div class="k">read next</div>
<a class="big" href="findings.html">Findings: what the evolved routers kept, dropped, and invented</a>
<p>
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
(<a class="link" href="findings.html#distillation">the distillation patch</a>),
what a second optimizer does with ten times the budget
(<a class="link" href="findings.html#ceilingarm">the ceiling arm</a>), what a
router bred against a channel that lies buys its robustness with
(<a class="link" href="findings.html#lying">the lying channel</a>), what
happens to all of it once the world has prices
(<a class="link" href="findings.html#econ">the priced world</a> and
<a class="link" href="findings.html#econevo">evolution learns to count</a>),
what happens when the whole evolved belief system is put inside lnd's own
payment lifecycle and measured there
(<a class="link" href="findings.html#integration">the integration benchmark</a>),
what happens to the whole ordering on a balance sheet nobody here drew
(<a class="link" href="findings.html#foreign">the foreign balance sheet</a>),
the one unexplained interaction on the release candidate's record and the trust
boundary that closed it
(<a class="link" href="findings.html#innocence">misattribution manufactures innocence</a>),
where the evolution track finally ran out of room
(<a class="link" href="findings.html#closed">the compose world is closed</a>),
the corrections we have had to make to our own record, and the process lessons
from thirty-one experiments.
</p>
</div>
</div>
</main>
<div class="shell">
<footer class="site-footer">
<span>lnd × GEPA — Lightning routing evolution</span>
<span id="foot-run">run data loading…</span>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>