GitHub переписал Copilot runtime с Node.js на Rust

GitHub полностью переписал рантайм агента Copilot с TypeScript/Node.js на Rust, задействовав при этом AI-агентов для основной части работы. В результате архитектурного портирования было создано более 800 000 строк продукционного кода на Rust, распределённых по 128 pull request, которые постепенно попадали в основную ветку и поставлялись в прод без единовременного «большого переключения».

Рантайм агента Copilot — это общий движок, стоящий за GitHub Copilot CLI, GitHub Copilot app и GitHub Copilot SDK, а также за интеграциями Microsoft и GitHub: VS Code, Visual Studio, Copilot cloud agent (CCA), Copilot Code Review (CCR), Copilot Cowork, Copilot Studio, Excel, Outlook, PowerPoint, Word и другими продуктами. Все они используют один и тот же агентный движок плюс свои настройки, получая общее поведение по части интеллекта, безопасности, стабильности и производительности.

Изначально рантайм был тесно связан с интерфейсом Copilot CLI и реализован целиком на TypeScript поверх Node.js и V8. Для программного доступа был создан SDK, который фактически вызывал CLI в безголовом режиме через JSON-RPC по stdin/stdout. Это упрощало запуск, но вело к росту накладных расходов: каждый потребитель SDK запускал отдельный процесс с Node и V8, парсил JavaScript-бандл, использовал модель потоков Node и платил за межпроцессное взаимодействие и большой объём памяти (около 100 МБ и более на клиента).

Чтобы уменьшить задержки, потребление ресурсов и упростить встраивание в различные хосты, команда решила перенести рантайм на Rust. Ключевыми требованиями были низкие накладные расходы, предсказуемое использование ресурсов и возможность встроить движок через C ABI. При этом в TypeScript оставались тесты и части CLI, а сам порт выполнялся «на месте» в основной ветке с инкрементальной поставкой, а не в виде отдельной долгоживущей ветки.

Первоначальная оценка в начале мая 2026 года насчитывала около 130 000 строк TypeScript в рантайме. Однако за время проекта в работу попало примерно 430 000 строк продукционного TypeScript (включая сопутствующие подсистемы), а параллельно активно добавлялся новый код как на TypeScript, так и на Rust. За период портирования рантайм принял примерно 300 000 строк TypeScript и «сбросил» около 430 000, тогда как в Rust было добавлено около 1,2 млн строк и удалено порядка 365 000, что отражает серьёзную «перетряску» кодовой базы.

Порт реализовывался поэтапно: сначала были подготовлены инфраструктура Rust (workspace, toolchain, lint, CI, сборка) и базовый crate рантайма, затем перенесены чистые функции без I/O и общего состояния с сильным покрытием тестами. Дальше шёл перевод вспомогательных утилит, работы с файловой системой сессий, а затем — состояниоёмких подсистем, инструментов, хуков, клиентов моделей, MCP-слоя, оркестрации сессий и других компонентов. Крупные подсистемы разбивались на серии pull request по волнам (чистая логика, перенос состояния, перенос оркестрации, удаление fallback-слоёв, упрощение Rust-кода после ликвидации временных прослоек).

Для временного взаимодействия между Rust и TypeScript применялась двухслойная схема. Внутри процесса Node использовался crate napi-rs, позволяющий экспортировать Rust-функции в виде N-API-аддона с автоматической генерацией деклараций TypeScript. Rust-код мог вызывать TypeScript обратно через «threadsafe functions», выполняемые на главном потоке Node. Эта прослойка постепенно исчезала по мере переноса подсистем: в пиковый момент было 2 019 внутренних экспортов N-API и 3 356 вызовов из TypeScript, а к концу внутренний interop был полностью ликвидирован.

Внешний слой, ориентированный на SDK, сохранил JSON-RPC-контракт, однако теперь рантайм поставляется как общая библиотека runtime.node с двумя «входами». Первый — через N-API для Node (путь CLI), второй — через C ABI для любых хостов на TypeScript, Python, Go, C#, Java и Rust. Все шесть SDK используют один и тот же двунаправленный JSON-RPC-протокол, но теперь могут, по выбору, запускать рантайм как отдельный процесс или загружать как библиотеку внутрь своего процесса, вызывая функции через FFI. На C ABI стороне всего 19 экспортируемых функций (жизненный цикл сервера, регистрация и конфигурация сессий, соединения и встроенный хост), поверх которых живёт 364 маршрута диспетчеризации (340 вызовы от SDK к рантайму и 24 обратных коллбека).

Несмотря на перенос внутрь процесса, JSON-RPC был сохранён как протокол обмена, чтобы сделать новый режим хостинга подменой транспорта, а не переписыванием всех SDK. Существующие клиенты продолжают формировать и разбирать JSON-RPC-сообщения, но теперь могут отправлять байты не в pipe или сокет, а в функцию FFI. Это позволяет поддерживать один набор API и диспетчеризации для локальных и удалённых режимов, а также, при необходимости, в будущем сменить формат сериализации (например, на MessagePack) без изменения ABI.

По данным истории приватного репозитория github/copilot-agent-runtime и журналов сессий агентов, за время портирования было выполнено тысячи инструментальных вызовов и проверок. Агентные сессии генерировали десятки тысяч сообщений от пользователя (включая инструкции, автоматические merge-прогоны, внутренний обмен между дочерними агентами) и свыше миллиона сообщений от ассистента и подагентов, с активным использованием инструментов чтения файлов, поиска по коду, shell-команд и запуска проверок.

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

Порт требовал высокой эффективности по стоимости запросов к моделям. GitHub Copilot специально формирует цикл агента так, чтобы сохранять длинный стабильный префикс промпта (системные инструкции, описания инструментов, накопленный контекст). В результате доля попаданий в кеш по входящим токенам достигла 96,22% (кеш-чтение), тогда как записи в кеш составили 3,07%, а действительно новые входные токены — 0,71%. Это критично для долгих автономных сессий: без сохранения промежуточных вычислений стоимость проекта была бы на порядки выше.

GitHub Copilot также автоматически выполнял уплотнение контекста (summarization) при переполнении окна моделей. В рамках портирования зафиксировано 5 116 операций компактации, в одной из крупных сессий — 647. Анализ показал, что после таких шагов поведение агента почти не смещалось в сторону «переориентации», что говорит о достаточно аккуратных резюме и сохранении курса работы.

Отдельное внимание уделялось роли компилятора Rust в работе агентов. Логи фиксировали 8 678 ошибок компиляции (по коду rustc), и 84% приходились на четыре категории, связанные с типами, сигнатурами, именами и отсутствующими реализациями — типичные механические ошибки при переводе. Ошибки, связанные с владением, заимствованием и временем жизни, составили лишь около 1,7% диагностик. Это подтверждает, что для подобной задачи особенно полезна строгая статическая типизация вообще, а не только специфические свойства системы владения Rust.

Во время портирования активно использовалось многосессионное и многоагентное взаимодействие. Сессии могли создавать дочерние сессии (с отдельными ветками и рабочими деревьями) и подагентов, а также обмениваться сообщениями. Один из самых сложных файлов — session.ts (~30 000 строк TypeScript, центральный узел оркестрации) — переводился отдельной сессией, которая потратила почти час только на чтение и анализ, а затем запустила 15 дочерних сессий в семь волн, параллельно используя подагентов для исследовательских задач. Родительская сессия координировала их, агрегировала результаты и решала конфликты при cherry-pick.

Параллельная работа большого числа агентов создавала существенную нагрузку на машину: когда все 15 сессий одновременно начали сборку и прогоны тестов, ноутбук «упёрся» в ресурсы. Это привело к появлению централизованного «диспетчера сборок» на основе сессии-координатора, который выдавал «аренду» на запуск сборки только одному потоку за раз, а остальные сессии в ожидании делали менее ресурсозатратную работу.

GitHub Copilot app оказался ключевым инструментом управления большим числом параллельных PR. Функция agent merge автоматически отслеживала состояние CI, отзывы ревьюеров и конфликты, запускала агент для исправления ошибок сборки, обработки комментариев (с пометкой, что отвечает автоматизация) и попытки решить конфликты. Для портирующих PR агенты доводили пул-реквесты до состояния «зелёный CI и учтённые замечания», после чего финальное решение о слиянии принимал инженер.

В ходе портирования возникали регрессии, что ожидаемо при таком объёме изменений. По состоянию на 14 сентября 2026 года команда отследила и исправила десятки ошибок, большинство из которых относились к трём крупным группам: изменения фактического поведения по сравнению с TypeScript-версией, нарушения в управлении состоянием и жизненным циклом, а также неполные или потерянные при ребейзах изменения. Отдельная часть проблем была связана с различиями библиотек и API в экосистемах JavaScript и Rust, а также с особенностями взаимодействия через FFI и системными ограничениями (например, флаги запуска процессов в Windows, блокировка основного потока Node, особенности среды выполнения).

