Skip to content

implement namespace isolation for multi-embodiment blueprints with du…#2519

Open
Karan24Soni wants to merge 6 commits into
dimensionalOS:mainfrom
Karan24Soni:main
Open

implement namespace isolation for multi-embodiment blueprints with du…#2519
Karan24Soni wants to merge 6 commits into
dimensionalOS:mainfrom
Karan24Soni:main

Conversation

@Karan24Soni

Copy link
Copy Markdown

Problem

so basically if you tried to run multiple robots in the same coordinator using namespaces, it just bricked. the worker booted up fine and listened on the namespaced topic (like robot1/ModuleA), but WorkerManagerPython completely ignored the namespace when building the RPCClient proxy. the proxy hardcoded its routing to just the base class name ModuleA.

because of this, the proxy would sit there for 120 seconds screaming into the void waiting for a response while the worker was listening somewhere else. totally blocked multi-embodiment setups where we need duplicate modules running side-by-side.

Solution

forced the proxy to actually respect the namespace so it routes correctly without touching the underlying rpc spec.

  • injected __dimos_rpc_name__ into the kwargs during initial deployment and inside _restart_module.
  • patched deploy and deploy_parallel in the coordinator so the exact second the proxy is handed back from the worker manager, we manually override proxy.remote_name = kwargs["__dimos_rpc_name__"].
  • now the proxy dynamically builds its method calls pointing exactly to the namespaced worker. no shared topic collisions, no timeouts.

you can now load autoconnect(...).namespace("robot1") and robot2 with the exact same module classes and it just works.

How to Test

added a brand new regression test to guarantee nobody breaks namespace isolation again:
PYTHONPATH=. python -m pytest dimos/core/tests/test_blueprint_namespaces.py -q --tb=short -x

and verify the reload/restart hooks are still totally green:
PYTHONPATH=. python -m pytest dimos/core/coordination/test_module_coordinator.py -q --tb=short -k "restart or reload or unload" -n auto -x

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces namespace isolation for multi-embodiment blueprints so the same module class can be deployed multiple times under different namespaces (e.g., robot1/ModuleA and robot2/ModuleA) without RPC routing collisions or stream topic conflicts.

  • Adds BlueprintAtom.namespace and Blueprint.namespace(), replaces the flat type[ModuleBase] key in every coordinator dictionary with InstanceKey(namespace, module), and updates _transport_registry to a 3-tuple (namespace, name, type) key — correctly scoping stream transports per namespace.
  • Injects __dimos_rpc_name__ and __dimos_namespace__ into worker kwargs so the in-process module and the coordinator proxy agree on the RPC topic, and patches proxy.remote_name immediately after deployment.
  • local_module_source.get_module was not updated to match the new namespaced names exposed by list_module_names(), causing a KeyError for any namespaced module on fetch; _restart_module also stores the refreshed BlueprintAtom without re-applying the namespace, leaving _deployed_atoms with an atom whose namespace field is None.

Confidence Score: 3/5

The core namespace isolation wiring is sound, but LocalModuleSource.get_module is broken for all namespaced modules — any enumerate-then-fetch caller will receive a KeyError in production.

The RPC routing fix and transport registry scoping are correct, but get_module still matches only bare class names while list_module_names() now returns namespaced strings, making the two methods inconsistent. Any component that lists then fetches modules by name will silently fail for every namespaced deployment.

dimos/porcelain/local_module_source.py has a broken get_module for namespaced modules. dimos/core/coordination/module_coordinator.py _restart_module stores an atom with namespace=None.

Important Files Changed

Filename Overview
dimos/porcelain/local_module_source.py get_module() still matches only on bare class name, but list_module_names() now returns namespaced strings; any enumerate-then-fetch caller will always get a KeyError for namespaced modules.
dimos/core/coordination/module_coordinator.py Core coordinator refactored around InstanceKey(namespace, module) — most paths updated correctly, but the restarted atom is stored with namespace=None, creating an inconsistency that will surface if any future code reads BlueprintAtom.namespace from _deployed_atoms.
dimos/core/coordination/blueprints.py Adds BlueprintAtom.namespace field and Blueprint.namespace() method; _eliminate_duplicates correctly scoped to (namespace, module) pairs.
dimos/core/module.py Cleanly pops __dimos_rpc_name__ and __dimos_namespace__ from config_args before passing to super().__init__, and routes the RPC server to the namespaced name.
dimos/core/tests/test_blueprint_namespaces.py New regression test verifies 4-module deployment across two namespaces; only checks module count, not RPC routing, stream isolation, or restart correctness.
dimos/core/coordination/test_module_coordinator.py Existing tests updated to use 3-tuple (None, name, type) registry keys; no new namespace-specific restart or module-ref tests added.

Reviews (6): Last reviewed commit: "fixed namespace" | Re-trigger Greptile

Comment on lines +290 to +296
for namespace, remapped_name, stream_type in streams.keys():
map_key = (remapped_name, stream_type)
if map_key in self._transport_registry:
transport = self._transport_registry[map_key]
else:
transport = _get_transport_for(blueprint, remapped_name, stream_type)
self._transport_registry[key] = transport
for module, original_name in streams[key]:
instance = self.get_instance(module) # type: ignore[assignment]
transport = _get_transport_for(blueprint, remapped_name, stream_type, namespace)
self._transport_registry[map_key] = transport

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stream transport isolation broken for same-named streams across namespaces

_transport_registry is keyed on (remapped_name, stream_type) without the namespace, so when two namespaced blueprints share a stream name and type (e.g., both robot1 and robot2 declare a camera: Out[Image] stream), the second namespace processed finds the first namespace's transport in the registry and reuses it. Both robots end up wired to the same underlying data channel, defeating the namespace isolation this PR is meant to provide.

Concrete failure: deploy autoconnect(...).namespace("robot1") and autoconnect(...).namespace("robot2") where both modules declare a camera output — robot2's camera transport will be robot1's topic (/robot1/camera instead of /robot2/camera), and messages will be shared.

Suggested change
for namespace, remapped_name, stream_type in streams.keys():
map_key = (remapped_name, stream_type)
if map_key in self._transport_registry:
transport = self._transport_registry[map_key]
else:
transport = _get_transport_for(blueprint, remapped_name, stream_type)
self._transport_registry[key] = transport
for module, original_name in streams[key]:
instance = self.get_instance(module) # type: ignore[assignment]
transport = _get_transport_for(blueprint, remapped_name, stream_type, namespace)
self._transport_registry[map_key] = transport
for namespace, remapped_name, stream_type in streams.keys():
map_key = (namespace, remapped_name, stream_type)
if map_key in self._transport_registry:
transport = self._transport_registry[map_key]
else:
transport = _get_transport_for(blueprint, remapped_name, stream_type, namespace)
self._transport_registry[map_key] = transport

Comment thread dimos/core/coordination/module_coordinator.py
Comment on lines +547 to 558
kwargs["__dimos_namespace__"] = key.namespace
kwargs["__dimos_rpc_name__"] = f"{key.namespace}/{new_class.__name__}" if key.namespace else new_class.__name__

python_wm = cast("WorkerManagerPython", self._managers["python"])
new_proxy = python_wm.deploy_fresh(new_class, self._global_config, kwargs)
self._deployed_modules[new_class] = new_proxy
if hasattr(new_proxy, "remote_name"):
new_proxy.remote_name = kwargs["__dimos_rpc_name__"]

self._deployed_modules[new_key] = new_proxy

new_bp = new_class.blueprint(**kwargs)
new_atom = new_bp.active_blueprints[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Internal framework keys leaked into BlueprintAtom.kwargs via blueprint() call

kwargs is augmented with __dimos_namespace__ and __dimos_rpc_name__ before being passed to new_class.blueprint(**kwargs). Those keys end up stored inside the new BlueprintAtom.kwargs. On the next restart, old_atom.kwargs already carries them, so they accumulate. While ModuleBase.__init__ pops them before passing to super().__init__, any code path that inspects BlueprintAtom.kwargs to reconstruct user-visible configuration will see framework-internal noise. Consider stripping these keys before the blueprint() call and re-injecting them when needed.

Comment on lines +472 to +476
reload_source: bool = True,
) -> None:
with self._modules_lock:
for cls in self._deployed_modules:
if cls.__name__ == class_name:
self._restart_module(cls, reload_source=reload_source)
for key in self._deployed_modules:
if key.module.__name__ == class_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 restart_module_by_class_name is non-deterministic when a class appears in multiple namespaces

The method iterates _deployed_modules and restarts the first key whose module.__name__ matches. Dict iteration order is insertion order in Python 3.7+, so if ModuleA is deployed as both robot1/ModuleA and robot2/ModuleA, the method will always restart whichever was inserted first with no way for the caller to choose the other. There is also no public API to restart a namespaced module by explicit (namespace, class_name) pair.

Comment thread dimos/core/coordination/blueprints.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant