File size: 8,786 Bytes
57a963b
7a3b3da
 
 
 
57a963b
7a3b3da
57a963b
7a3b3da
 
57a963b
 
7a3b3da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
---
title: DarwinX
emoji: 🧬
colorFrom: blue
colorTo: indigo
sdk: static
app_file: index.html
pinned: false
license: cc-by-4.0
short_description: Evolving Agent Harnesses Through Natural Selection
---

# DarwinX: Evolving Agent Harnesses Through Natural Selection

Project page for [arXiv:2608.07545](https://arxiv.org/abs/2608.07545).

DarwinX treats agent self-evolution as selection over a *population* of harnesses with the base
model frozen. Across four benchmarks, one loop adds ~17 points on average:

| Benchmark | Result | Gain | What it isolates |
| --- | --- | --- | --- |
| Terminal-Bench 2.1 (avg@5) | 84.7% | +7.7 on matched base | in-domain evolution |
| TerminalWorld (held-out) | 68.3% | +7.3 | disjoint held-out task split |
| WebArena-Infinity (audit-clean pass@1) | 93.0% | +49.5 pp | synthetic → real intent shift |
| SWE-bench Verified (zero-shot transfer) | 84.2% | +3.4 | cross-benchmark transfer |

## Page sections

`Abstract` · `Method` · `Results at a glance` · `Benchmark detail` · `Ablation: what evolution
changes` · `Limitations` · `BibTeX`

The ablation section reproduces the paper's framing: it is an *exploratory attribution, not a
per-skill causal ablation*, since the skills were co-selected rather than independently randomized.
The limitations section is carried over from the paper's Discussion.

## Interactive figures

Eight figures are interactive, and each is driven by real run artifacts or the paper's own plotting
scripts rather than illustrative numbers. `tools/build_data.py` regenerates `assets/data.js` from the
original files, so nothing is hand-transcribed.

**1. TerminalWorld merge explorer.** Toggle any subset of the four evolved specialists and watch
which of the 41 held-out tasks the selection covers. Built from the `per_task_results` arrays of the
five evaluation runs, joined on `task_id` (the run files list the tasks in different orders, so
position-based joining would silently misalign them).

It surfaces something the paper states only in aggregate. The four specialists solve 24/25/26/27
tasks and their **union is 29**, but the harness recombination actually produced solves **28**:

| | tasks |
| --- | --- |
| best single specialist (D) | 27/41 |
| union of all four | 29/41 |
| realized merge | 28/41 |
| solved by merge, by no specialist | `tw_448247` |
| solved by some specialist, not by merge | `tw_449421`, `tw_498533` |
| solved by all four | 21/41 |
| solved by none | 12/41 |

So recombination is not a free set union: it adds a capability no parent had and loses two. These are
single attempts and one task is worth 2.4 points, so individual flips sit inside the noise band —
the page says so next to the widget.

**2. TB2.1 cluster explorer.** Per-cluster base vs. evolved avg@5, sortable by gain, base rate,
cluster size, or name, with exact rates on hover. Numbers from `notes/TB21_RESULTS.md` (paired
protocol, 88 tasks). Deltas are carried over as reported rather than recomputed, because the source
rounds them from unrounded rates.

**3. WebArena-Infinity evolution curve.** All 37 evaluated variants with a best-so-far envelope,
from `notes/tw_dynamics.json``wai_adaptive_scores`. Toggle either series.

**4. Headline four-benchmark panels.** Replaces the right half of the teaser. From
`scripts/gen_summary_figure.py`. The reason to make this one interactive is that the paper's figure
has to *state* its truncation in the caption: on a shared axis WebArena-Infinity's +49.5 flattens the
two terminal benchmarks into slivers, so each panel is scaled to its own range. Here the reader can
switch between per-panel and shared 0–100 axes and see both framings. The best-prior-agent bar can be
hidden, since those systems use different models and effort settings and are context rather than a
controlled comparison. Per-panel labels carry the exceptions: on SWE-bench Verified the grey bar is
the fix-skill reference, not an unevolved Monet, and there is no prior-agent bar.

**5. TerminalWorld specialist bars.** From `notes/tw_dynamics.json``tw_heldout`. Same axis
question in miniature: the six arms span 58.5–68.3, so a 0–100 track renders them near-identical.
Defaults to a 55–70 axis with the full axis one click away and the truncation named in both notes.
Spec. D and the Claude Code reference are both 27/41, so D's bar landing exactly on the dashed
reference line is a built-in check that the axis transform is right.

**6. TB2.1 compute.** From `scripts/gen_tb21_compute.py` (medians over clean attempts). Switch
between turns and tokens; the bars share one scale across both task groups so the newly-solved vs.
already-solved contrast is not rescaled away.

**7. WAI invalid-trajectory composition.** From `scripts/gen_wai_invalid_composition.py`. Break the
293 → 17 collapse down by application or by mechanism. Both rows sit on a shared absolute scale by
default, which makes the "after" row a near-invisible sliver — that *is* the finding; normalizing
each row to its own total then shows what the remainder consists of. `build_data.py` asserts both
decompositions still total 293 and 17.

**8. WAI audit dumbbells.** From `scripts/gen_wai_audit_by_app.py`. One line per application from raw
pass@1 to audited pass@1, so line length reads directly as how much a harness was leaning on
trajectories the audit rejects. Sortable by audit loss. `build_data.py` cross-checks the audited
DarwinX column against the per-application table rendered elsewhere on the page.

Three figures stay static because they are conceptual diagrams with no underlying data: the teaser's
selection schematic, the method overview, and the per-generation operators. The archive lineage tree
also stays static for a different reason — its node/edge data lives in a `state.db` on the cluster,
not on this machine, so there is nothing truthful to make interactive yet.

Tables are click-to-sort. Numeric columns open descending, text columns A–Z, and `Overall` rows stay
pinned to the bottom.

## Local preview

```bash
python3 -m http.server 8000
# open http://localhost:8000
```

## Regenerate and test

```bash
python3 tools/build_data.py          # rebuild assets/data.js from the run artifacts
# then, with the server running, open:
#   http://localhost:8000/tools/interaction_test.html
# it drives every widget with real click events and writes PASS/FAIL into the page title
```

Headless run:

```bash
python3 -m http.server 8000 &
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --disable-gpu \
  --virtual-time-budget=6000 --dump-dom http://localhost:8000/tools/interaction_test.html \
  | grep -o '<title>.*</title>'
```

## Layout

```
index.html                          the page: inline CSS + vanilla JS, no build step, no external
                                    requests, no web fonts, no chart library
assets/data.js                      generated — real numbers behind the interactive figures
assets/*.png                        web-sized figures (max 2000px wide, 256-color) — 736 KB total
assets/full/*.png                   full-resolution originals for the three dense diagrams,
                                    linked from the page via click-to-zoom
tools/build_data.py                 regenerates assets/data.js from the original run artifacts
tools/interaction_test.html         click-driven test suite for the interactive figures (49 checks)
```

| Figure | Content |
| --- | --- |
| `teaser.png` | four-benchmark teaser (full figure, click-to-zoom target) |
| `teaser_schematic.png` | teaser's left half — the selection schematic shown on the page |
| `overview.png` | method overview |
| `population.png` | per-generation operators (mutation, classification, merge) |
| `tb21_category_gain.png` | per-cluster TB2.1 gain |
| `tb21_compute.png` | per-task compute: newly-solved vs already-solved |
| `tw_heldout_specialists.png` | held-out generalization vs archive diversity |
| `wai_evolution_curve.png` | best-so-far screening score |
| `wai_tree.png` | archive lineage tree |
| `wai_invalid_composition.png` | invalid trajectories, 293 → 17 |
| `wai_audit_by_app.png` | raw vs audited pass@1 per application |

Result figures are rendered from the paper's PDF sources at 200 DPI, then palette-reduced without
dithering (RMSE vs. truecolor < 0.5%, which is imperceptible for line art but 4.7x smaller).

## Hosting

The frontmatter above configures this directory as a Hugging Face **static Space**. The same files
work unchanged as a GitHub Pages site or behind any static file server.

One thing to change when hosting: `og:image` and `twitter:image` are relative paths, which most
crawlers resolve but X/Twitter wants absolute. Replace them with the deployed absolute URL to get a
large social card.