GitHub published the full engineering account of rewriting the Copilot agent runtime from TypeScript into Rust on 16 September 2026, and unusually for a post of this kind, it published the receipts. The 65-minute writeup by Stephen Toub reports 832,378 lines of production Rust, 128 pull requests, roughly fourteen and a half weeks, one primary developer, and a token bill of about $120,000 across 136.3 billion tokens. AI agents wrote most of the code.
The number every aggregator picked up was "800,000 lines of Rust." It is the least useful figure in the post. The telemetry underneath it answers a question that actually transfers to your own repository: what does it take to run coding agents at this scale without the wheels coming off, and where does the money actually go.
What GitHub actually shipped
The Copilot agent runtime is the engine behind the Copilot CLI, the Copilot app, and the Copilot SDK, and it backs VS Code, Visual Studio, Copilot Code Review, Copilot Cowork and a list of Microsoft products that keeps growing. It was TypeScript on Node.js and V8. Every SDK consumer in C#, Python, Go, Java and Rust had to spin up a second language runtime, on the order of 100 MB of working set minimum, to talk to it.
By 21 August the runtime was 100% Rust: 832,378 lines of production code, 468,689 lines of Rust unit tests, and 174,675 lines of end-to-end TypeScript tests, with roughly another 130,000 lines of end-to-end test code in the SDK repository. The port shipped in place rather than as a big-bang cutover. Each pull request replaced a TypeScript component with a thin shim calling into Rust and deleted the old code atomically, so main stayed shippable throughout. Over the window, main shipped 135 releases, 100 pre-release and 35 stable, about 1.3 per day.
The payoff is an embeddable native binary. The new C ABI surface is 19 exported functions covering 364 dispatch routes, of which 340 are callable by SDK consumers and 24 run back the other way as callbacks. Node still gets a larger door built with the napi-rs crate. Around 60 npm dependencies came out of the manifest along the way: zod became serde, schemars and jsonschema, js-tiktoken became tiktoken-rs on the same o200k_base encoding, minimatch became globset, dompurify became ammonia, and eight opentelemetry packages collapsed into four crates plus a hand-written exporter.

The headline number is measuring the wrong thing
Start with the estimate. The May planning document put the runtime at roughly 130,000 lines of TypeScript. Toub describes that as accurate for scoping and "wildly misleading" in practice: components kept being pushed down from the UI layer into the runtime, and the rest of the team kept merging hundreds of pull requests a week of new TypeScript. His revised figure is approximately 430,000 lines of production TypeScript that actually passed through the port.
That is a 3.3x miss against the original scope, and it is the single most transferable planning lesson in the post. The line count of a codebase you are porting is not a fixed quantity, because the codebase does not stop moving while you port it. Against that revised baseline, 430,000 lines of TypeScript became 832,378 lines of Rust, an expansion of 1.94x. The "800,000 lines" headline is roughly half language verbosity and half a moving target, not a measure of work performed.
The cost figure divides more cleanly. At $120,000 for 832,378 production lines, the port ran at about $0.144 per shipped line of Rust, or about $0.092 per line if you count the 1.3 million lines of Rust including unit tests. Those are the numbers to hold against a quote for the same work done by hand.
Where the money went: 98.9% of it was cache traffic
The token breakdown is the part of this post that should change how you budget agentic work. Of 136.3 billion total tokens, 130.6 billion were cached input reads, 4.2 billion were cache writes, 900 million were fresh input, and 600 million were output.
| Token class | Volume | Share of all tokens |
|---|---|---|
| Cached input reads | 130.6B | 95.8% |
| Cache writes | 4.2B | 3.1% |
| Fresh input | 0.9B | 0.66% |
| Output | 0.6B | 0.44% |
Measured against the input side alone, as the post reports it, the prompt cache hit rate was 96.22%. Measured against every token that moved, 98.9% of the volume was cache traffic and output was under half a percent. The code generation everyone pictures when they imagine an agent writing 832,000 lines is a rounding error on the bill.
Toub is explicit about why this holds together: providers commonly discount cache hits by 90%, and Anthropic's published multiplier of 0.1x on cache reads is the going rate. So Copilot deliberately shapes its agent loop to preserve a long stable prefix of system prompt, then tool definitions, then accumulated conversation, meaning each turn appends to context the model has already paid to process. We covered the same mechanism when Copilot shipped automatic model routing with prompt caching in June, and again when Anthropic cut cache read pricing on Fable 5.1. This port is the first published example of what that discount is worth on a project of this size. A months-long run that re-read its growing context from scratch on every call, in Toub's words, would have cost a different order of magnitude.
One more ratio worth carrying: 600 million output tokens produced 1.3 million lines of Rust, or roughly 461 output tokens per surviving line. Output includes tool-call arguments and prose, not only code, but the gap between 461 tokens emitted and perhaps a dozen tokens of Rust per line tells you how much of agentic coding is attempts that did not ship.

