- Anthropicの開発者たちは、Claude Fable 5、Claude Opus 4.8と動的ワークフローを使い、この1か月で数万〜数十万行規模のパッケージ10件を移行し、個々のコードを直す代わりにコードを生成する反復プロセスを改善した
- BunのZig→Rust移行では、2週間足らずで100万行を生成し、マージ前に既存テストを100%通過した。Python→TypeScriptプロジェクトでは、週末の間に16万5,000行を移し、数百のエージェント、8段階のゲート、3回の敵対的レビューを活用した
- 大規模移行は作業を並列化しやすく、既存コードが仕様と正解の役割を果たし、コンパイル・テストの失敗が次の作業キューを自動生成するため、客観的な検証ループを構成しやすい
- 判定基準の準備から、ルールブック・依存関係マップ・差分リストの作成、ルールのストレステスト、全体翻訳、コンパイル、実行、動作比較まで段階的に進め、反復するエラーはファイルごとに直さず上位ルールを修正して再生成する
- コストは依然として数万〜数十万ドル以上だが、失敗したブランチを破棄してやり直せる。Bun移行ではAPI価格ベースで約16万5,000ドルを使った後、メモリ使用量の削減、バイナリサイズ19%縮小、実ワークロード性能2〜5%向上を達成した
コードではなく生成ループを改善する方法
- AIコード移行とは、エージェントが本番コードベースを新しい言語やフレームワークへ移す方式である
- エンジニアはファイルを直接翻訳する代わりに、移行ルールと検証ループを書く
- エージェントは新しいコードの動作が元のコードと一致するまで、翻訳・コンパイル・テストを繰り返す
- 以前なら何年もかかっていたプロジェクトを数週間単位に短縮できる
- Anthropicでは、Claude Fable 5、Claude Opus 4.8と動的ワークフローを使い、1か月で数万〜数十万行規模のコードパッケージ10件を移行した
- 中核となる運用原則は、生成されたコードを直接手直しするのではなく、そのコードを作ったループを修正することにある
実際の移行事例
-
BunのZig→Rust移行
- Jarred SumnerはClaude CodeでBunをZigからRustへ移行した
- 2週間足らずで100万行のコードを生成した
- マージ前のCIでBunの既存テストスイートを100%通過した
- マージ後に見つかった回帰19件はすべて修正された
- Rustポートは6月にClaude Codeへ導入された
- Bunは月間ダウンロード数が1,000万回を超え、Claude Code内部でも広く使われている
-
Python→TypeScript移行
- Mike Kriegerは週末の間にPythonコードベースを16万5,000行のTypeScriptへ移行した
- 数百のエージェント、8段階のゲート、3回の敵対的レビューを使用した
- すべてのコマンド出力をPythonの元実装と比較する最終的な同等性チェックを実施した
- 移行結果全体を破棄してルールとワークフローを修正する工程を繰り返し、3回目の実行結果を採用した
言語移行を再検討する条件
- 初回開発後に技術環境が変わり、既存のトレードオフが制約になっていたり、より良いアプローチが現れたり、元のエコシステムが縮小していたりするなら、移行を検討できる
- ZigはC並みの性能と単純さを提供し、1人でBunを開発していた初期環境に適していたが、その単純さには既知のトレードオフがあった
- 以前は言語移行のためにロードマップを止め、複数四半期分のリソースを投じる必要があった
- 2つのコードベースを数四半期から数年にわたって並行維持しなければならないことがあった
- 最終的な動作一致率が90%にとどまると、開始前より大きな保守問題を抱える可能性があった
- いまでは失敗したブランチを削除して再実行する選択肢がある
- 100万行の移行はもはや4年間のエンジニアリング費用300万〜400万ドルを要するわけではないが、それでもなお数万〜数十万ドル以上かかりうる
- Bun移行では、キャッシュされていない入力トークン59億個と出力トークン6億9,000万個を消費した
- API価格ベースのコストは約16万5,000ドルである
- Mikeのポートではコア部分で2,700万トークンを使った
- 移行の事業的な根拠は、もはや存続を左右するほど重大である必要はなく、1年にわたるメモリバグ修正や慢性的なボトルネック1つでも正当化できる
-
Pythonビルドのボトルネック解消
- Mikeの社内ツールはユーザーに単一バイナリとして提供されていたが、Pythonツールチェーンでプラットフォーム別バイナリを作るのに約8分かかっていた
- ビルドマトリクス全体では、リリースごとに約30分待つ必要があった
- TypeScript移行後はコンパイルが約2秒に短縮され、バイナリ起動は6倍高速化し、別個のデプロイパイプラインも廃止できた
AIエージェントが移行に向いている理由
- 並列作業が可能である
- ファイルやクレートなど数千の独立単位に分け、複数のエージェントが同時に処理できる
- 既存コードが明確で包括的な仕様の役割を果たす
- 翻訳エージェント向けの指針を作る際の重要な参考資料としても使える
- テストスイートが組み込みの判定者として機能する
- 検証が客観的なら、人が品質を継続的に仲裁しなくても、モデルは正解を基準に何日も反復作業できる
- コンパイルやテストの失敗が自動で次の作業項目になるため、別途作業キューを書く必要が減る
- 一貫性と例外処理をループに組み込める
- レビュアーは各問題を違反したルールに結びつける
- 例外を解決した方法は、その後すべてのエージェントが従うルールに変わる
- 目立たない動作不一致ではなく、ルール違反が明示的な作業項目になる
- FableとOpus 4.8は、下位エージェントの並列作業を委任・指揮・検証し、目標に到達する複数の経路を見つけるために使われる
- 複数のモデル階層を組み合わせるアドバイザリーパターンでトークン使用量を最適化する
前提条件: 元コードとポートを同等に判定する
- 移行開始前に、元コードと対象コードを同じ基準で評価できる強力な判定者が必要である
- 判定者がなければ成功基準も終了条件もない
- 元言語の内部関数に依存するテストは、対象コードではそのまま実行できないことがある
- 既存テストを、外部呼び出しとして表現できるテストと、ポートされない内部実装依存テストに分類する
- 外部動作テストは、元コードとポートの両方で実行できるアサーションへ書き直す
- 敵対的エージェントが、書き換えの過程でアサーションが弱くなっていないか検証する
- 判定者を元コードに対して実行して成功するか確認したうえで、意図的に壊したコードでは失敗するかも検証する
- Jarredは、第三の言語であるTypeScriptで書かれた大規模テストスイートを持っていた
- Mikeは、実際の利用シナリオ7件で同等性ハーネスを作り、あらゆる動作変化を修正すべきバグとして扱った
1段階: ルールブック・依存関係マップ・差分リスト
- 基盤となる成果物は単なる翻訳結果ではなく、どこをリファクタリングするかの一覧、翻訳方式のルールブック、作業順序を決める依存関係マップである
- 作成順序が重要である
- まずルールブックのデフォルトを定め、そのデフォルトで処理できない項目を差分リストとして定義する必要がある
- ルールブックと差分リストは共同監査で一緒に検証する
-
ルールブック
- ルールブックの形は、新しいコードが既存構造を維持するか、完全に再設計するかによって変わる
- Jarredのように構造を維持する場合は、言語間の型やイディオムの対応表が中心となり、翻訳しにくい構成要素は差分リストを参照する
- Mikeのように再設計する場合は、ルールブックが設計書の役割を果たす
- JarredはClaudeと対話しながら曖昧な領域ごとに方針を作り、想定される8つのエラーカテゴリをそれぞれレビューするよう、8つの下位エージェントを構成した
-
依存関係マップ
- 並列移行では、どのファイルを先に移し、どのファイルを同じバッチに入れるか決めるために、ファイル依存関係を把握する必要がある
- 明示的なマニフェストがないレガシーコードや、C/C++、Pythonなどのコードベースでは、依存関係を自力で発見してマップ化する必要がある
- Claude Codeエージェントは、決定論的スクリプトを書いて実行し、レビューし、修正するループでマップを生成できる
- 一般化された例は依存関係マッププロンプトで確認できる
-
言語差分リストと懐疑的レビュアー
- 差分リストは、既存コードには暗黙に含まれているが、対象言語では明示する必要がある知識を記録する
- Zig→Rustでは、メモリ管理方式が主な差分だった
- Zigでは、呼び出し側がバッファを解放しなければならない事実がコメントにしか書かれていないことがあり、解放忘れでもコンパイルは通り、リークは実行時に見つかる
- Rustでは所有権が呼び出し側に移り、メモリは自動解放され、move後の使用や二重解放はコンパイルできない
- Python→TypeScriptでは、インターフェースと契約が主な差分だった
- Pythonは、受け取るオブジェクトや戻り値の形を宣言しなくてもよい
- TypeScriptでは、メソッド、引数、戻り値の形に関する契約を書かないとコンパイルできない
- Jarredは翻訳前に差分を一覧化し、Mikeは先に翻訳したうえで監査工程で一覧を作ったため、プロジェクトに応じて両方のアプローチを使える
- 一般化された例は差分リスト生成プロンプトで確認できる
2段階: ルールのストレステスト
- 全体移行の前に、小規模な試験移行でルールブックを揺さぶり、問題を見つける
- Jarredは3種類のエージェント作業を比較した
- 1つ目のエージェントは、ルールブックに従って3ファイルを翻訳する
- 2つ目のエージェントは、熟練したRustエンジニアのように同規模を翻訳する
- 3つ目のエージェントは、両者の差分を基に新しい翻訳ルールを書く
- この過程で、1,448ファイル全体へ広がる前に重大な問題2件を発見した
- この方式は、同一ファイルの2つの翻訳結果を行単位で比較できる構造維持型移行でのみ機能する
- Mikeのように再設計する場合は、敵対的レビュアーが設計書を攻撃し、破棄可能なエンドツーエンド実行で検証する必要がある
- 試験で翻訳したファイルはすべて捨てるべきであり、目的は段階的なコード進捗ではなくルール改善である
- 一般化された作業例はストレステストプロンプトで確認できる
3段階: 全コード翻訳
- 以後の段階ではすべて実装→レビュー→修正のマルチエージェントループを使う
- 大量実装は小さなモデルに任せ、レビューは大きなモデルが担当できる
- Mikeは中核移行の並列化にClaude Sonnetの下位エージェント12個を使った
- 作業キューは機械的に管理する
- バッチスクリプトが、翻訳済みファイルのディスク上の存在有無で完了状態を判断する
- 残りファイルを実装エージェント向けのバッチに分割する
- 実行のたびにディスク状態からキューを再構成するため、基本的に中断後の再開が可能である
- エージェントが慎重すぎて少量しか処理しないなら、次段階でコンパイラがエラーを拾うことを前提に、より直接的に指示できる
- 確信を持って処理できない項目は
// TODO(port): <reason>と記して4段階で解決する - 以後の作業一覧は、コンパイルエラー、スモークテストのクラッシュ、テスト失敗から自動生成される
-
敵対的レビューとルール更新
- 独立したコンテキストを持つ2つの敵対的レビュアーが実装結果を評価し、意見が割れた場合は3つ目のエージェントが裁定する
- 同じエラーが複数ファイルで繰り返される場合、ファイル単位では修正しない
- ルールブックに1文追加し、影響を受けたバッチを再生成する
- 翻訳段階でもルールブックは継続的に拡張され、ルールと食い違うコードを手作業でパッチしない
-
コンパイラを置く位置
- コンパイル時間が短ければ翻訳ループ内に含められる
- MikeはTypeScriptコンパイルが単位ごとに数秒で終わるため、すべてのループで実行した
- コンパイルが長い場合は次の段階へ回す
- Jarredは
cargo実行に数分かかるため、翻訳ループでのコンパイラ使用を禁止した - この段階からプロンプトは短くなり、例は翻訳開始プロンプトで確認できる
4〜6段階: コンパイル・実行・動作一致
- 3つの段階は同じループ構造を共有し、後ろへ進むほど必要な人間の判断は減る
- 言語やプロジェクト規模によっては、コンパイル段階は全体翻訳段階に吸収されうる
-
4段階: コンパイル
- Jarredはオーケストレータースクリプトがワークスペース全体でコンパイラを1回実行するよう構成した
- 修正エージェント群がエラー一覧を並列処理し、敵対的レビューを経て再ビルドする工程を繰り返した
- エラー一覧レビューは、個別エラーではなくシステム的問題を見つけるために使う
- Zigの遅延コンパイルが許していた循環インポートを修正した後、Rustモジュールエラーが数千件発生した
- どの依存関係を削除・移動するか、あるいは境界を再構成するかを分類するロジックをループに追加した
-
5段階: 実行とスモークテスト
- スモークテストで発生したクラッシュは、コンパイルエラー一覧と同じく機械的な正解として機能する
- 問題を個別に処理せず、根本原因ごとのカテゴリに束ねたうえで、敵対的下位エージェントがレビューする
-
6段階: 元コードとの動作比較
- 翻訳・コンパイル・スモークテストを終えたコードを分割し、事前段階で用意したテストスイートを元コードとポートに対して実行する
- 修正エージェントは失敗したテストと両コードベースを一緒にレビューし、敵対的レビュアーが修正結果を確認する
- ビルドデーモンだけがバイナリを再ビルドできるよう制限する
- 修正エージェントはパッチを書き、デーモンはそれらを集約して1回だけ再ビルドする
- 影響を受けたテストを再実行し、結果を返す
- 複数エージェントが高コストなビルドをそれぞれ実行しないよう、作業を直列化する
- 同じ失敗が複数テストで繰り返される場合は、バグを生んだ上位ルールを修正し、そのルールが影響したファイルだけを再生成する
-
テストスイートがない場合
- Mikeは、実際のシナリオ7件を元のPythonコードと新しいポートに対して実行し、結果を比較する小さなスクリプトをClaudeに生成させた
- 失敗したシナリオごとに別々の修正エージェントを割り当て、7件すべてが通るまで繰り返した
- Claudeは独自のエンドツーエンドテストスイートも設計し、夜通し自律実行した
- エラー修正と再実行を4晩にわたり繰り返した
- 事前に作成したシナリオ一覧によって、予測しにくい細かな使い勝手の問題まで発見できた
- 既存テストがなくても、元のコードベースを正解としてClaudeが判定者を作れる
反復実行で確認された運用原則
- ガイドをそのままなぞるのではなく、まずプロジェクト特性に合わせてClaudeと移行計画を立ててから着手すべきである
- 個々の失敗は修正エージェントに処理させ、人は繰り返されるエラーパターンに集中すべきである
- レビューは敵対的に、検証は機械的に構成する
- 敵対的レビューは長時間の作業に有効であり、追加トークン消費を受け入れる価値がある場合がある
- コンパイラ、diff、テストスイートのようなスクリプトを最終判定者として使う
- すべての作業に最大モデルを使うわけではない
- 小さなモデルは大量実装の並列化に使う
- 最大モデルはレビューと、他エージェントが従うルール作成に集中させる
- 人の作業時間はルールブックとストレステストに前倒し投入すべきであり、その後の工程は大半がキューを消化する作業である
- 完了状態は「出力ファイルがディスクに存在する」のように機械的に判定できる必要があり、キューは再開可能であるべきである
Bun移行の結果と制約
- BunのRustポートは本番運用中だが、Rustコードの約4%が
unsafeブロック内にある- 多くはC/C++境界での1行のポインタ演算である
- ツールで検出可能なすべてのメモリリークを修正した
- ビルドを2,000回繰り返したベンチマークでは、メモリ使用量が6,745MBから609MBへ減少した
- LinuxとWindowsのバイナリサイズは19%縮小した
- 言語間最適化により、HTTPサービスや
next build、tscのような実ワークロード性能が2〜5%向上した - 大規模移行では、生成されたコードを1行ずつ見るよりも、ループが生んだ結果と反復パターンをレビューすべきである
関連資料
- Migration starter kit: 本文の手順を一般化したテンプレートであり、実際の2つのポートがこのキットで実行されたわけではない
- Code-modernization plugin: 言語ポートではなく、レガシー近代化とフレームワーク更新向けのプラグイン
- Dynamic workflows in Claude Code: Claude Codeの動的ワークフロー紹介
まだコメントはありません。