- Les développeurs d’Anthropic ont migré, au cours du mois dernier, 10 packages de plusieurs dizaines à plusieurs centaines de milliers de lignes avec Claude Fable 5, Claude Opus 4.8 et des workflows dynamiques, en améliorant non pas le code individuellement, mais le processus itératif qui génère le code
- La migration Zig→Rust de Bun a généré 1 million de lignes en moins de deux semaines et a passé 100 % des tests existants avant la fusion ; un projet Python→TypeScript a migré 165 000 lignes en un week-end en utilisant des centaines d’agents, 8 portes d’étape et 3 revues adversariales
- Les migrations à grande échelle se prêtent bien à la parallélisation ; le code existant sert à la fois de spécification et de référence, et les échecs de compilation ou de tests génèrent automatiquement la file de tâches suivante, ce qui facilite la mise en place d’une boucle de validation objective
- Le processus avance par étapes : préparation des critères de jugement, rédaction du rulebook, de la carte des dépendances et de l’inventaire des écarts, stress test des règles, traduction complète, compilation, exécution, puis comparaison du comportement ; les erreurs répétées ne sont pas corrigées fichier par fichier, mais par modification des règles de plus haut niveau puis régénération
- Les coûts restent de l’ordre de dizaines à centaines de milliers de dollars, voire plus, mais il devient possible de jeter une branche ratée et de réessayer ; la migration de Bun a coûté environ 165 000 dollars aux tarifs API, avant d’obtenir une baisse de l’usage mémoire, une réduction de 19 % de la taille des binaires et une amélioration de 2 à 5 % des performances sur des workloads réels
Améliorer la boucle de génération plutôt que le code
- Une migration de code par IA consiste à faire déplacer par des agents une base de code de production vers un nouveau langage ou framework
- Au lieu de traduire les fichiers directement, les ingénieurs écrivent les règles de migration et les boucles de validation
- Les agents répètent traduction, compilation et tests jusqu’à ce que le comportement du nouveau code corresponde à l’original
- Des projets qui prenaient autrefois plusieurs années peuvent être ramenés à quelques semaines
- Chez Anthropic, Claude Fable 5, Claude Opus 4.8 et les workflows dynamiques ont été utilisés pour migrer, en un mois, 10 packages de plusieurs dizaines à plusieurs centaines de milliers de lignes de code
- Le principe opérationnel central n’est pas de réparer directement le code généré, mais de corriger la boucle qui l’a produit
Cas réels de migration
-
Migration Zig→Rust de Bun
- Jarred Sumner a utilisé Claude Code pour migrer Bun de Zig vers Rust
- 1 million de lignes de code ont été générées en moins de deux semaines
- Avant la fusion, la suite de tests existante de Bun passait à 100 % dans la CI
- Les 19 régressions découvertes après la fusion ont toutes été corrigées
- Le port Rust a été intégré à Claude Code en juin
- Bun dépasse 10 millions de téléchargements mensuels et est aussi largement utilisé en interne dans Claude Code
-
Migration Python→TypeScript
- Mike Krieger a migré une base de code Python vers 165 000 lignes de TypeScript en un week-end
- Il a utilisé des centaines d’agents, 8 portes d’étape et 3 revues adversariales
- Une vérification finale d’équivalence a comparé la sortie de toutes les commandes avec l’original Python
- Il a jeté le résultat complet de la migration puis corrigé les règles et le workflow à plusieurs reprises, avant d’adopter le résultat de la troisième exécution
Quand réexaminer une migration de langage
- Une migration peut être envisagée si l’environnement technique a changé depuis le développement initial, si les compromis existants sont devenus des contraintes, si une meilleure approche est apparue ou si l’écosystème d’origine s’est réduit
- Zig offrait des performances au niveau de C et une grande simplicité, ce qui convenait au contexte initial où Bun était développé par une seule personne, mais cette simplicité s’accompagnait de compromis connus
- Par le passé, une migration de langage obligeait à interrompre la roadmap et à mobiliser des ressources pendant plusieurs trimestres
- Il pouvait être nécessaire de maintenir deux bases de code en parallèle pendant plusieurs trimestres ou plusieurs années
- Si la correspondance finale des comportements n’atteignait que 90 %, le problème de maintenance pouvait devenir plus important qu’avant le démarrage
- Il existe désormais l’option de supprimer une branche ratée et de relancer
- Migrer 1 million de lignes ne demande plus nécessairement 3 à 4 millions de dollars de coûts d’ingénierie sur quatre ans, mais peut tout de même coûter des dizaines à centaines de milliers de dollars, voire plus
- La migration de Bun a consommé 5,9 milliards de tokens d’entrée non mis en cache et 690 millions de tokens de sortie
- Aux tarifs API, le coût est d’environ 165 000 dollars
- Dans le port de Mike, la section critique a utilisé 27 millions de tokens
- La justification business d’une migration n’a plus besoin de relever de la survie : une année passée à corriger des bugs mémoire récurrents ou un seul goulot d’étranglement chronique peuvent suffire à la justifier
-
Suppression d’un goulot d’étranglement de build Python
- L’outil interne de Mike était livré aux utilisateurs sous forme d’un binaire unique, mais produire des binaires par plateforme avec la toolchain Python prenait environ 8 minutes
- Sur toute la matrice de build, il fallait attendre environ 30 minutes à chaque release
- Après la migration vers TypeScript, la compilation est descendue à environ 2 secondes, le démarrage du binaire est devenu 6 fois plus rapide et un pipeline de déploiement séparé a été supprimé
Pourquoi les agents IA sont adaptés aux migrations
- Le travail en parallèle est possible
- La base peut être divisée en milliers d’unités indépendantes, comme des fichiers ou des crates, traitées simultanément par plusieurs agents
- Le code existant sert de spécification claire et complète
- Il peut aussi servir de référence principale pour créer les consignes destinées aux agents de traduction
- La suite de tests joue le rôle de juge intégré
- Quand la validation est objective, le modèle peut itérer pendant plusieurs jours en visant la bonne réponse, sans arbitrage humain continu sur la qualité
- Les échecs de compilation ou de tests deviennent automatiquement les éléments de travail suivants, ce qui réduit le besoin d’écrire une file de tâches séparée
- La cohérence et la gestion des exceptions peuvent être intégrées à la boucle
- Les reviewers relient chaque problème à une règle violée
- La façon de résoudre une exception devient ensuite une règle suivie par tous les agents
- Au lieu d’un écart de comportement silencieux, une violation de règle devient un élément de travail explicite
- Fable et Opus 4.8 sont utilisés pour déléguer, piloter et valider le travail parallèle de sous-agents, et pour trouver plusieurs chemins vers l’objectif
- Un pattern consultatif combinant plusieurs niveaux de modèles optimise l’utilisation des tokens
Prérequis : juger l’équivalence entre l’original et le port
- Avant de commencer une migration, il faut un juge robuste pour évaluer le code source et le code cible selon les mêmes critères
- Sans juge, il n’y a ni critère de succès ni condition d’arrêt
- Les tests dépendant de fonctions internes du langage source peuvent ne pas s’exécuter tels quels sur le code cible
- Les tests existants sont classés entre ceux qui peuvent être exprimés comme appels externes et ceux qui dépendent d’implémentations internes non portées
- Les tests de comportement externe sont réécrits sous forme d’assertions exécutables à la fois sur l’original et sur le port
- Un agent adversarial vérifie que les assertions n’ont pas été affaiblies pendant la réécriture
- Le juge est exécuté sur le code original pour vérifier qu’il réussit, puis sur un code volontairement cassé pour vérifier qu’il échoue
- Jarred disposait d’une vaste suite de tests écrite dans un troisième langage, TypeScript
- Mike a créé un harnais d’équivalence autour de 7 scénarios d’utilisation réels et a traité toute modification de comportement comme un bug à corriger
Étape 1 : rulebook, carte des dépendances et inventaire des écarts
- Les livrables de base ne sont pas une simple traduction, mais une liste des emplacements à refactorer, un rulebook décrivant la méthode de traduction et une carte des dépendances déterminant l’ordre de travail
- L’ordre de rédaction est important
- Il faut d’abord définir les valeurs par défaut du rulebook, afin de pouvoir identifier comme écarts les éléments que ces valeurs par défaut ne couvrent pas
- Le rulebook et l’inventaire des écarts sont validés ensemble par un audit commun
-
Rulebook
- La forme du rulebook dépend du choix de conserver la structure existante ou de reconcevoir entièrement le nouveau code
- Si la structure est conservée, comme chez Jarred, le cœur du document est une table de correspondance entre types et idiomes des deux langages, et les composants difficiles à traduire renvoient à l’inventaire des écarts
- Si l’on reconçoit, comme chez Mike, le rulebook joue le rôle de document de conception
- Jarred a échangé avec Claude pour établir une politique dans chaque zone ambiguë, puis a configuré 8 sous-agents chargés d’examiner chacun l’une des 8 catégories d’erreurs attendues
-
Carte des dépendances
- Dans une migration parallèle, il faut comprendre les dépendances entre fichiers pour décider quels fichiers migrer d’abord et lesquels mettre dans un même lot
- Dans les bases de code legacy sans manifeste explicite, ainsi que dans les bases C/C++ ou Python, les dépendances doivent être découvertes et cartographiées directement
- Les agents Claude Code peuvent générer une carte en écrivant et exécutant des scripts déterministes, puis en les revoyant et les corrigeant dans une boucle
- Un exemple généralisé est disponible dans le prompt de carte des dépendances
-
Inventaire des écarts de langage et reviewers sceptiques
- L’inventaire des écarts consigne les connaissances implicites dans le code existant mais qui doivent être explicitées dans le langage cible
- Dans Zig→Rust, la gestion mémoire était le principal écart
- En Zig, le fait que l’appelant doive libérer un buffer peut n’apparaître que dans un commentaire ; l’oubli de libération compile tout de même, et la fuite n’est découverte qu’à l’exécution
- En Rust, la propriété est transférée à l’appelant et la mémoire est libérée automatiquement ; l’utilisation après déplacement ou la double libération ne compilent pas
- Dans Python→TypeScript, les interfaces et les contrats étaient le principal écart
- Python n’exige pas de déclarer la forme des objets reçus ni des valeurs retournées
- En TypeScript, il faut écrire les contrats des méthodes, des arguments et des formes de retour pour compiler
- Jarred a listé les écarts avant la traduction, tandis que Mike a d’abord traduit puis créé la liste pendant l’audit ; les deux approches sont donc possibles selon le projet
- Un exemple généralisé est disponible dans le prompt de création d’inventaire des écarts
Étape 2 : stress test des règles
- Avant la migration complète, un port d’essai à petite échelle met le rulebook à l’épreuve et révèle les problèmes
- Jarred a comparé trois tâches d’agents
- Le premier agent a traduit 3 fichiers selon le rulebook
- Le deuxième agent a traduit le même volume comme le ferait un ingénieur Rust expérimenté
- Le troisième agent a écrit de nouvelles règles de traduction à partir des différences entre les deux résultats
- Ce processus a identifié 2 problèmes critiques avant qu’ils ne se propagent aux 1 448 fichiers
- Cette méthode ne fonctionne que pour une migration conservant la structure, où deux traductions du même fichier peuvent être comparées ligne par ligne
- Dans un cas de reconception comme celui de Mike, des reviewers adversariaux attaquent le document de conception et la validation passe par une exécution de bout en bout jetable
- Tous les fichiers traduits pendant l’essai doivent être jetés ; l’objectif n’est pas une progression incrémentale du code, mais l’amélioration des règles
- Un exemple de tâche généralisé est disponible dans le prompt de stress test
Étape 3 : traduction complète du code
- Les étapes suivantes utilisent toutes une boucle multi-agent implémentation→revue→correction
- L’implémentation de masse peut être confiée à de petits modèles, tandis que la revue revient à de grands modèles
- Mike a utilisé 12 sous-agents Claude Sonnet pour paralléliser la migration principale
- La file de tâches est gérée mécaniquement
- Un script de batch détermine l’état d’achèvement selon la présence sur disque des fichiers traduits
- Il découpe les fichiers restants en lots pour les agents d’implémentation
- À chaque exécution, la file est reconstruite à partir de l’état du disque, ce qui permet par défaut de reprendre après interruption
- Si les agents se montrent trop prudents et traitent des quantités trop faibles, on peut leur donner une instruction plus directe en rappelant que le compilateur attrapera les erreurs à l’étape suivante
- Les éléments dont ils ne sont pas sûrs doivent être marqués
// TODO(port): <reason>et résolus à l’étape 4 - La liste de travail ultérieure est générée automatiquement à partir des erreurs de compilation, des crashes de smoke tests et des échecs de tests
-
Revue adversariale et mise à jour des règles
- Deux reviewers adversariaux en contextes indépendants évaluent le résultat d’implémentation ; en cas de désaccord, un troisième agent tranche
- Si la même erreur se répète dans plusieurs fichiers, on ne corrige pas les fichiers un par un
- On ajoute une phrase au rulebook et l’on régénère les lots concernés
- Le rulebook continue de s’enrichir pendant la phase de traduction, et le code contraire aux règles n’est pas patché manuellement
-
Où placer le compilateur
- Si le temps de compilation est court, il peut être inclus dans la boucle de traduction
- Mike exécutait TypeScript dans toutes les boucles, car la compilation par unité se terminait en quelques secondes
- Si la compilation est longue, elle est repoussée à l’étape suivante
- Jarred a interdit l’usage du compilateur dans la boucle de traduction, car
cargoprenait plusieurs minutes à s’exécuter - À partir de cette étape, les prompts raccourcissent ; un exemple est disponible dans le prompt de démarrage de traduction
Étapes 4 à 6 : compilation, exécution et correspondance du comportement
- Ces trois étapes partagent la même structure de boucle ; plus on avance, moins le jugement humain est nécessaire
- Selon le langage et la taille du projet, l’étape de compilation peut être absorbée dans l’étape de traduction complète
-
Étape 4 : compilation
- Jarred a configuré un script orchestrateur pour exécuter le compilateur une seule fois sur tout le workspace
- Les agents de correction traitent la liste d’erreurs en parallèle, passent par une revue adversariale, puis relancent le build, en boucle
- La revue de la liste d’erreurs sert à détecter des problèmes systémiques plutôt qu’à examiner chaque erreur isolément
- Après correction d’imports circulaires que la compilation différée de Zig autorisait, des milliers d’erreurs de modules Rust sont apparues
- Une logique de classification a été ajoutée à la boucle pour déterminer quelles dépendances supprimer, déplacer ou dont les frontières devaient être restructurées
-
Étape 5 : exécution et smoke tests
- Les crashes sur les smoke tests jouent le même rôle de vérité mécanique que la liste des erreurs de compilation
- Les problèmes ne sont pas traités individuellement : ils sont regroupés par cause racine, puis examinés par des sous-agents adversariaux
-
Étape 6 : comparaison du comportement avec l’original
- Une fois traduction, compilation et smoke tests terminés, le code est découpé et la suite de tests préparée en amont est exécutée sur l’original et sur le port
- Les agents de correction examinent ensemble les tests en échec et les deux bases de code, tandis que des reviewers adversariaux vérifient les corrections
- Seul le démon de build est autorisé à reconstruire les binaires
- Les agents de correction écrivent les patches, et le démon les agrège pour ne reconstruire qu’une seule fois
- Les tests affectés sont réexécutés et les résultats renvoyés
- Le travail est sérialisé pour éviter que plusieurs agents ne lancent chacun des builds coûteux
- Si le même échec se répète dans plusieurs tests, on corrige la règle de plus haut niveau qui a créé le bug, puis on régénère uniquement les fichiers affectés par cette règle
-
En l’absence de suite de tests
- Mike a fait générer par Claude un petit script qui exécute 7 scénarios réels sur le code Python original et sur le nouveau port, puis compare les résultats
- Un agent de correction distinct a été assigné à chaque scénario en échec, et la boucle a continué jusqu’à ce que les 7 réussissent
- Claude a aussi conçu sa propre suite de tests de bout en bout et l’a exécutée de manière autonome pendant la nuit
- Le travail de correction puis de relance s’est répété pendant quatre nuits
- La liste de scénarios préparée à l’avance a permis de découvrir jusqu’à de petits problèmes d’utilisabilité difficiles à prévoir
- Même sans tests existants, Claude peut créer un juge en prenant la base de code originale comme référence
Principes opérationnels observés lors des exécutions répétées
- Plutôt que de suivre le guide tel quel, il faut planifier la migration avec Claude en fonction des caractéristiques du projet avant de commencer
- Les échecs individuels doivent être laissés aux agents de correction ; les humains doivent se concentrer sur les schémas d’erreurs récurrents
- La revue doit être adversariale, la validation mécanique
- La revue adversariale est utile pour les travaux longs et peut valoir le coût en tokens supplémentaires
- Des scripts comme le compilateur, les diffs et la suite de tests servent de juges finaux
- Ne pas utiliser le plus grand modèle pour toutes les tâches
- Les petits modèles servent à paralléliser l’implémentation de masse
- Le plus grand modèle se concentre sur la revue et l’écriture de règles que suivront les autres agents
- Le temps humain doit être investi en amont dans le rulebook et les stress tests ; la suite du processus consiste surtout à vider des files de tâches
- L’état d’achèvement doit être déterminable mécaniquement, par exemple « le fichier de sortie existe sur le disque », et la file de tâches doit être reprenable
Résultats et limites de la migration de Bun
- Le port Rust de Bun fonctionne en production, mais environ 4 % du code Rust se trouve dans des blocs
unsafe- Il s’agit pour l’essentiel d’opérations de pointeur sur une seule ligne aux frontières C/C++
- Toutes les fuites mémoire détectables par les outils ont été corrigées
- Dans un benchmark répétant le build 2 000 fois, l’usage mémoire est passé de 6 745 MB à 609 MB
- La taille des binaires Linux et Windows a diminué de 19 %
- Des optimisations entre langages ont amélioré de 2 à 5 % les performances de workloads réels comme des services HTTP,
next buildettsc - Dans les migrations à grande échelle, il faut examiner les résultats produits par la boucle et les motifs de répétition, plutôt que chaque ligne de code générée
Ressources liées
- Migration starter kit : un ensemble de templates généralisant la procédure décrite dans l’article ; les deux ports réels n’ont pas été exécutés avec ce kit
- Code-modernization plugin : un plugin destiné à la modernisation de legacy et aux mises à niveau de frameworks, plutôt qu’aux ports de langage
- Dynamic workflows in Claude Code : présentation des workflows dynamiques dans Claude Code
Aucun commentaire pour le moment.