Skip to content

feat(control): add extension sideloading#3175

Open
TomCC7 wants to merge 18 commits into
mainfrom
cc/exp/sideloading
Open

feat(control): add extension sideloading#3175
TomCC7 wants to merge 18 commits into
mainfrom
cc/exp/sideloading

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jul 24, 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.

@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 24, 2026, 11:26 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds explicit extension sideloading for ControlCoordinator hardware adapters and task factories.

  • Introduces a public registration facade for external packages.
  • Normalizes registry names, validates lazy factory paths, and rejects conflicting duplicate registrations.
  • Adds tests, documentation, and a runnable no-hardware extension example.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain within the scope of the previous review threads.

Important Files Changed

Filename Overview
dimos/control/extensions.py Adds the public facade that dispatches hardware registrations by embodiment and records lazy control-task factory paths.
dimos/control/tasks/registry.py Adds consistent task-name normalization and stricter validation for lazy factory paths.
dimos/hardware/adapter_registry.py Adds normalized adapter names, strict path validation, idempotent direct registration, and duplicate-conflict detection.
dimos/control/test_extensions.py Covers public extension registration, normalization, duplicate handling, lazy imports, and coordinator resolution.
examples/external_control_extension/dimos_external_control_extension/blueprints.py Demonstrates package-level registration and coordinator construction using externally registered adapter and task names.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    EXT[External package] --> API[dimos.control.extensions]
    API --> HWREG[Hardware adapter registry]
    API --> TASKREG[Control task registry]
    BP[Blueprint configuration] --> COORD[ControlCoordinator]
    COORD --> HWREG
    COORD --> TASKREG
    HWREG --> ADAPTER[External adapter]
    TASKREG -->|lazy import| TASK[External control task]
    TASK --> TICK[Coordinator tick loop]
    TICK --> ADAPTER
Loading

Reviews (3): Last reviewed commit: "fix(control): reject malformed factory p..." | Re-trigger Greptile

resolved later by the control task registry when a coordinator creates a
matching ``TaskConfig``.
"""
control_task_registry.register_path(task_type, factory_path)

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 Task bindings are never registered

When an external task consumes coordinator streams or exposes task commands, this facade stores only its factory path, so bindings_for() returns an empty binding set and the coordinator creates no routes, subscriptions, or declared commands. The task is constructed but never receives its streamed commands, while configurations using stream_bind are rejected as unknown inputs.

Knowledge Base Used: Control Coordinator and Control Tasks

Comment thread dimos/control/tasks/registry.py Outdated
Comment on lines +63 to +65
"""Validate a lazy factory path of the form ``module:function``."""
module_name, separator, attr = factory_path.partition(":")
if not factory_path.strip() or separator != ":" or not module_name or not attr:

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 Path validation accepts extra separators

The validation accepts paths such as module:factory:extra even though the documented format is module:function. Resolution later treats factory:extra as the attribute name and fails during task creation, so reject paths containing more than one separator at registration time; the adapter registry uses the same validation pattern and needs the corresponding check.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.57377% with 23 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 77.27% 3 Missing and 2 partials ⚠️
dimos/hardware/adapter_registry.py 77.77% 2 Missing and 2 partials ⚠️
dimos/control/tasks/registry.py 83.33% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #3175      +/-   ##
==========================================
- Coverage   74.17%   73.80%   -0.37%     
==========================================
  Files        1108     1112       +4     
  Lines      104052   104622     +570     
  Branches     9530     9621      +91     
==========================================
+ Hits        77176    77221      +45     
- Misses      24131    24706     +575     
+ Partials     2745     2695      -50     
Flag Coverage Δ
OS-ubuntu-24.04-arm 67.77% <90.57%> (+0.12%) ⬆️
OS-ubuntu-latest 69.83% <90.57%> (+0.10%) ⬆️
Py-3.10 69.82% <90.57%> (+0.10%) ⬆️
Py-3.11 69.82% <90.57%> (+0.10%) ⬆️
Py-3.12 69.82% <90.57%> (+0.10%) ⬆️
Py-3.13 69.81% <90.57%> (+0.10%) ⬆️
Py-3.14 69.83% <90.57%> (+0.10%) ⬆️
Py-3.14t 69.82% <90.57%> (+0.10%) ⬆️
SelfHosted-Large 29.65% <32.78%> (-0.02%) ⬇️
SelfHosted-Linux 36.11% <32.78%> (-0.12%) ⬇️
SelfHosted-macOS ?

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

Files with missing lines Coverage Δ
...control/test_external_control_extension_example.py 100.00% <100.00%> (ø)
dimos/hardware/test_adapter_registries.py 94.00% <100.00%> (+0.31%) ⬆️
dimos/control/tasks/registry.py 81.36% <83.33%> (+3.58%) ⬆️
dimos/hardware/adapter_registry.py 86.81% <77.77%> (-0.20%) ⬇️
dimos/control/extensions.py 77.27% <77.27%> (ø)
dimos/control/test_extensions.py 91.83% <91.83%> (ø)

... and 36 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 on lines +64 to +66
module_name, separator, attr = factory_path.partition(":")
if not factory_path.strip() or separator != ":" or not module_name or not attr:
raise ValueError(f"Invalid task factory path: {factory_path!r}")

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 Extra separators pass path validation

When an extension registers a path such as pkg.tasks:create_task:extra, this validator accepts the second separator and stores the path. Lazy resolution then calls getattr(module, "create_task:extra"), causing task creation to fail with AttributeError; the adapter registry has the same incomplete validation.

@TomCC7
TomCC7 requested a review from Dreamsorcerer as a code owner July 25, 2026 22:28
@TomCC7

TomCC7 commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

Addressed Greptile's factory-path validation finding in 9f92f2b. Both control-task and hardware-adapter registration now require exactly one : separator, so inputs such as module:attribute:extra fail immediately with ValueError instead of reaching lazy resolution. Added regression coverage for both registries.\n\nValidation:\n- uv run pytest dimos/control/tasks/test_registry.py dimos/control/test_extensions.py dimos/hardware/test_adapter_registries.py -q — 48 passed\n- uv run mypy — no issues in 918 source files\n- applicable pre-commit hooks — passed

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant