Skip to content

feat: extend discoverability through CLI - #2032

Closed
marta-sd wants to merge 13 commits into
mainfrom
martas/cli-1582
Closed

feat: extend discoverability through CLI#2032
marta-sd wants to merge 13 commits into
mainfrom
martas/cli-1582

Conversation

@marta-sd

@marta-sd marta-sd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Implementes #1582

@copy-pr-bot

copy-pr-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ritaneves ritaneves linked an issue Jul 15, 2026 that may be closed by this pull request
7 tasks
@ritaneves
ritaneves requested a review from prokotg July 15, 2026 13:53
marta-sd added 13 commits July 16, 2026 14:01
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Originally we've used the dataset name. These two are usually identical,
but for benchmarks with non-standard config location the output from
`gym list command` was not usable as value for `--benchmark` flag in
other commands. This commit fixes it.

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
…gwarmstrong

conversly to search_dir config field, env var allows to pass extra dirs deep into the code,
including rollouts colletion, prompt templates, config paths and env.yaml loading.
also it is propagated to any spawned process

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
…ogging too

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
…1264 from @gwarmstrong)

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Comment thread nemo_gym/cli/main.py
yield
return
original = os.environ.get(NEMO_GYM_EXTRA_ROOTS_ENV_VAR_NAME)
value = os.pathsep.join(search_dirs)

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.

Should --search-dir add to NEMO_GYM_EXTRA_ROOTS instead of replacing it?

For example:

export NEMO_GYM_EXTRA_ROOTS=/plugins/common
gym eval run --search-dir /plugins/experiment ...

During this command, /plugins/common is no longer searched. I expected the order to be /plugins/experiment, /plugins/common, cwd, then the Gym install.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense, I will modify this part

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 6087209

Comment thread nemo_gym/__init__.py
roots into the env (see nemo_gym.cli.main). With no extra roots this is just ``append(PARENT_DIR)``, as
before.
"""
for root in [*_extra_roots(), PARENT_DIR]:

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.

Could file lookup and Python imports share the same root-ordering logic?

component_search_roots() gives extra roots priority over PARENT_DIR, but _augment_sys_path() can leave PARENT_DIR before the extra roots because it was already added to sys.path. This means a plugin can win during file resolution but lose during Python import when Gym contains a module with the same import name.

Is there a way for both resolvers to use the same ordered roots, so their precedence cannot diverge over time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch.
I wonder if I should just ensure that PARENT_DIR is at the end, or in general that the extra roots are added before any other paths. I think the order should be: 1) extra roots, 2) original sys.path excluding PARENT_DIR, 3) PARENT_DIR. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done in ee0c74c, let me know if the ordering should be different

Comment thread nemo_gym/discovery.py
merged: Dict[str, _T] = {}
for entries in per_root:
for name, entry in entries.items():
merged.setdefault(name, entry)

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.

Could list/search and named selection use the same rule for duplicate names?

This code keeps the first match, but _asset_config_path() reports an error when the same name exists in two roots. A component can therefore appear in gym list but fail when selected by name.

Either rule seems reasonable, but using the same rule in both places would be clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. I think I'll modify _asset_config_path to pick up the first one and maybe print a warning to the user

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done in e6c6a88

Comment thread nemo_gym/benchmarks.py
if not benchmarks_dir.is_dir():
return []
config_paths = [benchmarks_dir / p for p in glob("**/*.yaml", root_dir=benchmarks_dir, recursive=True)]
return sorted(p for p in config_paths if "type: benchmark" in p.read_text(errors="ignore"))

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 only recognizes the exact text type: benchmark. Valid YAML such as type: "benchmark" or type : benchmark is skipped. I tested both cases.

Could we parse the YAML here, or make the check accept normal YAML formatting differences?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll try with yaml parsing and if it doesn't work for any reason - extend the pattern here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done in 97b5c9d I went with yaml and if the loading errors out we treat the config as potential benchmark. then all non-benchmarks fail to load and are filtered with a warning inside _load_benchmarks_from_config_paths

Comment thread nemo_gym/cli/main.py
# `gym search [<type>] <query>`: an optional component type plus the query. The query is surfaced to the
# chosen listing command as the reserved `query` config key; the type only picks which command to run
# (see `_search`). A lone positional is the query, defaulting to benchmarks — backward compatible.
_SEARCHABLE_TYPES = {

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.

Should datasets be included here too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This one is tricky - datasets are not really an independent concept in Gym, they are tightly coupled with benchmarks, envs and resources servers. But we want to make them separate (#2036) and I think this will be the right moment to add gym list and gym search commands for them

_cwd_path = Path.cwd() / _input_path
_input_path = _cwd_path if _cwd_path.exists() else PARENT_DIR / _input_path
# Search NEMO_GYM_EXTRA_ROOTS, cwd, then the install root.
_input_path = _resolve_under_cwd_or_install(config.input_jsonl_fpath)

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.

Could skills.path use this resolver too?

A few lines above, load_skill_directory(config.skills.path) calls _resolve_skills_path(), which only checks cwd and the Gym install. This means --search-dir /plugins/my-plugin can resolve the input path here, but it cannot resolve skills.path=skills from the same plugin.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, will fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done in b32c6d0

@marta-sd marta-sd self-assigned this Jul 17, 2026
@marta-sd

Copy link
Copy Markdown
Contributor Author

This PR got too big so I replaced it with stack. Comments from @gwarmstrong will be addressed in #2061

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants