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

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, 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 within Session.migrate_instance. This method directly replaces instance.__class__ with the freshly compiled class:


# 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. 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 (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 (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:

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:

{
  "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), 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →