Skip to content

Turing/ Playwright-based CUA environment with multi-provider adapter support - #946

Open
raveedturing wants to merge 70 commits into
NVIDIA-NeMo:mainfrom
turing-rlgym:feat/browser-gym-env-integration
Open

Turing/ Playwright-based CUA environment with multi-provider adapter support#946
raveedturing wants to merge 70 commits into
NVIDIA-NeMo:mainfrom
turing-rlgym:feat/browser-gym-env-integration

Conversation

@raveedturing

@raveedturing raveedturing commented Mar 24, 2026

Copy link
Copy Markdown

Turing/ Browser Gym — Playwright-based CUA environment for web navigation training

What does this PR do?

Adds a complete Computer-Use Agent (CUA) training environment to NeMo-Gym. Models interact with live web pages via Playwright — receiving screenshots and executing browser actions (click, type, scroll, keypress, drag, navigate). The environment supports three CUA providers: OpenAI (computer-use-preview), Anthropic (claude-sonnet-4-20250514 / claude-opus-4-6), and Google Gemini (gemini-2.5-computer-use-preview-10-2025).

This PR adds:

  • Resources server (resources_servers/browser_gym/) — browser session lifecycle, Playwright action execution, session pooling with async locking, graceful error propagation, session reaper for stale sessions, standalone prepare_data.py CLI for offline task fetching
  • Browser agent (responses_api_agents/browser_agent/) — CUA loop orchestration, provider-agnostic adapter architecture (OpenAICUAAdapter + GenericCUAAdapter), token ID tracking for RL training, debug trajectory output
  • Model servers (responses_api_models/turing_browser_agent_anthropic_model/, responses_api_models/turing_browser_agent_gemini_model/) — stateless translators that accept OpenAI Responses API format, translate to/from provider-native APIs, and return unified NeMoGymResponse
  • Core library changes — rollout cache memory optimization (retain_result_strs), CUA schema types (NeMoGymAction union + SDK-subclassing computer_call / computer_call_output), OpenAI organization header support

Architecture

Package layout

resources_servers/browser_gym/
├── app.py                  # FastAPI server: seed_session, step, verify, close, dump_local_storage
├── browser_pool.py         # Playwright browser pool with async session management
├── schemas.py              # CUA schemas (BrowserAction, CUAStepResponse, CUATrajectory, etc.)
├── setup_playwright.py     # Auto-install Chromium on startup (skips if already present)
├── prepare_data.py         # CLI for fetching tasks from gym URL → JSONL
├── configs/browser_gym.yaml
├── data/example.jsonl      # 5 example tasks
├── data/example_rollouts.jsonl
├── data/.gitignore
├── tests/test_app.py       # 68 tests
└── tests/test_prepare_data.py  # 17 tests

responses_api_agents/browser_agent/
├── app.py                  # CUA loop: seed → model → step → verify
├── trajectory_writer.py    # Debug trajectory output (screenshots + conversation JSONL)
├── adapters/
│   ├── base.py             # BaseCUAAdapter ABC + token ID extraction
│   ├── openai_adapter.py   # OpenAI computer-use-preview adapter (server-side context)
│   └── generic_adapter.py  # Generic CUA adapter for Anthropic + Gemini (client-side context)
└── tests/test_app.py       # 100 tests (209 with parameterization)

responses_api_models/turing_browser_agent_anthropic_model/
├── app.py                  # Stateless translator: OpenAI format ↔ Anthropic Messages API
├── configs/turing_browser_agent_anthropic_model.yaml
└── tests/test_app.py       # 41 tests

responses_api_models/turing_browser_agent_gemini_model/
├── app.py                  # Stateless translator: OpenAI format ↔ Gemini generate_content API
├── configs/turing_browser_agent_gemini_model.yaml
└── tests/test_app.py       # 52 tests

Data flow

gym eval run (--input <jsonl>)
  → agent /run
    → resources_server /seed_session (start browser, navigate to start_url, return screenshot)
    → loop:
        → model server /v1/responses (send screenshot + context in OpenAI format, get actions)
        → resources_server /step (execute action, return new screenshot + error if any)
        → adapter maps OpenAI-format response → unified BrowserAction
    → resources_server /dump_local_storage (capture browser state)
    → resources_server /close (tear down browser session)
    → resources_server /verify (send localStorage to gym API, compute reward)
  → JSONL output (reward + full trajectory with screenshots)

Adapter pattern

All providers follow the same interface. The adapter manages context internally, and all API calls use the standardized OpenAI Responses API format — model servers translate to/from provider-native APIs:

class BaseCUAAdapter(ABC):
    async def initialize(task_prompt, screenshot_b64) -> CUAAdapterResponse
    async def step(screenshot_b64, action_result, action_error) -> CUAAdapterResponse
    def reset()
Provider Adapter Context Management Model Server
OpenAI OpenAICUAAdapter Server-side via previous_response_id openai_model (proxies to api.openai.com)
Anthropic (Sonnet/Opus) GenericCUAAdapter Client-side: turn-based trimming, tool pair validation, screenshot GC turing_browser_agent_anthropic_model (stateless translator: OpenAI format ↔ Anthropic Messages API)
Gemini GenericCUAAdapter Client-side: paired-turn trimming, function pair validation, screenshot GC turing_browser_agent_gemini_model (stateless translator: OpenAI format ↔ Gemini generate_content API)

On model-server naming (addresses the earlier review note): the two turing_browser_agent_* model servers are deliberately generic stateless translators — they accept OpenAI Responses API format, translate to/from the provider-native API, and return NeMoGymResponse, with no browser-gym-specific logic (see their module docstrings). They are reusable by any environment, not coupled to Browser Gym. We are intentionally keeping the turing_browser_agent_ directory prefix — it reflects where the work originated, not a functional dependency.

Browser pool

  • Configurable pool of Chromium processes (browser_pool_size) with semaphore-bounded concurrency (max_concurrent_browsers)
  • Async locking for session management (add/remove)
  • Session reaper for stale sessions (configurable TTL)
  • Rollback on session creation failure (releases browser resources on error)
  • Per-action timeouts with graceful error propagation back to the model
  • Auto-installs Chromium on server startup (skips if already present)

Error handling

Action errors (e.g., invalid key names in keypress) are caught at the Playwright layer in browser_pool.py, returned through CUAStepResponse.error, and forwarded to the model via adapter-specific tool result formatting:

  • OpenAI (OpenAICUAAdapter): Error set in computer_call_output.output.current_url
  • Anthropic / Gemini (GenericCUAAdapter): Error set in computer_call_output.output.current_url (same OpenAI format — model server handles native translation)

This allows models to self-correct without crashing the trajectory. If the adapter's initialize() call fails (e.g., API unreachable), an empty trajectory is returned gracefully instead of a 500 that would crash rollout collection.

Verification

Verification uses localStorage assertions. After the CUA agent completes a task:

  1. Agent dumps the browser's localStorage
  2. Resource server sends form data (taskId, localStorageDump, initialState, modelResponse) to the gym's /api/v1/get_actual_state endpoint (with retry logic for transient 502/503/504 errors)
  3. Gym returns assertions — reward is 1.0 if all pass, 0.0 otherwise

Core framework changes

