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_instanceand 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_injecthandles safe compilation of new code strings before migration.- Global registries in
nallely/core/world.pystay synchronized viaregister_virtual_device_classduring 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →