Skip to content

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

Merged
hujc7 merged 12 commits into
isaac-sim:developfrom
hujc7:jichuanh/task-cleanup-dex-part04
Jul 29, 2026
Merged

[Task Clean-up] Dexterous Part 4/9: Fix MARL-to-single-agent training and enable handover Direct RSL-RL#6414
hujc7 merged 12 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
#6411 Part 1/9: Newton cloner/cubric/visualizer fixes merged
#6412 Part 2/9: OVPhysX articulation + manager runtime merged
#6413 Part 3/9: Reorient Direct, torch merged
📌 #6414 Part 4/9: MARL-to-single-agent fix + handover/camera Direct (this PR) — (on develop) Files changed tab
#6418 Part 5/9: Reorient manager counterparts #6413 changes
#6421 Part 6/9: Handover + camera manager counterparts #6414, #6418 changes
#6410 Part 7/9: Environment overview docs #6421 changes
#6415 Part 8/9: Benchmark success-rate utilities #6421 changes
#6582 Part 9/9: Warp variants → experimental (draft; merges last) changes
#6324 [DO-NOT-MERGE] Lumped validation reference ALL

Summary

Converts the two-hand Shadow Hand handover task from multi-agent (MARL) to single-agent (enabling RSL-RL training), fixes the underlying MARL→single-agent observation bridge, and consolidates the shared Shadow Hand definitions into the asset.

  • MARL → single-agent bridge (general, broader than this task). Single-agent RL libraries train DirectMARLEnv tasks via multi_agent_to_single_agent; the bridge dropped the latest observations from the public buffer. Fixed generally — RSL-RL observations read from the env-owned obs_buf, stored by reset like step on all env bases (DirectRLEnv, DirectMARLEnv, the experimental warp base). Every MARL task + single-agent runner benefits; handover is the first consumer.
  • Handover Direct → single-agent + RSL-RL. Single-agent conversion, RSL-RL runner config, shared identity in handover_common, success-rate reward metrics, and a fix for its Newton construction failure (see below).
  • Shadow Hand Newton robot → the asset. Moved the Newton (MJWarp) robot cfg into isaaclab_assets as SHADOW_HAND_NEWTON_CFG (beside SHADOW_HAND_CFG); reorient and handover consume it as siblings (neither imports the other). Reorient uses the default gains; handover overrides only the finger stiffness/damping to 20/2 for its catch.
  • Shadow camera cleanup. Renamed the camera Direct modules to shadow_hand_direct_camera_env[_cfg]; moved cube-keypoint math to the shared, unit-tested reorient/mdp/observations.py (replacing the per-env compute_keypoints shim); and added OVPhysX physics presets to the handover and camera Direct environments. The camera env config is otherwise unchanged from develop.

Behavior changes

Reorient-on-Newton is unchanged. The Newton hand definition moves verbatim into SHADOW_HAND_NEWTON_CFG — same joint expressions, effort limits, gains, friction and armature — so its config diff vs develop is purely the move, and the Direct env config has no diff at all.

Handover changes in three ways:

  • Its default physics backend moves from PhysX to Newton (MJWarp), matching the reorientation tasks. Pass physics=physx for the previous backend.
  • Its distal_passive actuator group is removed. It targeted robot0_(FF|MF|RF|LF)J0 — joints that do not exist on the Newton asset (its fingers are numbered J1J4/J5), so constructing the environment raised No joints found for actuator group. The fingers group already drives those joints, and the comment justifying the group (USD-baked stiffness=286/damping=57) does not apply to this asset, which authors no drive stiffness at all.
  • Two orientation fixes: the goal orientation was initialized to a 180-degree rotation instead of identity, and the Newton root rotation replaced the asset's baked rotation instead of composing with it, leaving both palms rotated 90 degrees.

Two pre-existing discrepancies were found while verifying against the asset and are left for a follow-up, since each is a behavior change needing its own validation: the config sets armature=2e-3 where the asset authors 0.0002, and the per-finger J1/J2 pair is coupled by a fixed tendon that the MJWarp solver currently skips — the actuator gains are what hold that pair together today, which is why the J4 knuckle-abduction joints cannot simply be added to the driven set.

Notes

  • Rebased onto latest develop; clean 3-commit history: Add single-agent flattening for MARL Direct environments / Convert Shadow Hand handover to single-agent Direct with RSL-RL / Clean up Shadow reorient/camera tasks and consolidate the Newton hand.

Validation

  • Kit-free suite green: handover, keypoint math, the MARL adapter, and new checks pinning the RSL-RL wrapper observation contract (verified to fail against the previous implementation).
  • Camera Direct reaches training takeoff under rsl_rl (frame-verified videos); its env config now matches develop's established vision env.
  • Reorient-on-Newton vs develop (rsl_rl, 2048 envs, 1500 iterations, 4 runs per config): this branch spans 524–664 reward, develop spans 654–822 — overlapping distributions whose per-config spreads (140–168) exceed the gap between them. The move is additionally verified at the config level: the Newton robot configuration is character-for-character identical to develop's.
  • Handover-on-Newton trains end-to-end on this branch (reward 1314), which the develop config cannot do at all — it fails during construction. Full evidence: [DO-NOT-MERGE][Task Clean-up] Dexterous: lumped validation branch (series reference) #6324.

@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/9: 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.

Toni-SM
Toni-SM previously requested changes Jul 9, 2026

@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 thread source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env.py
@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
@hujc7
hujc7 requested a review from StafaH as a code owner July 18, 2026 11:10
@hujc7
hujc7 requested a review from matthewtrepte as a code owner July 18, 2026 11:10
@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
hujc7 added a commit that referenced this pull request Jul 22, 2026
…isualizer teardown (#6411)

## Review Map

- **Exact changes**: a stacked PR's page shows the cumulative diff of
its dependency chain; the link pins the commit range that is the PR's
own contribution.
- Links pin specific SHAs and can go stale after a branch update — the
table on #6324 is refreshed first.

| PR | Status | Depends on | Exact changes |
|---|---|---|---|
| #6410 [Docs] Environment overview regen |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6410?label=)
| — | — |
| 📌 #6411 Part 1/8: Newton cloner/cubric/visualizer fixes (this PR) |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6411?label=)
| — | — |
| #6412 Part 2/8: OVPhysX articulation + manager runtime |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6412?label=)
| — | — |
| #6413 Part 3/8: Reorient Direct, torch |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6413?label=)
| — | — |
| #6414 Part 4/8: MARL-to-single-agent fix + handover/camera Direct |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6414?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6414/changes/6e8a63e4e028b2d43676ea30c446b9dc9068c7b5..5cb00e7cb5cc813b202521272e043007cd255194)
|
| #6418 Part 5/8: Reorient manager counterparts |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6418?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6418/changes/79f87501ac4c81de93a71dab00dc443da62113aa..e7c9a9a3fae3a7972b0c5165ae683abffb7d0e0f)
|
| #6421 Part 6/8: Handover + camera manager counterparts |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6421?label=)
| #6413, #6414, #6418 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6421/changes/01c9f4d8c5c35a5688b2a5bb90209e16b8f81b99..835a5815ec49b11aada1d20a76c177054505e6e7)
|
| #6415 Part 7/8: Benchmark success-rate utilities + docs |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6415?label=)
| #6413, #6414, #6418, #6421 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6415/changes/e1abb6b1790ccc57af42551eebccf743633f1f13..d6348539aa8032d9668c20a8fea462c5d88d3af9)
|
| #6582 Part 8/8: Warp variants → experimental (draft; merges last) |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6582?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6582/changes/21dbb1769c4e30c8e9e5b0f563c2dae24c230349..83e1587cadd9712a60615ed2a3cb2d177c2ac24d)
|
| #6324 [DO-NOT-MERGE] Lumped validation reference |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6324?label=)
| ALL | — |

## Summary

- Fixes Newton cloner label rows, the cubric IAdapter version audit
(exact-match fallback to the CPU hierarchy path), and visualizer
teardown.
- Retains an in-tree `ignore_paths` workaround for custom-frequency USD
traversal; it becomes redundant once the Newton pin advance
(#6584) merges — this PR then
only needs a rebase.

## Stacking

- Independent; based on `develop`.

## Review history

- Approved. The Newton pin + MuJoCo overrides were split out to
#6584 via revert commits
(2026-07-17) so this PR's CI runs against develop's pins.
@github-actions github-actions Bot added the asset New asset feature or request label Jul 23, 2026
@hujc7 hujc7 changed the title [Task Clean-up] Dexterous Part 4/8: Fix MARL-to-single-agent training and enable handover Direct RSL-RL [Task Clean-up] Dexterous Part 4/9: Fix MARL-to-single-agent training and enable handover Direct RSL-RL Jul 23, 2026
hujc7 added a commit that referenced this pull request Jul 24, 2026
…eorientation Direct tasks (#6413)

## Review Map

- **Exact changes**: a stacked PR's page shows the cumulative diff of
its dependency chain; the link pins the commit range that is the PR's
own contribution.
- Links pin specific SHAs and can go stale after a branch update — the
table on #6324 is refreshed first.

| PR | Status | Depends on | Exact changes |
|---|---|---|---|
| #6411 Part 1/9: Newton cloner/cubric/visualizer fixes |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6411?label=)
| — | merged |
| #6412 Part 2/9: OVPhysX articulation + manager runtime |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6412?label=)
| — | merged |
| 📌 #6413 Part 3/9: Reorient Direct, torch (this PR) |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6413?label=)
| — |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6413/changes/f4895f0f9ee..d29afc75e71)
|
| #6414 Part 4/9: MARL-to-single-agent fix + handover/camera Direct |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6414?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6414/changes/d29afc75e71..b10a84948f8)
|
| #6418 Part 5/9: Reorient manager counterparts |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6418?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6418/changes/d29afc75e71..707d37f8f99)
|
| #6421 Part 6/9: Handover + camera manager counterparts |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6421?label=)
| #6413, #6414, #6418 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6421/changes/707d37f8f99..b50ac8906fc)
|
| #6410 Part 7/9: Environment overview docs |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6410?label=)
| #6421 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6410/changes/b50ac8906fc..af259c0778d)
|
| #6415 Part 8/9: Benchmark success-rate utilities |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6415?label=)
| #6421 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6415/changes/b50ac8906fc..c7f2f019d8b)
|
| #6582 Part 9/9: Warp variants → experimental (draft; merges last) |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6582?label=)
| #6413 |
[changes](https://github.com/isaac-sim/IsaacLab/pull/6582/changes/d29afc75e71..167c28578b3)
|
| #6324 [DO-NOT-MERGE] Lumped validation reference |
![](https://img.shields.io/github/pulls/detail/state/isaac-sim/IsaacLab/6324?label=)
| ALL | — |

## Summary

- Adds a behavioral `Metrics/success_rate` signal (goal-reach streaks
per episode) and threshold-independent episode orientation-error
diagnostics to the Direct reorientation environments; success gates task
health, reward stays diagnostic.
- Task logic is **torch-first** per the mainline convention (plain torch
buffers; `.torch` accessors only at the core-lib boundary). This
supersedes the earlier warp-first revision of this PR; the warp
implementation moved to `isaaclab_tasks_experimental`
(#6582).
- Lands shared helpers in `isaaclab_tasks.core.utils`
(`EpisodeErrorRecorder`, `sample_joint_positions_within_limits`) with
torch math tests; fixes hand resets below lower joint limits.

## Stacking

- First stack PR; based on `develop`.

## Validation

- Kit-free torch math + utils tests green; Direct state training on
Newton: success rate 0.85–1.00, reward ≈3600–3900 at 2000 iterations,
frame-verified policy video. Full integrated evidence: #6324.

## Review history

- The earlier warp-era review rounds on this PR are superseded by the
torch restack (2026-07-16); the warp implementation and its
review-polished kernels live on in
#6582.
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 24, 2026
@hujc7
hujc7 force-pushed the jichuanh/task-cleanup-dex-part04 branch 4 times, most recently from 648a77b to c82f493 Compare July 25, 2026 22:35
("default", ["rgb", "depth", "semantic_segmentation"]),
("full", ["rgb", "depth", "semantic_segmentation"]),
("rgb", ["rgb"]),
("rgb_depth", ["rgb", "depth"]),

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.

rgbd?

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.

No longer applies — the rgb_depth preset was dropped and the camera config matches develop again.

)

gym.register(
id="Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct",

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.

As discussed offline, we can move benchmark to contrib. (if this is handled in a future PR please ignore)

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.

Taking the follow-up option — the ID is still registered unchanged; moving it needs a deprecated alias since the quickstart docs reference it.

self.env.episode_length_buf = value

@property
def obs_buf(self) -> VecEnvObs:

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.

It might be cleaner to do something like:

def reset(...):
    obs, extras = self.env.reset(seed, options)
    self._obs_buf = self._convert_observations(obs)
    return self._obs_buf, extras

def step(...):
    ...
    self._obs_buf = self._convert_observations(obs)
    return self._obs_buf, ...

@property
def obs_buf(self):
    return self._obs_buf

So obs buffer stays true to it's propery, instead of being a conversion mechanism

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.

Done in f142a22, as sketched — stored in reset()/step(), obs_buf is now a plain property. Also removes the repeated conversion greptile flagged.

@@ -0,0 +1,82 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).

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.

AI Review: The PR changes RslRlVecEnvWrapper.get_observations() from actively computing observations to unconditionally reading env.obs_buf, but there is no wrapper-level regression test. Please add tests showing that get_observations() exactly matches the most recent reset and step outputs, including a noisy/non-idempotent observation case. We should also verify every supported environment family exposes obs_buf.

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.

There wasn't one. 41b7ed5 adds kit-free test_rsl_rl_wrapper_observations.py: the wrapper returns the env buffer, tracks updates, never calls the private method, and DirectRLEnv publishes it in reset and step. Verified to fail against the previous implementation.

# concatenate agents' observations
@episode_length_buf.setter
def episode_length_buf(self, value: torch.Tensor) -> None:
self.env.episode_length_buf = value

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.

This might need to be something like: self.env.episode_length_buf.copy_(value)? Not entirely sure though.

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.

Changed in 6ec4220. Rebinding left anything holding a reference to the wrapped buffer stale; the in-place write keeps them in sync. The test now covers values and buffer identity.

import isaaclab.sim as sim_utils
from isaaclab.markers import VisualizationMarkersCfg

from isaaclab_assets.robots.shadow_hand import (

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.

Cleaner to write:

from isaaclab_assets.robots.shadow_hand import (
    SHADOW_ACTUATED_JOINT_NAMES as ACTUATED_JOINT_NAMES,
    SHADOW_FINGERTIP_BODY_NAMES as FINGERTIP_BODY_NAMES,
)

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.

ruff splits it back under our isort config (combine-as-imports isn't enabled), so the current shape is what the formatter produces.

self.goal_pos = torch.zeros((self.num_envs, 3), dtype=torch.float, device=self.device)
self.goal_pos[:, :] = torch.tensor([0.0, -0.64, 0.54], device=self.device)
# goal = object default position + shared offset (mirrors HandoverCommand.__init__)
self.goal_pos[:, :] = self.object.data.default_root_pose.torch[:, :3].to(self.device) + torch.tensor(

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.

Here and elsewhere, what's going on with this self.device stuff? Are the buffers on the wrong device? Everything should already be on the same device. I would remove all this .to(device) stuff, maybe add a test instead? to(device) could be expensive to always be calling in hot paths.

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.

Removed. Both were in __init__ rather than a hot path, but no-ops on correctly-placed data.

static_friction=1.0,
dynamic_friction=1.0,
),
render_interval=2,

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.

here and elsewhere let's make the default for base envs be render_interval = decimation? Seems like default behaviour should be to render at every physics step?

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.

Restored in 4144bf8. develop had render_interval=decimation and this PR had hard-coded it to 2 — same value today, but the coupling was lost. It was the only such site.

# The Newton hand inherits the shared actuator from ``SHADOW_HAND_NEWTON_CFG``,
# overriding only the finger gains: the catch needs a higher nominal gain
# (20.0/2.0) than reorient's base. Per-hand prim path and init pose also differ here.
newton_cfg = SHADOW_HAND_NEWTON_CFG.replace(

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.

newton_mjwarp_cfg, since kamino is unknown right now

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.

Renamed, both sites.

init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.39, 0.54), rot=(0.0, 0.0, 0.0, 1.0)),
)
default = physx
ovphysx = physx # OvPhysX is PhysX-based; reuse the PhysX-tuned rigid sphere

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.

Comment not really necessary

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.

Removed.

hujc7 added 5 commits July 27, 2026 01:15
Add helpers in envs/utils/marl.py (with tests) to flatten a multi-agent Direct env's per-agent action/observation dicts into single-agent tensors, wire them through DirectRLEnv and the RSL-RL vecenv wrapper, and mirror the path in the experimental warp Direct env. Lets a task authored as multi-agent train with a single-agent RL library.
Convert the two-hand Shadow Hand handover task from multi-agent (MARL) to single-agent, add an RSL-RL training configuration, shared identity in handover_common, success-rate reward metrics, and mdp helpers.
- Store the converted observations in the multi-agent-to-single-agent
  adapter instead of recomputing them on every obs_buf access.
- Cover the latest-observation contract for both the concatenated and
  state-as-observation modes.
- Drop redundant device transfers and a duplicated comment in the
  handover environment.
- Rename the Newton hand config and trim an unnecessary comment.
Add kit-free checks that the wrapper reads the environment-owned
observation buffer, tracks its updates, and never falls back to the
private environment observation method.

Also correct the handover comments: the gain override replaces the
per-joint mapping, so it applies to the whole actuator group rather
than the fingers alone.
@hujc7
hujc7 force-pushed the jichuanh/task-cleanup-dex-part04 branch from 1567317 to 41b7ed5 Compare July 27, 2026 08:15
@hujc7
hujc7 requested a review from pbarejko as a code owner July 27, 2026 08:15
hujc7 added 4 commits July 27, 2026 01:40
The multi-agent-to-single-agent adapter rebound the wrapped
environment's episode-length buffer, so anything holding a reference
to it silently went stale. Copy into the existing buffer instead.
The simulation config hard-coded the render interval to a literal that
happened to match the decimation, so changing the decimation would have
silently desynchronised rendering. Restore the reference.

@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.

Still some comment issues, but overall it looks fine


# return observations, rewards, resets and extras
# store the returned buffer so RslRlVecEnvWrapper.get_observations() can read env.obs_buf
self.obs_buf = {"policy": self.torch_obs_buf.clone()}

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.

I think this is coming from som rl libraries messing with the data from the simulator inplace

@@ -258,10 +275,14 @@ class PhysicsCfg(PresetCfg):
update_data_interval=2,

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 the update data interval to 4?

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.

Raised to 4 in 4fec9dd.

Every other task pairs update_data_interval with num_substeps 1:1 — all eight sites are 2/2 — and this PR raised handover's substeps to 4 without updating the interval, so 4 restores that invariant.

Validated at 1500 iterations on both 2048 and 8192 envs: reward 1341 and 1483, no NaN. The 8192 run is the scale that motivated the substep bump.

Comment on lines +278 to +280
# 4 substeps (vs the reorient task's 2): sustained ball-palm contact against the
# distal joints explodes ~0.7% of 8192 envs to NaN at 2 substeps (zero-action
# probe, 300 steps); 4 substeps shows none.

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.

I think this comment could be trimmed

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.

Cut to two lines in 4fec9dd. Dropped the probe details (~0.7% of 8192 envs, zero-action probe, 300 steps), kept the reason for 4 substeps.

Comment on lines +154 to +160
# Newton's importer bakes the asset's native root orientation into the
# root joint (see the note on SHADOW_HAND_NEWTON_CFG.init_state), so the
# task rotation must compose with that base rotation rather than replace
# it — replacing left both palms heading 90 degrees off and the object
# never rested in the right hand.
# Composed in float64 via the shared (x, y, z, w) quaternion product,
# matching the previously used wp.quatd math bit-for-bit.

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.

I think this comment could be trimmed

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.

Cut from 6 lines to 3 in 4fec9dd, dropping the migration note (matching the previously used wp.quatd math bit-for-bit). The reason stays: Newton's importer bakes the root orientation, so the task rotation composes rather than replaces it — otherwise both palms end up rotated 90 degrees.

Comment on lines +167 to +169
# The Newton hand inherits the shared actuator from ``SHADOW_HAND_NEWTON_CFG``, raising its
# gains: the catch needs a higher nominal gain (20.0/2.0) than reorient's base. Per-hand prim
# path and init pose also differ here.

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.

comment can be trimmed

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.

Removed entirely in 4fec9dd — it repeated the docstring 20 lines above.

Comment on lines +145 to +149
The Newton variant reuses the shared actuator defined on ``SHADOW_HAND_NEWTON_CFG``,
raising its gains to ``20.0`` / ``2.0`` -- the smallest tested setting at which the
handover policy learns the catch (mean reward at iter 200 / 2048 envs ~27 -> ~777 vs
reorient's base gains). The scalar replaces the per-joint gain mapping, so this applies
to every joint in the group, wrists included.

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.

This here are traces that could be trimmed / removed.

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.

Trimmed in 4fec9dd — dropped the tuning trace (mean reward at iter 200 / 2048 envs ~27 -> ~777) and kept only why the gains are raised.

hujc7 added 3 commits July 28, 2026 15:17
Drop tuning traces and a comment that duplicated the enclosing
docstring, keeping the reasons behind each setting.

Raise the MJWarp data-update interval to match the substep count.
The Shadow Hand yellow-background render test arrived from develop
importing the camera environment under its previous module name, which
this branch renames. The merge was textually clean but left the import
unresolvable, so the test process died during collection.
@hujc7
hujc7 dismissed Toni-SM’s stale review July 29, 2026 05:13

Needed the change to unblock. Will sync offline to followup

@hujc7
hujc7 merged commit cb12910 into isaac-sim:develop Jul 29, 2026
70 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants