Skip to content

Add GR00T policy-runner OSMO workflow - #848

Merged
alexmillane merged 91 commits into
mainfrom
xyao/feature/osmo_gr00t
Jul 3, 2026
Merged

Add GR00T policy-runner OSMO workflow#848
alexmillane merged 91 commits into
mainfrom
xyao/feature/osmo_gr00t

Conversation

@alexmillane

@alexmillane alexmillane commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a GR00T policy-runner evaluation OSMO workflow.

Detailed description

  • Adds Gr00tServerTask and Gr00tPolicyRunnerTask, their two-task workflow, and a submit script.
  • The server serves the GR00T droid checkpoint; the lead policy-runner task waits for it, then evaluates the GR00T remote closed-loop policy.
  • Adds EVAL_OUTPUT_* constants for per-run evaluation output uploads.

alexmillane and others added 30 commits June 1, 2026 09:59
Record every value drawn by an enabled variation's sampler so
downstream sensitivity-analysis tooling has the input factors that
produced each episode.

This adds a sample-observer layer the recorder builds on: SamplerBase
gains add_listener/remove_listener and a sample() template method that
notifies listeners around the concrete _sample(); VariationBase gains
add_sample_listener/remove_sample_listener, re-binding subscriptions onto
the sampler rebuilt by apply_cfg so they survive cfg swaps.

ArenaEnvBuilder constructs a VariationRecorder, attaches it after Hydra
overrides but before any sampling, and exposes it on env.unwrapped. The
recorder is stashed on the builder rather than the env cfg because the
configclass __post_init__ deep-copies its attributes, which would orphan
the listener closures.

Signed-off-by: alex <amillane@nvidia.com>
Move the inline RecordVideo / CameraObsVideoRecorder wrapping out of
policy_runner and eval_runner into a single video_recording module
(VideoRecordingCfg + wrap_env_for_video), so the gym-wrapper plumbing
lives in one place instead of being duplicated across the two runners.

Signed-off-by: alex <amillane@nvidia.com>
- Move video_recording.py to isaaclab_arena/utils/ and drop its module docstring.
- Rename VideoRecordingCfg fields and the corresponding CLI flags:
  --video -> --record_viewport_video, --camera_video -> --record_camera_video,
  --video_dir -> --video_base_dir.
- Write videos into a reverse-dated run subdirectory (timestamped_run_dir),
  shared across all jobs in an eval run, mirroring Isaac Lab's log layout.
- Guard _resolve_video_length when both num_steps and num_episodes are None.
- Tidy wrap_env_for_video docstring args.

Signed-off-by: alex <amillane@nvidia.com>
Use underscore-only flag names (--record_viewport_video, --record_camera_video,
--video_base_dir) to match the convention used by every other CLI argument.

Signed-off-by: alex <amillane@nvidia.com>
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Introduces the GR00T inference-server task (Gr00tServerTask), its paired policy-runner task (Gr00tPolicyRunnerTask), a two-task Gr00tPolicyRunnerWorkflow, and a submit script — mirroring the existing pi0 workflow structure. As part of the same change, Pi0RemotePolicyRunnerTask is refactored to receive remote_host via constructor and Pi0PlusPolicyRunnerWorkflow gains a proper _get_tasks() override, and POLICY_SERVER_PORT is centralised in workflow_constants.py.

  • New GR00T workflow: the server runs run_gr00t_server.py against the baked-in droid checkpoint; the runner connects via the OSMO {{host:gr00t_server}} token resolved by the new BaseTask.host_token() utility.
  • Pi0 refactor: remote_host is now passed explicitly from Pi0ServerTask.host_token() instead of being hardcoded, and the port is sourced from the shared POLICY_SERVER_PORT constant.

Confidence Score: 5/5

Safe to merge; the new GR00T workflow is additive, the pi0 refactor keeps existing behaviour intact, and all current call sites pass remote_host correctly.

All changed code paths are either new additions or straightforward refactors of the pi0 workflow to use shared constants. No existing workflows break: Pi0PlusPolicyRunnerWorkflow and Gr00tPolicyRunnerWorkflow both override _get_tasks() before instantiating the tasks that now require remote_host. The only open item is a documentation/contract gap that could trip up a future contributor but causes no current misbehaviour.

No files require special attention for this merge; the remote_host constructor contract in gr00t_policy_runner_task.py and pi0_remote_policy_runner_task.py is worth documenting before the pattern is extended further.