nemo_gym/rollout_collection.py — Rollout cache memory optimization

  • Optional result_strs retention in _load_from_cache(): Preserves the historical four-value return contract (input_rows, rows, results, result_strs) but adds a keyword-only retain_result_strs: bool = True parameter. When the caller will not upload to WandB, it skips keeping a second serialized copy (result_strs) of every cached rollout and parses results directly — avoiding unnecessary memory for large base64-heavy trajectories.
  • Gated at the call site: run_from_config() passes retain_result_strs=config.upload_rollouts_to_wandb, so the extra copy is only retained when it will actually be used. The WandB result_strs are rebuilt at the end from results (upstream's run_from_config WandB flow, unchanged by this PR).

nemo_gym/openai_utils.py — CUA schemas + organization header

  • NeMoGymAction union: Added a cross-provider computer-use action union (~20 types) covering the SDK actions (click, double_click, drag, keypress, move, screenshot, scroll, type, wait) plus the normalized browser actions the Anthropic/Gemini adapters emit (goto, go_back, go_forward, new_tab, switch_tab, close_tab, open_web_browser, search, click_at, hover_at, scroll_at, scroll_document, type_text_at, zoom, hold_key, …).
  • CUA schema types: Added NeMoGymResponseComputerToolCall and NeMoGymComputerCallOutput to the NeMoGymResponseInputItem union. They inherit the upstream SDK types (ResponseComputerToolCall / ComputerCallOutput) — so issubclass holds and upstream's schema-drift tests keep passing — and only widen the fields CUA needs: action → the NeMoGymAction union (a superset of the SDK action union), and output → a plain Dict[str, Any] for the browser agent's screenshot/result payload. pending_safety_checks keeps a default so callers may omit it.
  • Organization header: Added optional organization field to NeMoGymAsyncOpenAI — sends Openai-Organization header when set.

responses_api_models/openai_model/app.py — Organization support

  • Organization support: Added openai_organization config field, passed through to NeMoGymAsyncOpenAI.

responses_api_models/turing_browser_agent_gemini_model/requirements.txt — Missing transitive dependencies

  • Added cryptography and cffi as explicit dependencies. These are required by google-auth (a transitive dependency of google-genai) but were not being resolved automatically by uv during venv creation, causing ModuleNotFoundError at server startup.

tests/unit_tests/test_rollout_collection.py — Comprehensive coverage for rollout collection changes

  • Added a _load_from_cache assertion for the retain_result_strs=False path (memory-optimized load returns the same rows with an empty result_strs), alongside the existing four-tuple (input_rows, rows, results, result_strs) coverage.
  • Added run_from_config edge case tests: resume_from_cache with missing files, num_samples_in_parallel semaphore creation.
  • Added test for _call_aggregate_metrics skipping rows without agent_ref.

Configuration

env.yaml (required)

max_concurrent_browsers: 16
browser_pool_size: 4
cua_debug_trajectories: false

# At least one provider:
cua_openai_api_key: <YOUR_OPENAI_API_KEY>
cua_openai_org: <YOUR_OPENAI_ORG>
cua_anthropic_api_key: <YOUR_ANTHROPIC_API_KEY>
cua_gemini_api_key: <YOUR_GEMINI_API_KEY>

Running

# Start all servers (resource + 4 model + 4 agent = 9 servers)
gym env start --config resources_servers/browser_gym/configs/browser_gym.yaml

# Prepare data from gym URL (offline, before rollout collection)
python resources_servers/browser_gym/prepare_data.py \
  --gym-url https://your-gym-url.com \
  --output resources_servers/browser_gym/data/tasks.jsonl

# Collect rollouts from a JSONL file
gym eval run --no-serve \
  --agent browser_openai_agent \
  --input resources_servers/browser_gym/data/tasks.jsonl \
  --output results/cua_rollouts.jsonl \
  --num-repeats 1 \
  --max-output-tokens 16384 \
  --temperature 1.0

# Profile results (pass@1 / pass@k, per-category metrics)
gym eval profile \
  --inputs results/cua_rollouts_materialized_inputs.jsonl \
  --rollouts results/cua_rollouts.jsonl

Available agents: browser_openai_agent, browser_anthropic_sonnet_agent, browser_anthropic_opus_agent, browser_gemini_agent.


Testing

391 test functions across the new Browser Gym components, all passing, plus targeted additions to test_rollout_collection.py for the core-library changes:

Component Tests Covers
resources_servers/browser_gym 71 + 17 BrowserPool, BrowserAction, key normalization, verify endpoint, session reaper, shutdown, rollback, prepare_data CLI
responses_api_agents/browser_agent 210 OpenAICUAAdapter + GenericCUAAdapter parsers, CUA loop orchestration (happy path, init failure, step failure, browser crash, error propagation, multi-step, usage accumulation), AdapterFactory for all providers, token ID extraction, trajectory writer
responses_api_models/turing_browser_agent_gemini_model 52 Config, OpenAI ↔ Gemini translation, content serialization/deserialization, tool derivation, response mapping
responses_api_models/turing_browser_agent_anthropic_model 41 Config, OpenAI ↔ Anthropic translation, tool/beta derivation, message mapping, response translation, output_config
tests/unit_tests/test_rollout_collection added _load_from_cache four-tuple return + retain_result_strs=False memory-opt path, resume_from_cache edge cases, num_samples_in_parallel semaphore, aggregate-metrics skip logic (alongside the existing upstream suite)

Pre-commit hooks (ruff, ruff-format, verified flag, README table) all pass.

@copy-pr-bot

copy-pr-bot Bot commented Mar 24, 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.

@raveedturing
raveedturing force-pushed the feat/browser-gym-env-integration branch from d7d85c8 to 15592df Compare March 24, 2026 17:06
@raveedturing raveedturing changed the title feat: Playwright-based CUA environment with multi-provider adapter support Turing/ Playwright-based CUA environment with multi-provider adapter support Mar 24, 2026
@raveedturing
raveedturing force-pushed the feat/browser-gym-env-integration branch 4 times, most recently from 7aac8ea to 7ee7fb3 Compare March 27, 2026 19:42
@raveedturing
raveedturing force-pushed the feat/browser-gym-env-integration branch from 7ee7fb3 to 76d68fc Compare April 24, 2026 10:32
Comment thread nemo_gym/openai_utils.py Outdated
max_num_tries += 1

content = (await response.content.read()).decode()
backoff = min(2 ** (tries - 1), 30) + random.uniform(0, 1)

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.

can we revert this backoff logic pls? if we are hitting rate limits, my suggestion is to lower the concurrency

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done.

Comment thread nemo_gym/rollout_collection.py Outdated
# From a gym URL (fetches all tasks):
ng_collect_rollouts \
+agent_name=browser_openai_agent \
+input_gym_url=https://your-gym-url.com \

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.

what's the reason we need input_gym_url ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed this, and introduced the prepare_data script for the gym.

Comment thread nemo_gym/rollout_collection.py Outdated

rows.append(row)
results.append(result)
result_strs.append([orjson.dumps(result)])

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.

if the issue is the size, let's fork on if W&B is available and only append to result_strs when it is

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done.

Comment thread nemo_gym/server_utils.py Outdated
)

await asyncio.sleep(0.5)
backoff = min(2 ** (num_tries - 1), 30) + random.uniform(0, 1)

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.

same as above, if we are being rate limited let's lower the concurrency via ++num_samples_in_parallel

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

)
return super().model_post_init(context)

async def responses(self, body: AnthropicProxyRequest = Body()): # type: ignore[override]

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.

we need to strictly adhere to OpenAI responses format for this route

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

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.

what's the highest concurrency we've tested this at?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Tested at 20 concurrent sessions with OpenAI, Anthropic, and Gemini. Higher concurrency is gated by provider API rate limits (per-org RPM/TPM) — CUA trajectories are particularly token-heavy since every turn includes a full screenshot as a base64 image, and each task requires its own Chromium instance for the duration of the trajectory (5–25 min).

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.

we can decide to make this generic or specific. if we want to make it generic, we need some major refactor on this implementation. if specific, let's rename this folder to something like "browser_gym_anthropic_model" or something

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Went with the generic approach — both model servers are now stateless translators (OpenAI format ↔ provider-native API) with no browser-gym-specific logic, reusable for any future agent. On the agent side, Anthropic and Gemini both use a single GenericCUAAdapter that speaks pure OpenAI format — provider SDKs are only dependencies of the respective model servers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

In addition to the generic approach, the model server names have also been made specific, starting with turing-.

body_dict["model"] = self.config.openai_model
openai_response_dict = await self._client.create_response(**body_dict)
return NeMoGymResponse.model_validate(openai_response_dict)
try:

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.

can replace these try/excepts by setting ++global_aiohttp_client_request_debug=true

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — removed the try/except blocks and the _sanitize_error() utility.

@raveedturing
raveedturing force-pushed the feat/browser-gym-env-integration branch from 06ae9e0 to 49ec2d2 Compare April 27, 2026 13:48
This commit introduces the Browser Gym integration, enabling LLMs to interact with web applications through visual observations and browser actions. Key components include:

- New `BrowserGymResourcesServer` for managing browser sessions.
- `BrowserPool` to handle Playwright browser instances with concurrency control.
- Schemas for browser actions, session requests, and responses.
- API endpoints for session management, action execution, and local storage dumping.
- Configuration files and example data for testing and demonstration.

This integration enhances the capabilities of NeMo-Gym by allowing it to perform actions in web environments, capturing full trajectories for reinforcement learning training.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…bust context management

- Move all Anthropic context management (conversation history, trimming,
  tool pair validation) from model server to adapter in agent layer
- Replace sync Anthropic client with AsyncAnthropic in both adapter and
  model server to avoid blocking the event loop under concurrency
- Add tool pair validation after trimming to prevent broken tool_use/
  tool_result sequences from reaching the API
- Add screenshot memory GC to replace base64 data in old messages with
  placeholders, reducing memory usage in long trajectories
- Add 300s timeout on Anthropic client to prevent hung API calls
- Clear pending tool IDs at step() entry to prevent stale IDs on retry
- Only send model name from adapter when calling API directly; let model
  server use its own configured model when proxying
- Add comprehensive Playwright key normalization (X11/xdotool names like
  Super_L, Control_L) with graceful fallback on unknown keys
- Add anthropic dependency to browser_agent requirements.txt
- Update README to reflect stateless proxy architecture, correct
  diagrams, provider table, data flow, and max_steps (250)

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
Cover all three model servers (OpenAI, Anthropic, Gemini), the browser
agent adapters, and the resource server with 140 unit tests. Fix
pre-existing test bugs: OpenAI drag test used wrong coordinate key,
verify endpoint mocks used stale assertion format (isPassing vs result),
and BrowserAction zoom test passed dict instead of list for region.

New test files:
- responses_api_models/anthropic_model/tests/test_app.py (11 tests)
- responses_api_models/gemini_model/tests/test_app.py (22 tests)

Expanded existing tests:
- responses_api_agents/browser_agent/tests/test_app.py: rewrite Gemini
  adapter tests for coordinate denormalization, add tests for all 17
  Gemini actions, denorm helpers, URL tracking, and scroll semantics
- resources_servers/browser_gym/tests/test_app.py: add key normalization
  tests and BrowserAction clear_before_typing schema tests

Also add "Running Tests" section to browser_gym README with per-server
commands and coverage summary table.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
Extract input/output token counts from provider API responses, accumulate across CUA loop iterations, and return NeMoGymResponseUsage so rollout metrics are consistent across all three model providers (OpenAI, Anthropic, Gemini).

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
… gym URL input support

Route all OpenAI, Anthropic, and Gemini API calls through model server proxies via
injected api_caller, removing direct API clients and keys from adapters. Enforce
model_server configuration requirement for all browser agent adapters.

Add input_gym_url and input_gym_task_id options to rollout collection, enabling
direct task fetching from gym /api/v1/get_expected_state endpoints as an
alternative to JSONL files.

Complete token usage tracking for OpenAI adapter path (extract input/output
tokens from model server responses).

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…rios

This commit modifies the example JSONL file to include a series of new tasks related to CRM operations, such as logging notes, updating lead information, creating static lists, and analyzing engagement data. Each task is accompanied by specific metadata for verification, enhancing the dataset for testing and demonstration purposes.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…mprove error logging in OpenAI model server

This commit introduces a mechanism to track safety decisions in the Gemini and OpenAI adapters, ensuring that safety acknowledgments are included in function responses when applicable. Additionally, it enhances error logging in the OpenAI model server by sanitizing sensitive information from error messages. The timeout for verification requests in the BrowserGymResourcesServer is also increased to improve reliability.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces improved error handling in the BrowserGymResourcesServer, specifically in the `step` and `dump_local_storage` methods, to manage browser timeouts more effectively. It adds logging for timeout scenarios, ensuring that appropriate error messages are recorded when browser actions fail. Additionally, the BrowserPool class is updated to include per-action timeouts for various browser actions, enhancing the robustness of the browser interaction process. The README and configuration files are also updated to reflect these changes.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit enhances the Browser Agent architecture by introducing the tracking of prompt and generation token IDs, along with their log probabilities, across various adapters (OpenAI, Anthropic, Gemini). The `extract_token_ids_from_response` function is added to facilitate the extraction of these token IDs from model server responses, ensuring consistent data handling. Additionally, the `_build_nemo_response` function is updated to utilize a new output message format when token ID data is present. Comprehensive tests are included to validate the new functionality and ensure robustness in token ID extraction and usage across the system.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces a retry mechanism for Gemini API calls in the `GeminiModelServer` class, allowing for transient errors to be retried up to a configurable maximum number of attempts. It adds a helper function to determine if an exception is retryable based on its type or status code. The logging has been improved to provide detailed feedback on retry attempts and failures, enhancing the robustness of the API interaction. Additionally, the `gemini_max_retries` configuration option is added to allow customization of retry behavior.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…lection

This commit introduces a new Browser Gym entry in the README, detailing its purpose and configuration. It also updates the `rollout_collection.py` to improve the description of the `num_samples_in_parallel` parameter. Additionally, the `schemas.py` file is modified to streamline imports, and minor formatting adjustments are made in the `setup_playwright.py` and `browser_gym.yaml` files. The test files for the Browser Gym and model responses are updated to ensure consistency and clarity.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…ation updates

This commit introduces the ability to fetch tasks directly from the Browser Gym API endpoints, allowing for more dynamic task management. It updates the configuration options to support this new functionality and improves the overall structure of the Browser Gym integration. Additionally, minor adjustments are made to related files to ensure consistency and clarity in the implementation.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces new `__init__.py` files in the test directories for both the Anthropic and Gemini models. These files are essential for Python to recognize the directories as packages, facilitating the organization and execution of tests.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…e autonomous execution instructions in adapters

This commit introduces async locking in the BrowserPool class to ensure thread-safe access to session management methods. The `add_session` and `remove_session` methods are updated to use an async context manager for locking. Additionally, the autonomous execution instructions are added to the Anthropic, Gemini, and OpenAI adapters, emphasizing direct action execution without user confirmation.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces a comprehensive suite of tests for the trajectory writer in the browser agent. It includes tests for stripping base64 fields, validating base64 strings, initializing debug trajectories, appending debug steps, and finalizing debug trajectories. The tests ensure that the trajectory writer functions correctly, handling various scenarios and edge cases effectively.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
… documentation

This commit modifies the output directory for debug trajectories from `/tmp/cua_debug_trajectories` to `results/cua_debug_trajectories` in the README and configuration files. The change ensures that debug outputs are stored in a project-relative path within the `results/` directory, aligning with other output artifacts. Additionally, the BrowserAgentConfig class is updated to resolve the debug output directory correctly, enhancing the overall organization of output files.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…ests accordingly

This commit modifies the `GeminiModelServer` to directly call the asynchronous `generate_content` method from the `aio` client, removing the use of `asyncio.to_thread`. Corresponding updates are made to the test cases to mock the new asynchronous behavior, ensuring that the tests validate the correct model invocation and error handling. This change enhances the efficiency of API calls and aligns the implementation with asynchronous programming practices.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…es for async responses

This commit adds comprehensive README files for both the Anthropic and Gemini models, detailing their configuration, usage, and dependencies. Additionally, it updates the test cases for the Anthropic model to remove unnecessary result assignments, ensuring cleaner asynchronous response handling. These changes enhance documentation clarity and improve the testing framework for the models.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces a new `example_rollouts.jsonl` file containing structured data for analyzing engagement metrics from recent campaigns. The data includes user prompts and model responses, providing insights on optimal timing for outreach emails based on engagement statistics. This addition enhances the dataset available for testing and improving outreach strategies.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
This commit introduces an async context manager for the lifespan of the Browser Gym server, ensuring that the browser pool is properly shut down when the server is stopped. The implementation includes tests to verify that the shutdown process is invoked correctly and handles errors gracefully. This enhancement improves resource management and stability during server operations.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
@raveedturing
raveedturing force-pushed the feat/browser-gym-env-integration branch from 88466e4 to bcd1eaa Compare May 6, 2026 15:28
raveedturing and others added 6 commits May 6, 2026 20:29
…el servers

- Added new action types for CUA including ActionTripleClick, ActionClickAt, ActionHoverAt, and others to enhance interaction capabilities.
- Updated NeMoGymResponseComputerToolCall to utilize the new action types and modified the acknowledged safety checks to use the appropriate types.
- Introduced the Anthropic model server, which translates OpenAI Responses API format to the Anthropic Messages API, and added configuration for the new server.
- Updated README and configuration files to reflect changes in model server names and integration details for both Anthropic and Gemini.
- Refactored action mapping in GenericCUAAdapter to accommodate the new action structure and ensure compatibility with the updated action types.
- Enhanced tests to validate the new action mappings and server integrations.

This commit enhances the functionality and flexibility of the browser gym environment, allowing for more complex interactions and improved model server integration.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…v-integration

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
@github-actions github-actions Bot added the sla:review-overdue Review response is over the one-business-day SLA label Jul 17, 2026
…v-integration

# Conflicts:
#	README.md
#	nemo_gym/openai_utils.py
#	nemo_gym/rollout_collection.py
#	responses_api_models/openai_model/app.py
#	tests/unit_tests/test_rollout_collection.py
- Updated the `_load_from_cache` method to include an optional parameter `retain_result_strs` for controlling the retention of serialized result strings.
- Adjusted the return signature to include `result_strs`, allowing for more flexible handling of cached rollouts.
- Modified related logic in `run_from_config` to utilize the new parameter, improving memory efficiency when uploading results to W&B.
- Updated unit tests to reflect changes in the method signature and validate new functionality.

This change aims to optimize memory usage during rollout caching and improve integration with W&B uploads.

---------

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…reporting

- Introduced methods to extract expected assertion categories from the `get_expected_state` API and apply them to actual assertions during verification.
- Implemented caching for expected categories to optimize repeated lookups.
- Enhanced metrics computation to report pass rates for dynamically observed categories, improving the granularity of performance insights.
- Updated README to reflect changes in CLI commands and usage instructions.
- Added unit tests to validate new functionality and ensure robust category handling.

This update aims to improve the accuracy and usability of the BrowserGym framework by integrating dynamic category mapping and detailed metrics reporting.

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>
…v-integration

Signed-off-by: raveedturing <ahmad.muhammad@turing.com>

# Conflicts:
#	README.md
#	nemo_gym/openai_utils.py
#	nemo_gym/rollout_collection.py
#	tests/unit_tests/test_openai_utils.py
#	tests/unit_tests/test_rollout_collection.py
waple0820 added a commit to waple0820/Gym that referenced this pull request Aug 25, 2026
…tional idle sweeper

Gym's episode lifecycle is seed_session -> responses -> verify, with no teardown
step, so environments release external resources inside verify() - the one call
that does not happen when the training side aborts, cancels or times out.
simple_agent shows the gap: /seed_session, then /v1/responses guarded by
raise_for_status, then /verify, with no try/finally between them.

Of 102 resources servers, 11 override seed_session and they use five mutually
incompatible cleanup conventions; five have none at all. newton_bench
independently wrote a TTL sweeper and NVIDIA-NeMo#946 ships a second one.

* close_session on SimpleResourcesServer, default no-op, served at
  POST /close_session. Environments that hold something override it, and the
  contract requires idempotency - the same as SandboxProvider.close().
* session_scope() on the agent base pairs /seed_session with /close_session in a
  finally; simple_agent adopts it, so the guarantee is structural rather than
  per-environment discipline.
* Optional idle sweeper behind session_ttl_s (default None = today's behavior),
  as a backstop for what no call can reach: a killed trainer, a dropped
  connection. Modelled on newton_bench's working implementation.

This is the resource-reclaim half of NVIDIA-NeMo#23: cancelling the request is necessary
but not sufficient, because a cancelled handler still leaves the session behind.

Closes NVIDIA-NeMo#2609

Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com>
waple0820 added a commit to waple0820/Gym that referenced this pull request Aug 27, 2026
…tional idle sweeper

Gym's episode lifecycle is seed_session -> responses -> verify, with no teardown
step, so environments release external resources inside verify() - the one call
that does not happen when the training side aborts, cancels or times out.
simple_agent shows the gap: /seed_session, then /v1/responses guarded by
raise_for_status, then /verify, with no try/finally between them.

Of 102 resources servers, 11 override seed_session and they use five mutually
incompatible cleanup conventions; five have none at all. newton_bench
independently wrote a TTL sweeper and NVIDIA-NeMo#946 ships a second one.

* close_session on SimpleResourcesServer, default no-op, served at
  POST /close_session. Environments that hold something override it, and the
  contract requires idempotency - the same as SandboxProvider.close().
* session_scope() on the agent base pairs /seed_session with /close_session in a
  finally; simple_agent adopts it, so the guarantee is structural rather than
  per-environment discipline.
* Optional idle sweeper behind session_ttl_s (default None = today's behavior),
  as a backstop for what no call can reach: a killed trainer, a dropped
  connection. Modelled on newton_bench's working implementation.

This is the resource-reclaim half of NVIDIA-NeMo#23: cancelling the request is necessary
but not sufficient, because a cancelled handler still leaves the session behind.

Closes NVIDIA-NeMo#2609

Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com>
waple0820 added a commit to waple0820/Gym that referenced this pull request Aug 31, 2026
…tional idle sweeper

Gym's episode lifecycle is seed_session -> responses -> verify, with no teardown
step, so environments release external resources inside verify() - the one call
that does not happen when the training side aborts, cancels or times out.
simple_agent shows the gap: /seed_session, then /v1/responses guarded by
raise_for_status, then /verify, with no try/finally between them.

Of 102 resources servers, 11 override seed_session and they use five mutually
incompatible cleanup conventions; five have none at all. newton_bench
independently wrote a TTL sweeper and NVIDIA-NeMo#946 ships a second one.

* close_session on SimpleResourcesServer, default no-op, served at
  POST /close_session. Environments that hold something override it, and the
  contract requires idempotency - the same as SandboxProvider.close().
* session_scope() on the agent base pairs /seed_session with /close_session in a
  finally; simple_agent adopts it, so the guarantee is structural rather than
  per-environment discipline.
* Optional idle sweeper behind session_ttl_s (default None = today's behavior),
  as a backstop for what no call can reach: a killed trainer, a dropped
  connection. Modelled on newton_bench's working implementation.

This is the resource-reclaim half of NVIDIA-NeMo#23: cancelling the request is necessary
but not sufficient, because a cancelled handler still leaves the session behind.

Closes NVIDIA-NeMo#2609

Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com>
waple0820 added a commit to waple0820/Gym that referenced this pull request Sep 6, 2026
…tional idle sweeper

Gym's episode lifecycle is seed_session -> responses -> verify, with no teardown
step, so environments release external resources inside verify() - the one call
that does not happen when the training side aborts, cancels or times out.
simple_agent shows the gap: /seed_session, then /v1/responses guarded by
raise_for_status, then /verify, with no try/finally between them.

Of 102 resources servers, 11 override seed_session and they use five mutually
incompatible cleanup conventions; five have none at all. newton_bench
independently wrote a TTL sweeper and NVIDIA-NeMo#946 ships a second one.

* close_session on SimpleResourcesServer, default no-op, served at
  POST /close_session. Environments that hold something override it, and the
  contract requires idempotency - the same as SandboxProvider.close().
* session_scope() on the agent base pairs /seed_session with /close_session in a
  finally; simple_agent adopts it, so the guarantee is structural rather than
  per-environment discipline.
* Optional idle sweeper behind session_ttl_s (default None = today's behavior),
  as a backstop for what no call can reach: a killed trainer, a dropped
  connection. Modelled on newton_bench's working implementation.

This is the resource-reclaim half of NVIDIA-NeMo#23: cancelling the request is necessary
but not sufficient, because a cancelled handler still leaves the session behind.

Closes NVIDIA-NeMo#2609

Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com>
waple0820 added a commit to waple0820/Gym that referenced this pull request Sep 10, 2026
…tional idle sweeper

Gym's episode lifecycle is seed_session -> responses -> verify, with no teardown
step, so environments release external resources inside verify() - the one call
that does not happen when the training side aborts, cancels or times out.
simple_agent shows the gap: /seed_session, then /v1/responses guarded by
raise_for_status, then /verify, with no try/finally between them.

Of 102 resources servers, 11 override seed_session and they use five mutually
incompatible cleanup conventions; five have none at all. newton_bench
independently wrote a TTL sweeper and NVIDIA-NeMo#946 ships a second one.

* close_session on SimpleResourcesServer, default no-op, served at
  POST /close_session. Environments that hold something override it, and the
  contract requires idempotency - the same as SandboxProvider.close().
* session_scope() on the agent base pairs /seed_session with /close_session in a
  finally; simple_agent adopts it, so the guarantee is structural rather than
  per-environment discipline.
* Optional idle sweeper behind session_ttl_s (default None = today's behavior),
  as a backstop for what no call can reach: a killed trainer, a dropped
  connection. Modelled on newton_bench's working implementation.

This is the resource-reclaim half of NVIDIA-NeMo#23: cancelling the request is necessary
but not sufficient, because a cancelled handler still leaves the session behind.

Closes NVIDIA-NeMo#2609

Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sla:review-overdue Review response is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants