# Hot-Patch Devices Without Restarting Using Smalltalk-Style Migration in Nallely MIDI

> Nallely MIDI hot patches devices without restarting. Reload live code by swapping running virtual device classes, preserving connections and state. Learn how.

- Repository: [dr-schlange/nallely-midi](https://github.com/dr-schlange/nallely-midi)
- Tags: internals
- Published: 2026-02-28

---

**Nallely MIDI enables live code reloading by swapping the `__class__` of running virtual devices at runtime while preserving all active connections and session state.**

The `dr-schlange/nallely-midi` repository implements a **Smalltalk-style migration** system that allows developers to modify device behavior on-the-fly without tearing down the UI or dropping MIDI patches. This mechanism recompiles class definitions and migrates live instances to the new implementation instantly.

## The Four-Step Migration Pipeline

The hot-patch workflow mirrors classic Smalltalk "hot-swap" behavior through a precise sequence of operations defined in the core session and API layers.

### Step 1: Compile the New Class Definition

The process begins by compiling source code into a fresh class object. In [`nallely/trevor/meta_trevor_api.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/meta_trevor_api.py), the `MetaTrevorAPI.object_centric_compile_inject` method creates a new class from a string of Python code, optionally accepting a temporary name to avoid collisions during the transition.

This function returns a class object ready for instantiation or, in the case of migration, ready to be grafted onto an existing instance.

### Step 2: Swap the Instance Class

The critical operation occurs in [`nallely/session.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/session.py) within `Session.migrate_instance`. This method directly replaces `instance.__class__` with the freshly compiled class:

```python

# Conceptual flow based on Session.migrate_instance (lines 45-78)

instance.__class__ = new_class

```

This low-level swap preserves the instance’s identity, memory address, and existing attribute values while updating its method resolution order to the new implementation.

### Step 3: Update Global Registries

After the class swap, the system updates internal bookkeeping via [`nallely/core/world.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/world.py). The functions `register_virtual_device_class` and `unregister_virtual_device_class` ensure the global device registry reflects the new class hierarchy.

Instance counters are synchronized, and the old class is deregistered unless the migration is explicitly marked as temporary. This ensures that new instantiations use the updated definition while the migrated instance continues running.

### Step 4: Restore Runtime State

For **VirtualDevice** instances, the migration rebuilds internal setup routines and output routing configurations automatically. If the object is a freshly created device, the constructor is invoked and the device starts; for existing running devices, the session resumes operation with the new `process` method or event handlers immediately active.

## Public API Entry Points

Nallely MIDI exposes two primary interfaces for triggering migrations: a programmatic Python API for scripting and a WebSocket API for UI-driven changes.

### Using `Session.compile_save_new_class`

For Python-based automation, access the `MetaTrevorAPI` through a `Session` object to compile and migrate in one call:

- **Method**: `session.meta_trevor.compile_save_new_class(target_instance, class_code)`
- **Location**: [`nallely/trevor/meta_trevor_api.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/meta_trevor_api.py) (lines 17-27)
- **Behavior**: Compiles the code and immediately migrates the specified instance

### Using `TrevorBus.compile_inject_save`

The WebSocket/UI layer uses `TrevorBus` in [`nallely/trevor/trevor_bus.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/trevor/trevor_bus.py) (lines 82-99) to handle remote hot-patch requests:

- **Method**: `TrevorBus.compile_inject_save(device_id, class_code, …)`
- **Trigger**: Called by the Trevor UI when users submit code changes
- **Result**: Automatically invokes `migrate_instance` and returns confirmation to the client

## Practical Hot-Patching Examples

### Example 1: Migrate a Virtual Device via Python

The following script patches a running virtual device without interrupting its MIDI I/O:

```python
from nallely.session import Session

# Connect to the live session

session = Session()

# Retrieve the target instance by its UUID

target = session.trevor.get_device_instance("my‑vdev‑uuid")

# Define the new implementation

new_code = """
class MyVDev(VirtualDevice):
    def __init__(self):
        super().__init__()
        self.foo = 42

    def process(self, midi_msg):
        print("patched!", midi_msg)
"""

# Compile, inject, and migrate atomically

session.meta_trevor.compile_save_new_class(target, new_code)

# The device now executes the new `process` method immediately

```

### Example 2: Remote Hot-Patch via WebSocket

Send a JSON payload to the Trevor WebSocket endpoint to trigger migration from an external editor:

```json
{
  "command": "TrevorAPI::compileInjectSave",
  "arg": {
    "device_id": "my‑vdev‑uuid",
    "class_code": "class MyVDev(VirtualDevice):\n    def process(self, msg):\n        return msg.velocity * 2\n",
    "force_name": null,
    "commit": false
  }
}

```

The `TrevorBus` handler receives this request, delegates to `MetaTrevorAPI`, and executes `Session.migrate_instance`, returning a confirmation message that the instance has been migrated.

## Summary

- **Smalltalk-style migration** in Nallely MIDI swaps class definitions on live instances without process restarts.
- The core logic resides in `Session.migrate_instance` ([`nallely/session.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/session.py)), which updates `__class__` pointers directly.
- `MetaTrevorAPI.object_centric_compile_inject` handles safe compilation of new code strings before migration.
- Global registries in [`nallely/core/world.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/core/world.py) stay synchronized via `register_virtual_device_class` during swaps.
- Both programmatic (`compile_save_new_class`) and WebSocket APIs (`compile_inject_save`) support hot-patching workflows.
- Active MIDI patches and UI connections remain intact throughout the migration process.

## Frequently Asked Questions

### Does hot-patching drop existing MIDI connections?

No. Because `Session.migrate_instance` swaps only the class definition and preserves the instance object, all existing references, patch cables, and socket connections remain valid. The device resumes processing with the new logic immediately after the swap completes.

### Can I migrate multiple device instances simultaneously?

Yes. In addition to `migrate_instance`, [`nallely/session.py`](https://github.com/dr-schlange/nallely-midi/blob/main/nallely/session.py) provides `migrate_instances` (plural) for batch operations. This iterates over a collection of devices, applying the same class swap to each while maintaining atomicity per instance.

### Is this mechanism limited to virtual MIDI devices?

No. While commonly used for **VirtualDevice** subclasses, the migration system works for any non-MIDI device object managed by the session. The only requirement is that the target instance must be reachable through `session.trevor.get_device_instance`.

### What happens to instance variables during migration?

Existing instance variables persist because the underlying object ID remains unchanged. However, if the new class defines a different `__init__` signature or default values, those changes only apply to new instances. To initialize new state on a migrated instance, explicitly set attributes in the new class methods or use a post-migration hook.