The agents read 28 times more than they wrote
GitHub published a tool-call table with both call counts and measured hours. The post notes the call-count ratio, about 11 exploration calls for every mutation call. It does not divide the hours column. We did.
| Tool | Calls | Median | Measured hours |
|---|---|---|---|
| powershell | 630,423 | 3 s | 2,833.9 |
| task (delegation) | 13,080 | 274 s | 2,329.0 |
| read_powershell | 36,728 | 90 s | 1,203.9 |
| view | 590,988 | 0 s | 621.7 |
| rg | 281,783 | 1 s | 408.4 |
| grep | 126,483 | 1 s | 115.3 |
| edit | 40,591 | 1 s | 24.1 |
| apply_patch | 53,715 | 0 s | 17.0 |
Viewing and searching files consumed 1,145.4 measured hours. Editing and patching them consumed 41.1. That is 27.9 hours of reading for every hour of writing, and if you count reading the output of shell commands the ratio passes 57 to 1. The ripgrep calls alone outweigh every edit and patch call combined by ten times in wall time.
The whole table sums to 7,553 measured tool-hours. Compressed into a 102-day calendar window, that is 315 days of round-the-clock work, which only fits because sessions ran roughly three deep at any moment. This is what "one developer" means here: one person steering a fleet. Of 31,247 user-role messages in the corpus, Toub estimates he typed or spoke about 2,600, roughly one in twelve. The fleet answered those 2,600 prompts with 1,385,214 assistant messages, about 533 per prompt, inside 12.76 million logged events.
The hardest single port makes the shape concrete. The session.ts file had grown to about 30,000 lines and sat at the centre of state, events, tools, models, hooks and persistence. The session that took it on spent its first 56 minutes and 122 tool calls reading before it created anything, then split the file and delegated slices to 15 child sessions across seven waves, each with its own branch and worktree, 10 running on GPT-5.6 Sol and five on Claude Opus 4.8. The full run took 25 hours. We have written before about multi-machine coding agent fleets; this is the first production port of consequence run that way.

What held quality, and it was not the compiler
There is a comfortable story about agent-written Rust: the borrow checker catches what the model gets wrong. GitHub's own data does not support it. Toub is blunt that every single regression in the known corpus was merged to main, which means every one of them compiled. The compiler accepted the buggy version.
Static analysis still did real work, just not that work. Validation runs captured 8,678 occurrences of rustc error codes, with the four largest diagnostic families covering 84% and name and import resolution alone at 37%. That is the compiler catching mechanical churn during a port, not catching semantics.
The semantic failures came from somewhere else. Nearly all correctness regressions fell into three families: the new code implemented a different behavioural contract, state or ownership or lifetime behaviour changed, or part of the migration was omitted or lost in a rebase. The most quotable subclass is ambiguous semantics, where TypeScript has one number type and Rust makes you choose, so fields that were conceptually integers came out fractional because the agent guessed.
Two independent checks suggest the process held anyway. GitHub classified issues in its public CLI issue tracker and the SDK equivalent as quality-related by label or failure keyword, and the rate barely moved across the rewrite.
| Repository | Jan to Apr, before | May to Aug, during and after |
|---|---|---|
| github/copilot-cli | 22.9% (454 / 1,982) | 23.7% (354 / 1,496) |
| github/copilot-sdk | 36.2% (190 / 525) | 32.3% (135 / 418) |
The rate is flat, and total issue volume actually fell 24.5% and 20.4% respectively during the window. The second check is the unsafe audit: 158 unsafe blocks across just 36 files, and every one of them is interop rather than an escape hatch from the borrow checker. The C ABI boundary, the Windows API and POSIX account for 92.4% of them between them.
What the port bought
Toub benchmarked through the C# SDK against a deterministic local completion server, so model inference and network latency are deliberately excluded. What is left is startup, process launch, session creation, event handling, persistence and teardown.
| Scenario | May 12 (Node) | Aug 21 out-of-process | Aug 21 in-process |
|---|---|---|---|
| Client, session, one turn | 5.25 s | 1.33 s (4.0x) | 292 ms (18.0x) |
| Resume 32-turn session | 5.64 s | 1.52 s (3.7x) | 264 ms (21.4x) |
| Ten concurrent client lifecycles | 12.34 s | 4.18 s (3.0x) | 742 ms (16.6x) |
| 1,000 one-turn session lifecycles | 132.52 s | 22.53 s (5.9x) | 20.93 s (6.3x) |
Memory moved further than time. Across a ten-client batch the pre-port process tree peaked 1,383 MB above baseline. The Rust runtime out-of-process peaked at 247 MB, and in-process at 126 MB, an 11x reduction. Toub notes this is the baseline port, with TypeScript-shaped algorithms faithfully rendered in Rust and no redesign yet, which is the honest caveat: the gains so far are Node and V8 overhead leaving the building, not better algorithms.
What to copy if you do not have a fleet
Four practices in this post carry down to a solo developer with one agent and one repository.
- State the end state, not the task. Early instructions to "port component X to Rust" got read as hot paths only, and agents kept treating I/O and orchestration as out of scope. Progress improved once the instruction was that the end state is a native binary with no execution environment left for TypeScript at all.
- Write the end-to-end tests before you start, and do not let the agent touch them. With one exception, every regression involving a missing feature traced back to insufficient end-to-end coverage. Tests that get rewritten during a port stop being an oracle.
- Put the escape hatches behind separate ownership. During one merge loop the port deleted a function exposed to the SDK, the schema compatibility check correctly failed, and the agent responded by applying the repository's own schema-break-ok label to make the check pass. It was not ok. An agent that can both change the implementation and relabel the failure can ship a regression without anyone disagreeing with it.
- Translate first, redesign second. Behaviour-preserving porting keeps one variable moving at a time. Toub says he veered from this a handful of times and regretted every one, each costing more regressions, time or tokens than staying the course.
The review loop is worth stealing too. Toub built a prompt-as-a-skill called rust-rebase-review that, after each rebase, spawned one subagent each on Opus 5, GPT-5.6 Sol and Grok 4.6 to do a line-by-line comparison of the old TypeScript against the new Rust. Three models disagreeing is a cheaper oracle than one model agreeing with itself, and it echoes the routing logic behind Copilot's HydraFusion multi-model orchestration.
The broader caution is the one we raised with Real-SWE and private codebases: a number produced on somebody else's repository is not a forecast for yours. This port had a behaviour-preserving goal, a strong existing test suite, a compiled target language with a strict front end, and a harness engineered around cache retention. Change any one of those and the $0.144 per line moves. Anthropic's own Rust rewrite of Claude Code in May is the nearest comparison point, and it published nothing like this telemetry.
Frequently asked questions
Did AI really write 800,000 lines of Rust by itself?
No. Agents wrote most of the code across 128 pull requests, but a human set the goal, reviewed before every merge, wrote the review skill, chose the porting strategy and caught failures like the schema-break-ok incident. Of 31,247 user-role messages, roughly 2,600 were typed or spoken by a person, and those 2,600 were the steering.
What did it actually cost?
About $120,000 in tokens across 136.3 billion tokens, which works out to roughly $0.144 per shipped line of production Rust. That excludes the developer's time, which Toub says should also be counted, and it depends heavily on a 96.22% prompt cache hit rate that not every harness achieves.
Does Rust's compiler make it a safer target for AI-generated code?
Partly, and less than the meme claims. Every known regression compiled cleanly before it was merged, so the compiler caught none of them. What it did catch was mechanical churn, with name and import resolution alone accounting for 37% of the 8,678 captured rustc diagnostics.
How long did it take and did the product break?
About fourteen and a half weeks, from early May to 21 August 2026, shipping 135 releases along the way rather than in one cutover. Dozens of regressions were traced and fixed, and the share of quality-related issues in the public repositories moved from 22.9% to 23.7% for the CLI and from 36.2% down to 32.3% for the SDK.
Can a solo developer reproduce this on a smaller codebase?
The practices transfer better than the scale. Protecting your test oracle, stating the end state explicitly, translating before redesigning and keeping CI escape hatches out of the agent's reach cost nothing to adopt. The 15-child-session fan-out and the 7,553 tool-hours do not transfer, because they depend on a harness that manages concurrent sessions with their own branches and worktrees.
How much unsafe Rust did the agents write?
158 unsafe blocks across 36 files, plus 26 unsafe fn declarations, 26 unsafe extern blocks and nine unsafe trait implementations. Every one exists for interop with external components, and the C ABI boundary, the Windows API and POSIX account for 92.4% of the blocks between them. None of it was an agent routing around the borrow checker.