Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fe7a7de
spec: openspec init
TomCC7 Jun 4, 2026
76158b2
chore: revert change to doc folder
TomCC7 Jun 8, 2026
35c8b14
Merge branch 'main' into cc/feat/openspec
TomCC7 Jun 8, 2026
12d4346
Merge branch 'main' into cc/feat/openspec
TomCC7 Jun 10, 2026
6cd2fd3
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jun 12, 2026
45f7f73
Merge branch 'main' into cc/feat/openspec
TomCC7 Jun 15, 2026
86a600d
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jun 18, 2026
43fd853
Merge branch 'main' into cc/feat/openspec
TomCC7 Jun 20, 2026
8394a61
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jun 20, 2026
bae46c4
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jun 23, 2026
4cf815e
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jun 30, 2026
2c80dab
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 7, 2026
bc381cb
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 11, 2026
3a976da
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 14, 2026
4e25297
add mattskill
TomCC7 Jul 20, 2026
11f0d7f
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 23, 2026
9ffcd58
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 23, 2026
f873ddf
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 24, 2026
e87e93e
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 27, 2026
e689348
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 28, 2026
794e585
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Jul 29, 2026
cfa3e3a
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Aug 1, 2026
d221a4f
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Aug 4, 2026
df82a32
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Aug 5, 2026
9cac56d
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Aug 8, 2026
1d2b2d2
Merge remote-tracking branch 'origin/main' into cc/feat/openspec
TomCC7 Aug 11, 2026
7f11caa
feat: add Python native module runtime
TomCC7 Aug 15, 2026
294867d
spec: remove
TomCC7 Aug 15, 2026
927c171
chore: lock external Python example
TomCC7 Aug 15, 2026
dffb4df
feat: add isolated LeRobot policy module
TomCC7 Aug 15, 2026
2ffca94
feat: add isolated LeRobot policy module
TomCC7 Aug 15, 2026
4a97a73
refactor: promote LeRobot policy module
TomCC7 Aug 18, 2026
3469517
Merge remote-tracking branch 'origin/feat/experimental-lerobot-policy…
TomCC7 Aug 18, 2026
a999190
feat: add OpenYam LeRobot data collection flow
TomCC7 Aug 18, 2026
5a5f831
refactor: address lerobot review feedback
TomCC7 Aug 18, 2026
8d52039
fix(openyam): remove obsolete Quest task routing config
TomCC7 Aug 18, 2026
87c2924
chore: restore unrelated dependency changes
TomCC7 Aug 18, 2026
12acf30
Delete examples/native-modules/python_lerobot.py
TomCC7 Aug 18, 2026
1897fb3
fix(imitation): skip TF for collection streams
TomCC7 Aug 18, 2026
2938e9b
fix(openyam): improve single-arm teleop response
TomCC7 Aug 18, 2026
20e1afa
fix(openyam): default Quest teleop to fake hardware
TomCC7 Aug 19, 2026
79e6ac8
fix(openyam): prefer wrist motion in Quest IK
TomCC7 Aug 19, 2026
2854b1a
fix(imitation): record OpenYAM wrist frames over SHM
TomCC7 Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ global-exclude .DS_Store
# runtime data and must stay out. Add a new extension here when code starts
# loading a new data type — never go back to per-package globs.
recursive-include dimos *.yaml *.yml *.json *.urdf *.html *.css *.js *.svg *.tcss
include dimos/imitation/policy/lerobot/python/pyproject.toml
include dimos/imitation/policy/lerobot/python/uv.lock

# --- Exclusions (must come after the includes above so they win) ---
# Test fixtures must never ship.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Bootstrap an IsolatedPythonModule runtime subclass."""
"""Bootstrap an isolated PythonNativeModule runtime subclass."""

from __future__ import annotations

from importlib.metadata import EntryPoint
import inspect
import os
import pickle
Expand All @@ -27,19 +26,18 @@

import typer

from dimos.core.isolated_python_module import IsolatedPythonModule, contract_rpc_names
from dimos.core.python_native_module import PythonNativeModule, contract_rpc_names
from dimos.spec.utils import _signatures_compatible


def load_class(reference: str) -> type[Any]:
entry_point = EntryPoint(name="isolated-python", value=reference, group="dimos.runtime")
try:
if not entry_point.module or not entry_point.attr:
raise ValueError(f"Invalid import reference {reference!r}; use module:Class")
except AttributeError as error:
raise ValueError(f"Invalid import reference {reference!r}; use module:Class") from error

value = entry_point.load()
module_name, separator, class_name = reference.partition(":")
if not separator:
module_name, _, class_name = reference.rpartition(".")
if not module_name or not class_name:
raise ValueError(f"Invalid import reference {reference!r}; use module:Class")
module = __import__(module_name, fromlist=[class_name])
value = getattr(module, class_name)
if not isinstance(value, type):
raise TypeError(f"Import reference {reference!r} does not resolve to a class")
return value
Expand All @@ -50,10 +48,10 @@ def _method_owner(module_class: type[Any], name: str) -> type[Any] | None:


def validate_runtime(
declaration: type[IsolatedPythonModule], runtime: type[IsolatedPythonModule]
declaration: type[PythonNativeModule], runtime: type[PythonNativeModule]
) -> None:
if not issubclass(declaration, IsolatedPythonModule):
raise TypeError(f"{declaration.__name__} is not an IsolatedPythonModule contract")
if not issubclass(declaration, PythonNativeModule):
raise TypeError(f"{declaration.__name__} is not a PythonNativeModule contract")
if not issubclass(runtime, declaration):
raise TypeError(f"{runtime.__name__} must subclass {declaration.__name__}")

Expand Down Expand Up @@ -82,18 +80,18 @@ def main(
instance_name: str = typer.Option(..., "--instance-name"),
handshake_fd: int = typer.Option(..., "--handshake-fd"),
) -> None:
module: IsolatedPythonModule | None = None
module: PythonNativeModule | None = None
try:
declaration_class = load_class(declaration)
runtime_class = load_class(implementation)
if not issubclass(declaration_class, IsolatedPythonModule):
raise TypeError(f"{declaration!r} is not an IsolatedPythonModule contract")
if not issubclass(runtime_class, IsolatedPythonModule):
raise TypeError(f"{implementation!r} is not an IsolatedPythonModule subclass")
if not issubclass(declaration_class, PythonNativeModule):
raise TypeError(f"{declaration!r} is not a PythonNativeModule contract")
if not issubclass(runtime_class, PythonNativeModule):
raise TypeError(f"{implementation!r} is not a PythonNativeModule subclass")
validate_runtime(declaration_class, runtime_class)
kwargs = pickle.load(sys.stdin.buffer)
kwargs["instance_name"] = instance_name
module = runtime_class(_isolated_python_runtime=True, **kwargs)
module = runtime_class(_python_native_runtime=True, **kwargs)
os.write(handshake_fd, b"READY\n")
except Exception as error:
try:
Expand Down
115 changes: 115 additions & 0 deletions dimos/core/python_native_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Resolve locked Python-native projects without writing into their package."""

from __future__ import annotations

from collections.abc import Mapping
import hashlib
from importlib.metadata import version
import inspect
from pathlib import Path
import re
from typing import Any

import dimos
from dimos.constants import CACHE_DIR

PYTHON_NATIVE_VERSION = "3.12"


def require_locked_project(project: Path) -> Path:
"""Validate and return a Python-native project shipped with DimOS."""
project = project.resolve()
manifest = project / "pyproject.toml"
lock = project / "uv.lock"
if not project.is_dir():
raise FileNotFoundError(f"Python-native runtime project is missing: {project}")
if not manifest.is_file():
raise FileNotFoundError(f"Python-native runtime manifest is missing: {manifest}")
if not lock.is_file():
raise FileNotFoundError(f"Python-native runtime lock is missing: {lock}")
return project


def python_native_project(module_class: type[Any]) -> Path:
"""Return the locked sibling project for a Python-native module contract."""
source = Path(inspect.getfile(module_class)).resolve()
project = source.parent / "python"
try:
return require_locked_project(project)
except FileNotFoundError as error:
if not project.is_dir():
raise FileNotFoundError(
f"Python-native runtime project is missing: {project}; "
"create a sibling 'python/' directory"
) from error
raise


def dimos_overlay_args() -> list[str]:
"""Overlay the running checkout or exact installed DimOS release."""
package_root = Path(dimos.__file__).resolve().parent
source_root = package_root.parent
if (source_root / "pyproject.toml").is_file() and (source_root / ".git").exists():
return ["--with-editable", str(source_root)]
return ["--with", f"dimos=={version('dimos')}"]


def project_environment(project: Path) -> Path:
"""Return a writable, lock-versioned virtualenv location for ``project``."""
project = require_locked_project(project)
digest = hashlib.sha256((project / "uv.lock").read_bytes()).hexdigest()[:12]
name = re.sub(r"[^a-zA-Z0-9_.-]+", "-", project.parent.name)
return CACHE_DIR / "python-native" / f"{name}-{digest}"


def project_environment_vars(
project: Path, extra: Mapping[str, str] | None = None
) -> dict[str, str]:
"""Environment additions used by uv for a packaged project."""
result = dict(extra or {})
result["UV_PROJECT_ENVIRONMENT"] = str(project_environment(project))
return result


def uv_sync_command(project: Path) -> list[str]:
"""Build the command that installs a locked project's third-party stack."""
project = require_locked_project(project)
return [
"uv",
"sync",
"--project",
str(project),
"--locked",
"--python",
PYTHON_NATIVE_VERSION,
]


def uv_run_command(project: Path, *command: str) -> list[str]:
"""Run inside a locked project with the current DimOS overlaid."""
project = require_locked_project(project)
return [
"uv",
"run",
"--project",
str(project),
"--locked",
"--python",
PYTHON_NATIVE_VERSION,
*dimos_overlay_args(),
*command,
]
Loading