Working with Legacy Code
Reclaiming the Past: Working with Legacy Code and AI
One of the most daunting tasks for any software engineer is stepping into a massive, complex legacy codebase. These systems are often characterized by undocumented tribal knowledge, fragile architectures, and years of technical debt. The thought of introducing an autonomous AI agent into such an environment can be terrifying. What if it breaks something critical? What if it misunderstands a subtle edge case?
The fear is valid. Without a proper strategy, an AI agent will guess, and in a legacy system, guessing leads to regression.
However, by applying the principles of Continuous Alignment and Self-Improving Software, we can use agentic AI to not only maintain legacy systems but to actively reclaim and document them.
The Challenge of Legacy Systems
The primary hurdle in legacy code is the lack of context. The original developers might be long gone, and the design decisions they made are buried in thousands of lines of code.
When you ask an AI to "add a feature" to a legacy repo, it lacks the mental model of how the different parts of the system interact. Without that model, the AI defaults to its general training data, which may not align with the specific patterns present in your codebase.
The key to success is to stop the guessing by providing Living Documentation.
The Secret: Documentation for AI, by AI
To integrate AI into a legacy workflow, we must establish a knowledge base that the AI can actually use. In the previous post, we discussed Self-Improving Software—the idea that AI should update its own documentation. In a legacy context, this becomes the primary tool for de-risking development.
A practical solution for this is the build_docs method.
The core idea is to create a dedicated section of your project documentation (e.g., a /build_docs/ folder) that is specifically written by and for AI agents. While humans can read it, its primary purpose is to bootstrap future agentic work.
This AI-targeted documentation serves three critical roles:
- Bootstrap: It provides the "onboarding" context the next AI agent needs to understand the system.
- Guardrail: It explicitly lists known fragile areas, complex dependencies, and "don't touch" zones.
- Guide: It defines the patterns and conventions the AI should follow to ensure its changes are idiomatic to the existing system.
Introducing AI to a Legacy Repo: A Step-by-Step Workflow
If you’re starting with a legacy codebase and want to use AI safely, follow this iterative process:
Step 1: The AI Audit
Ask the AI agent to perform a high-level audit of the codebase. Have it explore the file structure, identify key modules, and summarize the main technologies used. This initial "read-only" pass builds the agent's internal model without changing a single line of code.
Step 2: Create the Initial Context
Have the AI author the first version of your AI-targeted documentation. Ask it to "summarize what you’ve learned about this system's architecture and common patterns for another AI agent." This is where the AI-to-AI communication begins.
Step 3: Human Review and Refinement
As the human lead, review the AI-generated summary. Correct any misunderstandings, add notes about particularly tricky legacy bugs, and specify the "don't touch" zones where the AI should be extra cautious. This is the Continuous Alignment step in action.
Step 4: Start Small
Once the context is established, give the AI a small, discrete, and verifiable task. Use the Task Management methods we discussed previously. Ensure the AI follows its own newly created "guide" and updates the documentation after it finishes.
Refactoring Legacy Code with AI
Refactoring legacy code is often avoided because of the risk of breaking undocumented behavior. AI can significantly lower this barrier by identifying patterns, suggesting modern equivalents, and performing transformations in small, controlled steps.
- Incremental Modernization: Instead of a "big bang" rewrite, use AI to refactor one module at a time. The agent can analyze the existing logic, suggest a cleaner implementation, and apply it while ensuring the external interface remains unchanged.
- Pattern Matching and Migration: AI excels at spotting repetitive patterns common in legacy systems. It can help automate the migration from outdated libraries or patterns (e.g., converting manual callbacks to
async/await) across the entire codebase with high consistency. - Contextual Reasoning: Before any change, ask the AI to "explain the logic of this function and identify any subtle edge cases." This forced thought process ensures that the AI repeats what it's discovered back to you. When you have the feeling that you and the AI are on the same page, only then is the AI ready to make changes.
When you have the feeling that you and the AI are on the same page, only then is the AI ready to make changes.
Unit Testing for Legacy Systems: Building the Safety Net
Legacy code is often "legacy" precisely because it contains many years of hard-won product knowledge and bugfixes. If the code lacks unit tests, then you can find yourself stuck in a tough spot: you're afraid to change the code because there are no tests, but the idea of adding tests to a massive codebase feels like it will take forever. I've been in this situation many times, and AI can help backfill hundreds, or thousands of tests in a day. The tests may not be perfect, of course, but they will lay the foundation that enables incremental improvement.
- Characterization Tests: Use AI to write "Golden Master" or characterization tests. These tests capture the current behavior of the system—even if that behavior is technically incorrect—providing a baseline to ensure that future refactoring doesn't introduce unexpected regressions.
- Untangling Dependencies: AI can analyze a complex class, identify its external dependencies, and generate the necessary boilerplate and mocks for a unit test. This significantly reduces the manual effort required to make untested code testable.
- Edge Case Discovery: Once a basic test suite is in place, have the AI specifically analyze the code for the code paths and conditionals that go far beyond the happy path, which can effectively identify missing edge cases. Then have the AI write tests for those edge cases.
AI has the ability to reason about boundary conditions and can uncover bugs that have been hiding in the legacy code for years.
Conclusion: Turning the Tide
Legacy code doesn't have to be a source of constant stress. By using agentic AI to build and maintain a living map of the system, you can turn a black box into a manageable project.
To summarize Continuous Alignment so far, we’ve explored how to collaborate with AI as a peer, how to build an autonomous developer stack, how to break down work effectively, and how to create self-improving systems that can even conquer legacy code.
The goal isn't to replace the human developer; it's to empower them. By investing in alignment and context, we can build software that is more robust, better documented, and easier to evolve than ever before.
Subscribe to Continuous Alignment
Get new posts delivered straight to your inbox.
No spam, just essays on using AI to accomplish more.