Przejdź do głównej zawartości

Modernizacja legacy z agentami: program, nie prompt

Modernizacja legacy z agentami kodującymi działa jako program z bramkami, a nie pojedynczy prompt: utrwal dzisiejsze zachowanie testami charakteryzacyjnymi, podziel system na plastry za szwami, migruj je w równoległych falach izolowanych uruchomień agentów, porównaj starą i nową ścieżkę na żywym ruchu, a potem przełącz na podstawie zapisanych dowodów. Agenci piszą kod, a utrwalone zachowanie decyduje, co zostanie scalone.

Twój zespół utrzymuje dwunastoletni monolit w PHP. Siedzą w nim rozliczenia, pokrycie testami jest cienkie, a dwie osoby, które rozumiały reguły podatkowe, odeszły z firmy. Zarząd chce, żeby do przyszłego roku rozliczenia działały jako serwis w TypeScripcie. Ktoś już poprosił agenta: „zmigruj billing/ do TypeScriptu” i dostał pull request na 900 plików, którego nikt nie przejrzy ani nie odważy się scalić.

Szybkość samej zmiany kodu przestała być ograniczeniem. Ogłoszenie Claude Opus 5.5 od Anthropic (22 września 2026) przytacza anonimowego testera, który ukończył „a 680,000-line code migration in less than a day”. To anegdota raportowana przez dostawcę i nie mówi, czy zmigrowany system nalicza ten sam podatek od tej samej faktury. Ta strona jest o tym, jak wytworzyć taki dowód.

Jest dla tech leada, który prowadzi program, dla CTO, który go finansuje i podpisuje przełączenie, oraz dla deweloperów, którzy uruchamiają fale.

Co zyskujesz z modernizacji prowadzonej przez agentów

Dział zatytułowany „Co zyskujesz z modernizacji prowadzonej przez agentów”
  • Program w pięciu fazach z bramką wyjścia i jej właścicielem dla każdej fazy oraz plan slices.yaml, wspólny dla agentów i ludzi.
  • Zestaw nagrywający (harness) testów charakteryzacyjnych na szwie HTTP (przetestowany z Vitest 5.0.2 oraz z pytest i syrupy 6.1.1) i strażnika w CI, który nie pozwala pull requestom migracyjnym zmienić utrwalonego zachowania.
  • Konfiguracje fal dla Claude Code, Codexa i Cursora, pomocnika do porównania w cieniu, checklistę przełączenia i cztery prompty do skopiowania.

Dlaczego „zmigruj to do TypeScriptu” nie działa jako jeden prompt?

Dział zatytułowany „Dlaczego „zmigruj to do TypeScriptu” nie działa jako jeden prompt?”

Zawodzi z czterech powodów i żaden z nich nie dotyczy umiejętności programistycznych modelu.

  1. Nie ma wyroczni. Kod legacy rzadko ma testy. Testy, które agent pisze po nowym kodzie, opisują nowy kod razem z jego błędami i przechodzą z definicji (zob. siła wyroczni).
  2. Partia jest za duża, żeby ją zweryfikować. Wytyczne DORA do modelu AI capabilities (Nathen Harvey i Allison Park, Google Cloud, 10 grudnia 2025) mówią wprost: „AI can easily generate massive blocks of code, which are hard to review and test. Enforcing the discipline of small batches counteracts this risk.”
  3. Nieudokumentowane zachowanie jest nośne. Dziwne zaokrąglenia, pusty identyfikator klienta oznaczający „klienta z ulicy”, raport sortowany w kolejności wstawiania: od każdego z nich ktoś zależy, a przepisanie kodu na podstawie jego lektury je gubi.
  4. Intencje się mieszają. Przeniesienie kodu, podbicie wersji frameworka i „poprawka” wyliczenia w jednej zmianie nie pozwalają ustalić, co zepsuło produkcję.

Każda faza kończy się dowodem, który człowiek może sprawdzić bez czytania zmigrowanego kodu. Większość pisania wykonują agenci, a ludzie są właścicielami bramek.

