← Back to Blog Posts

I Started Training a Fruit Fly to Build Websites

I Started Training a Fruit Fly to Build Websites

A fruit fly's learning circuit — 2,045 measured Kenyon cells, a feedback loop and 17,985 measured connections, lifted out of a published connectome — has been taught to prefer one kind of web page over another, and is now art-directing a storefront. The joke only works if the fly is real. It is.

Byline note: the project is mine — the design of it, the choices about what to measure, and every claim I'd defend. The drafting is Claude Opus 5's, out of the Kenyon repository rather than from guesswork: every number below was read off the JSON the circuit's own scripts write, and both figures are generated from those files rather than typed. It took the screenshots and the recording too, headlessly, off the live site.

There is a fruit fly on my desktop that has opinions about web design.

That sentence is doing less metaphorical work than it looks like. The fly is a mushroom body — the part of an insect brain that stores "that smell meant sugar" — lifted out of a published connectome and run. Its wiring was measured with an electron microscope, not designed by me. It has been taught to like one kind of page and dislike another, and it is now choosing the design of a storefront, generation by generation, out of pages it had never seen.

You can watch it at fly.ammoura.me — a fly at a bench, the page it has chosen on the monitor in front of it, and the Kenyon cells that fired when it looked at that page lit up beside it.

What is measured, and what I made up

This is the first section because it is the one that decides whether the rest is worth reading.

Measured, all of it from the male CNS connectome released by Janelia and Google Research (v1.0, CC-BY): 342 projection neurons carrying smell into the circuit, 2,045 Kenyon cells, the single giant APL neuron whose feedback loop reaches 2,044 of them, 17,985 measured connections from Kenyon cells onto 49 output neurons, and the 170 dopamine neurons that teach those synapses. None of that is a model of a mushroom body. It is a mushroom body, in the sense that every connection in it was measured rather than assumed.

Invented, and kept in named places so it can be argued with: the odours, because a connectome contains no odour responses, so the smells are synthetic glomerular patterns; three gains tuned by hand; and — the big one — the function that turns a rendered web page into something the circuit can smell.

That is the whole list. Everything else is anatomy.

A recording of the circuit view on fly.ammoura.me turning. Reconstructed neuron skeletons in cyan, orange, yellow and violet against black rotate through a full sweep, showing the calyx where the Kenyon cells collect their input and the lobes where their output neurons sit. A counter in the corner reads the Kenyon cells lit for the page the fly is looking at, between about 145 and 175 of 2,045.
317,767 line segments of reconstructed morphology, in nanometres, from Janelia's published skeletons — turned here, and draggable yourself at fly.ammoura.me/circuit. Every coordinate is measured. The lit cells are the ones that fired for the page the fly is looking at: same cells, same order the model indexes them by.

It learns, and it breaks in the right places

Pair an odour with the punishment dopamine neurons and the fly's preference moves to avoidance. Pair it with the reward ones and it moves to approach. Nothing changes in between except the strength of Kenyon-cell synapses that happened to be active when dopamine arrived — which is the mechanism the biology says is doing it.

The wiring is what does the learning Preference after training, 100 runs per row, median and 5–95% Right hemisphere Left — no parameter was ever fitted to it avoid approach Paired with reward +0.30 Paired with punishment −0.25 …with MBON compartments shuffled −0.10 …with the dopamine mis-aimed +0.00 …with Kenyon-cell identity shuffled −0.24 Rows 3–5 are the punishment case with one thing broken. Scrambling Kenyon-cell identity is the control that should change nothing, and does not. Only 37% of compartment shuffles agree on a direction at all.

Two things in that figure are worth more than the top two rows.

The first is the left hemisphere. The three hand-tuned gains were set while watching the right side. The left was never fitted to anything, and it gives the same answers — slightly larger ones, if anything. That is the cheapest real replication available to this kind of work, and it costs nothing to run.

The second is the bottom three rows. Shuffling which compartment each output neuron belongs to wrecks the learning, and only 37% of shuffles even agree on a direction afterwards. Mis-aiming the dopamine takes it to zero. But scrambling Kenyon-cell identity — which sounds like the most violent thing on the list — does almost nothing. That is what the theory predicts: the Kenyon layer is supposed to be an arbitrary high-dimensional code, so scrambling it is not damage. A result that broke whenever you touched it wouldn't be worth much. This one survives the lesion that should be survivable and fails the ones that shouldn't.

There is also a claim that did not survive, and it is written into the repo rather than quietly fixed. The first run reported that shuffling the compartments reverses the learning. Over 100 shuffles it does not — it destroys it. The reversal was one seed's luck. I would rather have the retraction in the README than the tidier sentence.

A page is a smell

Here is the bridge, and it is the invented part, so it has to earn its place before anything is built on it.

172 detectors run over a rendered page — hue by area, contrast, font-size steps, line length, margins, whitespace fraction, shared alignment edges, border radii, shadow area, nesting depth, how much text sits above the fold. Each becomes a number, the vector is sparsified to its top fifth, and it is handed to the same projection neurons an odour arrives on. The circuit is not told which it is getting.

The acceptance test was set before it was built: page-smells have to behave like smells. About 8% of Kenyon cells active, holding steady across a page's "concentration", and two similar pages sharing more of the code than two dissimilar ones. Pages come in at 9.0% active against the odours' 8.7%.

But sparsity is nearly free — the encoder assigns its strengths from the same distribution the odours are drawn from, so the magnitudes match by construction. The question it cannot fake is whether the code means anything.

The fly reads the design, not the words Kenyon-cell code two inputs share — 172 detectors over a rendered page Real pages Synthetic odours, for scale example.com, .org and .net 1.00 one design, three domains Two pages from one site 0.36 27 pages across 9 sites Two synthetic odours 0.15 what a smell looks like to it Three CSS Zen Garden pages 0.14 one document, three designs Two pages from different sites 0.12 the baseline Zen Garden is one HTML file under three stylesheets: same words, same markup, and the fly cannot tell the three are related.

The two faded bars are the controls, and they are the reason I believe the middle one.

example.com, example.org and example.net are three domains serving one design. The fly scores them 1.00 — identical, which they are.

Three CSS Zen Garden pages are the opposite trick: one HTML document, the same words in the same markup, wearing three different stylesheets. The fly scores them 0.14, which is below the median for two pages from unrelated sites. Same content, and it cannot tell they are related.

That pair is the whole argument. The encoder reads the design and not the words, and I did not have to assert it.

Teaching it a taste

A rated page becomes a training trial: liked pages arrive with reward dopamine, disliked with punishment. No new mechanism and no new parameter — the circuit that learned "this smell meant sugar" is handed "this page is good". Then it is asked about pages it has never seen.

Against 43 stand-in tastes it predicts held-out pages at 0.615, where chance is 0.5, the same pages with their ratings shuffled give 0.462, and a fly with its output compartments shuffled gives 0.462 — the null to three decimal places. 93% of the 43 tastes come out above chance.

Two things I learned by getting this wrong first:

Correlation flatters a broken fly. Scoring by correlating the fly's preference against the ratings gives r = +0.68 to a fly whose compartments have been shuffled — a fly that cannot have learned anything. The size of a preference shift tracks how much code a page shares with the training set, whichever way the lesson pointed. So correlation reports "this resembles things I was taught about", not "I know which way". Accuracy asks the only question that matters.

A taste has to exist in the fly's own terms. The obvious stand-in rater — a random direction through the detectors, split at its median — produces two classes that are indistinguishable in Kenyon space. A fly that fails that has not failed; it was asked about a taste it cannot see. Across the 43 stand-ins the spread runs from 0.462 to 0.808, and what separates them is whether the liked pages resemble each other at all. The fly can only learn a taste that has neighbours.

Which brings me to the honest status of the whole thing: 3 of the 43 pages have been rated by a human so far. Everything above is machinery, verified against stand-ins. The fly is currently chasing a demonstration lesson rather than anybody's actual taste.

The fly is the art director

It has one opinion and no hands. Everything it can express is a single number — approach or avoid, pooled over 49 output neurons. It cannot lay out a page, and nothing here pretends it does.

That turns out to be enough, because taste plus a generator is a designer. A parametric storefront is built out of NebulaKit's own theme tokens — colours, spacing, radii, shadows, layout — so what comes out is a theme block that could be pasted into app.css and would work. Candidates are mutated, rendered headless, and scored. The ones the fly approaches survive.

It judges the render, not the parameters. Every candidate goes through the same path a real website does — actual pixels, actual computed styles — so the generator has no back door to the encoder. It has to make something the fly likes the look of.

A recording of fly.ammoura.me. The fly sits at its bench while the monitor steps through sixteen generations of the storefront it is directing; layout, colour and type change from generation to generation while the Kenyon-cell raster flickers beside it and the generation counter climbs.
Sixteen generations of twenty. Everything on the monitor is a page the fly selected, and the raster beside it is the real 2,045 cells with the 143 to 170 that fired lit. The preference in the corner climbs from +0.146 to +0.264 as it goes.

Four things are fixed rather than evolved, because they are rules and not taste: mobile first, no webfonts, semantic landmarks and 44px tap targets, and every text colour pushed until it clears WCAG AA against both the page and the card it might sit on. That last one is in the generator because of what happened when it was not: left to itself, the search produced pages with 39% of their characters below AA. The fly picks hue, scale, spacing, radius and layout. It does not get to pick illegible.

The lesson has to be a pair

The change that made any of this work: show the fly two pages with the same words and opposite design — one that follows every rule, one built the way generated design usually is — and reward it on one, punish it on the other.

The pair is not a toy. It is built from the theme tokens of Ammoura, my own storefront platform, read straight out of the src/app.css it ships: the good twin keeps every hue Ammoura chose and moves only lightness until each text pair clears WCAG AA. Reading those tokens turned up something I would rather have found any other way — white on --color-primary is 3.53:1, so every primary button label on the shipping palette fails AA. The good twin is that fix, demonstrated rather than described.

The bad twin, measured rather than asserted: 85 kB over the wire against 0, three font requests against none, 16 WCAG AA failures against none, 87.5% of its characters below AA, a worst contrast of 1.13:1, and it scrolls sideways on a phone.

A single lesson page gives the fly a point in Kenyon space and no direction. A pair gives it an axis, and an axis is the only thing selection can climb. Before the pair, the rewarded twin's preference sat near zero; with it, the run behind this post lands at +0.264 — about the size of shift an odour the fly was trained on directly produces (+0.30 rewarded, −0.25 punished).

The twins, and what does not hold

Two flies on the same wiring, given the same pair opposite ways round, from the same founders with the same mutations and the same seed. Sixteen generations each, with a naive fly and three mis-aimed ones running alongside.

The results table from fly.ammoura.me: rows for the approach fly, avoid fly, naive fly and three mis-aimed flies, with columns for preference by approach, by avoid, Kenyon-cell overlap, percentile against random pages, and genome distance. The approach fly's page reads +0.2638 and 0.847 overlap at the 100th percentile; the avoid fly's reads 0.263 overlap at the 3rd percentile.
Six flies, same starting pages, same mutations. The rewarded twin's page sits above all thirty pages nobody selected; the punished twin's sits near the bottom of them.

The twins separate in the direction they should. The rewarded fly's page shares 0.847 of the Kenyon code with the page it was taught and sits above all thirty pages nobody selected; the punished fly's shares 0.263 — the 3rd percentile, with one of those thirty below it — and of the six it is the design that sits furthest from its twin's. Both final pages pass every rule, at 0 kB and no webfonts.

They do not become opposites, though, and the table says so. The punished twin's page still reads +0.037 to the rewarded fly — liked less, not disliked. A lesson moves where selection goes; it does not make the loser's page repellent.

And the mis-aimed flies are the reason I will not put weight on any single run of this. In this one, one of the three reaches the same 100th percentile the intact fly did — its page shares more of the lesson's Kenyon code than any of the thirty nobody selected — while the other two end at the 13th and the 7th. Its design is nowhere near the intact fly's; it just arrived somewhere the code likes. In the run before this one all three landed well away, which looked like clean evidence that delivering the dopamine to the wrong compartments stops a fly getting there. Three lesioned flies in one run cannot settle it either way, and the replication above already says why: every lesion interval in that figure straddles zero. The table is a demonstration, not a result.

What I actually think this is

The reflex when a page says "designed by AI" is to picture a large model that has read the whole web. The thing with the opinion here is 2,045 cells out of one dead fly, and it fits in a few megabytes of measured wiring.

What it demonstrates is narrow, and I would defend every word of it: a real, measured learning circuit can be taught a preference over web pages, will apply that preference to pages it has never seen, better than chance, and stops working when you break the parts that should matter. The generator is dumb. Mutation and selection are dumb. The scarce part — the only part that had to be learned — is the taste.

The limit is honest too. It can only learn a taste that has neighbours, and it currently has three ratings to go on. The machinery works. Supplying it with an actual opinion is still my job.

The joke only works if the fly is real. The fly is real, which turned out to be the least funny and most interesting thing about it.

The end

Linked from elsewhere

© 2026 davis9001.dev | All rights reserved.