Why the Bun Rewrite Is Really About Agent Engineering
Jarred Sumner rewrote Bun from Zig to Rust with 64 Claude agents in 11 days. The agent harness he designed made it feasible, not the model.
TL;DR: Jarred Sumner rewrote Bun from Zig to Rust using 64 Claude agents in 11 days. The agent harness he designed (adversarial review with split contexts, shared memory artifacts, process-over-code debugging) is what made it feasible, not the model. This is the best real-world case study of production agent harness design we have.
Key takeaways:
- The harness design mattered more than the model. Adversarial review with split context windows caught bugs that compiled clean and looked correct, including use-after-free and double-free in async code.
- Shared knowledge artifacts (PORTING.md, LIFETIMES.tsv) ensured consistency across 64 parallel agents. Every agent read the same validated rules instead of re-deriving them.
- When something broke, Jarred fixed the workflow prompt, not the code. A single process fix propagated to all 64 agents.
- The approach has limits: 19 regressions from semantically-different-but-syntactically-identical code, and 13,044 unsafe blocks (4% of the codebase) as safety debt.
- This succeeded partly because Bun had a language-independent TypeScript test suite with 1.38 million expect() calls as ground truth.
Jarred Sumner was tired of going to sleep worrying about crashes.
He’d done everything right. Bun’s Zig code ran Address Sanitizer on every commit. It fuzzed runtime APIs 24/7 with Fuzzilli. It shipped ReleaseSafe builds on Windows. It had end-to-end memory leak tests. Most projects don’t do half of this.
The bugs still came. Use-after-free in node:zlib. Double-free in the CSS parser. A memory leak in crypto.scrypt where the callback and protected buffers were never released when the output buffer allocation failed. A race condition in MessageEvent where the GC marker thread could observe a torn variant during concurrent access from a BroadcastChannel. Every night, another report.
The bugfix list felt bad, he wrote later. None of this was Zig’s fault. Other Zig users don’t have these bugs. The problem was that mixing a garbage-collected language (JavaScript) with manually-managed memory (Zig) creates edge cases that no amount of tooling can systematically prevent.
He could have kept fixing bugs one-off in perpetuity. Instead, he tried something different. He spent a week testing whether Anthropic’s newest model could rewrite Bun in Rust.
Claude Fable 5 wrote the code. But the harness was the real story: a system that orchestrated 64 agents across 4 worktrees, reviewed every line with adversarial reviewers, serialized knowledge into shared artifacts, and fixed the process when things broke. That is what made the rewrite feasible in 11 days.
This is the best real-world case study we have of production agent harness design. Here is how it worked.
How 50 dynamic workflows replaced 3 engineers
A dynamic workflow is a self-managed agent team. You define the process, not the individual tasks. The runtime handles queuing, parallelism, and error recovery.
Jarred defined about 50 workflows over the course of 11 days. Each was a loop:
while task = queue.pop():
result = implementer(task) # 1 Claude writes code
feedback = await Promise.all([ # 2 Claudes review independently
adversarial_review(result),
adversarial_review(result)
])
await apply_fixes(feedback, result) # 1 fixer applies suggestions
Each workflow had a specific job. Generate a porting guide mapping Zig patterns to Rust. Mechanically port every .zig file to a .rs file. Fix every crate’s compiler errors. Get bun test working. Get every test passing. Deduplicate code. Reduce unsafe usage. Windows cleanup.
This is different from spawning subagents. In the naive approach, a parent agent delegates tasks and waits for results. The parent’s context fills with subagent outputs. Subagents don’t know about each other. If one does something destructive like git stash or git reset --hard, the parent might not notice until too late.
Jarred hit exactly this failure mode. Two minutes into the first run, one Claude ran git stash. Another ran git reset --hard. They were stepping on each other. The fix was not to police individual agents. It was to edit the workflow definition to forbid any git command that doesn’t commit a specific file.
That is the fundamental difference: in a dynamic workflow, you debug the generator, not the output. A prompt edit propagates to all 64 agents. A fix to one file does not.
Why it works: The process layer decouples coordination from execution. The workflow defines what agents can and cannot do. Individual agents execute within those constraints. When something breaks, you have one place to fix it, not 64.
At peak, the harness ran 64 Claudes simultaneously across 4 Git worktrees. Each worktree ran 16 agents in 4 groups of (1 implementer, 2 reviewers, 1 fixer). At peak, Claude wrote about 1,300 lines of code per minute. Every line was reviewed by 2 adversarial reviewers before commit.
Adversarial review with split contexts
The most important mechanism in the harness was adversarial review with split context windows. Understand this in detail because it applies to any multi-agent code generation pipeline, not just language porting.
Here is the setup. The implementer Claude gets the original Zig file, the PORTING.md (the rules for translation), the LIFETIMES.tsv (the memory map), and its own reasoning. It produces a .rs file.
The adversarial reviewer Claude gets only the diff. None of the original Zig. None of the implementer’s reasoning. None of the plan. Only the new code. And it is told: assume this code is wrong. Find reasons why it does not work.
Why it works: Claude is biased to agree with itself, like humans are. A reviewer who shares the implementer’s context will rationalize: “oh, that makes sense.” A reviewer who sees only the output, with a brief to find fault, finds actual bugs.
The pattern is: 1 implementer, 2 adversarial reviewers per task. The implementer does not review. The reviewer does not implement. They never share a context window.
Real bugs this caught:
Bug 1. The async close. The implementer wrote code that looked correct: pipe.close(...) where pipe is a Box<uv::Pipe>. The Box drops at end of scope. But uv_close is asynchronous. libuv still holds the raw pointer until the next loop tick, then calls the close callback which frees it. The reviewer caught it. Use-after-free, then double-free. Fixed by leaking the Box so Drop does not run, letting libuv’s callback handle freeing.
Bug 2. The negative timestamp. Bug 3. A buffer overrun in the event loop.
All three compiled clean. All three looked plausible. The reviewer caught them because it was not carrying the implementer’s assumption that “this is correct.”
Every bug caught this way carries a specific commit convention: the subject line starts with win-review:. Jarred could grep the git log for win-review to see what the harness caught. That is observability built into the commit protocol.
PORTING.md and LIFETIMES.tsv as shared agent memory
Before writing any code, Jarred spent 3 hours talking to Claude about how to map Zig patterns to Rust. Claude serialized this into a PORTING.md with about 300 rules.
Then he prompted a dedicated workflow to analyze the proper lifetimes of every struct field in the codebase. Read every field across all 1,448 files. Trace the control flow. Propose a lifetime. Use 2 adversarial reviewers to validate that lifetime. Serialize into a LIFETIMES.tsv.
Why LIFETIMES.tsv exists: Zig does not have a borrow checker. A struct like TCPSocket has a handler: *Handler and an event_loop: *EventLoop. In Zig, those are raw pointers. The programmer knows that handler lives as long as the EventLoop that owns it, but that knowledge lives in their head. In Rust, every reference needs a lifetime annotation. Without LIFETIMES.tsv, every implementer agent would have to re-derive the same analysis from scratch. Different agents would infer different lifetimes for the same fields.
LIFETIMES.tsv is a tab-separated file. One row per struct field. Tab-separated instead of CSV because commas appear in Rust lifetime syntax (<'a, 'b>) and CSV quoting adds parsing complexity. Tab-separated instead of Markdown tables because agents parse TSV more reliably than table borders.
The PORTING.md and LIFETIMES.tsv went through their own adversarial review pass before any code was written. Two reviewers validated the porting rules. Two reviewers validated each lifetime entry. Conflicting suggestions were resolved before the first .rs file was created.
Every implementer agent then read both files as part of its context. Consistency across 64 parallel agents came from shared validated artifacts, not from identical prompts.
The catch: This works for mechanical translation where the output format is well-defined and the rules can be enumerated. It does not work for creative or open-ended tasks where the criteria for correctness cannot be serialized in advance. The Bun rewrite succeeded partly because “Rust that behaves like this Zig code” is a testable specification.
Debug the process, not the output
When something went wrong during the rewrite, Jarred did not fix the code. He edited the workflow prompt that generated the code. Three examples:
One. Claudes started adding long explanatory comments to document workarounds in the ported code. Instead of removing each comment, Jarred added a rule for the adversarial reviewers: “If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong. Fix the code.” The behavior stopped within hours across all 64 agents.
Two. The agents interpreted “let us get all the crates to compile” as “stub out every function that has compilation errors.” The adversarial reviewer rule caught this: reviewers rejected stubs that did not implement actual behavior.
Three. Agent coordination collapsed when multiple Claudes ran cargo check (a 30-minute operation on a codebase this size) at the same time. The fix was to run cargo check once at the start of each crate loop, not per agent.
Why it works: A fix to the process propagates to every agent on the next loop iteration. A fix to one file does not. The power is in the workflow definition, not the output.
Compiler errors as a work queue
After all the code was written, about 16,000 compiler errors remained. Most came from cyclical dependencies that emerged when splitting one Zig compilation unit into about 100 Rust crates.
Jarred ran cargo check, wrote the errors to a file, and let the workflow divvy them up among the 64 agents. Each agent fixed one crate, 2 reviewers checked the fixes, 1 fixer applied. The heaviest crate (bun_runtime) took 708 fix commits. The lightest (bun_zlib) took 1.
One workflow looped over the 4 worktrees, processing errors until cargo check passed on every crate. The output: 1,155 fix commits on May 6 alone.
The results and the catch
The Rust rewrite fixed 128 bugs that reproduced in Bun v1.3.14. Memory use under repeated builds went from unbounded growth (6,745 MB after 2,000 builds) to flat (609 MB). Binary size dropped 20% on Linux (88 MB to 70 MB). HTTP throughput improved 2-5%. Prisma launched its Compute public beta on the Rust port. Claude Code v2.1.181 ships on it.
The catch: The rewrite introduced 19 regressions, all fixed before merge. Most came from code that is syntactically identical but semantically different between Zig and Rust. debug_assert! vs assert. bytemuck::cast_slice vs reinterpretSlice. Compile-time format strings vs runtime. The adversarial reviewers could not catch these because the code looked correct.
The Rust port has 13,044 unsafe blocks (4% of the codebase, 78% single-line: a pointer from C++ or one call into a C library like JavaScriptCore). Comparable hand-written Rust projects average 73. This is the safety debt of AI-speed migration.
At API pricing, the rewrite cost about $165,000 in tokens: 5.9 billion uncached input tokens, 690 million output tokens, 72 billion cached input token reads.
Jarred’s calculation: 3 engineers with full context on the codebase, one year of work, during which Bun’s bugfix and feature pipeline freezes. The realistic alternative was not a human rewrite. It was never doing the rewrite.
What this means for agent engineering
The Bun rewrite tells us something specific about the current capability ceiling of coding agents. It is not that Claude Fable 5 can write Rust. It is that a well-designed agent harness (with separated roles, adversarial review, shared memory artifacts, and process-over-code debugging) can produce a million-line production port in 11 days that a team of humans would not attempt.
One engineer can do a lot more today than a year ago. But only if they design the harness first.
That is the lesson of this story. Not that AI rewrote Bun. That Jarred spent more time designing the agent architecture than he spent reviewing the output. The PORTING.md, the adversarial reviewer protocol, the worktree isolation, the forbidden-commands list, the crate-by-crate error pipeline. The harness gave the output its structure. The model filled it in.
Every bug the harness caught came from someone thinking through the process before the code. Every regression it missed came from a place where the process did not anticipate the failure mode. The quality of the output was bounded by the quality of the process, not the model’s capability.
If you take one thing from this post, take that: your agent harness design matters more than your model.
FAQ
What was the Bun Rust rewrite? Jarred Sumner rewrote Bun, a JavaScript runtime, from 535,496 lines of Zig to Rust using Claude Code dynamic workflows. 64 agents ran in parallel across 4 worktrees for 11 days, producing 6,502 commits at a cost of about $165,000 in API tokens. The rewrite fixed 128 bugs, reduced binary size by 20%, and flattened memory growth under repeated builds.
How did adversarial review catch bugs in the Bun rewrite? Each code change was reviewed by 2 separate Claude agents in isolated context windows. The reviewers received only the diff and were told to assume the code was wrong. This caught bugs that compiled clean and looked correct, including a use-after-free and double-free in the async close handler. The implementer’s context was never shared with the reviewer, preventing confirmation bias.
What is LIFETIMES.tsv and why does it matter? A tab-separated file that mapped every struct field in Bun’s Zig codebase to its correct Rust lifetime annotation. Zig does not have a borrow checker, so lifetime information exists only in the programmer’s head. LIFETIMES.tsv extracted that knowledge into a format every agent could read, validated by adversarial review. It ensured 64 parallel agents produced consistent lifetime annotations.
What are the limits of this approach? The harness caught logic bugs but missed 19 regressions from code that looks identical but behaves differently across languages. The resulting code has 13,044 unsafe blocks (4% of the code), the safety debt of AI-speed migration. The approach also depends on a language-independent test suite as ground truth for correctness.
Related Posts
- The 15 jobs every agent harness must do. A framework for what goes into an agent orchestration system: the state machine, policy gates, credential resolution, and observability every harness needs.
- Agentic abstention: Bigger agents are worse at stopping. Why process design matters more than model capability — larger models are better at executing but worse at knowing when to stop.
- Self-improving loop patterns in production. Patterns for debugging the generator rather than the output. The Bun rewrite demonstrates this at scale.
References
- Rewriting Bun in Rust by Jarred Sumner (July 8, 2026)
- Rewriting Bun in Rust (Simon Willison’s Weblog)
- Hacker News discussion (708 points, 789 comments)
- Anthropic’s Bun Rust rewrite merged at speed of AI by DevClass
- Bun’s Migration from Zig to Rust as a Potential Case Study on LessWrong
This article was published on Agentic Up (https://agenticup.dev): practical guides for developers and founders building with AI agents. Reach me at [email protected]