FazaCo robią agenciZa co odpowiadają ludzieBramka wyjścia (dowód)
0. MapaInwentaryzują punkty wejścia, wywołujących, magazyny danych i efekty uboczneZakres, docelowa architektura, granice plastrówslices.yaml zatwierdzony przez tech leada
1. UtrwalenieNagrywają testy charakteryzacyjne i pliki golden na systemie legacyKtóre wejścia są reprezentatywne; które osobliwości są błędamiPliki golden w repozytorium, wynik testów mutacyjnych na minimum lub wyżej, akceptacja właściciela wyroczni
2. SzewWprowadzają punkt routingu (fasada, trasa w proxy albo flaga) bez zmiany zachowaniaGdzie przebiega szewZestaw charakteryzacyjny zielony przez szew, zero zmian w plikach golden
3. Migracja w falachPrzenoszą każdy plaster w izolowanym worktree albo VM, jeden pull request na plasterKolejność fal, kolejka scalania, sporyDla każdego plastra: pliki golden bez zmian i zielone na nowej ścieżce, funkcje dopasowania zielone, pakiet dowodów w pull requeście
4. Dowód i przełączenieUruchamiają porównanie w cieniu, klasyfikują rozbieżności, szkicują zapis przełączeniaDecyzje o rozbieżnościach, wdrożenie, decyzja „jedziemy”Zero niewyjaśnionych rozbieżności przez pełny cykl biznesowy, przećwiczone wycofanie, podpisany zapis przełączenia
  1. Zmapuj system i spisz plan plastrów. Uruchom pierwszy prompt niżej w trybie planowania (/plan w Claude Code i Codexie, Plan Mode w Cursorze), przejrzyj plan, a potem go zatwierdź, żeby agent zapisał slices.draft.yaml i map.md. Poprawiaj szkic, aż każdy plaster ma jeden szew, jednego właściciela i klasę ryzyka. Plaster ma dobry rozmiar, gdy jego zestaw wykonuje się w minuty, a migracja mieści się w jednym pull requeście, który da się przejrzeć. Przy bardzo dużych systemach zajrzyj do baz kodu na miliony linii.

  2. Utrwal zachowanie pierwszych plastrów. Nagraj pliki golden na działającym systemie legacy zestawem nagrywającym opisanym niżej, na prawdziwych wejściach: zanonimizowanych żądaniach z produkcji, nietypowych kontach od zespołu wsparcia, przypadkach z zamknięcia miesiąca. Plik golden, który wygląda na błąd, trafia na listę quirks plastra, a decyzję podejmujesz później, w osobnej zmianie. Pełną technikę opisujemy na stronie testy charakteryzacyjne.

  3. Udowodnij, że pliki golden potrafią zawieść. Uruchom testy mutacyjne na plastrze legacy i trzymaj wynik na uzgodnionym minimum (narzędzia niżej). Mutant, który przeżył, to wejście, którego jeszcze nie nagrałeś: w naszym teście plik golden pustej faktury został zielony po zmianie stawki podatku, bo pusta faktura daje zero przy każdej stawce.

  4. Zablokuj wyrocznię. Przypisz tests/characterization/ w CODEOWNERS właścicielowi wyroczni, odbierz agentom prawo do edycji (zakładki narzędzi niżej) i dodaj strażnika CI. Od tej chwili pull request migracyjny, który dotyka pliku golden, odrzuca reguła, a nie uwaga recenzenta.

  5. Przetnij szew. Poprowadź ruch plastra przez jeden punkt z flagą, która wysyła każde żądanie do ścieżki legacy albo do nowej. Scal szew, gdy ścieżka legacy nadal odpowiada na wszystko, i potwierdź, że zestaw jest przez niego zielony (wzorzec strangler fig).

  6. Migruj w falach. Fala to zbiór niezależnych plastrów, z których każdy jest izolowanym zadaniem agenta kończącym się jednym pull requestem. Zacznij od jednego plastra niskiego ryzyka jako pilotażu, potem poszerzaj. Trzymaj plaster przy jednej intencji: przeniesienie, aktualizacja albo poprawka, nigdy dwie naraz.

  7. Najpierw cień, potem przełączenie. Wdróż nową ścieżkę po cichu, porównaj ją ze ścieżką legacy na żywym ruchu i napraw albo jawnie zaakceptuj każdą rozbieżność. Potem przesuwaj ruch krokami za flagą, jak w stopniowym wdrażaniu (progressive delivery), i złóż zapis przełączenia.

  8. Usuń ścieżkę legacy. Po uzgodnionym okresie na 100% usuń stary kod, flagę i okablowanie cienia w jednym pull requeście. Zestaw charakteryzacyjny zostaje jako zestaw regresyjny.

