Big-bang rewrites fail. Strangler fig migrations succeed when every cut is proven — not when an agent “migrates the service” in one PR. Use coding agents to accelerate endpoint-sized cuts: generate adapters, dual-run harnesses, and contract tests that fail the cutover when response drift exceeds semantic tolerances you define.
⚡ TL;DR: Agents propose strangler adapters and characterization tests; humans own cutover. Dual-run old vs new on shadow traffic; compare with schema-aware matchers (not brittle string equality). Cut traffic only when drift < threshold for N windows. Illustrative pace: 1–3 endpoints/week with agent assist vs months of stalled rewrite.
Strangler shape the agent must respect
Client → Facade / Routing layer
├─ Legacy monolith handler (default)
└─ New service handler (canary %)
└─ Dual-run comparator (shadow)
Encode this in Cursor rules / Claude instructions so the agent never deletes the legacy path in the same PR that adds the new one. See Cursor Rules for TypeScript Monorepos.
# migration rule excerpt
- One endpoint per PR unless a shared DTO must move first.
- Never remove legacy routes in the introducing PR.
- Always add dual-run tests under packages/migration-contracts.
- ❌ No “while we’re here” refactors outside the endpoint boundary.
Agent task: characterization tests first
Before writing the new handler, the agent should freeze legacy behavior:
// characterization from recorded fixtures — illustrative
import { legacyHandle } from "../legacy/invoices";
import fixtures from "./fixtures/invoices.get.json";
test.each(fixtures)("legacy characterization %s", async (fx) => {
const res = await legacyHandle(fx.req);
expect(res.status).toBe(fx.res.status);
expect(normalize(res.body)).toEqual(normalize(fx.res.body));
});
function normalize(body: any) {
const { requestId, serverTime, ...rest } = body;
return rest; // strip volatile fields
}
✅ Agents generate fixtures from sanitized traffic.
❌ Agents invent expected JSON from memory of another codebase.
Dual-run contract tests with semantic tolerances
type Tolerance = {
ignorePaths: string[]; // json-pointer
numericEpsilon?: Record<string, number>;
arrayOrderInsensitive?: string[];
};
export function semanticEqual(a: unknown, b: unknown, tol: Tolerance): string[] {
const drifts: string[] = [];
// walk both trees; skip ignorePaths; compare numbers with epsilon
// return human-readable drift list for the PR bot
return drifts;
}
test("dual-run GET /invoices/:id", async () => {
const req = sampleRequest();
const [legacy, next] = await Promise.all([
legacyHandle(req),
newHandle(req),
]);
const drifts = semanticEqual(legacy.body, next.body, {
ignorePaths: ["/requestId", "/serverTime"],
numericEpsilon: { "/taxCents": 1 },
});
expect(drifts).toEqual([]);
});
Wire shadow traffic in staging: copy a % of GETs to the new path, log drifts to S3, fail the promotion pipeline when drift rate > 0.1%. Semantic review bots (companion) should treat migration PRs as high-signal.
Cutover playbook the agent cannot skip
# cutover checklist enforced in CI labels
# 1. dual-run green for 48h on staging shadow
# 2. canary 5% → 25% → 100% with error-budget gates
# 3. only then open PR to remove legacy handler
gh pr edit "$PR" --add-label "migration/cutover-ready"
// routing facade — illustrative
export async function getInvoice(ctx, id: string) {
const mode = await flags.get("invoices.get.impl", ctx.tenantId);
if (mode === "new") return newHandle(ctx, id);
if (mode === "shadow") {
const legacy = await legacyHandle(ctx, id);
void shadowCompare(legacy, () => newHandle(ctx, id));
return legacy; // always return legacy until cut
}
return legacyHandle(ctx, id);
}
Keep agent tools sandboxed (Lambda sandboxes) — migration assistants should not apply production traffic flags themselves.
What to automate vs what stays human
| Step | Agent | Human |
|---|---|---|
| Draft adapter + DTOs | ✅ | review |
| Characterization fixtures | ✅ assist | approve redaction |
| Dual-run matcher tuning | propose | set tolerances |
| Canary % changes | ❌ | ✅ on-call |
| Delete legacy | draft PR | merge after soak |
Atomic commits help (Agentic Git Workflows): tests, adapter, flags, deletion as separate SHAs.
Closing checklist
✅ Dos
– ✅ One endpoint (or tightly bound group) per migration PR
– ✅ Characterization tests before new implementation
– ✅ Semantic dual-run with explicit tolerances
– ✅ Feature-flag cutover with soak windows
– ✅ Delete legacy only after canary success
❌ Don’ts
– ❌ Don’t let the agent rewrite the entire monolith in one branch
– ❌ Don’t compare raw JSON including volatile fields
– ❌ Don’t flip 100% traffic from an unattended agent tool
– ❌ Don’t skip CODEOWNERS on migration facades
– ❌ Don’t mix unrelated refactors into strangler PRs
Related reading
- Cursor Rules for TypeScript Monorepos: Make AI Edits Stick
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
- Semantic Diff Review: AI That Flags Behavioral Drift Only (companion)
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs (companion)
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: RAG Over ADRs: Architecture Decision Retrieval for Coding Agents - CheatCoders