Start With a Migration Claim
According to GitHub's engineering account, agents wrote most of a Copilot agent-runtime port that ended with more than 800,000 lines of production Rust across 128 pull requests. Those numbers describe effort and output. They do not prove that a rewrite preserved behavior, stayed operable during the transition, or improved the workloads your users care about.
Before asking an agent to translate anything, write a falsifiable claim: moving component X behind boundary Y will preserve contract Z while improving metric M by at least T under workload W. Include a maximum acceptable regression rate and a deadline for deciding whether to stop. “Move our runtime to Rust” names an implementation; it does not define success.
GitHub chose Rust for requirements that included a C ABI, in-process embedding, predictable resource use, and interoperability with six SDK languages. Its post explicitly says this is not a claim that every large TypeScript application should become Rust. Reproduce the decision process, not the target language.
Choose One Leaf-Slice Pilot
Pick one production component with a narrow input/output contract, no ownership of shared mutable state, and existing end-to-end coverage. A parser, serializer, policy evaluator, or deterministic content filter is a better first slice than the session coordinator. Keep the pilot small enough that one reviewer can explain both implementations and every boundary crossing.
GitHub started with workspace, toolchain, lint, CI, build, code-generation, and interop foundations. It then took side-effect-free helpers with strong tests through the complete shipping path before moving from leaf logic toward state and orchestration. That sequence made the pilot prove the machinery as well as the translated function.
pilot:
component: policy_evaluator
old_entrypoint: evaluatePolicy(input)
new_entrypoint: rust_evaluate_policy(input)
oracle: test/e2e/policy-contract.json
traffic: [internal, prerelease, stable]
rollback: route_to_typescript
owner: runtime-platform
decision_date: 2026-10-15
Freeze the fixture before the port begins. It should cover normal output, malformed input, cancellation, timeout, repeated calls, and any ordering or serialization promise callers observe. For guidance on keeping expected answers independent from the implementation under test, use the answer-key isolation fixture.
Make the Boundary Measurable
Define equivalence at the boundary rather than by comparing source files. For each fixture, record return value or emitted events, error category, ordering, persisted state, cancellation behavior, resource cleanup, and externally visible logs. Normalize only fields that are genuinely nondeterministic, such as generated IDs. A test that silently normalizes missing events can certify a broken port.
| Evidence | Pass condition | Stop condition |
|---|---|---|
| Behavior contract | All frozen fixtures produce equivalent observable results | Test or expected output changed to accommodate the port |
| Compatibility seam | Every old/new call is counted and attributed | Unowned shim or growing call surface |
| Release safety | Main remains shippable and the slice can roll back independently | Only a whole-program cutover can restore service |
| Runtime goal | Threshold met on the declared workload | Gain appears only in a microbenchmark users never exercise |
Do not rewrite the oracle along with the implementation. GitHub's migration instructions treated deleted or changed end-to-end tests as a warning sign, and its retrospective says missing-feature regressions were usually associated with insufficient end-to-end coverage. Unit tests in the new language can explain failures, but they cannot independently prove compatibility if they were generated from the same interpretation as the port.
Give Every Shim an Exit Condition
An incremental migration needs temporary interop. That is useful debt only when it is visible. Inventory every exported function, callback, call site, data conversion, thread hop, and error translation at the old/new boundary. Assign an owner and the component whose migration deletes it. Fail the build if an unregistered crossing appears.
GitHub reports that its temporary seam peaked on August 3 at 2,019 internal N-API exports and 3,356 TypeScript call sites, then declined to zero when the runtime port finished. Copy the shape of that evidence: a seam may grow while callers move, but the plan must say when it turns downward and what “gone” means. A permanent SDK boundary is different from a temporary internal bridge; track them separately.
Keep each replacement atomic: add the new implementation, route the existing caller through the shim, pass the unchanged contract suite, and delete the old component in the same pull request. GitHub used this pattern to keep main shippable. It also makes concurrent edits collide visibly: a branch modifying code that another branch deletes creates a conflict instead of quietly disappearing.
Release in Diagnostic Steps
A green test suite is the admission ticket, not the finish line. Expose the slice first to internal traffic, then a prerelease or opt-in cohort, then a bounded stable percentage. At each step, retain the same component-level rollback. Watch contract errors, crashes, timeouts, memory, CPU, latency distributions, and support reports by version.
GitHub says its roughly 14.5-week window included 135 releases: 100 prereleases and 35 stable releases. A trailing seven-day sample put prereleases at 10.5% of downloads. Those figures are not a cadence to imitate. They show that the team limited initial exposure and kept releases small enough to correlate reported problems with recent component changes.
For an explicit release-and-rollback structure, adapt the canary logic in the capability-gate guide: fixed workload, recorded baseline, approval thresholds, owner, and a tested route back. Replace model-output fields with your component contract and runtime metrics.
Score the Pilot, Not the Output
GitHub found and fixed dozens of known correctness and performance regressions by September 14. That is the relevant warning beside the large line count: agents can make translation volume affordable, while correctness still depends on architecture knowledge, stable oracles, review, staged releases, and diagnosis.
Measure the exact workload named in your migration claim. GitHub's own benchmark deliberately removed model and network latency with a deterministic local completion server. In one scenario, client creation, session creation, and one turn changed from 5.25 seconds before the port to 1.33 seconds with the Rust runtime out of process and 292 milliseconds in process. GitHub cautions that this was an end-to-end comparison and other changes landed during the same period, so it does not isolate the language change.
continue_when =
behavior_failures == 0 &&
rollback_seconds <= 300 &&
p95_latency_improvement >= 0.20 &&
peak_memory_regression <= 0.05 &&
unowned_shims == 0
Set your own thresholds before results arrive. Continue only if the frozen behavior suite passes, rollback works, the runtime target clears its threshold, and the next slice has a named owner. Pause when the shim surface grows without a retirement path, tests must be weakened, or gains vanish in the representative workload. Stop when the original constraint no longer justifies the migration cost.
The reusable lesson is not that one engineer and agents can produce a spectacular number of lines. It is that a large rewrite can be decomposed into independently testable, releasable, reversible substitutions. If your pilot cannot preserve that chain of proof, the responsible result is not a bigger prompt. It is a smaller boundary—or no rewrite.