Zmutowany kod musi wykonać się pod testami charakteryzacyjnymi, inaczej wynik nic nie znaczy.

  • JavaScript albo TypeScript. Runner command w Strykerze może uruchomić aplikację legacy z mutowanego sandboksu, a potem zestaw HTTP. Ustaw coverageAnalysis: "off", a thresholds.break na minimum. Instalacja: npm i -D @stryker-mutator/core (10.0.0 w npm we wrześniu 2026).
  • PHP. Infection 0.35.4 (aktualne wydanie w Packagist 26 września 2026; composer require --dev infection/infection) uruchamia w swoim procesie wyłącznie zestawy PHPUnit, Pest albo Codeception, więc nie oceni zestawu HTTP wołającego osobny serwer. Daj mu zestaw PHPUnit dla plastra, który odtwarza nagrane przypadki przez front controller i porównuje je z tymi samymi plikami golden.

Trzymaj plan w repozytorium jako dane: agenci mogą go czytać, CI może go sprawdzać, a stan programu jest o jeden grep stąd:

# modernization/slices.yaml: the program's single source of truth
program: billing-php-to-ts
oracle_owner: "@acme/billing-leads"
mutation_score_floor: 80 # percent, per slice, measured on the legacy code
slices:
- id: customer-search
seam: "GET /api/customers"
risk: low # the pilot: first wave, one low-risk slice
depends_on: []
goldens: tests/characterization/golden/customer-search/
quirks: []
status: shadow # mapped | pinned | seamed | migrating | shadow | cut-over | deleted
wave: 1
- id: invoice-total
seam: "POST /api/invoices/total"
risk: high # money: code owner review plus staged rollout
depends_on: []
goldens: tests/characterization/golden/invoice-total/
quirks:
- "Totals round half-down on line items (legacy). Keep until INV-311 decides."
status: seamed
wave: 2
- id: tax-lookup
seam: "GET /api/tax/rate" # an HTTP seam, so the wave's done command can verify it
risk: high
depends_on: []
goldens: tests/characterization/golden/tax-lookup/
quirks: []
status: seamed
wave: 2
- id: vat-id-check
seam: "POST /api/vat-ids/validate"
risk: medium
depends_on: []
goldens: tests/characterization/golden/vat-id-check/
quirks: []
status: seamed
wave: 2
- id: invoice-pdf
seam: "GET /api/invoices/:id/pdf"
risk: medium
depends_on: [invoice-total]
goldens: tests/characterization/golden/invoice-pdf/
quirks: []
status: mapped
wave: 3
# ...one entry per slice

Trzy reguły utrzymują go w uczciwości: plaster wchodzi do fali dopiero ze statusem seamed; plaster czeka, aż wszystko z jego depends_on osiągnie cut-over; a quirks to jedyne miejsce, w którym może żyć znany błąd. Wybieraj szwy HTTP, bo polecenia fal niżej traktują zestaw HTTP jako definicję ukończenia. Szew w postaci sygnatury funkcji wymaga testów w tym samym procesie w każdym z języków i własnego polecenia sprawdzającego ukończenie.

Zestaw nagrywający testów charakteryzacyjnych na szwie HTTP

Dział zatytułowany „Zestaw nagrywający testów charakteryzacyjnych na szwie HTTP”

Nagrywanie na szwie HTTP pozwala jednemu zestawowi utrwalić system w PHP i zweryfikować jego następcę w TypeScripcie, bo oba odpowiadają na te same żądania. Ustaw TARGET_URL na wdrożenie legacy, żeby nagrywać, i na nowe, żeby weryfikować.

