Migrating the GitHub Copilot runtime to Rust, using Copilot

The GitHub Copilot runtime has been rewritten in Rust. The original stack, built on TypeScript and Node.js, now powers more than 800,000…

By Vane September 17, 2026 5 min read
Migrating the GitHub Copilot runtime to Rust, using Copilot

The GitHub Copilot runtime has been rewritten in Rust. The original stack, built on TypeScript and Node.js, now powers more than 800,000 lines of production code. AI agents generated the majority of this new code across 128 pull requests. The work shipped incrementally rather than waiting for a single cutover. Performance improved by orders of magnitude. A project that would have required a team for a year or two was completed by a single developer in a few months. The rest of the team continued to expand the runtime’s capabilities during this period.

Why the port was necessary

The runtime powers the Copilot CLI, the Copilot app, and a growing list of Microsoft and GitHub solutions. These products share the same core engine. Each solution wraps the runtime with its own customisations. This includes the latest releases of VS Code, Visual Studio, the Copilot cloud agent, Copilot Code Review, Copilot Cowork, Copilot Studio, and integrations with Excel, Outlook, PowerPoint, and Word.

These are distinct products. None of them wants to build an agent harness from scratch. They need intelligence, security, reliability, and performance shared across the board. A fix in the runtime fixes it everywhere. Most products initially built their own agent loop but replaced it with the GitHub Copilot SDK. This allows them to focus on their core business value. It is vital given the industry pace and the need for best-in-class agent loops.

Shared runtime, good. The problem was the nature of the thing being shared.

The CLI is logically a terminal UI on top of an agent loop. The original stack used TypeScript, Node.js, and the V8 JavaScript engine. Ink and React handled the UI. This is a respectable choice for a console application. TypeScript and Node.js are accessible and enable rapid development. For a standalone tool, performance implications regarding startup, responsiveness, throughput, and memory are reasonable. They are not reasonable when used in other environments with different constraints. Demands for fast startup and excellent server density due to low memory overhead are common.

Architectural choices also created challenges. The industry moves fast. Decisions prioritise delivery speed and market reach. The CLI shipped quickly, intertwining the TUI and runtime rather than separating them into discrete layers. When an SDK was needed for programmatic access, the decision was to layer it on top of the CLI. Logically, the inverse architecture makes more sense. The CLI was updated with a headless mode. It reads commands from stdin and writes responses to stdout. A JSON-RPC protocol marshals function calls between an external process and the CLI. The SDK can embed in arbitrary programs. These programs spawn a CLI process to host the agent loop out-of-process. The SDK calls functions in the remote process via JSON-RPC.

This approach was neat and flexible. It was not great for performance or reliability. Creating a new CopilotClient from the SDK meant spawning another process:

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

The process launched and hosted Node and V8. It involved parsing significant JavaScript code, generating bytecode, and potentially optimising hot code in later JIT tiers. It brought the memory overhead of V8. It inherited Node’s threading model, which serialises CPU-bound work by default. It forced out-of-process communication for simple function calls. Every SDK consumer, in every language, shipped Node.js or a bundled binary containing V8. The C#, Python, Go, Java, and Rust SDKs all paid for a second language runtime per client. This was a minimum of 100 MB of working set for a runtime the application otherwise had no use for. Every event, message, and abstracted session file system read and write pushed across a process boundary. A crash in Node took the session with it. Anyone deploying this had to supervise, monitor, and debug at least two processes.

The goal was a runtime that:

  • excludes the TUI, allowing the TUI and other applications to layer cleanly on top.
  • uses a language with minimal dependencies and overhead.
  • embeds in-process rather than forcing out-of-process.
  • offers top-tier performance, scalability, and reliability.
  • supports interop for all six Copilot SDK language versions (C#, TypeScript, Python, Rust, Go, Java) via foreign function interface mechanisms.
  • provides a modern security posture with less supply chain risk and better support for correct-by-construction code.

These reasons, plus team experience and industry direction, led to the choice of Rust. This is not a claim that every large TypeScript program should become Rust. The requirements emphasised embedding through a C ABI, low startup, steady-state overhead, and predictable resource use. Rust made these goals possible. It introduced complications, such as representing lifetimes and shared state explicitly. Lifecycle regressions discussed later highlight the implications of this.

Two key tasks followed:

  1. Separating TUI-specific code from the runtime, layering the former strictly on top of the latter and the SDK’s public surface area. The CLI still calls directly into runtime internals in several places. Moving it fully onto the SDK’s surface area is ongoing work.
  2. Porting the runtime layer to 100% Rust. This results in a pure native binary exposing a C ABI for in-process consumption by all language front-ends. It also provides a stdin/stdout-based or socket-based server for out-of-process hosting where desired.

This post covers the second task: porting the runtime to Rust.

The Copilot runtime architecture before and after the Rust rewrite, showing the old SDK-to-CLI process boundary and the new in-process and out-of-process hosting paths.

The state before the rewrite

The initial porting plan in early May 2026 estimated the runtime at roughly 130,000 lines of TypeScript. This measurement was reasonably accurate for scoping but wildly misleading in two ways. Concurrent with the port:

  1. Pieces wrapped in the TUI layer were pushed down to the runtime layer. Entire components and significant percentages of code initially ignored in estimates were later considered relevant.
  2. Pull requests contributing significant amounts of new TypeScript constantly raised the repo volume. Tens of agentically assisted developers merged hundreds of pull requests per week.

Everything factored in, approximately 430,000 lines of production TypeScript ended up passing through the port. These factors made it hard to see progress. Until close to the end, production TypeScript volume appeared steady or slightly increasing as porting kept pace with incoming work.

This is confused further by incoming Rust code separate from the port over the timeframe. Early in the effort, incoming code was dominated by TypeScript. Later in the effort, the ratio shifted.

What changes for developers

For teams building on the Copilot SDK, the shift means applications no longer depend on a Node.js process for agent capabilities. The SDK can now embed the agent loop directly. This removes the memory overhead of shipping a V8 runtime. It eliminates the need to manage two processes for every session. Startup times drop. Throughput increases. A crash in the agent layer no longer takes down the host application. The performance gains are immediate for any language using the SDK.

Scroll to Top