Skip to content

osworld integration(for benchmarking) - #1408

Merged
ko3n1g merged 106 commits into
NVIDIA-NeMo:mainfrom
JeffPengCoder:feature/osworld
Aug 4, 2026
Merged

ko3n1g merged 106 commits into
NVIDIA-NeMo:mainfrom
JeffPengCoder:feature/osworld

Conversation

@JeffPengCoder

@JeffPengCoder JeffPengCoder commented May 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds OSWorld as a first-class NeMo Gym benchmark and introduces a reusable Gym rollout runtime for desktop-computer agents.

OSWorld is a stateful desktop benchmark covering Chrome, LibreOffice, GIMP, VS Code, VLC, multi-application workflows, and operating-system tasks. Each task requires a real desktop VM, iterative visual observations and actions, task-specific setup, and final-state evaluation.

One Gym /run request corresponds to one complete OSWorld rollout:

  1. create a fresh task environment;
  2. reset it with the original OSWorld task definition;
  3. run the selected model/agent loop;
  4. execute actions through OSWorld;
  5. call OSWorld's inline evaluator;
  6. return the trajectory, evaluator score, Gym reward, failure classification, and artifact metadata in a BaseVerifyResponse-compatible shape.

The integration preserves OSWorld's task, controller, action, and evaluator semantics. Gym owns orchestration, model routing, concurrency, sandbox lifecycle integration, reproducibility, observability, and result accounting.

The full benchmark guide is in benchmarks/osworld/README.md. The reusable runtime contract is documented in responses_api_agents/osworld_agent/README.md.

Why the integration is structured this way

OSWorld is not a stateless verifier. Its evaluator depends on the live DesktopEnv, task setup state, files inside the VM, and application-specific controller endpoints.

For that reason:

  • the complete rollout and evaluation stay in the same Ray worker;
  • each rollout owns one independent VM lifecycle;
  • OSWorld continues to own DesktopEnv.reset(), action execution, and DesktopEnv.evaluate();
  • Gym wraps that lifecycle instead of translating OSWorld tasks or reimplementing evaluators;
  • there is no separate resources_servers/osworld service.

Splitting evaluation into another service would require exporting live environment handles or reproducing task state outside the worker, without providing a useful stateless boundary.

Architecture

Benchmark preparation
  benchmarks/osworld/prepare.py
    |-- validates the selected task JSONL
    |-- optionally creates deterministic task shards
    |-- prefetches task setup and evaluator assets
    |-- records input, qcow2, and container identities
    `-- writes a private benchmark-local env.yaml
                         |
                         v
                    gym eval run
                         |
                         v
               OSWorld agent /run
                 async admission gate
                 concurrency semaphore
                         |
                         v
               one Ray task per rollout
                         |
                         v
                   RunnerRegistry
       +-----------------+------------------+
       |                 |                  |
 Gym-owned runner   upstream runners   model-specific adapter
 gym_pyautogui      Prompt/Pointer/M3   Nemotron Nano Omni
                    Qwen3 Omni
       +-----------------+------------------+
                         |
                  configured model
           Gym model server or runner-native
                    transport contract
                         |
                         v
                  OSWorld DesktopEnv
        reset -> observe -> model -> parse -> execute
                         |
                    evaluate()
                         |
             +-----------+------------+
             |                        |
     OSWorld provider          Gym Sandbox bridge
     owns VM lifecycle         GymSandboxDesktopProvider
                                      |
                               Gym Sandbox API
                                      |
                         Docker provider, local or SSH
                                      |
                         container -> QEMU/KVM -> desktop VM
             +-----------+------------+
                         |
                         v
                OSWorldVerifyResponse
          |-- raw OSWorld evaluator score
          |-- configured binary or raw reward
          |-- mask_sample and termination reason
          |-- executed model steps
          `-- optional artifact provenance

The physical roles are also explicit and may be co-located or split:

MODEL_HOST  <- model HTTP -  AGENT_CONTROL_HOST  - Docker API ->  ENVIRONMENT_HOST
                                  |
                                  `-- prepare / control / eval
  • The model host serves a compatible vision-language model.
  • The agent/control host runs benchmark preparation, Gym control, Ray workers, and evaluation.
  • The environment host runs Docker, QEMU/KVM, and the OSWorld desktop VMs.

For a local smoke test, the agent/control and environment roles can be the same machine. For a split-host deployment, Gym uses Docker's standard SSH transport through DOCKER_HOST=ssh://...; no persistent interactive SSH session is part of the rollout path.

Ownership boundaries

Layer Owns Does not own
benchmarks/osworld Dataset composition, profiles, task sharding, asset preparation, manifests, and launch recipes Agent loop or evaluator semantics
responses_api_agents/osworld_agent /run, concurrency, runner selection, model routing, rollout lifecycle, failure classification, and Gym response mapping OSWorld task setup or final-state evaluation logic
OSWorld DesktopEnv, controllers, setup, observations, action execution, and evaluators Gym orchestration or cluster lifecycle
Gym Sandbox Container lifecycle, endpoint discovery, status, and cleanup Desktop task semantics
Runner/adapter Prompt, history, model protocol, parser, and coordinate contract Environment scoring
Model server Model inference and optional model-call observability VM or task lifecycle

Design principles

Preserve benchmark semantics

The complete task object from verifier_metadata.osworld_task is passed to DesktopEnv.reset(task_config=...) without translating setup or evaluator behavior. Final scoring still comes from DesktopEnv.evaluate().

Separate independent axes

The following are independent configuration choices:

  • runner/model protocol;
  • observation and action contract;
  • VM lifecycle backend;
  • local versus split-host placement;
  • model-serving topology.

A remote environment host is not a different agent implementation, and selecting Gym Sandbox does not change the prompt, actions, or evaluator.

Adapt at stable contracts

RunnerRegistry makes each runner's environment class, agent class, observation type, action space, and default arguments explicit.

Where possible, Gym preserves an upstream OSWorld runner. Gym-owned adapters are used only when a model has a distinct prompt/history/parser contract that is not represented by an upstream runner.

The Gym Sandbox integration implements OSWorld's provider contract instead of replacing DesktopEnv or copying the rollout loop.

Distinguish model outcomes from infrastructure failures

A valid evaluator score of zero is still a valid benchmark or training sample.

Infrastructure failures, rollout timeouts, invalid environment configuration, and unfinished rollouts set mask_sample=true and retain a specific termination reason. They are not silently converted into ordinary model failures.

Task setup operations that explicitly declare on_nonzero: score_zero remain valid score-zero results rather than masked infrastructure failures.

Make scale reproducible

  • One rollout owns one VM lifecycle.
  • Agent concurrency is bounded explicitly.
  • Task sharding is deterministic, disjoint, and exhaustive.
  • Shard manifests include the source hash, selected task IDs, and task-ID digest.
  • The OSWorld dependency and container image are immutable.
  • Benchmark preparation records the qcow2 identity and reset semantics.
  • Completed task IDs can be retained while recovery runs only the missing canonical set.

Keep diagnostics opt-in

Screenshots, video, VM execution logs, and full model I/O can be large and sensitive. They are disabled by default and enabled only through explicit environment variables.

What this PR adds

Benchmark composition

benchmarks/osworld/ provides:

  • benchmark metadata and default composition;
  • five-task example data and example rollouts;
  • the 39-task test_small subset;
  • prepare.py for input validation, profile selection, environment generation, deterministic sharding, VM identity recording, and asset preparation;
  • model/runner profiles for the generic Gym runner, Pointer Agent, PromptAgent, and Nemotron 3 Nano Omni;
  • model endpoint, environment, VM preparation, control, evaluation, and run-scoped cleanup tools;
  • local and split-host deployment documentation.

The larger canonical task input is supplied explicitly at preparation time and is not committed as a generated repository artifact.

Reusable OSWorld runtime

responses_api_agents/osworld_agent/ provides:

  • the Gym /run server and aggregate OSWorld metrics;
  • the synchronous DesktopEnv rollout loop executed inside Ray;
  • explicit raw versus binary reward handling;
  • timeout and mask semantics;
  • runner registration and validation;
  • model-specific adapters;
  • pyautogui and control-action parsing;
  • proxy-task validation and non-secret provenance;
  • shared setup-cache staging;
  • task-scoped logs, screenshots, trajectories, manifests, and video hooks;
  • Gym Sandbox compatibility for standard and Pointer DesktopEnv variants.

Registered runner contracts

Runner Ownership and contract
gym_pyautogui Gym prompt, screenshot observations, and Python/pyautogui actions
prompt_agent and prompt_agent_* Upstream OSWorld PromptAgent observation/action variants, routed to the configured policy model
pointer_agent Upstream PointerAgent planner/executor/verifier behavior and Anthropic-compatible model contract
m3_agent Upstream MiniMax M3 scaffold, transport, prompt, and relative-coordinate protocol
nemotron_v3_nano_omni_agent Gym-owned Nemotron 3 Nano Omni prompt, bounded image history, parser, and coordinate projection
qwen3_omni_agent Upstream Qwen3VL scaffold with Gym-owned transport compatibility

The maintained prepare.py --profile entry points are currently default, pointer, and nano_omni. Other registered runner contracts can be selected through explicit compatible configuration.

Gym Sandbox integration

The runtime supports two execution backends:

Backend Lifecycle owner Intended use
osworld_provider OSWorld provider implementation Compatibility and direct OSWorld-provider deployments
gym_sandbox Gym Sandbox, through GymSandboxDesktopProvider Default benchmark workflow and provider-neutral lifecycle integration

In the Gym Sandbox path:

  • OSWorld still owns DesktopEnv, setup, controllers, actions, and evaluators;
  • Gym Sandbox owns container creation, endpoint discovery, status, and cleanup;
  • the OSWorld qcow2 is mounted read-only;
  • reset means closing the Sandbox and recreating a fresh VM from the same base;
  • /dev/kvm is validated by Docker on the actual environment host;
  • all required OSWorld service ports are published dynamically;
  • local and remote Docker daemons use the same provider code.

Small generic Gym changes required by OSWorld

This PR also includes narrowly scoped core changes needed by the integration:

  • optional Sandbox endpoint discovery as a provider capability;
  • explicit Sandbox service-port declarations;
  • Docker provider support for dynamic multi-port publication and remote environment hosts;
  • positional compatibility for existing SandboxSpec construction;
  • benchmark-config discovery in the generated environment table;
  • configurable preservation of reasoning in multi-turn vLLM assistant history;
  • opt-in correlated vLLM transport logging.

The endpoint capability is optional, model capture remains disabled unless configured, and existing non-OSWorld provider behavior is preserved.

Request, reward, and failure contract

The rollout request carries the complete OSWorld task under:

verifier_metadata.osworld_task

Per-task responses_create_params may override configured sampling parameters.

A completed response contains:

  • reward;
  • mask_sample;
  • verifier_metadata.osworld_score;
  • the number of executed OSWorld steps;
  • completion/error and termination state;
  • runner and model identity;
  • proxy provenance without credentials;
  • optional task artifact location;
  • one assistant output item per model step.

reward_mode controls the top-level Gym reward:

  • binary: reward is 1.0 only for a full OSWorld evaluator score of 1.0;
  • raw: reward preserves fractional OSWorld evaluator credit.

Aggregate metrics always report both:

  • osworld/binary_success_rate;
  • osworld/raw_reward_rate.

This prevents partial evaluator credit from being confused with full task success.

Dataset and denominator convention

The canonical full input used by the validation runs contains 369 unique OSWorld task IDs with SHA-256:

3be0746d59fc5a311447e3e36241381c022932c624315b851fef2c28ecbf5ac9

Eight GDrive tasks require an external Google OAuth client_secrets.json that was not available in the validation environment.

Validation therefore reports two explicit views:

  • full attempted input: 369 tasks;
  • comparable no-GDrive view: the same 361 task IDs after excluding exactly those eight credential-blocked IDs.

Every run first validates all 369 canonical IDs. Only the frozen GDrive-eight set is excluded from the 361-task comparison. Non-GDrive model failures, masks, proxy failures, and evaluator-zero outcomes remain in the denominator as zero.

Reproducibility and dependency boundary

The agent installs OSWorld from the immutable archive:

JeffPengCoder/OSWorld@31b76bf1c4d4e589238b314caa91470afc52651e

This is a fork commit, not an upstream OSWorld release. It includes upstream OSWorld main 83e85344 plus the nv-gym provider/proxy integration changes.

Gym does not modify the installed OSWorld tree at runtime. The phrase "unmodified dependency" in this integration means that the pinned archive is installed and consumed without run-local patching; it does not mean that the pinned commit is byte-identical to xlang-ai/OSWorld main.

The default OSWorld container is pinned to:

happysixd/osworld-docker@
sha256:0e6497a9295647cf05bf2b2af522fdd79bdeba2737595259cab310a3bcf6baa9

Benchmark preparation records:

  • task input SHA-256;
  • generated shard membership;
  • qcow2 path, size, and SHA-256;
  • container digest;
  • selected profile/backend;
  • output and cache locations.

Asset preparation is idempotent. Prefetched assets change download timing and resilience, not task semantics.

Verification

Current-head contract tests

Current PR head:

32f37c0daea45daad7754ccf89e9f042f8b4b67c

The following targeted suite passes on the current checkout:

.venv/bin/pytest -q \
  responses_api_agents/osworld_agent/tests \
  benchmarks/osworld/tests \
  tests/test_update_env_list.py \
  tests/unit_tests/test_update_env_list.py \
  tests/unit_tests/test_sandbox.py \
  tests/unit_tests/test_sandbox_connect.py \
  tests/unit_tests/test_base_responses_api_model.py \
  responses_api_models/vllm_model/tests/test_app.py

Result:

392 passed, 11 skipped (optional tenacity sandbox dependency not installed)

The CI-equivalent isolated server suites also pass:

.venv/bin/gym env test +entrypoint=responses_api_agents/osworld_agent
.venv/bin/gym env test +entrypoint=responses_api_models/vllm_model
.venv/bin/gym env test +entrypoint=responses_api_agents/stirrup_agent

Results:

OSWorld: 153 passed
vLLM:    110 passed
Stirrup: 151 passed

The suite covers runner contracts, action parsing, request/response mapping, raw and binary metrics, masks, proxy handling, setup caching, task artifacts, Sandbox lifecycle adaptation, endpoint resolution, sandbox serialization and reconnection, model-call observability, task sharding, VM manifests, launch-script contracts, reasoning history, and model transport behavior.

git diff --check upstream/main...HEAD is also clean.

Live Gym Sandbox validation

A near-head branch ancestor, 663e53e62ed740d803c65ac6df5494da93a7c687, completed the full canonical Nano Omni validation using:

  • nemotron_v3_nano_omni_agent;
  • Gym Docker Sandbox;
  • two deterministic 185/184 task shards;
  • 16 concurrent environments per shard;
  • 100 maximum actions;
  • temperature 0.6, top-p 0.95;
  • a shared TP=8 vLLM 0.23 model endpoint;
  • the pinned OSWorld and VM/container identities documented above.

Result:

View Tasks Raw evaluator Raw rate Binary successes Binary rate Masks
Full canonical input 369 180.9759876 49.0450% 175 47.4255% 11
Exact no-GDrive view 361 180.9759876 50.1319% 175 48.4765% 3

Integrity checks:

  • 369/369 unique canonical IDs;
  • 0 missing IDs;
  • 0 extra IDs;
  • 0 duplicate IDs;
  • exactly eight GDrive credential failures excluded from the comparable view;
  • all three non-GDrive masked outcomes retained in the 361 denominator;
  • zero run-owned containers remaining after cleanup.

Controlled repeated runs

The same fixed Nano Omni contract was repeated three times over the same canonical task set:

Run Official tasks Raw rate Binary rate Retained non-GDrive masks
Round 1 361 50.13% 48.48% 3
Round 2 361 49.48% 47.09% 11
Round 3 361 48.13% 45.98% 1
Mean +/- sample SD 361/run 49.25% +/- 1.02 pp 47.18% +/- 1.25 pp -

Every round contains the same 369 canonical IDs before the exact GDrive-eight exclusion and the same 361 IDs afterward.

These are system-level benchmark results at nonzero sampling temperature. They validate end-to-end completeness, task accounting, reproducibility, recovery, and score stability; they are not presented as an isolated model-quality claim.

Pointer Agent compatibility evidence

A separate Pointer/Opus 4.7 experiment at Gym commit 09a895d7 completed all 369 tasks.

On the comparable 361-task view:

Gym Pointer/Opus raw rate:       296.5627129 / 361 = 82.1503%
Verified leaderboard reference: 301.94 / 361       = 83.64%
Delta:                                                -1.4897 pp

That experimental commit is not an ancestor of the current PR head, so this result is compatibility evidence for the Pointer wrapping design, not a current-head merge gate.

Backward compatibility

  • sandbox_provider: null retains the OSWorld-native lifecycle path.
  • Sandbox endpoint lookup is an optional provider capability.
  • Existing SandboxSpec positional construction remains compatible.
  • Existing Docker provider behavior is unchanged unless ports, devices, mounts, or a remote Docker daemon are explicitly configured.
  • Reasoning-history preservation is opt-in.
  • Model-call capture and OSWorld artifact logging are opt-in.
  • Video recording has no runtime overhead when OSWORLD_RECORD_VIDEO_DIR is unset.
  • Existing non-OSWorld agents and resources servers do not consume the OSWorld runtime.

Security and artifact handling

  • Proxy credentials are external runtime configuration and are never committed.
  • Result metadata records only non-secret proxy provenance.
  • The pinned OSWorld revision prevents task setup DEBUG logging from serializing the complete worker environment.
  • Full model I/O may contain prompts and embedded screenshots.
  • Task artifacts and video may contain user-visible desktop content.
  • Large screenshots, trajectories, VM images, model logs, and caches are intentionally not part of the normal repository artifact set.

Limitations and planned follow-ups

  1. OSWorld fork dependency

    The runtime currently depends on the immutable JeffPengCoder/OSWorld commit above rather than an upstream release. Provider and proxy changes should be upstreamed or replaced with an official pinned revision before removing this dependency.

  2. Full dataset publication

    The committed five-task example and 39-task subset are runnable. The complete generated benchmark JSONL is intentionally not committed, and the registry-backed train.jsonl and validation.jsonl artifacts have not yet been published.

  3. GDrive credential tasks

    Eight canonical tasks require valid external Google OAuth credentials. This PR does not fabricate, distribute, or bypass those credentials.

  4. Default platform

    The default OSWorld Docker image is x86_64 and expects Linux with Docker and KVM. ARM64 environment images are outside this PR.

  5. Reset semantics

    Gym Sandbox resets by closing the current Sandbox and recreating it from the read-only qcow2 base. Live RAM/device-state snapshots are not implemented.

  6. OpenSandbox backend

    OpenSandbox has been exercised downstream but its provider implementation is not included here.

  7. Training recipes

    The response contains the reward, mask, trajectory, and model-step information needed by downstream training integrations, but a complete NeMo RL training recipe is outside this PR.

  8. Sensitive diagnostics

    Full model I/O, screenshots, VM execution logs, and video should be enabled only for controlled diagnostic runs with appropriate storage and access controls.

Reviewer guide

A suggested review order is:

  1. Request, reward, and failure semantics:

    • responses_api_agents/osworld_agent/app.py
    • responses_api_agents/osworld_agent/client.py
  2. Runner ownership and model-facing contracts:

    • responses_api_agents/osworld_agent/runner_registry.py
    • responses_api_agents/osworld_agent/adapter_agents.py
    • responses_api_agents/osworld_agent/action_parser.py
  3. Sandbox ownership boundary:

    • responses_api_agents/osworld_agent/sandbox_desktop_env.py
    • responses_api_agents/osworld_agent/sandbox_provider.py
    • nemo_gym/sandbox/
    • nemo_gym/sandbox/providers/docker/
  4. Benchmark preparation and operations:

    • benchmarks/osworld/prepare.py
    • benchmarks/osworld/assets.py
    • benchmarks/osworld/configs/
    • benchmarks/osworld/tools/
  5. Model-server compatibility and observability:

    • nemo_gym/base_responses_api_model.py
    • responses_api_models/vllm_model/app.py
  6. Contract and regression tests:

    • responses_api_agents/osworld_agent/tests/
    • benchmarks/osworld/tests/
    • tests/unit_tests/test_sandbox.py
    • tests/unit_tests/test_docker_provider.py

Documentation

Jeff Peng and others added 10 commits May 26, 2026 06:11
A NeMo Gym responses-API agent that wraps the OSWorld benchmark
(xlang-ai/OSWorld, ~369 real desktop tasks across Chrome, LibreOffice,
GIMP, VS Code, VLC, multi-app workflows, OS-level tasks).

One /run = one full OSWorld rollout (sync inside Ray, pyautogui action
space, returns final evaluator score as BaseVerifyResponse).

Supports two deployment modes (see README):
- local docker (single machine, no SSH; recommended for first run)
- remote_docker (controller + VM host on different machines; uses
  the remote_docker provider from forked OSWorld, with a bundled
  bringup_remote_host.sh for one-shot remote setup)

Ships example.jsonl, tests, and a comprehensive README.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
nemo-gym pyproject already requires ray[default]>=2.55.1 (CVE bump
2026-05-19). The agent's old pin at 2.52.1 conflicts, breaking uv
per-server venv install with "ray==2.52.1 and nemo-gym[dev] are
unsatisfiable".

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
Mode A (single-host local docker) is the README's recommended-first-run
path. The CLI Hydra override 'provider_name=docker' fails with
"Key 'osworld_simple_agent' is not in struct" due to NeMo-Gym's
config-load order (override parsed before configs load), so the yaml
default is the only working path.

Mode B (remote_docker via SSH) users can override locally.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
…s set

OSWorld's controller already exposes start_recording / end_recording (the
in-VM ffmpeg path that lib_run_single.py uses). Wire it into the agent's
rollout loop, gated on the OSWORLD_RECORD_VIDEO_DIR env var:

  - just after env.reset(), call env.controller.start_recording()
  - in the finally block (before env.close()), download the mp4 to
    ${OSWORLD_RECORD_VIDEO_DIR}/{task_id}.mp4

Both calls are best-effort (caught + logged) so recording failures
don't break the rollout. With the env var unset, the agent behaves
exactly as before — zero overhead.

Verified on Brev (Mode A, claude-opus-4-7, docker provider): 5 example
tasks → 5 mp4 files (1.5-5.2 MB each, 16 MB total), no impact on
reward (4/5 reward=1.0 same as without recording).

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
Recording was previously only mentioned in the Mode B architecture
diagram (the remote_docker / VNC-stream path). After the matching
controller-recording wiring in client.py, any provider works with the
env var. Add an explicit "Recording rollout videos" section and TOC
entry covering the usage, output naming, size expectation, and the
best-effort failure mode.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
uv 0.11.16 has a reproducible bug: when `-e nemo-gym[dev]` and
`osworld @ git+https://...` appear in the same `uv pip install -r
requirements.txt` invocation, 7 of osworld's pyproject deps are
silently dropped from the install (cryptography, cffi, scipy, Pillow,
scikit-learn, matplotlib, flask). The Ray task then crashes at first
/run with `ModuleNotFoundError: No module named 'cryptography'`.

Minimal repro: a fresh venv with ONLY `osworld @ git+...` installs
all 89 deps correctly; only the combination with the editable
nemo-gym in the same install triggers the skip.

Add the 7 (+ func-timeout, inconsistently affected) directly to this
requirements.txt — uv installs explicit top-level pins reliably.
Drop after the uv bug is filed + fixed upstream.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
OSWorld's docker provider lazy-downloads Ubuntu.qcow2 (12 GB) to
docker_vm_data/ on first DesktopEnv start. With osworld_agent.yaml's
default concurrency=4, all 4 rollouts race to write the same
Ubuntu.qcow2.zip → torn file (49 GB observed vs 12 GB normal) → every
rollout sees `BadZipFile: File is not a zip file`.

The existing "First-Run Cache Acquisition" section mentioned pre-
staging as a nice-to-have for shared clusters; upgrade the wording to
"required for any run with concurrency > 1" and include the actual
curl + unzip recipe (was implicit before). Split the recipe by
provider (Docker provider downloads to docker_vm_data/, Apptainer to
\$CACHE_DIR/osworld-images/) so users don't have to read source.

Also fix the qcow2 size in the cache table (~12 GB compressed /
~23 GB extracted vs the prior ~10 GB / ~25 GB).

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
The agent README's "Quickstart" handles Mode A install once you have
docker, uv, and a working PATH. But several host-prep details aren't
trivially repo-fixable: apt installs for ffmpeg/xvfb/tigervnc (video
recording stack), enabling the docker daemon, adding $USER to the
docker group, installing uv, and (the subtle one) symlinking uv into
/usr/local/bin so ng_run's `bash -c "uv run …"` subshells can find it.

Add `scripts/bringup_local_host.sh` as the Mode A symmetric counterpart
to the existing `bringup_remote_host.sh` (Mode B), an idempotent one-shot
host-prep helper. Add a `scripts/README.md` mapping the two scripts to
their deployment modes and showing the end-to-end usage flow for each.

Also add a Troubleshooting entry to the main README documenting the
`uv: command not found` symptom and the symlink fix, so users who hit
it without running the script have a self-serve recipe.

Verified end-to-end on a brand-new clone (no prior workarounds applied):
`uv venv && uv sync --extra dev` + per-server venv install lands all
89 forked-osworld deps including the 7 that uv 0.11.16 silently dropped
in earlier runs. desktop_env + DockerProvider + cv2 + PIL + cryptography
all import cleanly.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
…ipt mention)

After running `ruff format` and `pre-commit run --all-files`:

- responses_api_agents/osworld_agent/client.py: ruff-format normalization
  (multi-line json.dumps formatting + LOG.info arg splitting). No semantic
  change.
- README.md (root): the `update-readme-table` pre-commit hook auto-added
  an `Osworld Agent` row to the environments listing. First-time run after
  adding the agent — expected per CLAUDE.md "Pre-commit Hooks" section.
- responses_api_agents/osworld_agent/README.md: add a `scripts/bringup_local_host.sh`
  reference in the Mode A "One-time setup" subsection, mirroring how Mode B
  already calls out `scripts/bringup_remote_host.sh`. The bare-minimum
  `sudo apt-get install -y docker.io` kept as the manual fallback.

ruff check + ruff format check + pytest osworld_agent/tests/ (7/7) all
green at this commit.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
…ut user-required steps

Previously the bringup_local_host.sh final RECIPE block and the scripts/
README.md Mode A flow both said only `# fill in env.yaml at project root
(see README's "Quickstart")` — a brief, easy-to-miss reference. New users
following the helper would hit OmegaConf resolver errors at ng_run time
because env.yaml hadn't been written.

Surface the three user-must-do steps (clone + env.yaml + qcow2 prestage)
with inline copy-paste-ready commands, instead of pointers to the main
README. Specifically:

- bringup_local_host.sh: replace the one-line `# fill in env.yaml` hint
  with three clearly numbered REQUIRED sections covering git clone,
  env.yaml (with `cat > env.yaml <<YAML ... YAML` template inline), and
  qcow2 prestage (with the curl + unzip recipe inline). The optional
  OSWORLD_RECORD_VIDEO_DIR mention stays at the end.
- scripts/README.md: same — replace `# 3. Fill in env.yaml at project
  root (see main README Quickstart for fields)` with the inline
  `cat > env.yaml <<YAML ... YAML` template, and inline the qcow2
  curl + unzip recipe in step 4.

Verified end-to-end: smoke v7 on Brev with fresh `git clone -b feature/osworld`
(SHA 328aae8), pulls all 89 forked-osworld deps cleanly (P1-2 explicit pins
catch the uv 0.11.16 missing-deps bug), ng_run starts without `uv: command
not found` (P1 uv symlink), 5/5 tasks complete (4 reward=1.0 / 1 reward=0 on
impress as before), 5 mp4 recorded. No regression from prior smoke baselines.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented May 26, 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.

@cwing-nvidia cwing-nvidia added the env-integration Integrate with existing environments or gyms external to NeMo Gym label May 27, 2026
Comment thread benchmarks/osworld/data/example.jsonl Resolved

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

also could add this to benchmarks/

but needs further review from team

…s mode

Addresses review feedback on the OSWorld integration PR:

- data/example_rollouts.jsonl: saved rollouts for the 5 example.jsonl
  tasks (claude-opus-4-7, Screenshot only), 4/5 reward=1.0 — demonstrates
  the reward signal both awards (chrome/gimp/calc/writer) and withholds
  (impress) credit, per the contribution workflow.

- README Reward Profiling: replace the TBD table with a strict
  reproduction of the official OSWorld verified leaderboard — o3 via
  NVIDIA Inference Hub, Screenshot only, max_steps=15, test_all 369 ->
  9.21% (34/369) vs the board's 9.10% (32.85/361), delta +0.11 pp (within
  run-to-run variance).

