How GitHub ported Copilot's runtime to Rust with agents and kept shipping

GitHub's agent-driven rewrite of 800,000+ lines into Rust, decoded as a playbook for teams planning an incremental, behavior-preserving port.

Picture a platform engineer at a mid-size SaaS shop. Her team's core service is a large Node.js codebase, and it has become a cost problem: slow startup, heavy memory per instance, and a process boundary that every integration has to cross. Leadership wants it in a compiled language, and someone suggests letting coding agents do the port. She's seen how that goes. A long-lived feature branch falls behind main every week. An agent produces a giant PR nobody can review. The code compiles, and three weeks after cutover a retry path behaves differently and nobody can say why.

GitHub just published a detailed account of doing this at scale and avoiding most of that. In a post on the GitHub Blog, Microsoft Distinguished Engineer Stephen Toub describes rewriting the Copilot agent runtime from TypeScript into more than 800,000 lines of production Rust. Agents wrote most of it, across 128 pull requests that landed in main and shipped incrementally. The useful part for the rest of us isn't the line count. It's the method that kept the port behavior-preserving while dozens of other developers kept merging.

What it is

The Copilot agent runtime backs the Copilot CLI, the Copilot app and the Copilot SDK, and, per GitHub, products including VS Code, Visual Studio, Copilot Code Review and Office apps. It ran on Node.js and V8, and SDK clients reached it by spawning a CLI process and talking JSON-RPC. GitHub says that cost every SDK consumer "on the order of 100 MB of working set minimum" per client, plus a second process to supervise.

The new runtime is a native binary exposing a C ABI for in-process use, with a stdin/stdout or socket server kept for out-of-process cases. Byteiota reports the C ABI is 19 exported functions in front of 364 JSON-RPC dispatch routes, so new APIs don't need a binary interface change.

The method maps directly to the lede's failure points:

GitHub's reported results, per InfoQ: a measured client startup, session creation and single-turn scenario fell from 5.25 seconds to 292 ms with the Rust runtime embedded in-process. Byteiota's follow-up adds 10-client memory falling from 1,383 MB to 126 MB and throughput rising from 7.55 to 120 session lifecycles per second. Byteiota notes GitHub flags these as workload-specific and the product of language, process model and architecture changes together.

Alternatives, with what the sources actually support:

Who it's for

Pick this up if you're a Copilot SDK consumer: Byteiota and iMasters both say the TypeScript, Python, Go, .NET, Java and Rust SDKs work against the new runtime without application code changes, with an in-process option for latency-sensitive tools.

Pick it up as a playbook if you're planning a large agent-assisted port of a shared, high-concurrency component and you already have end-to-end tests worth trusting.

Skip the rewrite itself if your Node service's startup and memory are fine. Toub is explicit: "This is in no way a claim that every large TypeScript program should become Rust." Byteiota summarizes GitHub's own advice as: measure your bottleneck first, migrate incrementally, build compatibility tests first.

Before and after

The old way, for a large port:

  1. Estimate the codebase and open a rewrite branch.
  2. Port in isolation while main keeps moving.
  3. Rebase repeatedly; the gap grows.
  4. Big-bang cutover; behavior bugs surface all at once in production.

GitHub's way:

  1. Separate layers first (TUI from runtime), so the port target is a library.
  2. Build an interop bridge so ported pieces run inside the existing system.
  3. Port leaves first; each PR replaces one component with a Rust shim and deletes the TypeScript.
  4. Run existing end-to-end tests plus compile checks on every change. InfoQ reports 4,478 direct cargo check runs, 87.1% clean.
  5. Ship continuously, with prereleases soaking up early exposure.
  6. Remove the bridge when nothing calls it.

The human role changed too. Byteiota reports the lead engineer's 2,639 interactions broke down as 31% reviewing results and running CI, 17% challenging agent decisions and 15% pushing agents past intermediate stopping points, with only about 40 session starts. The engineer described the job as "operate the control loop." The hardest piece, session.ts, took a 25-hour run with 15 parallel child sessions and a custom "agentic mutex" to serialize CPU-heavy builds, per Byteiota.

One finding worth noting before crediting Rust's safety model: Byteiota reports compiler errors were 37% name resolution, 22% missing methods, 14% type mismatches and 11% trait bounds, with ownership, borrow and lifetime errors at 1.7%. The feedback came mostly from static typing.

Try it

If you use the Copilot SDK, the sources say there's nothing to change; the entry point stays the same. GitHub's post shows it like this (the comment describes the old, out-of-process behavior):

const client = new CopilotClient();
await client.start(); // spawns the CLI as a subprocess
const session = await client.createSession({
/* ... */
});

The sources don't give SDK version numbers, install commands, or the switch for choosing in-process versus out-of-process mode, so check the SDK's own docs for those.

If you want to apply the method, the smallest first step the sources support is not writing Rust. It's picking one leaf module with no dependencies, putting an interop shim in front of it, and confirming your existing end-to-end tests pass through the shim. If they can't, you've learned your test suite isn't ready to be the oracle.

Where it breaks

Who picks this method: teams with a measured bottleneck, solid end-to-end tests and someone willing to run the control loop full time. Who skips it: anyone hoping agents replace the review, or whose port has no oracle to diff against.

Sources