tests/characterization/invoice-total.char.test.ts
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
// Point TARGET_URL at the legacy system to record, at the new one to verify.
const BASE_URL = process.env.TARGET_URL ?? 'http://localhost:8080';
const cases: { id: string; body: unknown }[] = JSON.parse(
readFileSync(new URL('./invoice-total.cases.json', import.meta.url), 'utf8'),
);
// Mask what differs on every run, so the golden file pins behavior, not noise.
const scrub = (body: Record<string, unknown>) => ({ ...body, requestId: '<id>', generatedAt: '<ts>' });
describe('POST /api/invoices/total: pinned legacy behavior', () => {
it.each(cases)('$id', async ({ id, body }) => {
const res = await fetch(`${BASE_URL}/api/invoices/total`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
const observed = { status: res.status, body: scrub(await res.json()) };
await expect(JSON.stringify(observed, null, 2) + '\n').toMatchFileSnapshot(
`./golden/invoice-total/${id}.json`,
);
});
});

Nagrywasz poleceniem TARGET_URL=https://legacy.internal npx vitest run tests/characterization. Vitest zapisuje brakujący plik golden na maszynie dewelopera, ale gdy ustawiona jest zmienna CI, oblewa test (przetestowane na Vitest 5.0.2), więc pusta wyrocznia nie przejdzie w pipeline. Nigdy nie uruchamiaj vitest -u na gałęzi migracyjnej: aktualizacja plików golden to zmiana zachowania.

Funkcja scrub to miejsce, w którym ludzie najczęściej się mylą. Maskuj tylko wartości, które różnią się przy każdym uruchomieniu tego samego systemu, na przykład identyfikatory żądań i znaczniki czasu. Maskowanie pola, bo nowy system formatuje je inaczej, to decyzja, która należy do listy quirks plastra.

W trakcie fal agent sprawia, że pliki golden przechodzą, bez ich zmieniania. Egzekwuj to w sesji agenta i w CI. Ten strażnik działa dla każdego narzędzia, bo sprawdza pull request, a nie sesję:

.github/workflows/oracle-guard.yml
name: oracle-guard
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
jobs:
goldens-unchanged:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'behavior-change') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Fail if a migration PR changes pinned behavior
env:
BASE_REF: ${{ github.base_ref }}
run: |
changed=$(git diff --name-only "origin/$BASE_REF...HEAD" -- tests/characterization/)
if [ -n "$changed" ]; then
printf 'Pinned behavior changed:\n%s\n' "$changed" >&2
echo "Split it into its own PR labelled behavior-change, approved by the oracle owner." >&2
exit 1
fi

Etykietę behavior-change mogą dodać tylko osoby z uprawnieniami triage lub zapisu, ale agent z tokenem GitHub z prawem zapisu (Cloud Agent albo gh w sesji) doda ją sam: nie dawaj tokenom agentów prawa do etykiet albo niech strażnik wymaga dodatkowo akceptacji właściciela wyroczni. Ustaw strażnika i przebieg testów charakteryzacyjnych jako wymagane checki. Workflow pull_request uruchamia kopię strażnika z samego pull requesta, więc agent mógłby go przerobić tak, żeby przechodził: przypisz w CODEOWNERS także .github/workflows/ i modernization/slices.yaml właścicielowi wyroczni i włącz Require review from Code Owners, żeby zmiana strażnika wymagała tej samej akceptacji co zmiana pliku golden.

Zablokuj edycję wyroczni we wspólnych ustawieniach projektu, .claude/settings.json, gdzie wiodący / jest liczony względem projektu, do którego należy plik ustawień (głównego katalogu roboczego):

{
"permissions": {
"deny": [
"Edit(/tests/characterization/**)",
"Edit(/modernization/slices.yaml)",
"Edit(/.github/**)",
"Edit(/.claude/settings.json)",
"Edit(/.claude/settings.local.json)",
"Edit(/.claude/hooks/**)"
]
},
"sandbox": {
"enabled": true,
"filesystem": {
"denyWrite": [
"./tests/characterization",
"./modernization/slices.yaml",
"./.github",
"./.claude/settings.json",
"./.claude/settings.local.json",
"./.claude/hooks"
]
},
"network": { "allowLocalBinding": true }
}
}

Same reguły deny zatrzymują narzędzia edycji, a nie skrypt, który agent uruchomi. Przy włączonym sandboksie Claude Code stosuje reguły deny Edit(...) także do poleceń w sandboksie (v2.1.283); jawna lista denyWrite czyni to widocznym i obejmuje ścieżki, których nie blokujesz dla Edit. Wpisy dla .claude wskazują pliki, a nie cały .claude/, bo Claude Code tworzy swoje worktree w .claude/worktrees/ (v2.1.283), a reguła na cały katalog mogłaby zablokować edycje każdej jednostki /batch.

Na macOS sandbox domyślnie nie pozwala zająć lokalnego portu, więc jednostka fali nie uruchomiłaby serwera; pozwala na to sandbox.network.allowLocalBinding: true. Klucz działa tylko na macOS (sprawdzone na v2.1.283) i na Linuksie ani WSL2 niczego nie zmienia. Dodaj hook z ochrony wyroczni i sprawdź konfigurację na plastrze pilotażowym, zanim uruchomisz falę: poproś jednostkę /batch o edycję pliku golden w jej worktree (w .claude/worktrees/<nazwa>/) i potwierdź, że edycja zostaje odrzucona. Jeśli nie zostaje, reguły deny zakotwiczone w katalogu głównym nie sięgają tego worktree, więc polegaj na sandboksie i checku oracle-guard.

Kształt jest wszędzie ten sam: jeden plaster na izolowany checkout, jeden pull request na plaster, zestaw charakteryzacyjny jako definicja ukończenia.

Dla jednego plastra uruchom claude --worktree invoice-total (w skrócie -w) i wklej prompt migracyjny z sekcji niżej.

Dla całej fali wbudowany skill /batch „decomposes the work into 5 to 30 independent units, and presents a plan”; po twojej akceptacji „spawns one background subagent per unit in an isolated worktree”, a każdy z nich „implements its unit, runs tests, and publishes its change” (dokumentacja poleceń Claude Code, sprawdzona 26 września 2026 na v2.1.283). Nazwij plastry, żeby nie wymyślał własnego podziału:

/batch migrate wave 2 from modernization/slices.yaml (invoice-total, tax-lookup, vat-id-check). One unit per slice. Give unit i its own port, PORT=3001+i (3001, 3002, 3003). Each unit ports only its slice to services/billing-ts, leaves tests/characterization/ untouched, starts services/billing-ts from its own worktree on its PORT, and is done only when TARGET_URL=http://localhost:$PORT npx vitest run tests/characterization/<slice> passes.

Dla większych fal albo orkiestracji, którą chcesz uruchamiać ponownie, użyj dynamicznych przepływów pracy (dynamic workflows): Anthropic wymienia wśród zastosowań „large migrations” i podaje skalę „Dozens to hundreds of agents per run”. W planie Pro najpierw włącz je w /config.

Rozmiar fali dobieraj do przepustowości przeglądu i scalania, a nie do liczby agentów, które potrafisz uruchomić: 12 pull requestów na jednego recenzenta jednego popołudnia przesuwa wąskie gardło. Zobacz wzorcach orkiestracji.

Jak udowodnić nową ścieżkę na produkcji przed przełączeniem?

Dział zatytułowany „Jak udowodnić nową ścieżkę na produkcji przed przełączeniem?”

Testy charakteryzacyjne pokrywają wejścia, o których pomyślałeś; równoległe uruchomienie pokrywa to, co wysyłają użytkownicy. Uruchamia starą ścieżkę jako kontrolną, a nową jako kandydata, zwraca wynik kontrolnej i raportuje każdą różnicę:

src/modernization/shadow.ts
type Outcome = 'match' | 'mismatch' | 'error';
export interface ShadowOptions<I, O> {
slice: string;
legacy: (input: I) => Promise<O>;
modern: (input: I) => Promise<O>; // must have no side effects while shadowing
enabled: (input: I) => boolean; // your feature flag: per tenant or a percentage
normalize?: (output: O) => unknown; // sort keys, drop fields that may differ
record: (event: { slice: string; outcome: Outcome; detail?: unknown }) => void;
}
export function shadow<I, O>(opts: ShadowOptions<I, O>): (input: I) => Promise<O> {
const norm = opts.normalize ?? ((o: O) => o);
return async (input) => {
const control = await opts.legacy(input); // users still get the legacy answer
if (opts.enabled(input)) {
// Not awaited: the comparison never adds latency or errors to the request.
// Recording failures are swallowed by the final catch, so they cannot surface either.
void opts
.modern(input)
.then(
(candidate) => {
const same = JSON.stringify(norm(candidate)) === JSON.stringify(norm(control));
opts.record({ slice: opts.slice, outcome: same ? 'match' : 'mismatch', detail: same ? undefined : { control, candidate } });
},
(err: unknown) => opts.record({ slice: opts.slice, outcome: 'error', detail: String(err) }),
)
.catch(() => {});
}
return control;
};
}

Dwie reguły czynią go bezpiecznym. Kandydat w cieniu musi tylko czytać (skieruj jego zapisy na magazyn-cień albo je zaślep), inaczej każde żądanie wydarzy się dwa razy. Poza tym record dostaje prawdziwe dane, więc najpierw usuń dane osobowe.

Zdefiniuj liczby, zanim na nie spojrzysz, żeby nikt nie negocjował progu po zobaczeniu wyniku:

MetrykaDefinicjaBramka przełączenia, którą zalecamy
Porównane wywołaniaŻądania, w których zadziałały obie ścieżki i wywołało się recordPełny cykl biznesowy, na przykład zamknięcie miesiąca
Odsetek niewyjaśnionych rozbieżnościRozbieżności niesklasyfikowane jako naprawiony port-bug ani zaakceptowany legacy-quirk, na porównane wywołanieZero na koniec okna
Odsetek błędów kandydataWyniki error na porównane wywołanieZero albo każdy błąd powiązany ze zgłoszeniem
Pokrycie plików goldenUdział identyfikatorów przypadków golden, których kształt wejścia pojawił się w żywym ruchuKażdy przypadek golden widziany co najmniej raz albo luka wyjaśniona

Przełączenie podpisuje CTO albo wskazany właściciel serwisu; zapis szkicuje agent. Trzymaj go w modernization/cutover/<slice>.md i podlinkuj z pull requesta, który przestawia flagę:

  • Zestaw charakteryzacyjny zielony na nowej ścieżce, pliki golden bez zmian od utrwalenia (linki do przebiegów oracle-guard).
  • Wynik testów mutacyjnych plastra legacy na poziomie mutation_score_floor lub wyżej, zmierzony w chwili nagrywania plików golden.
  • Funkcje dopasowania i kontrola typów zielone w nowym serwisie.
  • Daty okna cienia, liczba porównanych wywołań i zero niewyjaśnionych rozbieżności.
  • Każdy legacy-quirk albo przeniesiony celowo, albo zmieniony we własnym, zatwierdzonym pull requeście behavior-change.
  • Kroki wdrożenia i metryka, która zatrzymuje każdy krok, spisane przed pierwszym krokiem (zob. stopniowe wdrażanie (progressive delivery)).
  • Przećwiczone wycofanie: flaga przestawiona z powrotem na stagingu, ścieżka legacy odpowiedziała poprawnie.
  • Data usunięcia ścieżki legacy i jej flagi.

Zatwierdzający może powiedzieć „tak” bez czytania zmigrowanego kodu, jak w dowodach zamiast diffów.

Co się psuje w modernizacji prowadzonej przez agentów?

Dział zatytułowany „Co się psuje w modernizacji prowadzonej przez agentów?”

Agent „poprawia” plik golden. Objaw: zielony pull request migracyjny dotyka tests/characterization/. Wyjście: oracle-guard go odrzuca; jeśli plik golden zmienił się już na gałęzi głównej, cofnij zmianę i nagraj go ponownie z legacy.

Pliki golden przechodzą, ale produkcja się nie zgadza. Objaw: rozbieżności w cieniu na wejściach, których zestaw nigdy nie nagrał. Wyjście: dodaj każde reprezentatywne wejście jako przypadek golden (czwarty prompt je szkicuje); wiele rozbieżności oznacza, że plaster utrwalono zbyt cienko.

Pull requesty z jednej fali wchodzą w konflikt. Objaw: plastry z jednej fali edytują ten sam router, manifest albo wspólny typ. Wyjście: scal współdzielone rusztowanie przed falą i zaostrz depends_on. Plastry, które ciągle się zderzają, były jednym plastrem.

Współdzielona baza danych to ukryte sprzężenie. Objaw: nowy serwis czyta tabelę, do której pisze kod legacy, więc plastrów nie da się przełączyć niezależnie. Wyjście: zrób ze szwu danych osobny plaster z plikami golden, a zmiany schematu prowadź wzorcem expand, migrate, contract z pracy z bazami danych.

Długi ogon staje w miejscu i pali budżet. Objaw: 80% plastrów jest przełączonych, a podejść agentów na pozostałych przybywa bez scaleń. Wyjście: daj każdemu pozostałemu plastrowi właściciela i datę usunięcia, po dwóch nieudanych uruchomieniach przekaż go człowiekowi po lepszy szew albo więcej plików golden, a na najtrudniejszych użyj podejść best-of-N (Codex cloud) albo pętli sterowanej celem. Mierz scalone plastry na tydzień, nie uruchomienia agentów. Modernizacja zatrzymana na 80% utrzymuje dwa systemy bez końca.