Технологии

GitHub переписала ядро Copilot на Rust: 832 378 строк production-кода и 128 пул-реквестов силами агентов

Стивен Тоуб из GitHub рассказал, как agent runtime — движок GitHub Copilot CLI, приложения Copilot и Copilot SDK — полностью перевели с TypeScript на Rust: 128 пул-реквестов в main, 135 релизов без единого «большого переключения», 96,22% попаданий в кэш промптов и счёт за токены около $120 000.

GitHub переписала ядро Copilot на Rust: 832 378 строк production-кода и 128 пул-реквестов силами агентов

16 сентября 2026 года инженер GitHub Стивен Тоуб опубликовал разбор того, как agent runtime — движок, на котором работают GitHub Copilot CLI, приложение GitHub Copilot и GitHub Copilot SDK, — полностью переписали с TypeScript на Rust.

К 21 августа runtime на 100% состоял из production-Rust: 832 378 строк production-кода и 468 689 строк модульных тестов на Rust, плюс 174 675 строк E2E-тестов на TypeScript; отдельный репозиторий GitHub Copilot SDK добавил ещё около 130 000 строк E2E-кода на шести языках.

Основной объём кода написали агенты: работа уложилась в 128 пул-реквестов, которые вливались в main и уходили к пользователям постепенно, а не одним переключением в конце.

Автор оценивает масштаб так: до появления агентов такой проект потребовал бы целой команды и года-двух, а здесь его в основном выполнил один разработчик за несколько месяцев — пока остальная команда продолжала расширять возможности runtime.

Почему уходили с Node.js и V8

К 2026 году runtime перестал быть движком только для CLI: он лежит в основе растущего набора решений Microsoft, GitHub и партнёров — от VS Code и Visual Studio до Copilot Code Review, Copilot Cowork и Copilot Studio.

Проблема была в том, как к нему получали доступ. SDK пристроили над CLI: её запускали в headless-режиме, а вызовы функций гоняли через JSON-RPC из отдельного процесса.

Каждый потребитель SDK в любом языке тянул за собой Node.js и V8 — минимум порядка 100 МБ рабочего набора, — платил переход через границу процесса на каждое событие, каждое сообщение и каждое чтение или запись в файловой системе сессии, а развёртывание требовало минимум двух процессов для сопровождения и отладки.

Целевой runtime описывался иначе: без TUI, с минимальными зависимостями и накладными расходами, со встраиванием внутрь процесса вместо принудительно внешнего, с хорошим FFI для шести языковых версий SDK.

Встроенный порт вместо одного переключения

Выбор был между «большим взрывом» — собрать альтернативу целиком и заменить одним движением — и встроенным портом компонент за компонентом. GitHub выбрал второй путь, причём в варианте атомарной замены: каждый пул-реквест заменяет реализацию на TypeScript тонкой прослойкой, которая вызывает Rust, и удаляет старый код одним изменением.

Так main остаётся в состоянии, когда его всегда можно выпустить, а diff становится меньше и понятнее для ревью — и человеческого, и агентского.

Инкрементальность видна по релизам: за примерно четырнадцать с половиной недель main выпустил 135 релизов, включая 100 pre-release и 35 стабильных версий, — в среднем около 1,3 релиза в день.

В семидневной выборке npm на pre-release версии пришлось лишь 10,5% загрузок: первый контакт с новым кодом у пользователей был ограниченным, и проблемы успевали поймать до стабильного выпуска.

Масштаб в цифрах

Первый план в начале мая 2026 года оценивал runtime примерно в 130 000 строк TypeScript.

Оценка оказалась обманчивой: параллельно часть кода переезжала из слоя TUI в runtime, а десятки разработчиков с агентами вливали сотни пул-реквестов в неделю, постоянно добавляя новый TypeScript.

С учётом всего через порт прошло около 430 000 строк production-TypeScript — более чем втрое больше первоначальной оценки.

Часть усилий ушла на временный шов: переведённые на Rust функции должны были оставаться вызываемыми из TypeScript, а Rust — уметь вызывать колбэки обратно. Пик пришёлся на 3 августа — 2 019 внутренних экспортов N-API и 3 356 мест вызова из TypeScript, — а к завершению не осталось ни одного.

Постоянная граница: 19 функций и шесть языков

Постоянная часть границы оказалась компактной: у двери C ABI всего 19 экспортируемых функций, за которыми стоит общий контракт из 364 маршрутов — 340 вызываются потребителями SDK, ещё 24 работают в обратную сторону, как колбэки из runtime в SDK.

Смысл конструкции в том, что SDK один раз привязывается к 19 точкам входа и дальше добирается до растущего API динамически, а добавление или удаление метода не трогает ABI.

Практический эффект — возможность загружать SDK прямо в процесс приложения: без Node.js и V8 в цепочке зависимостей и без второго процесса, который надо сопровождать.

Сам автор называет сохранение JSON-RPC внутри процесса осознанным компромиссом: переход между процессами убран, но накладные расходы на сериализацию остаются, и решение можно пересмотреть позже, не меняя объявленные экспорты.