- Accessibility-tree-only observation mode: a new
  OSWORLD_OMIT_SCREENSHOT_IN_OBS env var blanks the screenshot from the
  observation, leaving only the a11y tree. Combined with
  require_a11y_tree=true this is the paper's "a11y only" setting (Table 5,
  e.g. the GPT-4 12.24% row). README now documents all four observation
  modes and how to select each.

- scripts/paper_to_gym_jsonl.py: converter from upstream OSWorld manifests
  (test_all.json / test_small.json / ...) to the gym-shape JSONL the agent
  consumes. data/test_small.jsonl (39 tasks) checked in; test_all.jsonl
  (369) is regenerated via the script. Fix the Dataset Information table
  (test_small is 39 tasks, not 12).

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
ng_viewer is no longer a console_scripts entry point (only ng_run and
ng_collect_rollouts are). Replace the two `ng_viewer +jsonl_fpath=...`
invocations with a `jq` one-liner over the output JSONL — reward is
top-level and per-step actions live in verifier_metadata.osworld_steps.

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
@JeffPengCoder

Copy link
Copy Markdown
Collaborator Author

also could add this to benchmarks/

but needs further review from team

Makes sense — happy to move it under benchmarks/ if that's the preferred home. I'll defer to the team on placement; let me know the call and I'll restructure.

