Start with what must not break
A legacy system is usually one the business still depends on. It may be old, but it also carries years of operating decisions, exceptions, calculations, and integrations. Some of those rules were never written down outside the system.
Before choosing a path, list what must not break. Include money paths, calculations with legal or contractual weight, integrations other teams or customers rely on, reports people use to make decisions, and the behavior users expect even when it looks odd.
Name the people who know why those odd rules exist. Their time is part of the work. A modernization plan built only from code can miss the business reason behind behavior that looks accidental.
UCS product engineering modernizes in stages: a product and technology audit first, then the target architecture and migration sequence, with refactoring, API boundaries, cloud changes, and data migration as separate increments. That order protects the system before anyone changes it.
The first decision is what to preserve. Once the do-not-break list is visible, the team can choose where a wrapper is enough, where the internals need staged refactoring, and where a capability deserves a new foundation.
That list should also separate daily inconvenience from business risk. A slow screen may be annoying, while a calculation, entitlement rule, or integration failure may change money, service, or compliance outcomes. Treat the riskier behavior as the anchor for sequencing.
Reach
What hurts: New products and integrations can’t reach the system
Usual starting point: Wrap
Change
What hurts: Every change is slow or breaks something
Usual starting point: Pin behavior, then refactor in stages
Platform or data model
What hurts: The platform is unsupported, or the data model no longer fits the business
Usual starting point: Pin behavior, wrap, then rewrite by capability
Stable system
What hurts: The system is stable and rarely changes
Usual starting point: Keep and maintain it
Pin down today’s behavior before any change that touches rules or data.
Wrap, refactor, or rewrite
Most systems get a mix, decided part by part.
Scope
Wrap: A new interface in front of the core. The core stays as it is.
Refactor in stages: The existing code, changed in small steps that keep behavior.
Rewrite by capability: Capabilities rebuilt on a new foundation and switched one at a time.
Responsibility
Wrap: The core keeps its rules and risks. The interface needs its own owner.
Refactor in stages: Each step keeps agreed behavior, checked by tests that record it.
Rewrite by capability: Each new part must match the behavior users rely on before traffic moves.
Access
Wrap: The data and functions the core exposes, plus people who know its limits.
Refactor in stages: The codebase, a safe test environment, and people who know why odd rules exist.
Rewrite by capability: The old system’s behavior and data, for comparison and migration.
Continuity
Wrap: The old system runs as before, with its maintenance burden.
Refactor in stages: The system stays live, and each step can be reversed.
Rewrite by capability: Old and new run side by side until each part is switched and data has moved.
Fit
Wrap: The core works, but new software needs to reach it.
Refactor in stages: Behavior is right, but changes are slow or risky.
Rewrite by capability: The platform is unsupported, or the data model blocks the business.
Decide part by part. Wrapping is often the first step of the other two.
When wrapping is enough
Wrapping fits when the core works and changes rarely, but new software needs to reach it. A web product, mobile product, partner integration, report, or AI feature may need a clean way to read or request work without reaching into old code or tables.
Put a clean interface in front and leave the core unchanged. That interface also becomes useful later if you replace parts behind it.
This is a fair answer, not a compromise. The old core keeps its maintenance burden and risks, and the new interface needs an owner, but the business avoids changing rules that are already working.
A wrapped interface can also give production AI controlled access to approved data and functions. Permissions should stay at the system boundary, not inside a prompt.
Wrapping has limits. The old core still needs care, and the interface can become another system to maintain. Use it when the core deserves to stay quiet, not when the team is avoiding a decision it already knows it must make.
A good wrapper also narrows access. New software should call the interface, not bypass it. That gives the team a boundary for permissions, logging, and later replacement if the core eventually changes.
When to refactor in stages
Refactor when the behavior is right but every change is slow or risky. The code may be tangled, untested, or tied to aging libraries, while the platform and data model still fit the business.
Change it in small, reviewed steps behind tests that record current behavior. Each step should be releasable and reversible. If a step changes behavior, that change should be deliberate, accepted, and recorded.
Refactoring is not cleanup for its own sake. It is a way to make future change safer while preserving the behavior the business still needs. It fits best when the system can remain live and each step can be checked.
The work should be small enough for real review. A large refactor that nobody can compare against current behavior creates the same risk as a rewrite, only with less clarity. The safer pattern is steady change behind evidence.
Refactoring may begin with tests, dependency cleanup, clearer module boundaries, or removing dead paths that nobody uses. Each step should have a reason tied to safer change, not just a preference for cleaner code.
When a rewrite is justified, and how to stage it
A rewrite is justified when the platform is no longer supported, the data model blocks how the business works now, or the business process itself is changing. It should not be the default answer to code that is hard to read.
Stage the rewrite by capability, not as one switch. Put an interface in front, replace one capability at a time, run old and new side by side where needed, compare results, then retire the old part. Engineers sometimes call this the strangler fig approach, but the useful idea is simpler: do not ask the business to trust one large switch.
Validate the behavior users depend on at each step. Check migration and rollback before switching traffic or data. A staged rewrite runs old and new systems for a while, so budget for that, and for the people who know the old rules.
The acceptance question is practical: can the new capability do the work the old one did, including the awkward cases the business still needs? If the answer is not yet visible, the old capability should keep running.
The team should also decide how long side-by-side running is useful. Keeping both systems forever creates its own risk. Retire each old part only when the new part has passed the agreed behavior checks and data has moved safely.
Pin down what the system does today
Before changing code, record what the system currently does. Use tests that compare outputs for real inputs, including strange inputs and old cases people still rely on.
Those tests record today’s behavior, bugs included. People decide which oddities are rules to keep and which are defects to fix. Write that decision down so the next change does not reopen the same argument.
These tests become acceptance evidence for every later step. They also protect the team from a quiet loss of rules nobody remembered to describe. A modernization plan without this evidence can look clean and still break the work.
Pinning behavior does not mean preserving every defect forever. It means making change intentional. Once today’s behavior is recorded, the team can decide which differences belong in the new design and which must be prevented.
Where AI coding tools help, and where they don’t
AI coding tools can help read and explain old code, map what calls what, draft tests that pin current behavior, make small reviewed refactors, and compare old and new outputs. People still review the result.
They do not decide which business rules matter. They cannot recover the reason behind a rule that is not in the code. They should not choose the target architecture or data model, and they should not approve their own changes.
Guardrails matter. Keep changes small, reviewed, and checked by pinning tests and security checks. A human should approve anything that changes behavior.
Do not assume AI makes a rewrite cheap enough to skip staging. A faster rewrite of an unpinned system can still lose rules nobody wrote down. If you are adopting these tools across your team, set rollout rules separately from this modernization plan.
Treat AI output like any other change to a live system. It needs review, tests, and a person who understands the business consequence. The tool may speed up drafts, but it does not carry operating responsibility.
Who runs the old system while it changes
The old system still needs care during modernization. Decide who keeps it running and who delivers the change. A major modernization needs its own delivery scope, separate from support.
Your team can run the old system while a project team modernizes. Managed support can keep it running under agreed coverage. A dedicated team can carry a long modernization roadmap, and UCS can scope a partial allocation when the roadmap does not need a full team. For parts UCS delivers, Reverse FDE prepares your operators before they own them.
If modernization decisions themselves lack an owner, a fractional CTO may need to own the plan, tradeoffs, and acceptance route.
A decision checklist
Answer these with the people who run and use the system.
What must not break?
List the money paths, calculations, reports, integrations, user expectations, and exception behavior the business depends on. Do this before choosing wrap, refactor, or rewrite. The list becomes the first acceptance boundary for every later change, including parts that look purely technical. Review it with people who use the system daily.
Who knows why the odd rules exist?
Name the users, operators, finance owners, support people, and technical maintainers who know the history. Their time is not optional. When the code shows an odd rule, someone must decide whether it is still a business rule, a workaround, or a defect. Without them, the team guesses from code alone.
Do tests record what the system does today?
Use real inputs to record current outputs before changing behavior. Include ordinary work and old edge cases. These tests may capture bugs too, which is fine at first. The team can later decide which differences are intended changes and which are regressions. The point is to make behavior visible before change.
What else connects to this system?
List internal tools, customer workflows, partner integrations, reports, scheduled jobs, and manual workarounds. A system may look isolated in code and still be central to operations. Wrapping and staged replacement both depend on knowing who will feel a change. Ask support and operations, not only engineering, before scoping starts.
Is the platform still supported?
Check whether the runtime, libraries, hosting, and deployment path can still be maintained. Unsupported does not automatically mean rewrite everything, but it changes the risk. It may justify wrapping first, then replacing capabilities behind the interface. Record which parts are urgent, which can safely wait, and who watches the risk meanwhile.
Can each step be rolled back?
Each increment should have a release path and a rollback path before it touches traffic or data. If rollback is impossible, the acceptance evidence needs to be stronger and the switch smaller. Migration checks matter most when data shape or ownership changes. Do not discover the rollback path during an incident.
Who keeps the old system running until it is retired?
Name the operating owner during the change. The modernization team may not be the same team watching incidents, applying updates, or answering user issues. If ownership is vague, the old system becomes more fragile while attention shifts to the new one. Keep support and change work separated, and name who can pause modernization when operating risk rises.
When another option fits better
If the system is stable and rarely changes, keeping and maintaining it can be the right answer. A packaged product may now do the job, and buying it can be better than preserving custom software.
If the product is young and was built mostly with AI tools, see how to rescue or rebuild an AI-built MVP. If you cannot yet write the scope of the first increment, compare FDE, consultants, and systems integrators before choosing the delivery model.
Bring the system and its do-not-break list
UCS takes on staged modernization of systems the business still runs on.
Bring what the system does, who uses it, what connects to it, the do-not-break list, known incidents, platform versions, and who decides scope and approves access. Do not send credentials, production data, or repository dumps in the first message.
You can also email hello@ucs.tech with the system boundary and the behavior that must not break.
Questions before we start
Should we refactor or rewrite our legacy system?
Refactor in stages when the behavior is right but changes are slow or risky. Rewrite, one capability at a time, when the platform is unsupported or the data model blocks how the business works now. If the core works and only needs to be reachable, wrap it instead.
What does wrapping a legacy system mean?
Putting a clean interface in front of the system so new products, integrations, and reports use it instead of reaching into the old code or database. The core stays unchanged. It is often the first step before replacing parts behind it.
Can AI rewrite a legacy system for us?
AI coding tools can speed up reading old code, drafting tests that record current behavior, and making small reviewed changes. They can’t decide which old rules still matter or choose the target design. Keep the work staged, reviewed, and tested.
Is our legacy system ready for AI coding agents?
Only if the documentation, code, and tests agree. When they drift apart, agents work from the wrong picture. Reconcile them first by tracing specs and documents to the code and adding tests that record how the important paths behave today. Then let agents work in small, bounded passes, with a person approving each step. The slow part is usually getting the right people to decide, not writing the code.
AI tools rewrote part of our legacy system. How do we prove it does what the old one did?
Treat the old system as the specification. Trace each business rule from the old code to a requirement, a test, and the new code, and have a business owner confirm the rules are the intended behavior. Run both versions on the same inputs and compare the outputs field by field, then run them side by side on real inputs through full business cycles before you switch over. Keep the way back open until the comparison is clean.
How do we modernize without stopping the business?
Record what the system does today, then change one part at a time behind an interface, with old and new running side by side where needed. Check data migration and have a rollback plan before switching traffic or data.
What if nobody understands the old code anymore?
Start by reading and mapping it, which AI tools can speed up, and by writing tests that record its behavior on real inputs. Then ask the people who use it which oddities are rules and which are defects, and write the answers down.
Who keeps the old system running during modernization?
Decide that before work starts. Your team, managed support, or the delivery team can keep it running under an agreed scope, but the modernization itself needs its own delivery scope and acceptance checks.