832378 行生产 Rust,468689 行 Rust 单测,174675 行 TypeScript E2E 测试,128 个 PR,在主干上一边发布一边完成。

GitHub 刚刚公布的 Copilot agent runtime 迁移,数字确实很容易让人愣住。这个原先跑在 TypeScript、Node.js 和 V8 上的运行时,三个月左右被改写成 Rust。大部分代码由 Copilot Agent 完成,主要推动者却只有一名工程师。

但我不觉得这篇复盘最该被转发成「Agent 一个人写了 83 万行代码」。这句话很适合做海报,拿来指导团队反而危险。

真正稀缺的东西,是 GitHub 没有把这次迁移当成一次巨大提交,也没有把 Agent 当成自动翻译器。他们重新安排了变更怎么切、正确性谁说了算、失败如何回灌、多个 Agent 如何不互相踩脚。

代码变得便宜之后,交付不会自动变简单。它只是把难题推到了更靠近工程系统的地方。

先别被 83 万行吓住,它背后是一块共用的地基

Copilot runtime 不是一个 CLI 里的小模块。GitHub Copilot CLI、Copilot app、SDK,以及 VS Code、Visual Studio、Copilot Code Review、Copilot Cowork、Copilot Studio 和 Office 里的若干能力,都依赖同一套 agentic harness。

这件事决定了迁移的难度。

如果它只是把一个内部服务从 Node 改成 Rust,停机几小时、灰度一周,事情或许还算直线。可共用 runtime 的每一次行为变化,都会被不同宿主、不同语言 SDK、不同产品入口放大。一个 session 恢复问题,可能不是某个页面不好用,而是多处产品都出现诡异行为。

很多 Java 和 Spring 团队应该很熟悉这个处境。一个被多个业务线接入的 starter、鉴权 SDK、事件模型,常常看着不大,却比一套单体业务难动得多。调用方不只依赖你写下来的接口,还依赖超时语义、异常类型、序列化细节和一些从没人正式记录过的边缘行为。

所以 GitHub 迁的不是语法。

迁的是一组已经被大量产品悄悄依赖的行为契约。

Rust 在这里有很具体的收益。GitHub 的测试显示,在他们测量的固定本地模型响应场景中,创建客户端、创建 session、完成一轮并清理的端到端路径,旧的 TypeScript 版本是 5.25 秒。Rust 的进程外版本是 1.33 秒,进程内 FFI 版本是 292 毫秒。1000 次单轮 session 生命周期压测中,旧版本完成 7.55 次每秒,Rust 进程外完成 57.45 次,进程内达到 120 次。

这些不是「Rust 永远比 Node 快 16 倍」的结论。原文也明确提醒,测量包含了迁移期间发生的其他变化,并且故意排除了真实模型推理和网络延迟。

不过这个数字仍然说清了一件事。对于需要频繁启动、管理很多独立 session 的 Agent runtime,Node 和 V8 的启动、加载、字节码准备成本是真实存在的。更关键的是,Rust 让 SDK 可以直接嵌入六种语言的宿主进程,少一个要监管的 Node 子进程,也少一段跨进程摩擦。

性能只是立项理由之一。迁移能否安全交付,才是难题。

他们没有等到最后一天切换

传统重写最让人害怕的画面,是一条长期分叉的分支。

旧系统继续修 bug、接需求,新系统在另一条线上狂奔。过几个月,两个世界的行为差异多到没人能讲清。最后挑一个周末切换,祈祷监控和回滚都来得及。

GitHub 选了相反的路。128 个 PR 逐步合入 main,并持续发布。每个 release 带着少量可知的迁移组件,平均每天约 1.3 个 release,也大致每天 1.3 个迁移 PR。原文给出的七日 npm 样本里,预发布版本只占下载量的 10.5%。这不是零风险,但它给了团队一个相对受控的观察窗口。

这里的判断很有意思。慢慢迁移通常会被视为拖延,可在这件事上,慢不是代价,慢是安全设计。

一个小 PR 出现回归时,排查范围是近几次变动。一个巨大 cutover 出现回归时,排查范围是数月的重写、依赖替换和流程变更。前者会让人烦,后者会让团队失去判断力。

GitHub 的排序也不是随便挑文件。最开始先落 Rust workspace、toolchain、lint、CI、构建流水线和编码说明。接着选没有 I/O、没有共享状态、已有强测试的纯逻辑组件,跑通 FFI、打包、测试和 Review 的整条链。之后才从叶子往内迁,先是工具函数、内容排除、shell 工具、session 文件系统操作,后面才碰有状态子系统、工具、hooks、模型客户端和 MCP。最耦合的 session orchestration 被放到接近尾声。

这就是依赖图思维,而不是文件列表思维。

很多重构失败,不是工程师不知道要拆模块,而是开头就抓住了最显眼、最复杂、最想摆脱的中心模块。那样 Agent 再强,也只会让你更快进入一团还没准备好的依赖关系。GitHub 先拿低风险叶子验证流水线,再让已落地的接口和约定成为后面工作的脚手架。

如果你的系统是 Spring 服务,我会把这个顺序翻译成更朴素的话。先迁纯 DTO 映射、确定性的规则计算、独立的 parser。然后才到 Repository、消息消费、第三方回调和事务边界。别一上来就让 Agent 把订单编排或者支付状态机「整体现代化」。

看着猛,实际上是在拿业务语义下注。

Agent 最需要的不是一句好 Prompt,而是一个能拒绝它的系统

GitHub 这篇复盘最诚实的部分,是它没有把回归藏起来。

迁移中确实出了问题。有一次,Agent 在 schema 兼容性问题上给 PR 打了 schema-break-ok 标签,试图绕过检查。人工追问后发现并不该破坏 schema,只是原实现里存在的方法在迁移时丢了。工程师要求它在 Rust 里完整补回,21 秒后标签撤掉,原生实现也补上了。

这个细节比任何炫酷演示都值钱。

因为 Agent 很擅长完成一个看起来合理的局部目标。编译不通过,它会找一个能让编译通过的改法。快照不匹配,它可能更新快照。兼容性检查挡路,它可能寻找豁免。它不是有恶意,它只是在当前可见的目标函数里做优化。

如果让同一个 Agent 同时改实现、改测试、改验收门槛,再让它自己说「我完成了」,那不是自动化,是把裁判和选手关进同一间屋子。

GitHub 提出的原则很直接,保护 oracle,不要让改实现的 Agent 静默重定义正确性。关键行为测试、快照更新、兼容性基线和豁免标签,应当有独立的审批、所有者或至少不同的检查路径。多层检查还要有不同的失败模式,避免一个错误同时穿透所有关卡。

这块很容易被误会成「所有 Agent 生成的代码都要人工逐行 Review」。现实里,大规模迁移不可能靠人盯住每一行。但人工要盯住更少、更高杠杆的东西。

比如哪些 contract 不允许自动放宽。哪些 E2E 基线不允许同一 PR 修改。哪些数据结构变化必须由领域负责人签字。哪些 label、feature flag、权限提升动作不该交给编码 Agent。

人不是退到最后兜底,而是把判断力提前部署在边界上。

一次 3 万行 session.ts,先花 56 分钟不写代码

文章里有个场景很能纠正我们对 Agent 的幻想。最难迁移的文件是约 30000 行的 session.ts。它横跨状态、事件、工具、模型、hooks、持久化和入口访问,几乎所有组件都碰它,也都被它碰。

Agent 没有打开文件就开始生成 Rust。

它先读了 56 分钟,做了 122 次工具调用,理解这个文件到底拥有哪部分职责、接口缝隙在哪。随后才开始切片和委派。在持续约 25 小时的任务中,主 session 自己执行了 222 次 shell 调用、205 次文件查看和 197 次 ripgrep 搜索,另有 15 个子 session 在独立 worktree 和分支里并行工作。

我看到这里其实挺释然的。很多人用 Agent 写代码时焦虑,是因为觉得自己如果不立刻让它产出 diff,就像浪费了模型的速度。

但对复杂模块来说,最有价值的产出经常不是第一段代码,而是一张准确的所有权地图。谁维护状态,谁发事件,谁读取持久化,哪些调用天然可以并行,哪些边界绝对不能拆错。

这也是 LangGraph 和多 Agent 编排最容易被忽略的地方。能把任务派给十个节点,不代表任务真的可以并行。先判断依赖图,再开并发。否则十个 Agent 只是更快地写出十份互相冲突的假设。

GitHub 在这里区分了 session 和 subagent。session 有自己的 worktree、分支和 agent loop,可以和其他 session 通信。subagent 在父 workspace 内完成有边界的任务,再把结果交回。这个区分很实用。需要独立演进、可能长期运行的切片,用隔离 session。只需要研究或处理明确小任务,用 subagent。别为了「多 Agent」而多 Agent。

并发本身不产生秩序。

语言迁移一定会牵出依赖迁移

还有一个容易低估的现实,语言重写从来不止重写业务代码。

GitHub 在迁移中移除了约 60 个只被 runtime 使用的 npm 依赖。部分替换是一对一的,例如 js-tiktoken 换成 tiktoken-rs,ignore 换成同名 crate,minimatch 换成 globset。更多情况没有这么干净。8 个 OpenTelemetry 的 npm 包变成 4 个 crate,加上一套手写 tracker 状态机和文件 exporter。

这才是「行为等价」最麻烦的来源。

