The starting position is often the same. A legacy system needs modernizing, the in-house team knows the system but has neither experience with the rebuild nor time for it, and somebody from outside is supposed to help. The engagement runs six months, after which the modernization is finished.
And a year later the situation is what it was before, only with newer technology. The team changes nothing in the new parts, because it does not know them, and the person who built them is gone.
This article describes why that happens and what helps against it. I am writing it as somebody who takes the external role; that this makes follow-up work harder for me is something I am aware of and consider correct.
The pattern that goes wrong
The sequence leading to that outcome looks sensible from the inside, and that is exactly the problem.
The external is faster, because they have done the rebuild many times. The team is busy with day-to-day work. So the external takes over the new parts completely, and the team carries on with the old system. Both sides are content, because progress happens.
What that creates is a second boundary through the middle of the system. Not between old and new, but between "our code" and "their code". That boundary is written down nowhere and is effective anyway: on a fault in the new part, the team asks instead of looking.
The test for it is simple and worth running in any ongoing engagement: who has changed anything in the new parts in the last four weeks? If only one name appears and it is the external one, a second knowledge monopoly is forming. It is just not visible yet, because the person is there.
What handover readiness actually means
"The team should be able to do it afterwards" is too soft a goal to act on. Four criteria make it testable, and all four can be measured in an afternoon.
One: somebody on the team can ship a small change to a new part on their own. Not in theory, but has done it, in the last four weeks.
Two: somebody on the team can build the environment from scratch. With no questions, using the description in the repository.
Three: for every significant decision there is a reason somebody can find without asking. Why this library, why this split, why not the obvious route.
Four: there is nobody whose absence stops the work. Including the external. Especially the external.
Those four belong at the start of the engagement, not at the end. As a goal at the end they are a statement of intent; as a checkpoint every four weeks they are a steering instrument.
The division of work that holds
The split that has worked in my projects is not "we do it together", because that is too vague. It is a distribution of roles that shifts over the course of the engagement.
At the start: the external builds, the team reviews. The first steps are taken by the person who knows them. But every change gets read and approved by somebody on the team, with the explicit brief to ask about anything unclear. An approval with no questions is a warning sign at this stage, not a good sign.
In the middle: the other way round. The team builds, the external reviews. That is the actual transition, and it does not happen by itself; it gets scheduled. In practice that is the point where the work briefly slows down, and that slowdown is the price for everything after it.
At the end: the team builds, the external is reachable. For the last weeks the external works on something else and answers questions. If many questions come in that phase, the transition was too late. If none come, it worked.
The shift needs a date in the calendar, otherwise it does not happen. The reason is the same as for every measure that costs pace in the short term: it gets postponed while things are going well.
The trigger for an arrangement like this is often the same one: Knowledge in one person is an architecture problem.
What has to remain, and what does not
Something is left behind at the end of an engagement, and the list is shorter than many expect. Four things, no more.
The code, in the client's repository. That sounds obvious and is not always true: a script on the external's machine that prepares the build belongs there just as much as the application.
A way to build the environment. Verified by somebody on the team having done it once.
The decisions, with reasons. More on that below, because it is the part most often missing.
The accounts, in the client's name. Service accounts, keys, admin access. An account registered to the external's email address is an incident waiting for its date.
What explicitly does not belong on that list is a closing documentation. A document written at the end of an engagement describes a state that starts changing the following week, and it does not get maintained. That time is better spent on the third point.
Record decisions, do not document systems
The difference is the single most important point in this article. Documentation describes how something works; the code says that too. A decision record describes why it is that way and what was rejected, and the code never says that.
In practice that is one file per decision, in the repository, written in half an hour, at the moment the decision is made.
docs/decisions/2026-10-28-job-table-instead-of-sqs.md
# Job table instead of SQS
Status: decided, 28 Oct 2026
Involved: platform team, external
## Situation
The export to the ERP runs as a cron job with no retries. Around 4,000
records a day, peaks around 11am.
## Decision
Job table in the existing database, worker as a console command,
SKIP LOCKED for concurrency.
## Why not SQS
Would introduce a second piece of infrastructure the team does not
operate yet. At 4,000 records a day the table carries comfortably.
The switch stays possible: the interface in the code is the same.
## When we revisit this
If the number rises above 100,000 a day, or if a second system starts
producing the same jobs.The last section is what makes the difference. It answers the question that gets asked in two years, and it takes the finality out of the decision: anybody who knows the condition under which a choice stops applying can change it later without an argument.
Five to fifteen such files are normal for an engagement of this size. They replace the closing documentation entirely, and they get read, because they are short and answer a concrete question.
What the end of an engagement like this looks like when no dependency is left behind is covered in The handover day.
The test before the end
Two to three weeks before the planned end, a trial is worth running that is uncomfortable and gives the only reliable answer: the external is unavailable for a week. Not announced as a test, but planned as an absence.
What happens that week is the result of the engagement. If the team carries on normally, the handover has happened. If things stall, there is a list of what still needs doing, and it is exactly the right list.
Scheduling that week early enough is the point. An absence in the final week produces the same insight and no time left to act on it.
What regularly surfaces in that week is not the big things. It is access to a service only one person has. A build step that runs on one particular machine. A setting nobody knows about, because it was once set by hand. All small things, and together they are the difference between able to act and blocked.
What an external should not do
Finally, four things I avoid in that role, because they help in the short term and hurt in the long one.
Take on operational responsibility. Anybody carrying the pager for a system the client is meant to run prevents exactly the learning this is about. Reading along yes, helping in incidents yes, carrying the responsibility no.
Introduce tools only the external knows. A language, a framework, an infrastructure with no second head for it on the team. Sometimes that is still right, and then it is part of the decision who on the team learns it, with a name and a date.
Work alone on a branch. Two weeks without merging means two weeks without review by the team. What arrives at the end is a change set nobody reads, and the approval is a formality.
Avoid the uncomfortable question. If after three months nobody on the team has changed anything in the new parts, that belongs said out loud, even when the engagement is going well. It is the finding that decides at the end whether the result belongs to the client.
How I cut modernization work so that this distribution of roles is possible at all is on the legacy modernization page. And if the question is rather who takes over an existing PHP system in the first place: that has its own page.
This article belongs to a series about systems that already exist. The retrospective orders every article in it by situation.

