GitHub reemplazó el runtime que sostiene a Copilot CLI, a la app de Copilot y al Copilot SDK: pasó de unas 430.000 líneas de TypeScript sobre Node.js a más de 800.000 líneas de Rust, en unas 14,5 semanas y con un solo desarrollador supervisando a agentes de IA. El dato que más circula es el arranque: de 5,25 s a 292 ms. Conviene leerlo con cuidado, porque depende del escenario medido.
El runtime de Copilot es el motor de agente que ejecuta sesiones, herramientas, llamadas a modelos y la capa MCP detrás de los productos de Copilot. Hasta 2026 corría en TypeScript sobre Node.js y V8. GitHub lo reescribió en Rust para poder embeberlo dentro del proceso de cada aplicación anfitriona, sin lanzar un subproceso Node por cliente, y lo expone mediante una C ABI.
¿Qué problema tenía el runtime de Copilot en TypeScript?

El problema no era la velocidad de la CLI, sino el costo de usar el runtime como librería. Según el blog de GitHub (Stephen Toub), cada consumidor del SDK tenía que llevar o lanzar Node.js con V8 y hablar con el runtime por JSON-RPC fuera de proceso.
- Un subproceso por cliente: el SDK llamaba al runtime a través de una CLI lanzada como proceso aparte.
- Unos 100 MB mínimos por cliente: es el working set que GitHub atribuye a Node.js más V8 (parseo, bytecode, posible JIT).
- Seis SDKs afectados: C#, TypeScript, Python, Rust, Go y Java dependían de ese mismo esquema.
- Densidad limitada en servidor: si levantás muchas sesiones por host, ese piso de memoria y el tiempo de arranque se multiplican.
Es un patrón conocido: una herramienta pensada como aplicación de consola se convierte en componente embebido, y las decisiones que eran razonables para una TUI pasan a ser el cuello de botella. El propio autor reconoce que TypeScript y Node eran una elección razonable para esa aplicación.
¿Por qué GitHub eligió Rust y no otra opción?
GitHub eligió Rust porque una C ABI permite embeber el runtime como biblioteca nativa, sin necesidad de llevar un runtime propio ni lanzar un subproceso por cliente. El artículo aclara que es una decisión atada a sus requisitos de embedding y no una recomendación universal para migrar desde TypeScript.
¿Cómo quedó la arquitectura después de la migración?
El runtime en Rust expone dos puertas permanentes: una C ABI para cargarlo en proceso desde cualquier SDK vía FFI, y una API JSON-RPC. El modo fuera de proceso (TCP o socket) sigue disponible.
La decisión más interesante es mantener JSON-RPC incluso en proceso. Los seis SDKs ya tenían un cliente JSON-RPC funcionando; lo único que cambió fue el transporte, de pipes o sockets a llamadas a función.
- C ABI: el punto de entrada para embeber el runtime desde C#, Python, Go, Java, etc.
- JSON-RPC: el contrato de API no cambió, así que agregar o quitar métodos toca la tabla de despacho del motor y no la ABI.
- Puente temporal con
napi: durante la migración el código Rust y el TypeScript convivieron; el puente llegó a 2.019 exports N-API y 3.356 puntos de llamada en TypeScript y se fue a cero a medida que se portaban los componentes.
¿Qué resultados midió GitHub?
Según InfoQ, que reporta las mediciones de GitHub, el escenario de crear un cliente, abrir una sesión y completar un turno pasó de 5,25 s a 292 ms con el runtime Rust embebido en proceso. La misma cobertura registra unos 100 MB menos de working set por cliente.
| Métrica | Antes (TypeScript) | Después (Rust) | Fuente |
|---|---|---|---|
| Arranque (cliente + sesión + un turno) | 5,25 s | 292 ms (en proceso) | InfoQ |
| Memoria, lote de 10 clientes | 1.383 MB | 126 MB | byteiota |
| Throughput (ciclos de sesión por segundo) | 7,55 | 120 | byteiota |
Una advertencia de lectura: los 292 ms corresponden al modo embebido. Si necesitás aislamiento por proceso y usás el modo fuera de proceso, la mejora es menor. Y 5,25 s a 292 ms mide ese escenario puntual, no la latencia de una respuesta de modelo, que depende de la red y del proveedor.
¿Cómo se reescriben 430.000 líneas en 14,5 semanas?
GitHub lo hizo con reemplazos atómicos in-place, no con un desarrollo paralelo: 128 pull requests, cada uno sustituyendo un componente de TypeScript por su versión Rust y borrando el código viejo en el mismo cambio. El orden fue de componentes hoja a subsistemas con estado y, al final, la orquestación.
- Se publicó todo el tiempo: hubo 135 releases durante la migración (100 pre-release y 35 estables), con unos 1,3 por día.
- Un humano en el bucle de control: un desarrollador principal supervisó a los agentes. De 31.247 mensajes de usuario en los logs de sesión, solo unos 2.600 fueron escritos por humanos; el resto vino de skills y automatización.
- Agentes en paralelo: sesiones padre que lanzan sesiones hijas con ramas y worktrees aislados. Los agentes dedicaron unas diez veces más tiempo a explorar que a modificar código.
- Costo en tokens: la cobertura secundaria habla de unos USD 120.000 en uso de modelos.
Los números de volumen: 832.378 líneas de Rust de producción y 468.689 de tests unitarios. Que el Rust duplique casi las líneas del original no es sorpresa: la reescritura incluye tipado explícito, manejo de errores y la frontera de interop.
¿Qué regresiones aparecieron y qué enseñan?
Aparecieron decenas de regresiones, que tuvieron que ver con el comportamiento, el manejo de estado y lifetimes, la semántica de librerías y optimizaciones perdidas. El autor admite que seguramente hay más sin descubrir.
- Tipos numéricos: la diferencia entre
42.0y42en un lenguaje dinámico no se traduce de forma limpia a Rust. - Comportamiento ambiental: JavaScript hereda zona horaria, directorio de trabajo, variables de entorno y autenticación sin que se note; en Rust hay que pasarlos explícitamente.
¿Te conviene reescribir tu servicio Node.js en Rust?
Probablemente no, salvo que tu cuello de botella sea el costo fijo de proceso: arranque, memoria por instancia o densidad por host. Esta es una opinión nuestra a partir del caso, no una conclusión de GitHub. Su beneficio vino de embeber el runtime en seis lenguajes, algo que un servicio web típico no necesita.
Antes de decidir, medí tu línea base. Un ejemplo con herramientas estándar en Linux:
# tiempo de arranque, con warmup
hyperfine --warmup 3 'node app.js --healthcheck'
# memoria residente (KB) de tus procesos Node
ps -o pid,rss,cmd -C node- Si el piso de memoria por proceso domina: consolidar workers o reducir procesos suele rendir más que reescribir.
- Si tenés muchas instancias de corta vida: el arranque en frío pesa y un binario nativo ayuda.
- Si el cuello es I/O o red: Rust no te lo va a resolver.
- Si vas a migrar igual: hacelo por componentes, con releases frecuentes y tests E2E que no se borren sin aprobación explícita.
Preguntas frecuentes
¿En cuánto tiempo reescribió GitHub el runtime de Copilot?
En unas 14,5 semanas, a través de 128 pull requests y 135 releases. La supervisión estuvo a cargo de un desarrollador principal, con agentes de Copilot generando casi todo el código.
¿De cuánto a cuánto bajó el arranque?
De 5,25 segundos a 292 milisegundos, según InfoQ, en un escenario que incluye crear el cliente, abrir una sesión y completar un turno con el runtime embebido en proceso.
¿Cuántas líneas de código tenía el original y cuántas tiene ahora?
Unas 430.000 líneas de TypeScript pasaron a 832.378 líneas de Rust de producción, más 468.689 de tests unitarios, según el blog de GitHub.
¿Qué lenguajes pueden usar el runtime en Rust?
Los seis SDKs de Copilot: C#, TypeScript, Python, Rust, Go y Java. Cargan el runtime en proceso a través de la C ABI y siguen usando sus clientes JSON-RPC existentes.
¿Cuánto costó la migración?
Según byteiota, unos USD 120.000 en tokens de modelos.
Conclusión
Lo transferible del caso no es "reescribí todo en Rust", sino el método: reemplazos atómicos, releases constantes, un contrato de API estable (JSON-RPC) que no se tocó y un humano que audita en lugar de tipear. Lo que no se transfiere es el resultado: 292 ms corresponde a un runtime embebido y a un escenario puntual. Medí tu línea base, identificá si tu costo es de proceso o de I/O y recién ahí decidí.
Fuentes: The GitHub Blog, InfoQ, byteiota.