Decompiling C with 2,489 Haiku agents
Given compiled object files and a disassembly of each function, a swarm of Claude Haiku 5.5 agents rewrote 796 of 1,070 C functions from an obscure codebase so that gcc compiles them back to the identical bytes. The short ones were easy: every function under 16 instructions, mostly getters and wrappers, came back exact. One in five between 128 and 383 instructions did, and none of the 34 longer than that. Of the 274 functions Haiku never matched, 268 contain a loop.
The task
Matching decompilation is what game-preservation projects do to old console games: write C source that the original compiler turns into the original binary, byte for byte. Most published work on LLM decompilers scores whether the output compiles and runs correctly; a byte-identical match at -O2 is a stricter bar, and the compiler grades every attempt.
Oskar's brief asked for two targets, one obscure and one famous. I picked Ali Rudi's neat* suite, a troff typesetter and a small C compiler written by one person, with 13 to 180 stars per repository and commits as recent as August 2026, and Lua 5.4.7. I compiled both with gcc 13.3 at -O2 and deleted the source before any agent started. The agents had no web tools, and shell commands that fetch from the network were refused.
Each agent usually got three functions, the object file with its function and variable names, a disassembly listing per function, and a test runner that compiles the agent's C and compares it with the original, relocation by relocation. A merge gate rebuilt every file after each change and accepted it only if nothing that matched before stopped matching. It is the same harness that built a C compiler from 1,436 Haiku agents the day before.
Solve rates by function size and effort
| function size (instructions) | neat* matched |
|---|---|
| under 16 | 284 of 284 |
| 16–47 | 330 of 362 (91%) |
| 48–127 | 161 of 285 (56%) |
| 128–383 | 21 of 105 (20%) |
| 384 or more | 0 of 34 |
Loops matter within each size band, not only because long functions tend to have them. Under 48 instructions, 99% of functions without a loop matched and 88% of those with one. At 48–127 instructions it was 90% and 54%. Above 128 instructions nearly every function loops, and 20 of 138 matched. Switch statements that gcc compiles to jump tables matched 3 times out of 19.
Haiku 5.5 takes an effort setting, which buys more reasoning per step. The swarm started at the default and retried failures at high. Oskar asked to climb further before trying a bigger model, to see where Haiku itself runs out. Each level worked only on what the cheaper levels had left, so the price below is the cost of the next function, not of the same function at a different setting. Counts are neat* functions, credited to the level of the agent whose change first matched them; another 173 matched as a side effect of other agents' changes.
| effort | functions matched | spend | per function |
|---|---|---|---|
| default | 340 | $18 | $0.05 |
| high | 245 | $113 | $0.46 |
| xhigh | 24 | $42 | $1.75 |
| max | 14 | $73 | $5.20 |
From default to max the price of the next function rose a hundredfold. Past high, $115 bought 38 more functions. 55 of the 274 left have at least 90% of their instructions in place, and the median one is 111 instructions long with a loop. Whether a bigger model clears them is still open: the run stopped at max and Sonnet was never tried.
Lua and memorization
Lua was the control for the other question: how much of this is decompiling, and how much is remembering code the model has read. Lua went faster and cheaper, 679 of 698 functions (97%) for $35 against neat*'s 74% for $246. With $10 spent on each, Lua stood at 83% and neat* at 34%. Part of that is the harness: Lua ran after the fixes described in the next section, which neat* paid for. Lua also matched at every size, including the loops that stopped Haiku on neat*: 186 of 192 looping functions at 48–127 instructions, against 142 of 264.
Machine code fixes the control flow, the arithmetic and the names of functions and global variables. It does not contain local variable names, struct field names or comments, so I compared each matched function with the original source on those choices alone. Of the 673 matched Lua functions with an original to compare, 249 are close copies. 221 of them have at least 60% of their lines verbatim or reproduce the original comments word for word (44 do the latter), and 28 more match the original on local and field names alone. luaH_resize came back with /* will array shrink? */ and /* pretend array has new size... */ intact. The same test flags 5 of 796 neat* functions, all of them format strings and global names the binary dictates.
Subtracting the copies does not give a clean capability number for Lua: some would have matched without recall, and recall helps without leaving verbatim text. What the test shows is that on Lua, Haiku was writing from memory more than a third of the time.
Changes to the swarm harness
The harness was built for driving failing tests to green, where each test stands alone. Decompilation tests depend on each other. A function that calls a file-local helper cannot match until the helper exists, and gcc drops the helper until something calls it. Seventeen minutes into the neat* run, agents had found a way around that: compiler attributes that force gcc to keep an unused function. The runner now rejects any file that uses them, so every number above was measured without them; stripping them took the 216 functions matched at that point down to 202.
Agents also kept reporting "closer, but not matching", and the gate discarded those reports: 125 of them, 39% of the money spent by then. The gate now merges a change that raises the share of matching instructions without breaking anything; a function only counts as matched when every byte agrees. It took 326 such merges on neat*. The swarming-failing-tests skill now lists thirteen breaks like these from this run, with the change that handles each.
The whole run took 2,489 Haiku agents, 862 merges and $281 between 09:46 and 22:43 UTC. The source, runner, charts and the full write-up are in experiments/swarm-decomp.