teachyou.ai academy
← All posts
Codex

OpenAI Codex for Legacy Code Modernization

Pramod Dutta · May 18, 2026 · 13 min read

Why Legacy Modernization Keeps Stalling

Every engineering leader has the same slide in their backlog: "Modernize the legacy system." And every year it slips, because the risk math never works out. The COBOL batch job that reconciles investment portfolios overnight, the fifteen-year-old Java monolith that nobody fully understands anymore, the PL/SQL stored procedures encoding business rules that were never written down anywhere else — these systems are terrifying to touch precisely because they work. Nobody wants to be the engineer who "modernized" a payroll system and broke tax withholding for six weeks.

The traditional options have both been bad. Big-bang rewrites take eighteen months, burn budget, and usually get cancelled halfway through when the original team lead leaves. Manual, line-by-line refactoring is safer but so slow that the legacy system outlives the modernization effort. Neither approach scales to the actual size of the problem, which is that most large organizations are sitting on decades of undocumented, business-critical code that nobody wants to own.

OpenAI Codex CLI — the open-source, terminal-based coding agent built in Rust — changes the economics of this problem. It doesn't eliminate the risk of legacy modernization, but it does something more useful: it makes the *process* auditable, repeatable, and incremental, so that instead of one terrifying rewrite you get a series of small, provable, reversible steps. This article walks through how that actually works in practice, using the structured methodology OpenAI itself documents for exactly this use case, and where the sharp edges still are.

What Makes Codex CLI Different From "Just Asking an LLM"

If you've tried pasting a COBOL program into a chat window and asking "please modernize this," you already know why that doesn't work. The model has no persistent memory of the codebase, no ability to run anything, and no way to verify its own output against real behavior. It will happily hallucinate a plausible-looking rewrite that silently drops an edge case in the interest calculation.

Codex CLI is built differently. It runs locally in your terminal, inside the actual project directory, with the ability to read files, write files, execute commands, and iterate based on real output — not guesses. A few capabilities matter specifically for legacy work:

  • Full repository context — Codex is designed to read and reason about large, sprawling codebases rather than isolated snippets, which matters enormously when business logic is scattered across copybooks, job control language, and a dozen interdependent modules.
  • Command execution — it can actually run the legacy build, execute the old binary against sample input, and diff the output against a new implementation, rather than assuming both versions match.
  • AGENTS.md project memory — a version-controlled instructions file that tells Codex your team's actual conventions, so it doesn't have to reverse-engineer them from scratch every session.
  • Subagents for parallelizing independent chunks of discovery or migration work.
  • Cloud tasks for kicking off longer-running modernization jobs without tying up your local terminal.

None of this makes the agent infallible. What it does is turn "rewrite the legacy system" from a single unverifiable leap into a chain of small steps you can check at every stage.

AGENTS.md: Teaching Codex Your Legacy System's Rules

The single most underused feature for legacy work is AGENTS.md. This is a plain markdown file — no special format, no DSL — that Codex reads before doing any work in a repository. Codex discovers these files using a clear precedence order: it starts in your global Codex home directory (checking for AGENTS.override.md, falling back to AGENTS.md), then walks from your project root down to your current working directory, checking each folder along the way. All discovered files get concatenated together, with more specific (closer) files taking precedence over more general ones.

That layering is exactly what legacy systems need. A payments-processing subsystem inside a twenty-year-old monolith often has different testing rules, different deployment gates, and different "do not touch this without sign-off" comments than the rest of the codebase. Instead of repeating those constraints in every prompt, you write them once:

# AGENTS.md — legacy-portfolio-system

## Build & test
- Build with the vendor COBOL compiler: `make -C cobol/ all`
- Run parity tests with: `pytest modern/tests/ -k parity`
- Never modify files under `cobol/copybooks/` without a documented
  ExecPlan entry explaining why.

## Business rules of record
- Interest accrual uses 30/360 day-count convention (see
  cobol/INTCALC.cbl, lines 40-120). This is intentional and
  regulatory — do not "fix" it to actual/365.
- The nightly batch job (JCL: PORTRPT01) must complete before
  6:00 AM ET. Any modernized replacement must preserve this SLA.

## Conventions for modernized code
- Target stack: Python 3.12, FastAPI, SQLAlchemy
- All new modules require a parity test comparing output against
  the legacy COBOL binary before merge.

This single file does an enormous amount of work. It stops Codex from "helpfully" fixing the day-count convention that looks like a bug but is actually a regulatory requirement. It tells the agent which files are off-limits without documented justification. And because it's just a file in version control, it evolves with the project and gets reviewed like any other change.

The Five-Phase Modernization Framework

Rather than treating "modernize this system" as one undifferentiated task, the effective pattern — documented by OpenAI using a COBOL-based investment portfolio system as the running example — breaks the work into five phases. Each phase produces a concrete artifact you can read, review, and challenge before moving forward.

  1. Phase 0 — Governance setup. Create .agent/AGENTS.md and .agent/PLANS.md files that define how planning and architectural decisions will be documented going forward. This is the scaffolding phase: before any code changes, you establish how the team will track decisions.
  2. Phase 1 — Scope definition. Identify one realistic pilot flow — not the whole system — and have Codex draft pilot_execplan.md, the central coordination document. A good ExecPlan answers four questions: what's in scope, why it matters, what steps will be taken, and how you'll know you're done.
  3. Phase 2 — Discovery. Codex generates a document (for example pilot_reporting_overview.md) that inventories the existing programs, job orchestration, data flows, and business rules involved in the pilot flow — so engineers can review the system's actual behavior without reading every line of legacy code themselves.
  4. Phase 3 — Forward design. Three artifacts get produced: a design document describing the target architecture, a machine-readable API contract (an OpenAPI YAML file), and a validation document defining the parity-testing strategy — how you'll prove the new implementation matches the old one.
  5. Phase 4 — Implementation and parity testing. Codex generates the modernized implementation, then runs the legacy and modern versions side by side against identical inputs, comparing outputs and iteratively fixing discrepancies until behavior matches.
  6. Phase 5 — Scale the pattern. Once the pilot is proven, the templates, ExecPlan structure, and validation approach get reused for the next flow, rather than reinventing the process each time.

The point of this structure isn't ceremony for its own sake. It's that every phase transition is a natural review gate. A tech lead can read pilot_execplan.md and say "no, that's the wrong pilot flow" before a single line of new code exists. A domain expert can review pilot_reporting_overview.md and catch a misunderstood business rule before it gets baked into the new implementation.

Running the Discovery Phase in Practice

Discovery is where most modernization efforts quietly go wrong, because engineers assume they understand what the legacy system does and skip straight to rewriting it. Codex's advantage here is patience — it will actually trace a batch job's data flow across a dozen COBOL copybooks and JCL steps without getting bored or missing a file.

A typical discovery prompt inside the Codex CLI session looks like this:

Create or update pilot_reporting_overview.md with two top-level
sections: "Current System" and "Business Rules". Under Current
System, trace the full execution path of job PORTRPT01 from JCL
submission through each COBOL program it calls, including which
DB2 tables are read and written at each step. Under Business
Rules, extract every calculation, validation, or conditional
branch that encodes a business decision, and cite the exact
file and line number for each.

The output is not a rewrite — it's documentation of the current behavior, with citations back to source lines so a human reviewer can verify each claim rather than trusting it blindly. This is the artifact that gets circulated to the domain experts (often people who haven't looked at the COBOL in years) for sign-off before anything gets touched.

Designing the Target Architecture

Once discovery is validated, Phase 3 shifts Codex into forward design. This is where you specify the target stack, and Codex proposes how the legacy structure maps onto it. A practical prompt:

Based on pilot_reporting_overview.md, draft pilot_reporting_design.md
describing the target architecture for the portfolio reporting
flow. Propose a FastAPI service structure, a SQLAlchemy data model
for the DB2 tables identified, and a batch/cron replacement for
JCL job PORTRPT01. Also draft an OpenAPI file at
modern/openapi/pilot.yaml capturing the report generation endpoint.

The OpenAPI contract matters more than it might seem. It's a machine-readable, unambiguous specification of what the new system must do — which means it can be validated, linted, and used to generate client stubs, independent of whatever implementation Codex eventually writes. If the contract is wrong, you find out before implementation, not after.

Parity Testing: The Part You Cannot Skip

This is the single most important discipline in the entire framework, and it's the part teams are most tempted to shortcut under deadline pressure. The core idea: never trust that the new implementation is correct because it "looks right." Prove it by running both systems against identical input and diffing the output.

A parity test scaffold, generated early in Phase 3 and filled in through Phase 4, looks something like this:

import subprocess
import json
from pathlib import Path

FIXTURES = Path("modern/tests/fixtures")

def run_legacy_cobol(input_file: Path) -> dict:
    """Invoke the compiled COBOL program and parse its report output."""
    result = subprocess.run(
        ["./cobol/bin/PORTRPT01", str(input_file)],
        capture_output=True, text=True, check=True,
    )
    return parse_legacy_report(result.stdout)

def run_modern_service(input_file: Path) -> dict:
    """Call the new FastAPI implementation with the same input."""
    from modern.pilot.reporting import generate_report
    return generate_report(input_file)

def test_parity_daily_portfolio_report():
    for fixture in FIXTURES.glob("*.dat"):
        legacy_output = run_legacy_cobol(fixture)
        modern_output = run_modern_service(fixture)
        assert legacy_output == modern_output, (
            f"Mismatch on {fixture.name}: "
            f"legacy={legacy_output} modern={modern_output}"
        )

When this fails — and it will, repeatedly, especially on rounding, date boundaries, and edge-case account states — the fix-and-recheck loop is where Codex earns its keep. A useful debugging prompt at this stage:

The parity test fails on fixture q4_negative_balance.dat: legacy
output shows interest of 142.50, modern output shows 142.55.
Explain why the outputs differ by tracing both code paths, and
propose the smallest possible change to the modern implementation
to fix it — do not change the legacy COBOL.

Notice the framing: "smallest possible change." This is deliberate. In legacy modernization, sprawling fixes that touch ten files to solve one rounding bug are how new bugs get introduced. Constrain the agent to minimal, explainable diffs, and review every one.

Handling the Business Rules Nobody Wrote Down

The hardest part of any legacy modernization isn't the syntax translation — Codex is quite good at converting COBOL PERFORM loops into Python functions or JCL steps into a scheduler DAG. The hard part is the business logic that exists only as tribal knowledge encoded in decades-old conditionals: the special handling for accounts opened before a certain date, the rounding exception that only applies to a specific product code, the "temporary" workaround from 2009 that became permanent.

Codex cannot know which of these are load-bearing business rules and which are genuine bugs worth fixing. That distinction has to come from domain experts, which is exactly why the discovery phase produces a reviewable document instead of jumping straight to code. Treat every extracted rule as a question to a human, not a fact:

  • "This branch skips fee calculation for account type 07 — is this an intentional exemption, or dead code from a discontinued product line?"
  • "This job assumes the batch always runs after 5 PM ET — is that assumption still safe with the new scheduler?"

Bake these open questions directly into pilot_reporting_overview.md as a "Needs Confirmation" section, and don't let Phase 3 design start until they're resolved. This is a governance step, not a technical one, but skipping it is how modernized systems ship with silently deleted business rules.

Scaling Beyond the Pilot

The reason the framework insists on a small pilot flow first — one report, one batch job, one bounded slice — rather than "modernize the whole claims system" is that the first pilot is where you calibrate the process itself. How long does discovery actually take for your codebase? How many parity mismatches surface per thousand lines? How much domain-expert review time does each ExecPlan actually consume? You cannot answer these questions from a whiteboard; you learn them by running one flow through all five phases.

Once the pilot is proven — parity tests green, domain experts signed off, the new service running in shadow mode against production traffic without being the system of record yet — Phase 5 turns the pilot into a template. The AGENTS.md conventions, the ExecPlan structure, the parity-test harness, and the OpenAPI contract pattern all get reused for the next flow, and the next. This is what actually gets a legacy modernization program to scale past a single proof-of-concept: not a bigger rewrite, but the same small, verifiable loop run many times with decreasing overhead each round, because Codex — and the humans reviewing it — already know the conventions from AGENTS.md.

Where This Approach Still Needs Human Judgment

It's worth being honest about the limits. Codex CLI does not understand your regulatory environment, does not know which "bug" is actually a compliance requirement, and cannot decide whether a modernization program is worth the engineering investment in the first place. Long-running autonomous sessions still need checkpoints — don't let an agent run unsupervised through Phase 4 implementation without reviewing diffs at reasonable intervals. And parity testing is only as good as your fixture coverage: if your test fixtures don't include the account states that only occur once a quarter, a passing parity suite gives false confidence.

The realistic framing is that Codex compresses the mechanical part of modernization — reading old code, drafting the equivalent new code, running comparisons, iterating on discrepancies — from weeks of manual effort into hours, while leaving the judgment calls (which rules are intentional, which pilot to pick first, when a design is actually ready) exactly where they belong: with the engineers and domain experts who own the system's outcomes.

Getting Started on Your Own Legacy System

If you're sitting on a legacy system that's overdue for this treatment, the practical first move is small: pick the single lowest-risk, most well-understood batch job or report in the system — not the scariest one — and run it through all five phases before touching anything else. Write the AGENTS.md file first, even if it's three lines long. Let Codex draft the discovery document and read it line by line yourself before approving Phase 3. The goal of the first pilot isn't to modernize anything important; it's to prove the loop works on your codebase, with your team, before you point it at the system that actually keeps the business running.

For engineers who want hands-on practice with this exact workflow — installing Codex CLI, structuring AGENTS.md files, writing ExecPlans, and building parity-test harnesses against real legacy-style codebases — the OpenAI Codex CLI Tutorial course on teachyou.ai walks through each phase step by step, with working examples you can adapt directly to your own modernization backlog.