Important Files Changed

Filename Overview
osmo/tasks/base_task.py Adds host_token() classmethod that composes the OSMO {{host:<name>}} template token from get_task_name() — clean utility, no issues.
osmo/tasks/gr00t_server_task.py New GR00T inference-server task; all interpolated values are hardcoded module constants so shell injection risk is minimal; image has no :tag pin (flagged in previous thread).
osmo/tasks/gr00t_policy_runner_task.py New GR00T policy-runner task; remote_host injected from constructor rather than task_args, deviating from the base Workflow._get_tasks() instantiation contract.
osmo/tasks/pi0_remote_policy_runner_task.py Refactored to accept remote_host via constructor and use the POLICY_SERVER_PORT constant; same constructor-contract deviation as Gr00tPolicyRunnerTask.
osmo/workflows/gr00t_policy_runner_workflow.py New two-task workflow; correctly overrides _get_tasks() to pass Gr00tServerTask.host_token() as remote_host, keeping token and task name in sync.
osmo/workflows/pi0_plus_policy_runner_workflow.py Updated to override _get_tasks(), passing Pi0ServerTask.host_token() as remote_host; pattern mirrors the new GR00T workflow consistently.
osmo/workflows/workflow_constants.py Adds POLICY_SERVER_PORT = 8000, centralising the port constant previously hardcoded in Pi0RemotePolicyRunnerTask.
osmo/submit_gr00t_policy_runner_workflow.py Submission script that parses CLI args and delegates to Gr00tPolicyRunnerWorkflow.submit_workflow(); straightforward, mirrors the existing pi0 submit script.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant S as submit_gr00t_policy_runner_workflow.py
    participant W as Gr00tPolicyRunnerWorkflow
    participant RT as Gr00tPolicyRunnerTask (lead)
    participant ST as Gr00tServerTask

    S->>W: build_parser() → parse args
    S->>W: __init__(workflow_args, task_args)
    W->>W: _get_tasks()
    W->>ST: "host_token() → {{host:gr00t_server}}"
    W->>RT: "__init__(..., remote_host={{host:gr00t_server}}, lead=True)"
    W->>ST: "__init__(..., lead=False)"
    S->>W: submit_workflow(dry_run, pool, priority)
    W->>W: render_yaml()
    Note over RT: Waits for GR00T server ready
    RT-->>ST: gRPC connect :8000
    ST-->>RT: policy inference responses
    RT->>RT: Runs evaluation episodes → uploads to DATASET_SWIFT_URL
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 S as submit_gr00t_policy_runner_workflow.py
    participant W as Gr00tPolicyRunnerWorkflow
    participant RT as Gr00tPolicyRunnerTask (lead)
    participant ST as Gr00tServerTask

    S->>W: build_parser() → parse args
    S->>W: __init__(workflow_args, task_args)
    W->>W: _get_tasks()
    W->>ST: "host_token() → {{host:gr00t_server}}"
    W->>RT: "__init__(..., remote_host={{host:gr00t_server}}, lead=True)"
    W->>ST: "__init__(..., lead=False)"
    S->>W: submit_workflow(dry_run, pool, priority)
    W->>W: render_yaml()
    Note over RT: Waits for GR00T server ready
    RT-->>ST: gRPC connect :8000
    ST-->>RT: policy inference responses
    RT->>RT: Runs evaluation episodes → uploads to DATASET_SWIFT_URL
Loading

Reviews (3): Last reviewed commit: "Self review and cleanup." | Re-trigger Greptile

@arena-review-bot arena-review-bot Bot left a comment

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.

🤖 Isaac Lab-Arena Review Bot

Summary

Adds a two-task OSMO workflow (GR00T inference server + Arena policy runner, lead) that gates the eval on the server coming up and uploads per-run eval outputs. The structure closely follows the established pi0 sibling files, the wait-for-server flags line up exactly with isaaclab_arena_gr00t.utils.wait_for_gr00t_server, and the runner reuses server_port for both the server bind and the client connect so the two can't drift. Only minor nits below.

Findings

🔵 Improvement: osmo/tasks/gr00t_policy_runner_task.py:41 — the getattr(task_args, "arena_image", image) fallback never fires (see inline).
🔵 Improvement: osmo/tasks/gr00t_server_task.py:54 — same dead getattr fallback (see inline).
🔵 Nit: osmo/tasks/gr00t_policy_runner_task.py:67 — stray paren in help text (see inline).

Test Coverage

These are OSMO submission-tooling files (rendered YAML + bash entry scripts) that mirror the test_gr00t_closedloop_e2e CI job the docstring references; like the pi0/zero-action siblings they aren't unit-tested here, which is consistent with the existing pattern. No new test needed.

Verdict

Ship it

Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py
Comment thread osmo/tasks/gr00t_server_task.py

@alexmillane alexmillane left a comment

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.

Partial self-review.

Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py Outdated
Comment thread osmo/tasks/gr00t_server_task.py
Comment thread osmo/workflows/gr00t_policy_runner_workflow.py Outdated
- Slim Gr00tPolicyRunnerTask to just its policy args + gr00t-specific
  flags; rely on the base for the run script, outputs, task name, and
  image (drops the out-of-scope image override and the wait-for-server
  preamble for now).
- Align Gr00tServerTask with pi0_server_task: inline task name, drop the
  wait-for-server helper, and fold model/embodiment/port into constants.
- Centralize the policy-server port in workflow_constants (POLICY_SERVER_PORT
  = 8000) and use it for both the GR00T and pi0 remote tasks.
- Reverse the workflow task order to match the pi0 workflow.

Signed-off-by: alex <amillane@nvidia.com>

@arena-review-bot arena-review-bot Bot left a comment

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.

🤖 Isaac Lab-Arena Review Bot

Summary

Adds a GR00T policy-runner OSMO workflow (a GR00T inference server + a lead policy runner) that closely mirrors the existing pi0 two-task workflow, and centralizes the shared server port into POLICY_SERVER_PORT. The change is small, cleanly reuses the PolicyRunnerTask/Workflow/BaseTask bases, and the get_task_name() / {{host:gr00t_server}} tokens line up correctly. One minor duplication observation inline, plus a note on the PR description.

Findings

See the inline comment on osmo/tasks/gr00t_server_task.py.

Test Coverage

No tests are added. This is consistent with the sibling pi0 and zero-action workflows, which have none either — these are YAML-rendering submit scripts, and a --dry-run render smoke test would be the natural coverage. That's a pre-existing gap rather than something this PR must close, so not blocking.

Note on the PR description

The detailed description says the PR "Adds EVAL_OUTPUT_* constants for per-run evaluation output uploads," but the diff only adds POLICY_SERVER_PORT — no EVAL_OUTPUT_* constants exist in workflow_constants.py at HEAD. This looks stale after the "Address review" commit slimmed things down; worth updating the description so reviewers aren't looking for code that isn't there.

Verdict

Ship it

Comment thread osmo/tasks/gr00t_server_task.py

@arena-review-bot arena-review-bot Bot left a comment

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.

🤖 Isaac Lab-Arena Review Bot

Summary

This PR adds a two-task OSMO workflow (GR00T inference server + Arena policy-runner) plus a submit script, and refactors the pi0 remote runner to share the same server-host wiring via a new BaseTask.host_token() helper and a centralized POLICY_SERVER_PORT. The composition is clean — server specifics live in extension-package config paths, the runner reaches the server through an OSMO host token, and the port is a single source of truth. My main comment is that the server↔runner wiring is now duplicated across the gr00t and pi0 workflows.

Design, Boundaries & Scope

The new _get_tasks override in gr00t_policy_runner_workflow.py is near-identical to the one this PR also adds to pi0_plus_policy_runner_workflow.py — both build [runner(remote_host=Server.host_token()), server]. Since task_cls_list already names the runner/server pair, this wiring could live once in a shared base (RemotePolicyRunnerWorkflow(Workflow)), with the subclasses only declaring the class pair. See the inline comment for a concrete shape. Not a blocker, but the two bodies will drift.

Findings

🟡 Warning: osmo/workflows/gr00t_policy_runner_workflow.py:20 — duplicated server-host wiring; lift into a shared base (inline).
🔵 Improvement: osmo/submit_gr00t_policy_runner_workflow.py:13 — "Default" usage example is missing the required --arena_env_args (inline).
🔵 Improvement: osmo/workflows/workflow_constants.pyPOLICY_SERVER_PORT is now the single source for the client port and the GR00T server's --port, but Pi0ServerTask still relies on serve_policy.py's implicit default rather than the constant, so the pi0 server bind port and the client's POLICY_SERVER_PORT can silently diverge. Consider threading the constant into the pi0 server too (its file isn't touched here, so strictly out of this PR's scope).

Test Coverage

These are pure-Python workflow-config generators (they render YAML dicts, no sim), so the sim inner/outer pattern doesn't apply. There don't appear to be unit tests exercising the new wiring; a small test asserting that _get_tasks() gives the runner remote_host == "{{host:gr00t_server}}" would cheaply guard the runner↔server task-name sync (which host_token() couples). Optional given alpha, but it's the one thing most likely to break silently if a task name changes.

Verdict

Minor fixes needed

Note: the PR description mentions adding EVAL_OUTPUT_* constants, but the final diff only adds POLICY_SERVER_PORT — description looks stale.

Comment thread osmo/workflows/gr00t_policy_runner_workflow.py Outdated
Comment thread osmo/submit_gr00t_policy_runner_workflow.py Outdated
@alexmillane
alexmillane changed the base branch from alex/feature/osmo_pi05 to main July 3, 2026 10:11
- Quote --model_path/--embodiment_tag in the GR00T server run script.
- Give the GR00T server image an explicit :latest tag.
- Add nvidia-smi to the pi0 server run script for symmetry with GR00T.

Signed-off-by: alex <amillane@nvidia.com>
@alexmillane

Copy link
Copy Markdown
Collaborator Author

/review

Comment thread osmo/workflows/gr00t_policy_runner_workflow.py Outdated
Comment thread osmo/tasks/gr00t_policy_runner_task.py
@arena-review-bot

Copy link
Copy Markdown
Contributor

🤖 Isaac Lab-Arena Review Bot

Summary

This PR adds a GR00T server + policy-runner OSMO workflow (Gr00tServerTask, Gr00tPolicyRunnerTask, and their two-task workflow) and refactors the pi0 workflow to resolve the server host via a shared BaseTask.host_token() helper and a POLICY_SERVER_PORT constant. The refactor is behavior-preserving (Pi0ServerTask.host_token() resolves to the same {{host:policy_server}} the code hardcoded before), and the vendor-specific isaaclab_arena_gr00t policy is referenced only by string in deployment tooling, so the core-framework boundary stays clean. Two small points below; nothing blocking.

Findings

🟡 Warning — osmo/workflows/gr00t_policy_runner_workflow.py:20_get_tasks is identical to Pi0PlusPolicyRunnerWorkflow._get_tasks (both added in this PR), differing only by the runner/server classes already in task_cls_list. A shared ServerPlusPolicyRunnerWorkflow base that unpacks runner_cls, server_cls = self.task_cls_list and injects remote_host=server_cls.host_token() would collapse both overrides and keep the runner→server wiring in one place.

🔵 Improvement — osmo/tasks/gr00t_policy_runner_task.py:48 — the pi0 runner carries --ping_timeout 300 for the documented OSMO kernel-compile cold-start; worth confirming whether the GR00T runner needs the same safeguard or already handles it in the closed-loop config YAML.

Test Coverage

This is OSMO submit/deploy tooling (workflow-dict assembly, no sim), so the inner/outer simulation-test pattern does not apply. No unit tests accompany the new task/workflow classes; given the deterministic dict-rendering surface, a light test that the gr00t workflow renders two tasks with the runner as lead and remote_host wired to the server token would be cheap insurance, but its absence is consistent with the existing pi0/zero-action tasks.

Verdict

Ship it — minor, non-blocking suggestions only.

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

LGTM!

Comment thread osmo/workflows/gr00t_policy_runner_workflow.py Outdated
@alexmillane
alexmillane enabled auto-merge (squash) July 3, 2026 12:37
@alexmillane
alexmillane merged commit d28748f into main Jul 3, 2026
6 checks passed
david-tingdahl-nvidia pushed a commit that referenced this pull request Jul 10, 2026
## Summary
Add a GR00T policy-runner evaluation OSMO workflow.

## Detailed description
- Adds `Gr00tServerTask` and `Gr00tPolicyRunnerTask`, their two-task
workflow, and a submit script.
- The server serves the GR00T droid checkpoint; the lead policy-runner
task waits for it, then evaluates the GR00T remote closed-loop policy.
- Adds `EVAL_OUTPUT_*` constants for per-run evaluation output uploads.

---------

Signed-off-by: alex <amillane@nvidia.com>
Co-authored-by: aiguldzh-nvidia <adzhumamurat@nvidia.com>
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.

3 participants