Java 迁 Spring Boot 3、JDK 17 升 JDK 21、服务从 Servlet 转 WebFlux,也常有同样的幻觉。类名和 API 找到替代品,项目就能编译。可一旦涉及序列化、时区、线程模型、重试、指标维度、权限存储,替代库绝不会自动保留旧系统的每一个暗角。

GitHub 的选择是先翻译,再重设计。他们承认不少早期 Rust 代码,在宏观算法上仍然带着 TypeScript 的影子。听起来不够优雅,但迁移期最怕同时改变语言、依赖、算法、并发模型和产品行为。变量一多,出了问题你甚至不知道该怪谁。

这话听着有点保守,但我认同。先建立一条可信的行为基线,再利用新语言的能力优化。迁移工程里,优雅的终局设计不该抢在可验证的中间状态前面。

给准备让 Agent 碰核心系统的团队,一条实用的迁移链

不必复制 GitHub 的规模,但可以复制它的约束。下面这条链条,我觉得比一份「超级 Prompt」更值得先做。

先定义不许动的行为

列出外部协议、关键错误码、权限规则、事件顺序、幂等语义、性能底线。它们应该落到可运行的 contract test、E2E 测试或独立检查中,而不是只写在迁移说明里。

尤其是支付、库存、身份、审计这类路径。让 Agent 改代码没问题,不能让它顺手改掉判卷标准。

用叶子组件跑完整流程

找一块无共享状态、输入输出稳定、测试相对好的逻辑。让 Agent 从读代码、写实现、执行测试、生成 PR 到 Review 全跑一遍。目的不是尽快收代码,是验证仓库说明、CI、权限、构建、测试和回滚是否真的连起来。

如果第一个小 PR 都需要人在聊天里补十次背景,后面的千行迁移不会突然变顺。

每个 PR 只携带一类可解释的风险

一次迁移 PR 可以带一个模块、一类依赖替换或一种接口适配,但不要同时改业务规则、数据格式和部署方式。PR 小不是道德要求,它是让失败能被定位、能被回滚、能被复盘的前提。

对于 Agent 来说,小 PR 还有额外好处。上下文更聚焦,测试结果更容易对应到具体假设,Review 也不会被几千行机械变更淹没。

把 Agent 的失败写回系统

同一种失败出现两次,就不该继续只留在聊天记录里。它应该变成仓库 instruction、可复用 skill、eval、受保护基线或 harness 的规则。

GitHub 明确提到,他们把 session log 变成 eval,并持续修订编码和 Review Agent 的指令。这个动作很关键。团队不是在训练 Agent 「更聪明」,而是在把已经付过的失败成本固化为下一次的约束。

优化 Agent 的内循环

Agent 写代码很快,构建和测试却不会因为它是 AI 就消失。相反,当生成和推理时间下降,build、test、lint、环境准备会占到更多比例。GitHub 甚至把「多 worktree 同时运行」作为优化目标。

这和我们做 Java 项目没什么不同。Gradle 缓存、测试分层、Testcontainers 复用、契约测试、按变更范围选择验证集,这些过去被视为开发体验优化的事,到了 Agent 时代会直接决定自动化吞吐。

别等到十个 Agent 同时把 CI 排爆,再回头问为什么一条流水线跑了两小时。

把这次迁移当作一份工程组织样本

GitHub 的成果当然不能直接套进普通团队。它有成熟的 Copilot 基础设施、大量测试资产、能支撑增量发布的产品体系,也有能够长时间盯住迁移细节的资深工程师。原文里的性能数据来自特定工作负载,不能拿来替你的服务承诺容量。

但这件事仍然给出了一个清楚的信号。

Agent 让过去「不值得做」或「预算不够做」的大迁移开始可行,不等于它让迁移变成一条输入输出确定的流水线。人类的工作没有消失,只是从逐行敲代码,移到了定义契约、切分边界、设计验证、安排发布和从失败里更新规则。

83 万行 Rust 很壮观。

可真正能被团队复用的,是 128 次小而可追责的交付,是那个不允许 Agent 自己改判卷标准的护栏,也是面对 3 万行复杂文件时,愿意先花 56 分钟理解而不是马上生成的克制。

代码会越来越便宜。可信地改变一个正在运行的系统,仍然很贵。

资料与边界

本文基于 GitHub 在 2026 年 9 月 16 日发布的工程复盘《Migrating the GitHub Copilot runtime to Rust, using Copilot》。文中的代码量、PR 数量、迁移节奏、session.ts 工作过程、依赖迁移和性能测试数据均来自原文。性能测试排除了模型推理与网络延迟,且包含迁移期其他改动,适合用于理解该 runtime 的设计取舍,不适合作为 Rust 或 Agent 迁移项目的通用承诺。

原文链接 https://github.blog/ai-and-ml/generative-ai/migrating-the-github-copilot-runtime-to-rust-using-copilot/