AIDLC on Legacy Code
Most real software is not new. Agents can read a large old codebase faster than any person, and they can also change it faster than anyone can check. The method that works is the one that makes behavior visible first: build the context, pin the behavior with tests, then change small pieces at a time.

Why Legacy Code Is Different
Legacy and brownfield systems are where agents help most and hurt most. They help because the tedious work is enormous: reading unfamiliar modules, tracing call paths, upgrading frameworks across thousands of files. They hurt because the intent behind old code is often undocumented, the tests are thin or absent, and an agent will produce a confident, plausible change either way.
Michael Feathers, in Working Effectively with Legacy Code (Prentice Hall, 2004), defines legacy code simply as code without tests. The definition fits the AI era well. Without tests there is no way to tell a correct agent change from a wrong one, so the first job is never the change itself. It is making the current behavior observable. The rest of this page follows that order, and it applies the same engineering practices the playbook describes everywhere else, only with a stricter gate on what counts as safe.
Build the Context First
An agent working in an old codebase needs a map before it needs a task. Have an agent draft architecture notes: the main modules, how data flows between them, the entry points, the jobs that run on a schedule, and the parts it could not understand. Then have people who know the system review and correct those notes. The agent is fast at the survey; the people supply what the code cannot say, such as why a strange branch exists or which customer depends on it.
Keep the reviewed notes in the repository next to the code, where the agent loads them on every run, and link them to tickets, incident history, and design documents through a context graph. Treat the notes as living files, because a stale map is worse than none: the agent follows it anyway. One rule matters above the rest. Never trust an agent's summary of code that no person has read. A summary is a hypothesis until someone who knows the system has checked it.
Characterization Tests Before Any Change
A characterization test, a technique Feathers describes in the same book, records what the code actually does today, not what it should do. You call the code, observe the result, and assert that result. The suite becomes a net: if a later change alters behavior, a test fails and shows exactly where.
Agents are well suited to producing these. Point one at a module, have it exercise real inputs and write down the outputs, then have a person sample the tests for quality. Check that they would fail if the behavior changed; the mutation-testing advice under tests first applies fully, because a generated test that asserts nothing gives false comfort. The result is a characterization suite that later gates every agent change in this area.
Preserve behavior, record the bugs
Characterization tests pin bugs along with everything else. Resist fixing them inline. Mark the test, log the defect as its own ticket, and fix it in a separate, reviewed change. Mixing a refactor with a behavior change makes the suite useless as a safety net, because a failure no longer says which kind of change caused it.
Strangler Fig Migration
Martin Fowler's "Strangler Fig" (martinfowler.com, 08-22-2024) describes modernizing a legacy system gradually: build new components alongside the old system and move functionality over piece by piece, instead of attempting a risky full replacement. The old system keeps serving traffic the whole time, and it shrinks until it can be retired.
The pattern suits agents because each slice is a bounded task with a clear finish line. Route one capability to the new implementation, compare its output against the old one on real traffic or the characterization suite, and only then take the next slice. If a slice goes wrong, the old path is still there. This is small batches applied at the architecture level, and each slice can be assigned a risk tier: a read-only report moves with light review, a payment path moves with the heaviest.
Upgrades and Dead Code as Agent Work
Framework and language upgrades
A strong work class for agents: the change is mechanical, repetitive, and checkable. Use codemods for the patterns you can express exactly, and an agent for the long tail the codemod misses. The characterization suite is the verifier: an upgrade is done when the same tests pass against the new version. Upgrade one major version at a time, and keep each step as its own reviewed change.
Dead-code identification
Agents are good at finding code nothing seems to call. Treat the list as a candidate list, not a verdict, because reflection, configuration, scheduled jobs, and external callers hide real use. Confirm with production traces or logs, remove in small batches, and keep each removal easy to revert.
Capturing Knowledge From People Who Are Leaving
The most valuable context in an old system lives in the heads of the people who built it, and it leaves when they do. Use the agent to make capture cheap. Have it prepare questions from the code: why does this function special-case that value, what does this job do on the last day of the month. Record the interview, have the agent turn the answers into notes, and let the expert correct the notes. File each answer against the code it explains, in the architecture notes, so the next agent run inherits it. A short, structured conversation before a departure is worth far more than a handover document written afterward, and the team side of this work is part of upskilling the people who stay.
Data Migrations With Expand and Contract
Code can be rolled back; data often cannot. Danilo Sato's "Parallel Change" (martinfowler.com, 05-13-2014), also called expand and contract, gives the safe shape: add the new version beside the old, move clients across, then remove the old. For a schema change that means add the new column or table, write to both, backfill, switch reads, and only later drop the old structure. Each step is small, reversible, and deployable alone.
Let agents draft the migration scripts and the backfill, but put the production run behind a human gate, a rehearsal on a copy of real data, and a written rollback. This is the highest tier of risk-tiered review, and it is not a place for unattended agents.
Legacy Anti-Patterns
The agent-driven big-bang rewrite
A fast agent makes a full rewrite feel cheap, so teams attempt one. The old system's undocumented behavior is exactly what a rewrite loses. Strangle it instead, one slice at a time.
Refactoring without tests
An agent will restructure untested code and report success. With nothing to check against, success is only a claim. Write the characterization tests first, every time.
Trusting a summary nobody has read
Decisions built on an agent's description of code that no person has verified inherit its errors. Have someone who knows the system review the notes before they drive any change.
Where It Fits in AIDLC
- Analyze: agent-drafted architecture notes, reviewed by people who know the system, plus an inventory of risk areas and missing tests.
- Ideate: choose the strangler slices and assign each a risk tier before any code moves.
- Develop: characterization tests first, then small batches of agent changes, with bugs recorded rather than fixed inline.
- Launch: route traffic to the new slice gradually, compare outputs, and keep the old path ready for rollback.
- Curate: retire the old slice, remove confirmed dead code, and fold what was learned back into the notes and the rules.
The worked example shows the phases on a small feature. Old systems are usually run by Kanban-style flow, covered in Scrum and Kanban. Teams that carry this work also need the learning practices in Teams and Skills. For how the method differs from the classical ML lifecycle, see AIDLC vs the ML lifecycle.
Frequently Asked Questions
Yes, when behavior is pinned first. Michael Feathers defines legacy code as code without tests, so the safe order is to build reviewed architecture notes, write characterization tests that record what the code does today, and only then let agents change it in small batches. Without that net, an agent's change can look right and still alter behavior nobody documented.
A characterization test records what existing code actually does, not what it should do. You run the code, capture the result, and assert it, so any later change that alters behavior fails a test. Michael Feathers describes the technique in Working Effectively with Legacy Code. Agents can draft these tests quickly, but a person should check that they fail when behavior changes.
No. A big-bang rewrite throws away behavior the old system carries without documenting it. Martin Fowler's strangler fig pattern is the safer route: build new components alongside the old system and move functionality across one slice at a time, comparing results as you go. Agents speed up each slice, but the old path stays available for rollback.
Start With the Map and the Tests
Before an agent touches an old system, give it a reviewed map and a net of tests. Everything after that is small, reversible steps.