The upgrader stuns the old object by waiting until the old object is idle and demanding a fault capability to it. At this point, the old object is frozen and cannot be invoked by anyone else.
The upgrader now clones the process nodes of the old object's process. In effect, we have just copied the old object into a new process.
The upgrader now installs into the old process a new address space: the address space for the new object. It then sets the old process registers to the appropriate startup values to give the new object a chance to initialize itself. It also places a capability to the old object into a well-defined register in what is now the new object's process. Note that we've now effectively done a complete brain transplant on the original process. All holders of the old object still have capabilities to this process, which is now obeying new code.
The upgrader now lets the transplanted process start running. During initialization, the new object uses its capability to the old object to transfer any necessary data from the old object and then tells the old object to destroy itself.
Finally, the new object returns to the null capability, becoming available for new invocations.
It all sounds much more complicated than it is. Most of this can be built in a library.
A mathematician is a device for turning coffee into theorems. -- P. Erdos