Показательно, что ни один из известных регрессий не относился к участкам с unsafe-блоками в Rust. Всего в рантайме сейчас 158 unsafe-блоков в 36 файлах, а также 26 unsafe-функций, 26 unsafe extern-блоков и девять небезопасных реализаций trait. Все они связаны с внешними границами: C ABI, системные вызовы Windows и POSIX, SQLite, динамическая загрузка библиотек, управление глобальным окружением процесса. Собственно модель, MCP-слой, логика агента и работа с промптами реализованы без unsafe.

Для оценки влияния портирования на пользователей команда сравнила метрики до и после миграции. Анализ публичных репозиториев github/copilot-cli и github/copilot-sdk показал, что уровень issue с признаками проблем качества (bug, regression, crash, hang, timeout, broken и т.п.) существенно не изменился в период до, во время и после переноса, несмотря на радикальные изменения в рантайме.

Главная цель переноса была связана с производительностью и масштабируемостью. Для оценки использовались сценарии через C# SDK до и после миграции, где сессия обращалась к локальному детерминированному серверу chat-completion, чтобы исключить влияние сети и задержек модели и измерить только клиент, запуск процесса, создание сессии, обработку событий, сохранение состояния и завершение.

В тесте «клиент, сессия, один ход, завершение» выигрыш Rust-рантайма проявился ярче всего за счёт отсутствия расходов на запуск Node, инициализацию V8 и загрузку/байткод-компиляцию JS-бандла. В стресс-тесте из 1 000 «одноходовых» жизненных циклов сессий (100 параллельных конвейеров по 10 сессий каждый) TypeScript-CLI до порта выполнял 7,55 таких циклов в секунду. Rust в отдельном процессе — 57,45, а Rust внутри процесса через FFI — 120 циклов в секунду. При этом суммарное CPU-время для той же нагрузки сократилось с 312 секунд до примерно 110 секунд, а пиковое увеличение приватной резидентной памяти при партии из десяти клиентов уменьшилось с ~1 383 МБ до ~247 МБ (Rust out-of-process) и ~126 МБ (Rust in-process), то есть более чем на порядок для варианта в процессе.

Важно, что это лишь «прямой» перенос алгоритмов из TypeScript в Rust, без масштабных переработок под особенности владения и конкурентности Rust. Существенные дальнейшие оптимизации ещё впереди, однако уже сейчас экономия ресурсов позволяет располагать на той же машине значительно больше одновременных сессий и избавляет хосты от необходимости тянуть за собой Node.js и V8 как обязательные зависимости.

Портирование потребовало значительного, но измеримого бюджета. На запросы к моделям в процессе работы агентов было потрачено около 136,3 млрд токенов, включая ~130,6 млрд кешированных входных токенов (чтение), ~4,2 млрд токенов записи в кеш, ~900 млн «свежих» входных токенов и ~600 млн выходных токенов. Денежные затраты на это составили примерно 120 000 долларов. Временные затраты ведущего инженера, курировавшего порт, оцениваются примерно в три недели полной занятости, при том что параллельно он вёл другие задачи. Фактически проект стал возможен благодаря тому, что основную механическую работу выполняли агенты, а человек выступал в роли архитектора, координатора, ревьюера и конечного гаранта качества.

Над проектом также работали другие инженеры Microsoft и GitHub: часть команды занималась временным interop-слоем napi-oop и FFI-реализациями SDK, упаковкой бинарей, оптимизацией структуры Rust-подкрейтов для ускорения сборки, кешированием артефактов в CI и многочисленными ревью. При этом основная концепция и большая часть кода были выполнены под руководством одного автора — Distinguished Engineer Microsoft Стивена Туба (Stephen Toub).

В результате рантайм Copilot полностью переведён на Rust, при этом сохраняет поведение, максимально близкое к исходному TypeScript-варианту, и поддерживается непрерывная поставка продукта. Следующие шаги включают дальнейшее улучшение системы сборки и внутреннего цикла разработчика, приведение архитектуры к более «естественной» для Rust, переработку алгоритмов под новую модель владения и конкурентности, а также поиск дополнительных выигрышев по производительности и надёжности. Ключевым эффектом уже сейчас стало то, что SDK Copilot можно загружать напрямую в процесс хоста на шести языках без Node.js и V8 и без отдельного процесса, а рантайм стал достаточно компактным и экономным по ресурсам, чтобы расширить диапазон сценариев — от облака до настольных и встраиваемых систем.

Источник: GitHub / Microsoft, статья Стивена Туба о переносе Copilot agent runtime с TypeScript на Rust

Читать новости ИИ и технологий в Telegram.


Оцените статью
Gimal-Ai