@cmunley1

cmunley1 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

cc @gchlebus

…r false positives)

- copyright-check: add SPDX/Apache header to scripts/paper_to_gym_jsonl.py
- Lint: ruff-format scripts/paper_to_gym_jsonl.py (+ ruff check --fix)
- secrets-detector: mark false positives. The OSWorld guest VM password is
  literally "password" and the tests use dummy values; no real secret.
  - inline allowlist pragma on prompts.py, configs/osworld_agent.yaml, tests/test_app.py
  - data/test_small.jsonl (JSON takes no inline comment) added to
    .github/workflows/config/.secrets.baseline

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
@JeffPengCoder
JeffPengCoder requested a review from a team as a code owner June 9, 2026 10:06
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
@JeffPengCoder JeffPengCoder changed the title osworld integration osworld integration(for benchmarking) Jul 23, 2026
Glorf added 2 commits July 24, 2026 15:06
Use Pointer's OSWorld environment with Gym Sandbox, defer policy credential validation until rollout configuration is available, and keep proxy-disabled tasks on native direct-network behavior. Pin numkong to the last Linux-buildable release.

Signed-off-by: Michal Bien <mbien@nvidia.com>
Add a backward-compatible derived capture mode for model-call metrics without request, response, reasoning, or tool-call payloads. Correlate Gym-backed OSWorld calls by rollout and keep the legacy raw model I/O log opt-in.

Signed-off-by: Michal Bien <mbien@nvidia.com>

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

I think the integration is close, but I would request changes for two concrete issues before merging:

  1. The generic HeadServer service registry remains after its only intended in-tree consumer was deferred and removed.
  2. The local /dev/kvm validation breaks the advertised split-host deployment.

Also, please try to pass a simplification round with the agent of your choice, as the amount of code is still very large

Comment thread nemo_gym/server_utils.py Resolved
Comment thread nemo_gym/sandbox/providers/base.py Resolved
fix(osworld): preserve Pointer behavior in Gym Sandbox
@github-actions

github-actions Bot commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

🌿 Preview your docs: https://nvidia-preview-feature-osworld.docs.buildwithfern.com/nemo/gym

Here are the markdown pages you've updated:

Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Glorf
Glorf previously approved these changes Jul 29, 2026

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

Cond. approved if sandbox changes are well tested. I'd strongly recommend merging only after 0.5.0 release freeze (today-tomorrow) to give some more time for testing (we don't need this in 0.5.0 AFAIK)

Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
@ko3n1g
ko3n1g merged commit 57c15a2 into NVIDIA-NeMo:main Aug 4, 2026
15 checks passed
kajalj22 added a commit that referenced this pull request Aug 4, 2026
## Summary

- stirrup 0.2.0 (published 2026-08-04) made `context_window_tokens` a
required constructor argument in `ChatCompletionsClient`, breaking the
stirrup_agent unit tests on the r0.5.0 release branch
- Caps `stirrup<0.2` so CI stays green until the 0.2 migration is ready
- Identical change to what landed on `main` via #1408

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
OlegSudakov pushed a commit to OlegSudakov/Gym that referenced this pull request Aug 7, 2026
# Summary

This PR adds OSWorld as a first-class NeMo Gym benchmark and introduces
a reusable Gym rollout runtime for desktop-computer agents.

[OSWorld](https://github.com/xlang-ai/OSWorld) is a stateful desktop
benchmark covering Chrome, LibreOffice, GIMP, VS Code, VLC,
multi-application workflows, and operating-system tasks. Each task
requires a real desktop VM, iterative visual observations and actions,
task-specific setup, and final-state evaluation.

One Gym `/run` request corresponds to one complete OSWorld rollout:

1. create a fresh task environment;
2. reset it with the original OSWorld task definition;
3. run the selected model/agent loop;
4. execute actions through OSWorld;
5. call OSWorld's inline evaluator;
6. return the trajectory, evaluator score, Gym reward, failure
classification, and artifact metadata in a
`BaseVerifyResponse`-compatible shape.

The integration preserves OSWorld's task, controller, action, and
evaluator semantics. Gym owns orchestration, model routing, concurrency,
sandbox lifecycle integration, reproducibility, observability, and
result accounting.

The full benchmark guide is in
[`benchmarks/osworld/README.md`](benchmarks/osworld/README.md). The
reusable runtime contract is documented in
[`responses_api_agents/osworld_agent/README.md`](responses_api_agents/osworld_agent/README.md).

## Why the integration is structured this way

OSWorld is not a stateless verifier. Its evaluator depends on the live
`DesktopEnv`, task setup state, files inside the VM, and
application-specific controller endpoints.

For that reason:

- the complete rollout and evaluation stay in the same Ray worker;
- each rollout owns one independent VM lifecycle;
- OSWorld continues to own `DesktopEnv.reset()`, action execution, and
`DesktopEnv.evaluate()`;
- Gym wraps that lifecycle instead of translating OSWorld tasks or
reimplementing evaluators;
- there is no separate `resources_servers/osworld` service.

Splitting evaluation into another service would require exporting live
environment handles or reproducing task state outside the worker,
without providing a useful stateless boundary.

## Architecture

```text
Benchmark preparation
  benchmarks/osworld/prepare.py
    |-- validates the selected task JSONL
    |-- optionally creates deterministic task shards
    |-- prefetches task setup and evaluator assets
    |-- records input, qcow2, and container identities
    `-- writes a private benchmark-local env.yaml
                         |
                         v
                    gym eval run
                         |
                         v
               OSWorld agent /run
                 async admission gate
                 concurrency semaphore
                         |
                         v
               one Ray task per rollout
                         |
                         v
                   RunnerRegistry
       +-----------------+------------------+
       |                 |                  |
 Gym-owned runner   upstream runners   model-specific adapter
 gym_pyautogui      Prompt/Pointer/M3   Nemotron Nano Omni
                    Qwen3 Omni
       +-----------------+------------------+
                         |
                  configured model
           Gym model server or runner-native
                    transport contract
                         |
                         v
                  OSWorld DesktopEnv
        reset -> observe -> model -> parse -> execute
                         |
                    evaluate()
                         |
             +-----------+------------+
             |                        |
     OSWorld provider          Gym Sandbox bridge
     owns VM lifecycle         GymSandboxDesktopProvider
                                      |
                               Gym Sandbox API
                                      |
                         Docker provider, local or SSH
                                      |
                         container -> QEMU/KVM -> desktop VM
             +-----------+------------+
                         |
                         v
                OSWorldVerifyResponse
          |-- raw OSWorld evaluator score
          |-- configured binary or raw reward
          |-- mask_sample and termination reason
          |-- executed model steps
          `-- optional artifact provenance
```

The physical roles are also explicit and may be co-located or split:

```text
MODEL_HOST  <- model HTTP -  AGENT_CONTROL_HOST  - Docker API ->  ENVIRONMENT_HOST
                                  |
                                  `-- prepare / control / eval
```

- The model host serves a compatible vision-language model.
- The agent/control host runs benchmark preparation, Gym control, Ray
workers, and evaluation.
- The environment host runs Docker, QEMU/KVM, and the OSWorld desktop
VMs.

For a local smoke test, the agent/control and environment roles can be
the same machine. For a split-host deployment, Gym uses Docker's
standard SSH transport through `DOCKER_HOST=ssh://...`; no persistent
interactive SSH session is part of the rollout path.

## Ownership boundaries

| Layer | Owns | Does not own |
| --- | --- | --- |
| `benchmarks/osworld` | Dataset composition, profiles, task sharding,
asset preparation, manifests, and launch recipes | Agent loop or
evaluator semantics |
| `responses_api_agents/osworld_agent` | `/run`, concurrency, runner
selection, model routing, rollout lifecycle, failure classification, and
Gym response mapping | OSWorld task setup or final-state evaluation
logic |
| OSWorld | `DesktopEnv`, controllers, setup, observations, action
execution, and evaluators | Gym orchestration or cluster lifecycle |
| Gym Sandbox | Container lifecycle, endpoint discovery, status, and
cleanup | Desktop task semantics |
| Runner/adapter | Prompt, history, model protocol, parser, and
coordinate contract | Environment scoring |
| Model server | Model inference and optional model-call observability |
VM or task lifecycle |

## Design principles

### Preserve benchmark semantics

The complete task object from `verifier_metadata.osworld_task` is passed
to `DesktopEnv.reset(task_config=...)` without translating setup or
evaluator behavior. Final scoring still comes from
`DesktopEnv.evaluate()`.

### Separate independent axes

The following are independent configuration choices:

- runner/model protocol;
- observation and action contract;
- VM lifecycle backend;
- local versus split-host placement;
- model-serving topology.

A remote environment host is not a different agent implementation, and
selecting Gym Sandbox does not change the prompt, actions, or evaluator.

### Adapt at stable contracts

`RunnerRegistry` makes each runner's environment class, agent class,
observation type, action space, and default arguments explicit.

Where possible, Gym preserves an upstream OSWorld runner. Gym-owned
adapters are used only when a model has a distinct prompt/history/parser
contract that is not represented by an upstream runner.

The Gym Sandbox integration implements OSWorld's provider contract
instead of replacing `DesktopEnv` or copying the rollout loop.

### Distinguish model outcomes from infrastructure failures

A valid evaluator score of zero is still a valid benchmark or training
sample.

Infrastructure failures, rollout timeouts, invalid environment
configuration, and unfinished rollouts set `mask_sample=true` and retain
a specific termination reason. They are not silently converted into
ordinary model failures.

Task setup operations that explicitly declare `on_nonzero: score_zero`
remain valid score-zero results rather than masked infrastructure
failures.

### Make scale reproducible

- One rollout owns one VM lifecycle.
- Agent concurrency is bounded explicitly.
- Task sharding is deterministic, disjoint, and exhaustive.
- Shard manifests include the source hash, selected task IDs, and
task-ID digest.
- The OSWorld dependency and container image are immutable.
- Benchmark preparation records the qcow2 identity and reset semantics.
- Completed task IDs can be retained while recovery runs only the
missing canonical set.

### Keep diagnostics opt-in

Screenshots, video, VM execution logs, and full model I/O can be large
and sensitive. They are disabled by default and enabled only through
explicit environment variables.

## What this PR adds

### Benchmark composition

`benchmarks/osworld/` provides:

- benchmark metadata and default composition;
- five-task example data and example rollouts;
- the 39-task `test_small` subset;
- `prepare.py` for input validation, profile selection, environment
generation, deterministic sharding, VM identity recording, and asset
preparation;
- model/runner profiles for the generic Gym runner, Pointer Agent,
PromptAgent, and Nemotron 3 Nano Omni;
- model endpoint, environment, VM preparation, control, evaluation, and
run-scoped cleanup tools;
- local and split-host deployment documentation.

The larger canonical task input is supplied explicitly at preparation
time and is not committed as a generated repository artifact.

### Reusable OSWorld runtime

`responses_api_agents/osworld_agent/` provides:

- the Gym `/run` server and aggregate OSWorld metrics;
- the synchronous `DesktopEnv` rollout loop executed inside Ray;
- explicit raw versus binary reward handling;
- timeout and mask semantics;
- runner registration and validation;
- model-specific adapters;
- pyautogui and control-action parsing;
- proxy-task validation and non-secret provenance;
- shared setup-cache staging;
- task-scoped logs, screenshots, trajectories, manifests, and video
hooks;
- Gym Sandbox compatibility for standard and Pointer `DesktopEnv`
variants.

### Registered runner contracts

| Runner | Ownership and contract |
| --- | --- |
| `gym_pyautogui` | Gym prompt, screenshot observations, and
Python/pyautogui actions |
| `prompt_agent` and `prompt_agent_*` | Upstream OSWorld `PromptAgent`
observation/action variants, routed to the configured policy model |
| `pointer_agent` | Upstream PointerAgent planner/executor/verifier
behavior and Anthropic-compatible model contract |
| `m3_agent` | Upstream MiniMax M3 scaffold, transport, prompt, and
relative-coordinate protocol |
| `nemotron_v3_nano_omni_agent` | Gym-owned Nemotron 3 Nano Omni prompt,
bounded image history, parser, and coordinate projection |
| `qwen3_omni_agent` | Upstream Qwen3VL scaffold with Gym-owned
transport compatibility |

The maintained `prepare.py --profile` entry points are currently
`default`, `pointer`, and `nano_omni`. Other registered runner contracts
can be selected through explicit compatible configuration.

### Gym Sandbox integration

The runtime supports two execution backends:

| Backend | Lifecycle owner | Intended use |
| --- | --- | --- |
| `osworld_provider` | OSWorld provider implementation | Compatibility
and direct OSWorld-provider deployments |
| `gym_sandbox` | Gym Sandbox, through `GymSandboxDesktopProvider` |
Default benchmark workflow and provider-neutral lifecycle integration |

In the Gym Sandbox path:

- OSWorld still owns `DesktopEnv`, setup, controllers, actions, and
evaluators;
- Gym Sandbox owns container creation, endpoint discovery, status, and
cleanup;
- the OSWorld qcow2 is mounted read-only;
- reset means closing the Sandbox and recreating a fresh VM from the
same base;
- `/dev/kvm` is validated by Docker on the actual environment host;
- all required OSWorld service ports are published dynamically;
- local and remote Docker daemons use the same provider code.

### Small generic Gym changes required by OSWorld

This PR also includes narrowly scoped core changes needed by the
integration:

- optional Sandbox endpoint discovery as a provider capability;
- explicit Sandbox service-port declarations;
- Docker provider support for dynamic multi-port publication and remote
environment hosts;
- positional compatibility for existing `SandboxSpec` construction;
- benchmark-config discovery in the generated environment table;
- configurable preservation of reasoning in multi-turn vLLM assistant
history;
- opt-in correlated vLLM transport logging.

The endpoint capability is optional, model capture remains disabled
unless configured, and existing non-OSWorld provider behavior is
preserved.

## Request, reward, and failure contract

The rollout request carries the complete OSWorld task under:

```text
verifier_metadata.osworld_task
```

Per-task `responses_create_params` may override configured sampling
parameters.

A completed response contains:

- `reward`;
- `mask_sample`;
- `verifier_metadata.osworld_score`;
- the number of executed OSWorld steps;
- completion/error and termination state;
- runner and model identity;
- proxy provenance without credentials;
- optional task artifact location;
- one assistant output item per model step.

`reward_mode` controls the top-level Gym reward:

- `binary`: reward is `1.0` only for a full OSWorld evaluator score of
`1.0`;
- `raw`: reward preserves fractional OSWorld evaluator credit.

Aggregate metrics always report both:

- `osworld/binary_success_rate`;
- `osworld/raw_reward_rate`.

This prevents partial evaluator credit from being confused with full
task success.

## Dataset and denominator convention

The canonical full input used by the validation runs contains 369 unique
OSWorld task IDs with SHA-256:

```text
3be0746d59fc5a311447e3e36241381c022932c624315b851fef2c28ecbf5ac9
```

Eight GDrive tasks require an external Google OAuth
`client_secrets.json` that was not available in the validation
environment.

Validation therefore reports two explicit views:

- full attempted input: 369 tasks;
- comparable no-GDrive view: the same 361 task IDs after excluding
exactly those eight credential-blocked IDs.

Every run first validates all 369 canonical IDs. Only the frozen
GDrive-eight set is excluded from the 361-task comparison. Non-GDrive
model failures, masks, proxy failures, and evaluator-zero outcomes
remain in the denominator as zero.

## Reproducibility and dependency boundary

The agent installs OSWorld from the immutable archive:

```text
JeffPengCoder/OSWorld@31b76bf
```

This is a fork commit, not an upstream OSWorld release. It includes
upstream OSWorld main `83e85344` plus the `nv-gym` provider/proxy
integration changes.

Gym does not modify the installed OSWorld tree at runtime. The phrase
"unmodified dependency" in this integration means that the pinned
archive is installed and consumed without run-local patching; it does
not mean that the pinned commit is byte-identical to `xlang-ai/OSWorld`
main.

The default OSWorld container is pinned to:

```text
happysixd/osworld-docker@
sha256:0e6497a9295647cf05bf2b2af522fdd79bdeba2737595259cab310a3bcf6baa9
```

Benchmark preparation records:

- task input SHA-256;
- generated shard membership;
- qcow2 path, size, and SHA-256;
- container digest;
- selected profile/backend;
- output and cache locations.

Asset preparation is idempotent. Prefetched assets change download
timing and resilience, not task semantics.

## Verification

### Current-head contract tests

Current PR head:

```text
32f37c0
```

The following targeted suite passes on the current checkout:

```bash
.venv/bin/pytest -q \
  responses_api_agents/osworld_agent/tests \
  benchmarks/osworld/tests \
  tests/test_update_env_list.py \
  tests/unit_tests/test_update_env_list.py \
  tests/unit_tests/test_sandbox.py \
  tests/unit_tests/test_sandbox_connect.py \
  tests/unit_tests/test_base_responses_api_model.py \
  responses_api_models/vllm_model/tests/test_app.py
```

Result:

```text
392 passed, 11 skipped (optional tenacity sandbox dependency not installed)
```

The CI-equivalent isolated server suites also pass:

```bash
.venv/bin/gym env test +entrypoint=responses_api_agents/osworld_agent
.venv/bin/gym env test +entrypoint=responses_api_models/vllm_model
.venv/bin/gym env test +entrypoint=responses_api_agents/stirrup_agent
```

Results:

```text
OSWorld: 153 passed
vLLM:    110 passed
Stirrup: 151 passed
```

The suite covers runner contracts, action parsing, request/response
mapping, raw and binary metrics, masks, proxy handling, setup caching,
task artifacts, Sandbox lifecycle adaptation, endpoint resolution,
sandbox serialization and reconnection, model-call observability, task
sharding, VM manifests, launch-script contracts, reasoning history, and
model transport behavior.

`git diff --check upstream/main...HEAD` is also clean.

### Live Gym Sandbox validation

A near-head branch ancestor, `663e53e62ed740d803c65ac6df5494da93a7c687`,
completed the full canonical Nano Omni validation using:

- `nemotron_v3_nano_omni_agent`;
- Gym Docker Sandbox;
- two deterministic 185/184 task shards;
- 16 concurrent environments per shard;
- 100 maximum actions;
- temperature `0.6`, top-p `0.95`;
- a shared TP=8 vLLM 0.23 model endpoint;
- the pinned OSWorld and VM/container identities documented above.

Result:

| View | Tasks | Raw evaluator | Raw rate | Binary successes | Binary
rate | Masks |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Full canonical input | 369 | 180.9759876 | 49.0450% | 175 | 47.4255% |
11 |
| Exact no-GDrive view | 361 | 180.9759876 | **50.1319%** | 175 |
**48.4765%** | 3 |

Integrity checks:

- 369/369 unique canonical IDs;
- 0 missing IDs;
- 0 extra IDs;
- 0 duplicate IDs;
- exactly eight GDrive credential failures excluded from the comparable
view;
- all three non-GDrive masked outcomes retained in the 361 denominator;
- zero run-owned containers remaining after cleanup.

### Controlled repeated runs

The same fixed Nano Omni contract was repeated three times over the same
canonical task set:

| Run | Official tasks | Raw rate | Binary rate | Retained non-GDrive
masks |
| --- | ---: | ---: | ---: | ---: |
| Round 1 | 361 | 50.13% | 48.48% | 3 |
| Round 2 | 361 | 49.48% | 47.09% | 11 |
| Round 3 | 361 | 48.13% | 45.98% | 1 |
| Mean +/- sample SD | 361/run | **49.25% +/- 1.02 pp** | **47.18% +/-
1.25 pp** | - |

Every round contains the same 369 canonical IDs before the exact
GDrive-eight exclusion and the same 361 IDs afterward.

These are system-level benchmark results at nonzero sampling
temperature. They validate end-to-end completeness, task accounting,
reproducibility, recovery, and score stability; they are not presented
as an isolated model-quality claim.

### Pointer Agent compatibility evidence

A separate Pointer/Opus 4.7 experiment at Gym commit `09a895d7`
completed all 369 tasks.

On the comparable 361-task view:

```text
Gym Pointer/Opus raw rate:       296.5627129 / 361 = 82.1503%
Verified leaderboard reference: 301.94 / 361       = 83.64%
Delta:                                                -1.4897 pp
```

That experimental commit is not an ancestor of the current PR head, so
this result is compatibility evidence for the Pointer wrapping design,
not a current-head merge gate.

## Backward compatibility

- `sandbox_provider: null` retains the OSWorld-native lifecycle path.
- Sandbox endpoint lookup is an optional provider capability.
- Existing `SandboxSpec` positional construction remains compatible.
- Existing Docker provider behavior is unchanged unless ports, devices,
mounts, or a remote Docker daemon are explicitly configured.
- Reasoning-history preservation is opt-in.
- Model-call capture and OSWorld artifact logging are opt-in.
- Video recording has no runtime overhead when
`OSWORLD_RECORD_VIDEO_DIR` is unset.
- Existing non-OSWorld agents and resources servers do not consume the
OSWorld runtime.

## Security and artifact handling

- Proxy credentials are external runtime configuration and are never
committed.
- Result metadata records only non-secret proxy provenance.
- The pinned OSWorld revision prevents task setup DEBUG logging from
serializing the complete worker environment.
- Full model I/O may contain prompts and embedded screenshots.
- Task artifacts and video may contain user-visible desktop content.
- Large screenshots, trajectories, VM images, model logs, and caches are
intentionally not part of the normal repository artifact set.

## Limitations and planned follow-ups

1. **OSWorld fork dependency**

The runtime currently depends on the immutable `JeffPengCoder/OSWorld`
commit above rather than an upstream release. Provider and proxy changes
should be upstreamed or replaced with an official pinned revision before
removing this dependency.

2. **Full dataset publication**

The committed five-task example and 39-task subset are runnable. The
complete generated benchmark JSONL is intentionally not committed, and
the registry-backed `train.jsonl` and `validation.jsonl` artifacts have
not yet been published.

3. **GDrive credential tasks**

Eight canonical tasks require valid external Google OAuth credentials.
This PR does not fabricate, distribute, or bypass those credentials.

4. **Default platform**

The default OSWorld Docker image is x86_64 and expects Linux with Docker
and KVM. ARM64 environment images are outside this PR.

5. **Reset semantics**

Gym Sandbox resets by closing the current Sandbox and recreating it from
the read-only qcow2 base. Live RAM/device-state snapshots are not
implemented.

6. **OpenSandbox backend**

OpenSandbox has been exercised downstream but its provider
implementation is not included here.

7. **Training recipes**

The response contains the reward, mask, trajectory, and model-step
information needed by downstream training integrations, but a complete
NeMo RL training recipe is outside this PR.

8. **Sensitive diagnostics**

Full model I/O, screenshots, VM execution logs, and video should be
enabled only for controlled diagnostic runs with appropriate storage and
access controls.

## Reviewer guide

A suggested review order is:

1. Request, reward, and failure semantics:
   - `responses_api_agents/osworld_agent/app.py`
   - `responses_api_agents/osworld_agent/client.py`

2. Runner ownership and model-facing contracts:
   - `responses_api_agents/osworld_agent/runner_registry.py`
   - `responses_api_agents/osworld_agent/adapter_agents.py`
   - `responses_api_agents/osworld_agent/action_parser.py`

3. Sandbox ownership boundary:
   - `responses_api_agents/osworld_agent/sandbox_desktop_env.py`
   - `responses_api_agents/osworld_agent/sandbox_provider.py`
   - `nemo_gym/sandbox/`
   - `nemo_gym/sandbox/providers/docker/`

4. Benchmark preparation and operations:
   - `benchmarks/osworld/prepare.py`
   - `benchmarks/osworld/assets.py`
   - `benchmarks/osworld/configs/`
   - `benchmarks/osworld/tools/`

5. Model-server compatibility and observability:
   - `nemo_gym/base_responses_api_model.py`
   - `responses_api_models/vllm_model/app.py`

6. Contract and regression tests:
   - `responses_api_agents/osworld_agent/tests/`
   - `benchmarks/osworld/tests/`
   - `tests/unit_tests/test_sandbox.py`
   - `tests/unit_tests/test_docker_provider.py`

## Documentation

- [OSWorld benchmark guide](benchmarks/osworld/README.md)
- [OSWorld runtime
reference](responses_api_agents/osworld_agent/README.md)
- [Deployment tool reference](benchmarks/osworld/tools/README.md)
- [Gym Docker Sandbox provider
notes](nemo_gym/sandbox/providers/docker/README.md)

---------

Signed-off-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Signed-off-by: Michal Bien <mbien@nvidia.com>
Co-authored-by: Jeff Peng <jepeng@cw-dfw-cs-001-login-02.cm.cluster>
Co-authored-by: JeffPengCoder <239749643+JeffPengCoder@users.noreply.github.com>
Co-authored-by: Michal Bien <mbien@nvidia.com>
ko3n1g added a commit that referenced this pull request Aug 13, 2026
…2308)

## Summary

This PR adds a production-oriented OpenSandbox Pool backend for the
OSWorld benchmark while preserving the existing Gym Docker/KVM path.

It also fixes the root dependency policy that previously made OSWorld's
managed-server environment impossible to express cleanly: dependency
exclusions are now scoped to the upstream packages that declare the
unwanted edges instead of globally removing those package names from
every server environment.

Key outcomes:

- Adds `gym_opensandbox` as an OSWorld execution backend backed by a
pre-provisioned, server-managed KVM Pool.
- Keeps the OSWorld agent, task setup, action loop, and evaluators
identical across Docker and OpenSandbox.
- Adapts OpenSandbox path-based gateway endpoints, routing headers, and
Chrome CDP WebSockets to OSWorld's host-and-port interface.
- Makes create, cancellation, shutdown, and interrupted-run cleanup
bounded and ownership-aware.
- Moves the OSWorld managed-server dependency declaration to the
repository's established `requirements.txt` convention and pins OSWorld
to an immutable archive.
- Replaces global dependency exclusions with source-package-scoped
exclusions and aligns CI on the minimum `uv` version that supports that
policy.

## Motivation

The existing OSWorld integration assumes that the Gym client owns a
Docker container, mounts a local qcow2 image, and can expose the guest
services as plain `host:port` endpoints. An OpenSandbox Pool changes all
three assumptions:

1. The server-side Pool owns the VM image, QEMU entrypoint, and
capacity.
2. Allocation is image-less from the client perspective and is selected
by `poolRef`.
3. Externally reachable services may be returned as gateway URLs with
path prefixes and required routing headers rather than directly routable
Pod addresses.

The goal is therefore not to fork the OSWorld execution path. It is to
keep OSWorld's behavior unchanged and translate only the lifecycle and
endpoint boundary beneath it.

## Architecture

```text
                                  model HTTP
                         +--------------------------+
                         |                          v
Task JSONL -> prepare -> OSWorld agent/control -> Nano Omni endpoint
                         |
                         | DesktopEnv provider contract
                         v
                 GymSandboxDesktopProvider
                         |
                         v
                  Gym Sandbox public API
                    /                 \
                   /                   \
      Docker provider                  OpenSandbox provider
      - client-owned image             - server-owned KVM Pool
      - client-owned qcow2 mount        - image-less allocation by poolRef
      - direct host:port endpoints      - gateway path/header endpoints
                   \                   /
                    \                 /
                     OSWorld guest services
              screenshot | input | file | CDP | VLC
```

For gateway endpoints, the OSWorld adapter starts one loopback forwarder
per guest service:

```text
OSWorld http://127.0.0.1:<local-port>/...
        -> preserve gateway path prefix
        -> inject required routing headers
        -> forward HTTP or Chrome CDP WebSocket
        -> OpenSandbox gateway -> allocated VM
```

## Design principles

### Preserve one OSWorld execution contract

`GymSandboxDesktopProvider` continues to implement OSWorld's existing
provider interface. Backend selection changes resource acquisition and
endpoint resolution only; task setup, observations, actions, proxy
behavior, and evaluators remain on the same code path.

### Make resource ownership explicit

- Docker mode requires the client-owned image and readable qcow2 path,
then supplies the QEMU entrypoint and mounts.
- OpenSandbox Pool mode rejects client images and VM paths. It sends
only lifecycle fields, metadata, exposed ports, and
`provider_options.extensions.poolRef`; the Pool owns the image,
entrypoint, and compute capacity.

This prevents local Docker defaults from leaking into a server-managed
Pool allocation.

### Translate endpoints at the narrowest boundary

OSWorld expects one host and integer ports, whereas OpenSandbox can
expose URL paths, headers, and WebSocket routes. The local forwarder
performs that translation at the adapter boundary, disables ambient
proxy inheritance, and leaves OSWorld itself unaware of
transport-specific routing.

### Bound failure and cancellation

- Provider operations have explicit timeouts and retry policies.
- Synchronous Sandbox calls cancel and drain their underlying async task
before returning on timeout, interruption, or another `BaseException`.
- Pool creates carry a unique marker so an allocation whose response is
lost can be found and reaped without matching another run.
- Startup failures close forwarders and best-effort release the
allocated Sandbox.

### Clean up only owned resources

Each run carries exact run metadata. Normal shutdown releases its
Sandbox; the recovery tool first audits and then optionally terminates
only instances whose metadata value exactly matches the requested run
ID. It checks both the OSWorld and Gym metadata keys and revalidates the
match immediately before deletion.

Credentials remain environment-only and are not written to generated
`env.yaml` files.

## Dependency policy

The OSWorld agent now uses
`responses_api_agents/osworld_agent/requirements.txt`, matching the
managed-server convention used throughout `responses_api_agents`.

The prior root `exclude-dependencies` list acted globally when the
editable root project was installed into an isolated server environment.
That meant a package excluded only because it was an unwanted dependency
of `mlflow` was also silently unavailable when OSWorld or another
component required it directly. Keeping a component-local workaround
would preserve that incorrect repository-wide behavior.

This PR fixes the policy at its source:

- `mlflow` exclusions apply only to dependency edges declared by
`mlflow`.
- OSWorld's unavailable optional `agp-client` edge and conflicting GUI
OpenCV edges are scoped to `osworld`.
- Repeated GUI OpenCV edges are scoped to `paddleocr` and
`albumentations`.
- Direct dependencies such as `cryptography` remain resolvable for other
agents and model servers.
- `uv >= 0.11.25` is required because package-scoped exclusions need
that resolver behavior; both test workflows install the same minimum
version.
- OSWorld is installed from immutable revision
`dc23424e9f6316b181bde149e0dc9bc3c5ff78c9`, with the compatible headless
OpenCV wheel declared explicitly.

This keeps the root policy composable for every managed server instead
of special-casing OSWorld around a global exclusion.

## User-facing workflow

`benchmarks/osworld/prepare.py` accepts `--execution-backend
gym_opensandbox`, validates that `--vm-path` is absent, and renders the
Pool reference from `OPENSANDBOX_POOL_REF` (default: `osworld-kvm`). The
normal control and evaluation wrappers are unchanged.

The OSWorld README documents:

- deployment roles and resource ownership for both backends;
- model endpoint probing and run preparation;
- OpenSandbox configuration through environment variables;
- normal shutdown and exact-run recovery cleanup;
- direct, proxy-required, Chrome CDP, and VLC endpoint behavior.

## Validation

All validation below applies to the PR patch now published as commit
`435ba3bc38373c3b04ccd685f565b89b7066920a` on Linux/x86_64 unless noted
otherwise. Live artifacts were initially recorded at
`949a5e5963d241d4179712070fe954d6f7f468d5`; adding missing DCO trailers
and rebasing onto current `main` changed only commit metadata and the
unrelated base. The complete `git diff --binary main...HEAD` remained
byte-for-byte identical, with SHA-256
`49e8c919c5c39f4054d8d45398d7ba2c11142eb6344d74d65d26ce4206b6cb6d`.

### Dependency and test gates

- Fresh root environment with `uv 0.11.25`: 178 packages installed.
- Relevant root, Sandbox, OSWorld agent, and benchmark suite: **2041
passed, 4 skipped, 5 subtests passed**.
- Fresh OSWorld managed-server environment from `requirements.txt`: 336
packages installed; **157 agent tests + 32 benchmark tests = 189
passed**.
- Fresh vLLM managed-server environment: 175 packages installed;
`cryptography 49.0.0` resolved without a compatibility cap; **110
passed**.
- Pre-commit hooks passed for every changed file.

### Live Nano Omni + OSWorld smoke matrix

| Backend | Scenario | Task ID | Reward |
| --- | --- | --- | ---: |
| Gym local Docker + KVM | Direct Chrome |
`2ad9387a-65d8-4e33-ad5b-7580065a27ca` | 1.0 |
| Gym local Docker + KVM | Proxy-required Chrome |
`06fe7178-4491-4589-810f-2e2bc9502122` | 1.0 |
| Gym local Docker + KVM | VLC | `59f21cfb-0120-4326-b255-a5b827b38967`
| 1.0 |
| OpenSandbox KVM Pool | Direct Chrome |
`2ad9387a-65d8-4e33-ad5b-7580065a27ca` | 1.0 |
| OpenSandbox KVM Pool | Proxy-required Chrome |
`06fe7178-4491-4589-810f-2e2bc9502122` | 1.0 |
| OpenSandbox KVM Pool | VLC | `59f21cfb-0120-4326-b255-a5b827b38967` |
1.0 |

The Docker route used a real qcow2 VM with KVM acceleration
(`accel=kvm`, `-enable-kvm`), not a mocked lifecycle. The first
independent Docker/proxy sample completed end-to-end but scored 0.0; a
second sample scored 1.0, consistent with model sampling variance rather
than a transport or evaluator failure.

No runtime source overlay or `PYTHONPATH` patch was used. The installed
pinned OSWorld package supplied the VLC gateway-auth fallback directly.

### Cleanup gate

- No labeled Docker container remained after the run.
- Post-run OpenSandbox audits for direct Chrome, proxy-required Chrome,
and VLC each returned zero matching instances.

## Scope

This branch is based on current `main` after #1408. It intentionally
excludes developer-local ignore rules, local experiment harnesses and
paths, `.pre-commit-config.yaml` changes, and unrelated vLLM component
metadata changes.

---------

Signed-off-by: Jeff Peng <jepeng@nvidia.com>
Co-authored-by: Marta Stepniewska-Dziubinska <marta-sd@users.noreply.github.com>
Co-authored-by: oliver könig <okoenig@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

env-integration Integrate with existing environments or gyms external to NeMo Gym

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate OSWorld

5 participants