Skip to content

feat(control): add extension sideloading#2547

Open
TomCC7 wants to merge 15 commits into
dimensionalOS:mainfrom
TomCC7:cc/exp/sideloading
Open

feat(control): add extension sideloading#2547
TomCC7 wants to merge 15 commits into
dimensionalOS:mainfrom
TomCC7:cc/exp/sideloading

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jun 20, 2026

Copy link
Copy Markdown
Member

Problem

External robot packages need a supported way to register ControlCoordinator hardware adapter names and control task types without placing files inside the DimOS source tree. Without this, an outside package can define a blueprint but still cannot use HardwareComponent(adapter_type=...) or TaskConfig(type=...) for custom hardware/control unless DimOS itself is modified.

Closes DIM-1031

Solution

Add a public dimos.control.extensions facade for explicit extension registration:

  • register_hardware_adapter(HardwareType, adapter_type, factory) dispatches to the existing manipulator, base, or whole-body adapter registry.
  • register_control_task(task_type, factory_path) registers lazy control task factories without importing the target module at registration time.
  • Hardware and task registries now reject conflicting duplicate registrations while keeping exact same mapping re-registration idempotent.
  • Add docs and a runnable no-hardware external package example that logs registration, adapter construction, task creation, task ticks, and adapter writes.

This keeps blueprint usage unchanged: external packages register before coordinator construction, then use normal HardwareComponent and TaskConfig names.

How to Test

Manual QA:

uv run python examples/external_control_extension/demo_external_control.py

Expected output includes [external_test_robot] logs for:

  • registering BASE/external_test_base
  • registering external_test_drive
  • constructing and connecting ExternalTestBaseAdapter
  • creating and ticking ExternalTestDriveTask
  • writing velocities through the external adapter

Contributor License Agreement

  • I have read and approved the CLA.

Comment thread dimos/hardware/drive_trains/transport/adapter.py Outdated
@TomCC7 TomCC7 marked this pull request as ready for review June 20, 2026 20:20
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a public extension facade (dimos.control.extensions) so external robot packages can register hardware adapters and control tasks into the ControlCoordinator without touching DimOS source files. Shared normalization helpers (registry_utils) were extracted and applied consistently to both register() and create() across all three hardware registries; all registries now enforce idempotent-or-error duplicate semantics.

  • register_hardware_adapter and register_control_task route to the matching built-in registry after normalizing names via the new shared helpers, keeping the coordinator's blueprint API unchanged.
  • All three hardware adapter registries (drive_trains, manipulators, whole_body) and the control-task registry now use the same strip().lower() normalization in both register() and create(), fixing the previously noted asymmetry where register() stripped but create() did not.
  • A complete no-hardware example package (dimos_external_control_extension) and matching integration tests verify registration, adapter construction, task ticking, and adapter writes end-to-end.

Confidence Score: 5/5

Safe to merge — all changes are additive, the new public facade delegates cleanly to existing registries, and the normalization fix makes register/create consistent across all three hardware registries.

The extension surface is well-scoped: new code adds to registries rather than modifying coordinator internals, the duplicate-registration guard prevents silent overwrites, and the shared normalization helpers eliminate the register/create asymmetry that was previously flagged. Tests cover all hardware types, idempotent re-registration, conflict rejection, padded-name round-trips, and full coordinator integration. No correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
dimos/control/extensions.py New public facade for extension registration; dispatches to the correct registry after shared normalization. Logic is correct and validates inputs before delegating.
dimos/control/tasks/registry.py Switched name normalization in register_path and create to shared normalize_task_name; adds idempotent-or-error duplicate detection for factory paths.
dimos/control/tasks/registry_utils.py New shared helpers normalize_task_name and validate_task_factory_path; validation correctly handles all edge cases (empty, no colon, empty module, empty attr).
dimos/hardware/registry_utils.py New shared normalize_adapter_name helper; correctly raises on empty/whitespace-only names.
dimos/hardware/drive_trains/registry.py register() and create() both now use normalize_adapter_name; duplicate detection uses identity check for idempotent re-registration.
dimos/hardware/manipulators/registry.py Same normalization and duplicate-detection pattern as drive_trains; _adapters type widened from type[ManipulatorAdapter] to Callable[..., ManipulatorAdapter] to accept factories.
dimos/hardware/whole_body/registry.py Consistent normalization and duplicate-detection applied; unchanged API surface.
dimos/control/test_extensions.py Comprehensive unit tests covering all hardware types, idempotent re-registration, conflict rejection, padded-name lookup consistency, and end-to-end coordinator integration.
dimos/control/test_external_control_extension_example.py Integration test for the example package; correctly saves/restores registry state and sys.modules to avoid cross-test pollution.
examples/external_control_extension/dimos_external_control_extension/blueprints.py Module-level register_extensions() call is idempotent since the same factory objects are re-registered. run_demo returns an already-stopped coordinator (previously flagged).
examples/external_control_extension/dimos_external_control_extension/tasks.py External task factory correctly slices velocity list to joint count; logging is informative without being excessive.
examples/external_control_extension/dimos_external_control_extension/adapters.py In-memory twist-base adapter with threading.Event for synchronization; correctly models all required protocol methods.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[External Package\nimports blueprints.py] --> B[register_extensions called\nat module level]
    B --> C[register_hardware_adapter\nhardware_type, adapter_type, factory]
    B --> D[register_control_task\ntask_type, factory_path]

    C --> E[normalize_adapter_name\nstrip + lower]
    E --> F{hardware_type\nmatch}
    F -->|MANIPULATOR| G[adapter_registry.register]
    F -->|BASE| H[twist_base_adapter_registry.register]
    F -->|WHOLE_BODY| I[whole_body_adapter_registry.register]
    G & H & I --> J{existing\nentry?}
    J -->|same factory| K[idempotent return]
    J -->|different factory| L[raise ValueError]
    J -->|none| M[store in _adapters]

    D --> N[normalize_task_name\nstrip + lower]
    N --> O[validate_task_factory_path\nmodule:attr format]
    O --> P[control_task_registry.register_path]
    P --> Q{existing\npath?}
    Q -->|same path| R[overwrite same value]
    Q -->|different path| S[raise ValueError]
    Q -->|none| T[store in _factory_paths]

    U[ControlCoordinator.start] --> V[HardwareComponent\nadapter_type lookup]
    V --> W[registry.create\nnormalize_adapter_name]
    W --> M

    U --> X[TaskConfig type lookup]
    X --> Y[registry.create\nnormalize_task_name]
    Y --> Z[_resolve_factory\nlazy importlib.import_module]
    Z --> T
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[External Package\nimports blueprints.py] --> B[register_extensions called\nat module level]
    B --> C[register_hardware_adapter\nhardware_type, adapter_type, factory]
    B --> D[register_control_task\ntask_type, factory_path]

    C --> E[normalize_adapter_name\nstrip + lower]
    E --> F{hardware_type\nmatch}
    F -->|MANIPULATOR| G[adapter_registry.register]
    F -->|BASE| H[twist_base_adapter_registry.register]
    F -->|WHOLE_BODY| I[whole_body_adapter_registry.register]
    G & H & I --> J{existing\nentry?}
    J -->|same factory| K[idempotent return]
    J -->|different factory| L[raise ValueError]
    J -->|none| M[store in _adapters]

    D --> N[normalize_task_name\nstrip + lower]
    N --> O[validate_task_factory_path\nmodule:attr format]
    O --> P[control_task_registry.register_path]
    P --> Q{existing\npath?}
    Q -->|same path| R[overwrite same value]
    Q -->|different path| S[raise ValueError]
    Q -->|none| T[store in _factory_paths]

    U[ControlCoordinator.start] --> V[HardwareComponent\nadapter_type lookup]
    V --> W[registry.create\nnormalize_adapter_name]
    W --> M

    U --> X[TaskConfig type lookup]
    X --> Y[registry.create\nnormalize_task_name]
    Y --> Z[_resolve_factory\nlazy importlib.import_module]
    Z --> T
Loading

Reviews (5): Last reviewed commit: "chore(docs): fix external package guide ..." | Re-trigger Greptile

Comment thread dimos/hardware/drive_trains/registry.py Outdated
@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.04545% with 21 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/control/test_extensions.py 91.83% 12 Missing ⚠️
dimos/control/extensions.py 81.48% 3 Missing and 2 partials ⚠️
dimos/control/tasks/registry_utils.py 80.00% 1 Missing and 1 partial ⚠️
dimos/hardware/registry_utils.py 66.66% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #2547      +/-   ##
==========================================
- Coverage   70.86%   70.57%   -0.30%     
==========================================
  Files         852      869      +17     
  Lines       77118    77686     +568     
  Branches     6855     6923      +68     
==========================================
+ Hits        54647    54824     +177     
- Misses      20669    21081     +412     
+ Partials     1802     1781      -21     
Flag Coverage Δ
OS-ubuntu-24.04-arm 63.03% <92.04%> (+0.09%) ⬆️
OS-ubuntu-latest 65.86% <92.04%> (+0.08%) ⬆️
Py-3.10 65.85% <92.04%> (+0.08%) ⬆️
Py-3.11 65.85% <92.04%> (+0.07%) ⬆️
Py-3.12 65.85% <92.04%> (+0.08%) ⬆️
Py-3.13 65.85% <92.04%> (+0.08%) ⬆️
Py-3.14 65.86% <92.04%> (+0.07%) ⬆️
Py-3.14t 65.86% <92.04%> (+0.08%) ⬆️
SelfHosted-macOS ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/control/tasks/registry.py 73.84% <100.00%> (ø)
...control/test_external_control_extension_example.py 100.00% <100.00%> (ø)
dimos/hardware/drive_trains/registry.py 90.24% <100.00%> (+2.00%) ⬆️
dimos/hardware/manipulators/registry.py 85.36% <100.00%> (+3.54%) ⬆️
dimos/hardware/whole_body/registry.py 80.85% <100.00%> (ø)
dimos/control/tasks/registry_utils.py 80.00% <80.00%> (ø)
dimos/hardware/registry_utils.py 66.66% <66.66%> (ø)
dimos/control/extensions.py 81.48% <81.48%> (ø)
dimos/control/test_extensions.py 91.83% <91.83%> (ø)

... and 33 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread docs/capabilities/external_robot_packages.md Outdated
Comment thread docs/capabilities/external_robot_packages.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md 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