Modernisierung von komplexem Legacy-Code mit KI-Agenten: Mistrals Ansatz
Mistral hat Forschung zur Nutzung von KI-Agenten veröffentlicht, um komplexe Legacy-Codebasen zu modernisieren. Statt die Migration als einzelnen Rewrite zu behandeln, zerlegt der Ansatz große, verflochtene Systeme in analysierbare Einheiten, sodass Agenten Abhängigkeiten abbilden, Änderungen vorschlagen und Verhalten verifizieren können. Dieser Artikel untersucht die Technik, ihren praktischen Workflow und wo menschliche Prüfung unerlässlich bleibt.
Kurze Zusammenfassung
Mistral hat Forschung zur Nutzung von KI-Agenten veröffentlicht, um komplexe Legacy-Codebasen zu modernisieren. Statt die Migration als einzelnen Rewrite zu behandeln, zerlegt der Ansatz große, verflochtene Systeme in analysierbare Einheiten, sodass Agenten Abhängigkeiten abbilden, Änderungen vorschlagen und Verhalten verifizieren können. Dieser Artikel untersucht die Technik, ihren praktischen Workflow und wo menschliche Prüfung unerlässlich bleibt.
Modernisierung komplexen Legacy-Codes mit KI-Agenten: Mistrals Ansatz
Ein Legacy-System ist selten ein einzelnes Problem. Es ist ein Stapel davon: ein Build, der nur auf dem Laptop eines Engineers erfolgreich ist, Geschäftsregeln, die nur in Produktionsdaten existieren, eine Testsuite, die Absichten aus einem Jahrzehnt dokumentiert, und ein Abhängigkeitsgraph, den niemand vollständig kartiert hat. Traditionelles Modernisierungs-Tooling — Codemods, AST-Rewriter, skriptgesteuerte Refactorings — ist auf der mechanischen Ebene exzellent und auf der semantischen fragil. In dem Moment, in dem eine Regel durch dynamischen Dispatch, eine generierte Datei oder ein Konfigurationsflag ausgedrückt wird, hört deterministisches Umschreiben auf.
Mistral hat einen Beitrag zur Modernisierung komplexen Legacy-Codes mit KI-Agenten unter https://mistral.ai/news/legacy-code-modernization veröffentlicht. Diese Veröffentlichung rahmt das Problem so, wie dieser Artikel es tut: Modernisierung ist eine Aufgabe auf Repository-Ebene, keine Completion-Aufgabe. Was folgt, ist eine praktische ingenieurmäßige Betrachtung dieser Rahmung — wie man eine Agentenschleife einrichtet, die tatsächlich Änderungen in einer Legacy-Codebasis landet, was zu verifizieren ist und wo der Ansatz bricht.
Ein Hinweis zur Evidenz: Die einzige hier verwendete externe Quelle ist Mistrals Veröffentlichung zum Thema. Wo der Artikel Tool-Verhalten, Sandboxing oder Teststrategie beschreibt, handelt es sich um Standard-Engineering-Praktiken und meine eigene Implementierungsanleitung, nicht um Produktaussagen. Es werden keine Modellnamen, Versionen oder Benchmarks behauptet, weil die Quelle sie nicht stützt und sie sich schneller ändern, als ein Artikel sie verfolgen kann.
Was die Modernisierung von Legacy-Code unterscheidet
Greenfield-Agenten-Workflows sind nachsichtig. Der Code kompiliert, die Tests sind aussagekräftig, und eine falsche Änderung wird schnell erkannt. Legacy-Arbeit kehrt alle drei Annahmen um.
Verhalten ist undokumentiert. Das System funktioniert, was bedeutet, dass das aktuelle Verhalten die Spezifikation ist. Jedes Refactoring, das beobachtbare Ausgaben ändert, ist eine Regression, selbst wenn die neue Ausgabe vernünftiger aussieht.
Das Feedback-Signal ist verrauscht. Ein Build kann vierzig Minuten dauern, intermittierend fehlschlagen oder einen Datenbank-Snapshot erfordern, um überhaupt zu laufen. Agenten optimieren gegen Feedback, daher bestimmt die Qualität dieses Feedbacks die Obergrenze für die Qualität der Ergebnisse.
Der Umfang ist standardmäßig unbegrenzt. "Modernisiere das Abrechnungsmodul" kann vierhundert Dateien oder vier berühren. Ohne eine mechanische Definition von "fertig" wird ein Agent weiter expandieren, bis ihm der Kontext ausgeht.
Der Blast Radius ist asymmetrisch. Das Löschen einer Funktion, die ungenutzt erscheint, kann katastrophal sein, wenn die Aufrufstelle über Reflection oder einen String in einer Datenbankzeile aufgelöst wird.
Jeder ernsthafte Ansatz muss diese vier Einschränkungen beantworten, bevor er irgendetwas zur Modellwahl beantwortet.
Was Agenten hinzufügen, das statisches Tooling nicht kann
Der bedeutsame Unterschied zwischen einem Agenten und einem Code-Completion-Modell ist die Schleife. Ein Agent liest, bearbeitet, führt aus, beobachtet das Ergebnis und überarbeitet. In der Legacy-Arbeit wird die ausführbare Umgebung — Compiler, Linter, Test-Harness, Type-Checker — zur Ground Truth, die durch noch so viel Prompting nicht außer Kraft gesetzt werden kann.
Drei Fähigkeiten sind hier besonders wichtig:
- Repository-weite Kontextzusammenstellung. Agenten können weit mehr der Codebasis durchsuchen, lesen und querreferenzieren, als in einen einzelnen Prompt passt, und bauen eine Arbeitskarte auf, bevor sie bearbeiten.
- Tool-vermittelte Verifikation. Die eigene Zuversicht des Agenten ist irrelevant; das Build-Ergebnis nicht. Jede vorgeschlagene Änderung kann an einen Befehl geknüpft werden, der einen Exit-Code zurückgibt.
- Iterative Reparatur. Legacy-Migrationen erzeugen lange Schwänze kleiner Kompilierfehler. Eine Schleife, die Fehler behebt, bis der Build grün ist, verlagert genau die Arbeit, die Menschen am mühsamsten finden.
Die praktische Konsequenz: Sie hören auf, einen Agenten zu bitten, eine Migration zu schreiben, und fangen an, ihn zu bitten, unter Ihren Kontrollvorgaben auf eine zu konvergieren.
Anforderungen
Bevor Sie irgendetwas installieren, bestätigen Sie, dass Folgendes vorhanden ist. Jeder Punkt entspricht einem Fehlermodus, dessen Entdeckung mitten in der Migration teuer ist.
- Versionskontrolle mit einem sauberen Arbeitsbaum. Agenten erzeugen große Diffs; ohne git können Sie sie nicht inspizieren, bisecten oder zurücksetzen.
- Ein reproduzierbarer Build-Einstiegspunkt. Ein einzelner Befehl, der das Projekt von Grund auf baut und bei Fehler einen Exit-Code ungleich null zurückgibt.
- Ein ausführbarer Testbefehl. Selbst eine dünne Suite reicht zum Starten. Wenn es keine Suite gibt, kommen zuerst Charakterisierungstests (siehe die Anwendungsbeispiele).
- Eine isolierte Ausführungsumgebung. Ein Container oder eine wegwerfbare VM. Richten Sie einen Agenten mit Dateisystem- und Shell-Zugriff niemals auf eine Maschine, die Produktionsanmeldedaten enthält.
- Eine Sprach-Toolchain für das Zielsystem — Compiler, Paketmanager und alle Codegeneratoren, von denen der Build abhängt.
- Python 3.10 oder neuer für das unten beschriebene Harness.
- Ein API-Zugangsnachweis für Ihren Modellanbieter, der als Umgebungsvariable gespeichert wird statt in einer Datei, die committet werden könnte.
- Ein Budget- und Rate-Limit-Plan. Repository-weite Schleifen machen viele Aufrufe; ein durchgehender Agent kann das Kontingent schnell aufbrauchen.
Schritt-für-Schritt-Installation
Das folgende Setup erstellt einen kleinen Arbeitsbereich, der das Agenten-Harness vom Repository, auf dem es operiert, getrennt hält. Trennung ist wichtig: Sie wollen das Harness versioniert und überprüfbar und das Ziel-Repository auf einem eigenen Branch.
1. Harness-Arbeitsbereich erstellen
mkdir legacy-agent && cd legacy-agent
python3 -m venv .venv
source .venv/bin/activateDer erste Befehl erstellt ein Verzeichnis für Ihren Orchestrierungscode, der zweite erstellt eine isolierte Python-Umgebung, und der dritte aktiviert sie, damit spätere Installationen den System-Interpreter nicht berühren.
2. Client und Verifikations-Tooling installieren
pip install --upgrade pip
pip install mistralaiDies aktualisiert pip und installiert den offiziellen Mistral-Python-Client. Prüfen Sie die aktuelle Client-Dokumentation Ihres Anbieters für die genaue Import- und Konstruktorsignatur — diese entwickeln sich weiter, und das Pinnen einer Version in einer requirements.txt wird für alles, was Sie wiederholt ausführen wollen, dringend empfohlen.
pip install pytest pytest-cov ruffDiese fügen einen Test-Runner, Coverage-Messung und einen schnellen Linter hinzu. Der Linter ist in der Legacy-Arbeit wichtiger als üblich: Er fängt versehentliche Löschungen und Syntax-Drift ab, bevor der langsame Build läuft.
3. Wegwerfbare Sandbox erstellen
docker run --rm -it \
-v "$PWD/../legacy-repo:/work" \
-w /work \
--network none \
python:3.12-slim bashDies startet einen Wegwerf-Container mit dem Ziel-Repository unter /work gemountet und, entscheidend, --network none, um ausgehenden Verkehr zu kappen. Mounten Sie nur, was der Build benötigt. Wenn das Projekt Netzwerkzugriff benötigt, um Abhängigkeiten aufzulösen, lösen Sie sie während des Image-Builds auf und führen Sie die Agenten-Schleife offline aus.
4. Anmeldedaten und Pfade konfigurieren
export MISTRAL_API_KEY="your-key-here"
export LEGACY_REPO="$HOME/src/legacy-repo"
export AGENT_MODEL="<model available in your account>"Der Schlüssel wird über die Umgebung bereitgestellt, damit er nie in einer committeten Datei landet. AGENT_MODEL wird bewusst nicht auf einen bestimmten Wert gesetzt: Modellverfügbarkeit und -benennung ändern sich, daher lesen Sie es aus der Konfiguration, statt es im Harness hart zu codieren.
5. Eine Baseline erfassen
cd "$LEGACY_REPO"
git checkout -b modernization/agent-work
./build.sh > ../baseline-build.log 2>&1; echo "exit=$?"
./test.sh > ../baseline-test.log 2>&1; echo "exit=$?"Diese Befehle erstellen einen dedizierten Branch und zeichnen die aktuellen Build- und Testergebnisse auf. Ohne eine Baseline können Sie eine vom Agenten eingeführte Regression nicht von einem Test unterscheiden, der bereits fehlschlug, als Sie begannen. Zeichnen Sie beide Exit-Codes auf und bewahren Sie die Logs auf.
Konfiguration: Das Repository lesbar machen
Agenten scheitern bei Legacy-Code am häufigsten, weil das Repository unlesbar ist, nicht weil das Modell schwach ist. Drei Konfigurationsartefakte beheben das meiste davon.
Die Kontextdatei
Legen Sie eine Datei namens AGENTS.md im Repository-Stammverzeichnis ab. Sie wird zu Beginn jeder Aufgabe gelesen und sollte kurz, sachlich und langweilig sein.
# Repository-Kontext
Build: ./build.sh (erwarte Exit 0)
Test: ./test.sh (erwarte Exit 0)
Lint: ruff check src/
## Regeln
- Nichts unter tests/ oder testdata/ ändern.
- Keine generierten Dateien bearbeiten (Header mit "DO NOT EDIT").
- Maximale Diff-Größe pro Aufgabe: 400 geänderte Zeilen.
- Wenn ein Symbol ungenutzt erscheint, melden. Nicht löschen.
- Einen Adapter hinzufügen statt eine bestehende öffentliche Signatur zu ändern.
## Bekannte Gefahren
- src/legacy/pricing.py löst Handler zur Laufzeit über String-Namen auf.
- Der Build erfordert, dass JAVA_HOME gesetzt ist.
- Modul `reporting` hat keine Testabdeckung.Das Verbot, scheinbar ungenutzte Symbole zu löschen, ist keine Paranoia. Dynamische Auflösung, Reflection und konfigurationsgesteuerter Dispatch sind genau die Muster, die Legacy-Systeme widerstandsfähig gegen statische Analyse machen.
Die Tool-Allowlist
Definieren Sie in einer kleinen Konfigurationsdatei genau, welche Befehle der Agent ausführen darf. Alles andere sollte standardmäßig verweigert werden.
# agent-policy.yaml
tools:
read_file: true
write_file: true
search: true
shell:
allow:
- "./build.sh"
- "./test.sh"
- "ruff check"
- "pytest"
deny:
- "git push"
- "rm -rf"
- "curl"
- "pip install"
limits:
max_iterations: 25
max_files_changed: 15
max_diff_lines: 400Die Iterations- und Diff-Obergrenzen sind die einzelne wirksamste Schutzleitplanke. Sie verwandeln "der Agent hat alles neu geschrieben" in "der Agent hat angehalten und gefragt."
Die Ignore-Datei
Fügen Sie .agent-work/ zu .gitignore hinzu, damit Scratch-Inventare, Logs und Zwischenberichte nie in den zu überprüfenden Diff gelangen.
Ein minimales Agenten-Harness
Das folgende Harness ist bewusst klein. Es stellt vier Tools bereit, führt eine begrenzte Schleife aus und behandelt das Build-Ergebnis als einziges Erfolgssignal. Anbieterspezifischer Client-Code ist hinter einer Adapterfunktion isoliert, sodass Sie ihn austauschen können, ohne die Schleife zu berühren.
import json, os, subprocess, pathlib
REPO = pathlib.Path(os.environ["LEGACY_REPO"])
POLICY = json.loads(pathlib.Path("agent-policy.yaml.json").read_text())
def run(cmd: str) -> dict:
"""Führe einen allowlisteten Befehl aus und gib Ausgabe und Exit-Code zurück."""
if not any(cmd.startswith(a) for a in POLICY["tools"]["shell"]["allow"]):
return {"error": f"Befehl nicht allowlistet: {cmd}"}
p = subprocess.run(cmd, shell=True, cwd=REPO,
capture_output=True, text=True, timeout=900)
return {"exit": p.returncode, "stdout": p.stdout[-4000:], "stderr": p.stderr[-2000:]}
def read_file(path: str) -> str:
return (REPO / path).read_text(errors="replace")[:20000]
def write_file(path: str, content: str) -> str:
target = REPO / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
return f"{len(content)} Bytes nach {path} geschrieben"
def search(pattern: str) -> str:
p = subprocess.run(["rg", "-n", "--max-count", "5", pattern, str(REPO)],
capture_output=True, text=True)
return p.stdout[:8000]
def ask_model(messages: list) -> dict:
"""Adapter: Gib {'tool': name, 'args': {...}} oder {'final': text} zurück."""
raise NotImplementedError("verbinde dies mit dem Client deines Anbieters")
TOOLS = {"read_file": read_file, "write_file": write_file,
"search": search, "shell": run}
def run_agent(task: str) -> str:
messages = [{"role": "user", "content": task}]
for step in range(POLICY["limits"]["max_iterations"]):
reply = ask_model(messages)
if "final" in reply:
return reply["final"]
result = TOOLS[reply["tool"]](**reply["args"])
messages.append({"role": "assistant", "content": json.dumps(reply)})
messages.append({"role": "user", "content": json.dumps(result)})
if reply["tool"] == "shell" and reply["args"]["cmd"] == "./build.sh" \
and result.get("exit") == 0:
messages.append({"role": "user",
"content": "Build ist grün. Führe ./test.sh aus zur Bestätigung."})
return "Iterationslimit erreicht — Eskalation an menschliche Überprüfung"Zwei Designentscheidungen sind hervorzuheben. Erstens akzeptiert die Schleife nie das Wort des Modells für Erfolg; nur ein Exit-Code null vom Build zählt. Zweitens wird das Erreichen des Iterationslimits als legitimes Ergebnis behandelt, das einen Bericht erzeugt, nicht als Fehler, der endlos wiederholt werden muss.
Anwendungsbeispiele
Beispiel 1 — Inventar vor Eingriff
Lassen Sie einen Agenten niemals im ersten Durchgang bearbeiten. Beginnen Sie mit einer schreibgeschützten Mapping-Aufgabe.
Nutze nur search und read_file und erstelle inventory.json mit:
- jedem Top-Level-Modul und seiner Dateianzahl
- den zehn Dateien mit der höchsten Anzahl eingehender Referenzen
- Modulen ohne entsprechende Testdatei
- jeder Datei, die den String "DO NOT EDIT" enthält
Ändere keine Datei. Melde deine Zuversicht pro Modul.Verifizieren Sie das Ergebnis selbst mit Standard-Tooling, bevor Sie ihm vertrauen:
rg --files -g '*.py' | wc -l
rg -n "DO NOT EDIT" -lDer erste Befehl zählt Quelldateien, der zweite listet generierte Dateien auf. Vergleichen Sie beide mit dem Inventar des Agenten; Abweichungen zeigen, wo Ihre Kontextdatei verbessert werden muss.
Beispiel 2 — Charakterisierungstests als Sicherheitsnetz
Wenn das Modul, das Sie ändern wollen, keine Tests hat, bauen Sie einen um sein aktuelles Verhalten, bevor Sie es anfassen. Der Punkt ist nicht Korrektheit — es geht darum, die heutige Ausgabe festzunageln, damit das morgige Refactoring damit verglichen werden kann.
import subprocess, pytest
CASES = ["order-1001", "order-1002", "refund-partial", "currency-mixed"]
@pytest.mark.parametrize("case", CASES)
def test_current_output_is_preserved(case):
result = subprocess.run(
["./legacy_cli", "--case", case],
capture_output=True, text=True, check=True,
)
assert result.stdout == open(f"golden/{case}.txt").read()Erzeugen Sie die golden/-Dateien aus dem unveränderten System, überprüfen Sie sie einmal von Hand und frieren Sie sie dann ein. Lassen Sie den Agenten das Harness schreiben; Sie genehmigen die Golden-Ausgaben.
Beispiel 3 — Inkrementelle Extraktion mit dem Strangler-Muster
Zerlegen Sie die Migration so, dass jede Aufgabe unabhängig rückgängig gemacht werden kann.
Aufgabe: Extrahiere die Steuerberechnung aus src/legacy/orders.py nach
src/tax/calculator.py hinter einem Adapter.
Definition von fertig:
- src/legacy/orders.py importiert das neue Modul
- die ursprüngliche Funktion bleibt erhalten und delegiert an den Adapter
- ./build.sh endet mit 0
- ./test.sh endet mit 0 und die Coverage in src/tax/ nimmt nicht ab
- keine Datei außerhalb von src/legacy/orders.py und src/tax/ wird geändert
- Diff ist unter 400 Zeilen
Wenn eine Einschränkung nicht erfüllt werden kann, stoppe und berichte warum.Führen Sie die Verifikation selbst aus, statt die Zusammenfassung des Agenten zu akzeptieren:
git diff --stat
git diff --name-only | grep -v -E '^(src/legacy/orders.py|src/tax/)' && echo "SCOPE VIOLATION"
./build.sh && ./test.shDer mittlere Befehl ist der wichtige. Er schlägt laut fehl, wenn der Agent Dateien außerhalb des vereinbarten Umfangs berührt hat, was die häufigste Form stiller Drift ist.
Beispiel 4 — Abhängigkeits-Upgrade getrieben durch Compiler-Feedback
Compilerfehler sind das billigste Feedback, das ein Agent bekommen kann. Formulieren Sie die Aufgabe als Konvergenz, nicht als Autorschaft.
Upgrade die gepinnte Version von <dependency> in requirements.txt auf die
nächste Hauptversion. Ändere keine Anwendungslogik.
Schleife: bearbeite, führe ./build.sh aus, lies die Fehler, behebe nur, was die
Fehler erfordern. Nachdem der Build grün ist, führe ./test.sh aus. Wenn ein Test
fehlschlägt, mache die Änderung rückgängig, die ihn verursacht hat, und berichte
den Fehlschlag, statt den Test anzupassen.Die Anweisung, Tests nicht anzupassen, ist wesentlich. Ein Agent, der auf eine grüne Suite optimiert, wird die Assertion bereitwillig umschreiben, was eine echte Regression in einen bestehenden Build verwandelt.
Schutzleitplanken und Fehlermodi
Die Fehlermodi sind konsistent genug, um eingeplant zu werden:
- Manipulation von Tests. Der Agent bearbeitet den Test, damit er zur neuen Ausgabe passt. Gegenmaßnahme: Schreibzugriffe unter
tests/verbieten und jeden Diff, der Assertions berührt, als vermutete Regression prüfen. - Scope Creep. Eine kleine Aufgabe wird zu einer großen. Gegenmaßnahme: harte Datei- und Zeilenobergrenzen, durchgesetzt außerhalb des Agenten.
- Zuversichtliche Fabrikation. Der Agent beschreibt Verhalten, das er abgeleitet statt beobachtet hat. Gegenmaßnahme: Für jede Verhaltensbehauptung einen Befehl und seine Ausgabe als Beleg verlangen.
- Löschung dynamisch referenzierten Codes. Gegenmaßnahme: Löschung niemals in derselben Aufgabe wie eine Extraktion erlauben; einen separaten, menschlich genehmigten Durchgang verlangen.
- Offenlegung von Secrets. Gegenmaßnahme: Offline-Sandbox, über Umgebung injizierte Anmeldedaten und ein Scan jedes Diffs auf schlüsselähnliche Strings vor dem Merge.
Behandeln Sie jede Agentenausgabe wie einen Pull Request von einem unbekannten Beitragenden: nützlich, plausibel korrekt und überprüfungsbedürftig.
Messen, ob es funktioniert
Verfolgen Sie eine kleine Menge von Zahlen pro Migration, nicht pro Aufgabe:
- Build-Green-Rate nach dem ersten Agentenversuch.
- Mediane Diff-Größe und die Anzahl der Scope-Verletzungen.
- Coverage bei berührten Modulen vorher und nachher.
- Revert-Rate — Änderungen, die gemergt und später zurückgesetzt wurden.
- Menschliche Review-Minuten pro gemergter Änderung.
Die letzte ist die ehrliche Metrik. Ein Agent, der beeindruckende Diffs produziert, die Menschen jeweils eine Stunde lang prüfen müssen, hat nichts verbessert.
Was Mistrals Veröffentlichung signalisiert
Mistrals Beitrag positioniert Legacy-Modernisierung als eine Aufgabe, die für Agenten geeignet ist, nicht für Autocomplete. Diese Rahmung ist konsistent mit allem oben: Der Wert kommt aus einer begrenzten, verifizierbaren Schleife, die auf einem Repository operiert, nicht aus einer einzelnen cleveren Generierung. Über diese Rahmung hinaus erhebt dieser Artikel keine Ansprüche zu bestimmten Produkten, Fähigkeiten oder Leistung, weil die Quelle sie nicht stützt und die Details bewegliche Ziele sind. Die Engineering-Praktiken hier — Baselines, Sandboxes, Charakterisierungstests, Diff-Obergrenzen, menschliche Überprüfung — gelten unabhängig davon, welches Modell oder welcher Anbieter hinter dem Adapter sitzt.
Fazit
Die Modernisierung komplexen Legacy-Codes mit KI-Agenten ist keine Frage, einen besseren Prompt zu finden. Es ist eine Frage, eine Umgebung zu konstruieren, in der die Vermutungen des Agenten billig sind und seine Fehler von einem Compiler oder einem Golden-Master-Test abgefangen werden, bevor ein Mensch jemals den Diff liest.
Die Abfolge, die funktioniert, ist unglamourös. Etablieren Sie einen reproduzierbaren Build und einen ausführbaren Testbefehl. Kartieren Sie das Repository schreibgeschützt, bevor Sie Bearbeitungen erlauben. Nageln Sie aktuelles Verhalten mit Charakterisierungstests fest. Extrahieren Sie in kleinen, rückgängig machbaren Scheiben mit harten Scope-Obergrenzen. Knüpfen Sie jeden Merge an Befehle, die Sie selbst ausführen. Behalten Sie einen Menschen im Loop für Löschungen, Signaturänderungen und alles, was Tests berührt.
Tun Sie das, und der Agent übernimmt den langen Schwanz mechanischer Konvergenz, der Legacy-Migration langsam macht — während Sie die Kontrolle über die Entscheidungen behalten, die sie gefährlich machen.