Что показали журналы сессий

Значительная часть выводов построена на структурированных журналах сессий, а не на впечатлениях: в них 12 760 995 событий, 31 247 сообщений роли user, 1 385 214 сообщений ассистента, 68 типов событий и 67 различных имён инструментов.

Из 31 247 «пользовательских» сообщений человек написал примерно 2 600 — около одного из двенадцати; остальное — инструкции навыков, автоматические тики слияния, межсессионные сообщения и трафик дочерних агентов.

Из 1 130 921 вызова инструментов 61% пришёлся на субагентов, а не на основной поток сессии.

Профиль работы оказался не про генерацию кода: чтение файлов и поиск по репозиторию заняли примерно в десять раз больше усилий, чем правки.

Экономику держал кэш промптов: 96,22% попаданий, 3,07% записей и 0,71% свежего ввода. Контекст автоматически сжимался 5 116 раз — по словам автора, без этого сотни часов автономной работы были бы невозможны.

Компилятор Rust ловит не всё

Тезис о том, что Rust идеален для кода от агентов из-за строгого компилятора, автор проверил на данных: в результатах команд валидации зафиксировано 8 678 вхождений кодов ошибок rustc, и четыре крупнейших семейства диагностик покрывают 84%.

Разбивка выглядит так: 37% — разрешение имён и импортов (в первую очередь E0425, «cannot find value in this scope»), 22% — отсутствующие методы и поля, 14% — несовпадения типов, 11% — невыполненные трейт-баунды.

Все четыре категории — обычная статическая типизация, которую точно так же поймали бы компиляторы C#, Java или Go; ошибки владения, заимствования и времени жизни дали лишь 1,7% диагностик.

Из 4 478 запусков cargo check, по которым удалось считать результат, 87,1% закончились чисто — что соответствует правкам малыми шагами с постоянной перекомпиляцией.

Отдельный контраргумент к мифу «если компилируется, значит корректно»: каждый известный регресс из корпуса был влит в main, то есть успешно скомпилировался.

Что купили и во сколько обошлось

Замеры делали через C# SDK: сборку до порта сравнивали с результатом от 21 августа, где Rust работал и вне процесса, и внутри него через FFI.

Каждый замер шёл к детерминированному локальному серверу ответов, поэтому вывод модели и сетевые задержки из цифр исключены: измеряли запуск клиента, создание сессии, обработку событий, персистентность и завершение.

В тесте на 1 000 жизненных циклов «одна сессия — один ход» прежний TypeScript-CLI давал 7,55 цикла в секунду, Rust вне процесса — 57,45, Rust внутри процесса — 120,0.

По CPU прогон 100×10 занял у прежнего дерева процессов 312 секунд агрегированного времени, у конфигураций на Rust — около 110 секунд; пик добавленной резидентной памяти на десяти клиентах упал с 1 383 МБ до 247 МБ вне процесса и 126 МБ внутри него.

Полный цикл «клиент — сессия — один ход» в процессе укладывается примерно в 55 миллисекунд.

Автор оговаривает, что это не универсальное «в 15,9 раза быстрее», а результат под конкретную нагрузку множества независимых сессий на одном хосте, и что часть изменений за тот же период в замер не вошла.

Цена названа прямо: около 136,3 млрд токенов — примерно 130,6 млрд чтений из кэша, 4,2 млрд записей в кэш, 900 млн свежих входных и 600 млн выходных, — то есть счёт около $120 000, плюс оценка времени автора примерно в три недели, выведенная из доли его пул-реквестов во всех репозиториях.

Сам автор называет эту арифметику расчётом на пальцах, а миграцию — работой команды, а не одного человека.

Что осталось и какие выводы

Порт сознательно оставался переводом: поведение старались сохранить максимально близким, алгоритмы не переписывали, поэтому на макроуровне многое до сих пор выглядит как алгоритмы на TypeScript в синтаксисе Rust.

Правила, которые автор вынес из проекта: цель нужно формулировать полностью и сразу; сквозные тесты критичны, и их нельзя переписывать по ходу порта, иначе теряется оракул; оракул нужно защищать от агента, который меняет реализацию; сначала переводить и только потом редизайнить; повторяющиеся сбои превращать в постоянные инструкции, навыки или eval.

Что именно доказывает публикация: это отчёт самой GitHub о собственном внутреннем проекте, построенный на истории приватного репозитория copilot-agent-runtime и журналах сессий самого автора.

Независимой проверки приведённых чисел нет, замеры производительности выполнены автором, и он сам оговаривает, что сравнение идёт между поставленными системами, а не изолирует эффект смены языка.

Открытым для читателя остаётся существенное: методика подсчёта токенов и стоимости известна только через личный биллинг автора, а оценка трудозатрат выведена из доли пул-реквестов, то есть является приближением.

Первоисточники

  1. GitHub Blog: «Migrating the GitHub Copilot runtime to Rust, using Copilot», Stephen Toub (16 сентября 2026)