[Odin] Fix preset-based agent selection for rsl_rl, rl_games and sb3 - #7532
[Odin] Fix preset-based agent selection for rsl_rl, rl_games and sb3#7532AntoineRichard wants to merge 3 commits into
Conversation
Preset-based --agent auto-selection was unreachable for every entrypoint that registers --agent with a non-None default. The selection guard could not distinguish a default-supplied value from a user-typed one, so presets=resnet18 and presets=theia_tiny on Isaac-Cartpole-Camera kept the raw-camera entry point and RSL-RL failed to construct its runner. Detect an explicit --agent by re-parsing into a sentinel-seeded namespace instead, which keeps an explicitly typed value winning over auto-selection. The rsl_rl, rl_games and sb3 benchmark train and play entrypoints also never passed agent_library to setup_preset_cli, so auto-selection was not attempted there at all. Only the skrl entrypoints wired it.
Greptile SummaryThe PR repairs preset-based agent selection when
Confidence Score: 5/5The PR appears safe to merge, with no actionable correctness, security, or compatibility issue identified. The new selection path preserves explicit user choices and existing defaults while selecting a different agent only when task registration provides an unambiguous compatible entry point. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Parse benchmark or train arguments] --> B{Was --agent explicitly supplied?}
B -->|Yes| C[Preserve explicit agent]
B -->|No| D[Enumerate task agent entry points]
D --> E{Exactly one preset-compatible agent?}
E -->|Yes| F[Select compatible agent]
E -->|No| G[Preserve parser default]
C --> H[Resolve task and agent configuration]
F --> H
G --> H
Reviews (1): Last reviewed commit: "Fix preset-based agent selection for rsl..." | Re-trigger Greptile |
There was a problem hiding this comment.
Isaac Lab Review Bot
Reviewed the preset-agent selection fix across the shared CLI helper and all six rsl_rl, rl_games, and sb3 benchmark train/play entrypoints. The patch distinguishes an explicit --agent from an argparse default, enables backend-aware selection in the benchmark paths, and preserves canonical defaults when no unambiguous preset-compatible agent exists.
- Design and architecture: Returning str | None from _auto_select_agent centralizes namespace mutation in setup_preset_cli and cleanly preserves caller defaults when selection is absent or ambiguous. The sentinel namespace probe is consistent with argparse behavior and retains explicit-user-choice precedence. Re-parsing does execute parser actions twice, but the affected entrypoints use ordinary argument actions; custom side-effecting actions remain a non-blocking future compatibility consideration.
- API: setup_preset_cli remains signature-compatible, and its agent_library behavior is now documented. The changed _auto_select_agent helper is private, its local callers were updated, and both affected source packages include changelog fragments. Explicit --agent values continue to override automatic selection.
- Implementation: The producer-to-consumer path was traced from benchmark request argv generation through setup_preset_cli to resolve_task_config. Preset compatibility selects the feature entrypoints for rsl_rl and rl_games, ambiguous or absent matches retain existing defaults, and the default-absent rule still supports tasks with a single noncanonical agent entrypoint. Tests cover feature-preset selection, explicit override precedence, and default preservation across the wired backends. The added hasattr guard means parsers without an agent destination no longer receive one implicitly, but the changed benchmark consumers all define --agent.
No blocking issues. No inline issue met the actionable-evidence threshold; the assessment above records the review feedback.
Automated review; human maintainers own approval decisions.
Keep _auto_select_agent mutating args in place instead of returning the selected entry point: the return-value refactor was a tidy-up riding along, not something the fix needs. Replace the library x preset cross product in the unit test with a single selection case, and cover the three --agent spellings argparse accepts (separate, =VALUE, abbreviated) instead. The abbreviated form is what rules out scanning argv for a literal token; the benchmark test keeps the full resnet18/theia_tiny x rsl_rl/rl_games matrix that mirrors the failing rows.
The three benchmark_play_* wirings were unproven: reverting them broke no test, since every regression test drove the training path. Playback resolves the same agent config through the same resolve_task_config call and builds the same runner, so it mis-selects the raw-camera entry point identically and would load a feature-trained checkpoint into the wrong policy architecture. The sweep never surfaced it only because a row that dies at train_rc=1 never reaches playback. Add the playback selection test so the wiring is justified by a test that fails without it, rather than kept for symmetry.
Description
Preset-based
--agentauto-selection is dead code forrsl_rl,rl_gamesandsb3. Two defects sit in series on the same code path:_auto_select_agentinsource/isaaclab_tasks/isaaclab_tasks/utils/preset_cli.pywas only reached whenargs.agent is None.add_common_train_argsregisters--agentwithdefault=agent_default, and every backend exceptskrlpasses a non-Noneagent_default(rsl_rl_cfg_entry_point,rl_games_cfg_entry_point,sb3_cfg_entry_point). The parsed value is therefore neverNone, so the branch never runs.benchmark_train_{rsl_rl,rl_games,sb3}.pyandbenchmark_play_{rsl_rl,rl_games,sb3}.pycalledsetup_preset_cli(parser, argv)withoutagent_library, so auto-selection was not attempted at all (and the registered-agent help listing was missing too). Only theskrlbenchmark entrypoints wired it.Provenance and symptom
Benchmark sweep dispatch
20260901-153531(image built fromrelease/3.0.0atf88dbc59c82,rsl_rl) produced 60 failed rows:resnet18(30) andtheia_tiny(30), failing 100% across all three renderers and all physics backends. Every one died before the first training step:Call chain:
OnPolicyRunner.__init__→ppo.py construct_algorithm→rsl_rl/utils/utils.py:233 resolve_obs_groups.Why this is an agent-entrypoint bug, not a missing observation group
Isaac-Cartpole-Cameraalready declares the correct pairing:and
CartpoleCameraFeaturePPORunnerCfgsetsobs_groupswithcritic: ["policy"]. The feature entry point exists, is registered, is correct — it was simply unreachable, soresnet18/theia_tinyran against the raw-cameraCartpoleCameraPPORunnerCfg, whoseobs_groupsasks for acriticgroup the env does not expose. Nothing needs to be added to the environment.Why both defects are in one PR
Neither half fixes the observed failure alone; this was measured, not assumed. Reverting either half of the change and running the regression test:
agent_librarywired, guard unchanged)--agentstill parses to the non-Nonedefault, so the guard rejectsagent_librarynot wired)if agent_library and ...isFalse, auto-selection never attemptedThey are two links in one chain, so splitting them yields a PR that fixes nothing observable and a PR that cannot be tested end-to-end.
Scope: train and play, benchmark and non-benchmark
To pre-empt the obvious question — this is not a play-only or a train-only fix.
benchmark_train_{rsl_rl,rl_games,sb3}preset_cli.py+agent_library=wiringtest_training_request_selects_preset_compatible_agent— this is the observed 60-row failurebenchmark_play_{rsl_rl,rl_games,sb3}preset_cli.py+agent_library=wiringtest_play_request_selects_preset_compatible_agentisaaclab_rltrain_*/play_*(non-benchmark)agent_librarypreset_cli.pyalone; no file in this PR touches themtest_setup_preset_cli_auto_selects_agent_over_non_none_defaultEach half of the benchmark wiring was reverted independently and re-tested:
There is no train/play asymmetry that would justify wiring only one of them: both register
--agentwith the same non-Nonedefault via the same helper, both callsetup_preset_cli, and both feedargs.agentinto the sameresolve_task_config(...)and then into the sameOnPolicyRunner(...)construction. The reason the sweep only surfaced the training failure is ordering, not asymmetry — a row that dies attrain_rc=1never reaches playback. Measured on the playback path before the wiring was added:Playback would therefore have loaded a feature-trained checkpoint into the raw-camera policy architecture.
Fix chosen
Detect an explicitly typed
--agentby re-parsing the same argv into a namespace pre-seeded with a sentinel: argparse only applies a default for a destination the namespace does not already carry, so the sentinel survives unless the user actually typed the flag. Auto-selection runs only when it does survive; an explicit--agentstill wins. The six benchmark entrypoints now passagent_library.The probe uses the same parser on the same argv as the real parse, so its verdict is argparse's verdict. Verified across every spelling —
--agent V,--agent=V, the abbreviation--age V, repeated flags, and--agentafter a--separator (correctly not explicit: argparse does not set it in the real parse either). A literal argv scan for--agentwould get the abbreviation wrong and silently override a user's explicit choice, which is why the probe is preferred; the repo's existingExplicitActionidiom would work too but requires touching all ten--agentregistration sites across three packages, and a missed site fails the same silent way.Rejected: make
--agentdefault toNoneeverywhereThis is the obvious fix and it is not safe.
skrlcan default toNonebecausetrain_skrl.pyreconstructs the entry point from--algorithmwhen it isNone. The other three passargs_cli.agentstraight intoresolve_task_config(...), which has no such fallback —hydra.py:619setsagent_cfg = load_cfg_from_registry(...) if agent_entry else None, and the entrypoints then dereferenceagent_cfg.max_iterations. ANonedefault would break every plain--task=Xrun that relies on the canonical entry point, i.e. the overwhelmingly common case. Fixing that would mean adding a fallback to each of the six benchmark entrypoints plus the four train/play entrypoints: a much wider blast radius than the bug.Blast radius
Behavior changes only where auto-selection actually fires, and only when the user did not type
--agent:agent_preset_compatibility— todayIsaac-Cartpole-Cameraand the two cartpole-showcase tasks. That is the declared contract finally being honored. Anyone who wants the previous (broken) pairing can still pass--agent rsl_rl_cfg_entry_pointexplicitly; covered by a test.<library>_cfg_entry_pointis not registered and exactly one other entry point is. That path previously resolved an unregistered entry point and crashed, so this is strictly a repair.skrlis unaffected: it already passedagent_default=None, and the explicit/implicit distinction collapses to the oldis Nonecheck for it.rl_gamesandsb3share the defect and are fixed by the same change.sb3has no task declaringagent_preset_compatibility, so there is no positive selection test for it — its wiring is covered by the shared code path and by a default-preservation test only.Type of change
Screenshots
Not applicable.
Validation
Regression tests were confirmed to fail on
developwithout the fix and pass with it.Before the fix (production changes reverted to
develop, tests in place):After the fix:
(The last suite fails on a bare
--extra testenvironment ondeveloptoo —ModuleNotFoundError: No module named 'skrl'etc. — so it was rerun with the RL extras.)Tests added:
source/isaaclab_tasks/test/core/test_preset_cli.py— auto-selection over a non-Nonedefault; explicit--agentwins over the preset, parametrized over the three spellings argparse accepts. The two pre-existing auto-selection tests only coveredskrlwithagent_default=None, which is exactly why this bug went unnoticed.source/isaaclab/test/benchmark/test_api.py— drives the real benchmark entrypoints viaBenchmarkTrainingRequest/BenchmarkPlayRequestand_parse_args, asserting the feature entry point is selected forresnet18/theia_tiny×rsl_rl/rl_gameson the train path (mirroring the failing rows), the same on the play path, and that the canonical default survives otherwise (rsl_rl,rl_games,sb3).Both assert the selected agent config, not merely absence of an exception.
Not run: a real training job on the failing rows (requires GPU sim). The failure is a construction-time config selection, fully reproduced at CLI level.
Related PRs — checked, no overlap
isaaclab_rl/entrypoints/common.py, the fourplay_*entrypoints, the fourbenchmark_play_*entrypoints,cartpole/__init__.pyandtest_preset_cli.py— adjacent, but changes neitheragent_defaultnor the selection guard. Textual conflicts are possible intest_preset_cli.pyand thebenchmark_play_*files (one-linesetup_preset_cli(...)call); no semantic conflict.fix(rsl_rl): default obs_groups for new runners) adds anobs_groupsdefault inisaaclab_rl/rsl_rl/utils.py. It would mask this symptom for runners that omitobs_groups, butCartpoleCameraPPORunnerCfgsetsobs_groupsexplicitly, so it does not fix these 60 rows — and it would not make the correct feature config get selected either. Complementary, not duplicate.gh pr list --search "agent preset"returned nothing touching this code.