Skip to content

[Task Clean-up] Dexterous Part 4/8: Fix MARL-to-single-agent training and enable handover Direct RSL-RL#6414

Open
hujc7 wants to merge 4 commits into
isaac-sim:developfrom
hujc7:jichuanh/task-cleanup-dex-part04
Open

[Task Clean-up] Dexterous Part 4/8: Fix MARL-to-single-agent training and enable handover Direct RSL-RL#6414
hujc7 wants to merge 4 commits into
isaac-sim:developfrom
hujc7:jichuanh/task-cleanup-dex-part04

Conversation

@hujc7

@hujc7 hujc7 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Review Map

PR Status Depends on Exact changes
#6410 [Docs] Environment overview regen
#6411 Part 1/8: Newton cloner/cubric/visualizer fixes
#6412 Part 2/8: OVPhysX articulation + manager runtime
#6413 Part 3/8: Reorient Direct, torch
📌 #6414 Part 4/8: MARL-to-single-agent fix + handover/camera Direct (this PR) #6413 changes
#6418 Part 5/8: Reorient manager counterparts #6413 changes
#6421 Part 6/8: Handover + camera manager counterparts #6413, #6414, #6418 changes
#6415 Part 7/8: Benchmark success-rate utilities + docs #6413, #6414, #6418, #6421 changes
#6582 Part 8/8: Warp variants → experimental (draft; merges last) #6413 changes
#6324 [DO-NOT-MERGE] Lumped validation reference ALL

Summary

Scope note: the core of this PR is a fix to the multi-agent-to-single-agent adapter (source/isaaclab/isaaclab/envs/utils/marl.py) and the RSL-RL vecenv wrapper — the adapter did not expose the latest observations through the public buffer, which silently breaks any MARL (Multi-Agent Reinforcement Learning) task trained through single-agent runners, not just handover. The handover Direct task is the exercised instance and gains RSL-RL training on top of the fix; the camera presets ride along for the camera Direct task.

  • Adds the RSL-RL runner configuration and behavioral-success metrics for the Shadow handover Direct task, and fixes handover construction on Newton (renamed distal joints).
  • Adds camera Direct renderer presets (including an RGB-depth preset for the Newton Warp renderer), configuration validation, and OVPhysX physics presets — consolidating the scope formerly tracked in [Task Clean-up][Camera] Dexterous Part 6/11: Add renderer presets to the Direct camera task #6416 and [Task Clean-up][OVPhysX] Dexterous Part 7/11: Add OVPhysX presets to the dexterous tasks #6417.
  • General MARL-to-single-agent bridging (broader than this task): single-agent RL libraries train DirectMARLEnv tasks through the multi_agent_to_single_agent conversion, and that bridge was broken at the observation contract. This PR fixes it generally — RSL-RL observations read from the public environment-owned obs_buf, stored by reset like step on all env bases (DirectRLEnv, DirectMARLEnv via isaaclab.envs.utils.marl, and the experimental warp base), replacing the adapter-side private hook. Every MARL task paired with every single-agent runner benefits; the handover task is the first consumer, not the scope of the fix.

Stacking

Validation

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enables RSL-RL PPO training on the Shadow Hand handover Direct task by fixing the MARL-to-single-agent adapter to expose live observations via _get_observations, adding a HandoverPPORunnerCfg, registering it on Isaac-Shadow-Handover-Direct, and fixing the Newton distal-joint actuator override from J0 to J1 to match the renamed asset joints.

  • MARL adapter fix: adds _convert_observations/_get_observations helpers plus an episode_length_buf property with setter and __getattr__ delegation so RSL-RL sees current rather than stale step-return observations; covered by three new unit tests.
  • Handover env additions: success-rate metrics, EpisodeErrorRecorder for per-episode goal-distance diagnostics, and sample_joint_positions_within_limits for cleaner reset sampling are wired in; these depend on isaaclab_tasks.core.utils from PR [Task Clean-up] Dexterous Part 3/8: Add success-rate metrics to the reorientation Direct tasks #6413 (declared dependency).
  • Missing module: handover_env.py also imports evaluate_handover_success and handover_reward from isaaclab_tasks.core.handover.mdp.rewards, a module that is absent from the repository and not named as coming from any dependency PR — this will cause an ImportError at task load time.

Confidence Score: 3/5

Not safe to merge as-is: handover_env.py will raise ModuleNotFoundError on import due to a missing mdp/rewards.py module that is neither present in the repo nor named as a dependency.

The handover_env.py change adds an import from isaaclab_tasks.core.handover.mdp.rewards (evaluate_handover_success, handover_reward). That package path does not exist anywhere in the repository, and neither this PR nor its declared dependency (#6413) explicitly accounts for it. Every code path that constructs or imports HandoverEnv will fail immediately with an unresolved import. The MARL adapter changes and the RSL-RL config are clean, and the Newton joint-name fix is correct, but the broken import makes the core task class unusable until the missing module is supplied or the import is corrected.

source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py — the import from isaaclab_tasks.core.handover.mdp.rewards references a module that does not exist.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/envs/utils/marl.py Adds __getattr__ delegation, episode_length_buf property+setter, and _get_observations/_convert_observations helpers to the MARL-to-single-agent adapter so RSL-RL gets fresh observations instead of stale step/reset buffers. Logic is correct; minor inefficiency when state_as_observation=True calls the inner env's _get_observations unnecessarily.
source/isaaclab/test/envs/test_marl_utils.py New unit tests covering concatenation, state-as-observation mode, and episode-length forwarding for the MARL adapter. Correctly uses torch.testing.assert_close and validates both getter and setter paths.
source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py Imports evaluate_handover_success and handover_reward from isaaclab_tasks.core.handover.mdp.rewards, a module that does not exist in the repository; will raise ModuleNotFoundError at import time. Also imports from isaaclab_tasks.core.utils (declared dependency from #6413).
source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py Renames distal-joint regex from J0 to J1 in the Newton actuator override to match the renamed Shadow Hand Newton asset joints; docstring updated to match. One inline comment still says "J0" (stale).
source/isaaclab_tasks/isaaclab_tasks/core/handover/agents/rsl_rl_ppo_cfg.py New RSL-RL PPO runner config (HandoverPPORunnerCfg) with a 4-layer MLP actor/critic and standard PPO hyperparameters; consistent with other dexterous task configs in the repo.
source/isaaclab_tasks/isaaclab_tasks/core/handover/init.py Adds rsl_rl_cfg_entry_point to the Isaac-Shadow-Handover-Direct Gym registration, pointing at the new HandoverPPORunnerCfg.
source/isaaclab_tasks/test/core/test_handover_rsl_rl_cfg.py Tests that the Gym entry point resolves to the correct config class and that combined observation/action dimensions (314/40) match expectations for the two-hand setup.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant RSL as RSL-RL Runner
    participant Adapter as multi_agent_to_single_agent (Env)
    participant MARL as HandoverEnv (DirectMARLEnv)

    RSL->>Adapter: reset(seed, options)
    Adapter->>MARL: reset(seed, options)
    MARL-->>Adapter: obs (per-agent dict), extras
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: "{"policy": concat_obs}, extras"

    RSL->>Adapter: _get_observations()
    Note over Adapter: NEW: exposes live obs for RSL-RL direct access
    Adapter->>MARL: _get_observations()
    MARL-->>Adapter: obs (per-agent dict)
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: "{"policy": concat_obs}"

    RSL->>Adapter: step(action)
    Adapter->>Adapter: split action by agent
    Adapter->>MARL: "step({right_hand: a0, left_hand: a1})"
    MARL-->>Adapter: obs, rewards, terminated, time_outs, extras
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: obs, sum(rewards), AND(terminated), AND(time_outs), extras

    RSL->>Adapter: "episode_length_buf = value"
    Note over Adapter: Property setter forwards write to MARL env
    Adapter->>MARL: "episode_length_buf = value"
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"}}}%%
sequenceDiagram
    participant RSL as RSL-RL Runner
    participant Adapter as multi_agent_to_single_agent (Env)
    participant MARL as HandoverEnv (DirectMARLEnv)

    RSL->>Adapter: reset(seed, options)
    Adapter->>MARL: reset(seed, options)
    MARL-->>Adapter: obs (per-agent dict), extras
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: "{"policy": concat_obs}, extras"

    RSL->>Adapter: _get_observations()
    Note over Adapter: NEW: exposes live obs for RSL-RL direct access
    Adapter->>MARL: _get_observations()
    MARL-->>Adapter: obs (per-agent dict)
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: "{"policy": concat_obs}"

    RSL->>Adapter: step(action)
    Adapter->>Adapter: split action by agent
    Adapter->>MARL: "step({right_hand: a0, left_hand: a1})"
    MARL-->>Adapter: obs, rewards, terminated, time_outs, extras
    Adapter->>Adapter: _convert_observations(obs)
    Adapter-->>RSL: obs, sum(rewards), AND(terminated), AND(time_outs), extras

    RSL->>Adapter: "episode_length_buf = value"
    Note over Adapter: Property setter forwards write to MARL env
    Adapter->>MARL: "episode_length_buf = value"
Loading

Comments Outside Diff (1)

  1. source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py, line 130 (link)

    P2 The inline comment above _SHADOW_HAND_NEWTON_CFG still references "J0" after the joint rename to "J1" was applied to both the actuator config and the function docstring, leaving this brief description stale.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Enable RSL-RL training for the handover ..." | Re-trigger Greptile

)

from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg
from isaaclab_tasks.core.handover.mdp.rewards import evaluate_handover_success, handover_reward

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 Missing module: isaaclab_tasks.core.handover.mdp.rewards

handover_env.py imports evaluate_handover_success and handover_reward from isaaclab_tasks.core.handover.mdp.rewards, but no such module exists anywhere in the repository — there is no mdp/ subdirectory under core/handover/. The PR description names PR #6413 as the only declared code dependency (for isaaclab_tasks.core.utils), but that PR's description only mentions "core.utils helpers" and does not describe adding a handover-specific mdp/rewards.py. Without this file the task class will raise ModuleNotFoundError on import, blocking all training runs.

Comment on lines +106 to +108
def _get_observations(self) -> VecEnvObs:
"""Return current observations through the single-agent interface."""
return self._convert_observations(self.env._get_observations())

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 Unnecessary _get_observations() call in state-as-observation mode

When _state_as_observation=True, _get_observations still calls self.env._get_observations() (which may trigger sensor data collection) and then immediately discards the result inside _convert_observations, which pivots to self.env.state(). The call is harmless but wasteful — consider short-circuiting before the inner call, e.g. returning {"policy": self.env.state()} directly when in state-as-observation mode.

@Toni-SM

Toni-SM commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes the multi-agent-to-single-agent adapter to expose current observations through the Direct RL interface...

Such implementation should not be accepted as it is trying to fix a problem a RL library has (require a private method, that is not part of the standard API Gymnasium / Petting-Zoo Parallel API to initialize its logic) from Isaac Lab side.

There is nothing to fix here. The fix must be implemented in the RL library to do not depend on the private _get_observations method that is not part of the standard API, but instead to rely on public properties and method of the standard environment API to initialize the library.

@kellyguo11 for viz

Comment on lines +96 to +111
def _convert_observations(self, obs: dict[AgentID, ObsType]) -> VecEnvObs:
"""Convert multi-agent observations to the single-agent policy observation."""
if self._state_as_observation:
obs = {"policy": self.env.state()}
# concatenate agents' observations
# FIXME: This implementation assumes the spaces are fundamental ones. Fix it to support composite spaces
else:
obs = {
"policy": torch.cat(
[obs[agent].reshape(self.num_envs, -1) for agent in self.env.possible_agents], dim=-1
)
}
return {"policy": self.env.state()}
return {
"policy": torch.cat(
[obs[agent].reshape(self.num_envs, -1) for agent in self.env.possible_agents], dim=-1
)
}

return obs, extras
def _get_observations(self) -> VecEnvObs:
"""Return current observations through the single-agent interface."""
if self._state_as_observation:
# the state replaces the observations entirely; skip computing them
return {"policy": self.env.state()}
return self._convert_observations(self.env._get_observations())

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

@AntoineRichard AntoineRichard left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

My concern is that this PR is doing some real (important and needed) work on MARL and RSL-RL while hiding that under a RSL-RL only refactor.

Also same concerns around moving entirely to warp.

Comment on lines 78 to 80
self.goal_rot = torch.zeros((self.num_envs, 4), dtype=torch.float, device=self.device)
self.goal_rot[:, 0] = 1.0
self.goal_rot[:, 3] = 1.0 # identity quaternion in (x, y, z, w) layout
self.goal_pos = torch.zeros((self.num_envs, 3), dtype=torch.float, device=self.device)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not use warp arrays directly?

hujc7 added 2 commits July 16, 2026 17:44
Add a behavioral Metrics/success_rate signal (goal-reach streaks per
episode) and threshold-independent episode orientation-error diagnostics
to the Direct reorientation environments, with the shared helpers in
isaaclab_tasks.core.utils and torch math tests. The task logic is
torch-first per the mainline convention; success gates task health while
reward stays diagnostic. Also fix hand resets that could initialize
joints below their lower position limits.
Add the RSL-RL runner configuration for the Shadow handover Direct task
and success-rate metrics on the torch-first path, fix handover
construction on Newton (renamed distal joints), and land the camera
Direct renderer presets with configuration validation. RSL-RL
observations now read from the public environment-owned obs_buf on all
Direct env bases (reset stores the buffer like step), replacing the
adapter-side private hook.
@hujc7
hujc7 force-pushed the jichuanh/task-cleanup-dex-part04 branch from 5918610 to 392e7d9 Compare July 17, 2026 00:53
@hujc7 hujc7 changed the title [Task Clean-up] Dexterous Part 4/11: Enable RSL-RL training for the handover Direct task [Task Clean-up] Dexterous Part 4: Enable RSL-RL training for the handover Direct task and add camera presets Jul 17, 2026
@hujc7 hujc7 changed the title [Task Clean-up] Dexterous Part 4: Enable RSL-RL training for the handover Direct task and add camera presets [Task Clean-up] Dexterous Part 4/11: Enable RSL-RL training for the handover Direct task Jul 17, 2026
hujc7 added a commit that referenced this pull request Jul 17, 2026
…nager runtime (#6412)

## Summary

- Fixes OVPhysX actuator joint indices to follow the common actuator
indexing contract.
- Fixes OVPhysX initialization alongside Kit by reusing Kit's registered
PhysX schema provider.
- Fixes the OVPhysX manager to support both the declared public runtime
API and the current runtime API.
- Regression tests included. Validated by full dexterous training runs
on the OVPhysX backend; split out of the lumped validation branch #6324
(Part 2 of 11).

## Dependencies

- None.

## Series review map

Full integrated diff + training/validation evidence: the lumped
validation PR #6324
(DO-NOT-MERGE).

| Part | PR |
|---|---|
| Docs: regenerate the environment overview table |
#6410 |
| Part 1/11: Newton runtime fixes (cloner rows, cubric fallback, viz
teardown) | #6411 |
| **Part 2/11: OVPhysX runtime fixes (this PR)** |
#6412 |
| Part 3/11: success-rate metrics for the Direct reorientation tasks |
#6413 |
| Part 4/11: RSL-RL training for the handover Direct task |
#6414 |
| Part 5/11: success-rate support in the benchmark utilities |
#6415 |
| Part 6/11: renderer presets for the Direct camera task |
#6416 |
| Part 7/11: OVPhysX presets for the dexterous tasks |
#6417 |
| Part 8/11: Allegro manager counterpart |
#6418 |
| Part 9/11: Shadow + OpenAI manager counterparts |
#6419 |
| Part 10/11: Shadow camera manager counterpart |
#6420 |
| Part 11/11: Shadow handover manager counterpart |
#6421 |


---
### Exact changes in this PR

- OVPhysX backend changes + tests:
1f7a433
@hujc7 hujc7 changed the title [Task Clean-up] Dexterous Part 4/11: Enable RSL-RL training for the handover Direct task [Task Clean-up] Dexterous Part 4/8: Enable RSL-RL training for the handover Direct task and add camera presets Jul 17, 2026
Fold the reviewed lump changes that belong to this part's content:

- Share the handover sim settings through the HandoverTaskCfgBase
  mixin instead of a module-level cfg variable.
- Reuse isaaclab.utils.math.quat_mul for the Newton initial-rotation
  composition instead of a local xyzw product helper.
- Nest the single-consumer tiled-camera helper cfg inside the camera
  task cfg.
- Drop the stale kit-less OpenUSD bullet from the isaaclab changelog
  fragment.

Source commits on the lump branch: 2c22af0, 2c5ea5b,
42675b6, 1f05f85.
@hujc7
hujc7 requested a review from a team July 18, 2026 11:10
@hujc7 hujc7 changed the title [Task Clean-up] Dexterous Part 4/8: Enable RSL-RL training for the handover Direct task and add camera presets [Task Clean-up] Dexterous Part 4/8: Fix MARL-to-single-agent training and enable handover Direct RSL-RL Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants