Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4936d98
feat: make Legal Agent Bench agent configurable
rystewart-nvidia Jul 28, 2026
aff95be
pass an empty request context from the inner LAB runner instead of No…
rystewart-nvidia Jul 30, 2026
0658a0e
propagate partial agent harness failures
rystewart-nvidia Aug 3, 2026
14ad16d
isolate LAB runtime dependency provisioning
rystewart-nvidia Aug 3, 2026
82060c3
remove writable host mounts from LAB sandboxes
rystewart-nvidia Aug 3, 2026
907c9ec
support LAB evaluations on ECS Fargate
rystewart-nvidia Aug 3, 2026
10a7564
document matching LAB concurrency limits
rystewart-nvidia Aug 3, 2026
6a3dc5b
provide Codex model metadata in LAB sandboxes
rystewart-nvidia Aug 3, 2026
bccbfcd
add the Gym-native LAB runner and make it the default
rystewart-nvidia Aug 3, 2026
362b522
validate LAB runner boundary inputs
rystewart-nvidia Aug 4, 2026
b6ed2b1
test: cover LAB runner boundary and failure paths
rystewart-nvidia Aug 4, 2026
d756acb
propagate LAB Harbor agent failures
rystewart-nvidia Aug 4, 2026
d2c890a
route LAB operational failures for retry
rystewart-nvidia Aug 5, 2026
a0e75a1
support reasoning effort for LAB judges
rystewart-nvidia Aug 6, 2026
78fa529
serialize LAB runtime cache replacement
rystewart-nvidia Aug 6, 2026
9d31715
pass LAB tool payloads over stdin
rystewart-nvidia Aug 6, 2026
8a1cd59
exclude raw OOXML from LAB aggregate scoring
rystewart-nvidia Aug 6, 2026
78e4172
classify LAB native and verifier failures
rystewart-nvidia Aug 6, 2026
702c956
document LAB output and timeout guidance
rystewart-nvidia Aug 6, 2026
b072890
make the LAB runner sandbox-provider agnostic
rystewart-nvidia Aug 13, 2026
a5c1e15
support LAB on Apptainer and Enroot
rystewart-nvidia Aug 13, 2026
a877fbb
support LAB on OpenSandbox
rystewart-nvidia Aug 13, 2026
df0bb45
support LAB on OpenShell
rystewart-nvidia Aug 13, 2026
953bc87
normalize Codex response replay for LAB
rystewart-nvidia Aug 13, 2026
c7c19af
document how to publish LAB sandbox images
rystewart-nvidia Aug 13, 2026
27f06f3
fix LAB CI checks
rystewart-nvidia Aug 15, 2026
6855cdb
Merge branch 'main' into legal-agent-bench-agent-configurable
rystewart-nvidia Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
562 changes: 536 additions & 26 deletions benchmarks/legal_agent_bench/README.md

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions benchmarks/legal_agent_bench/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
# SPDX-License-Identifier: Apache-2.0

config_paths:
- resources_servers/legal_agent_bench/configs/legal_agent_bench.yaml
- responses_api_agents/legal_agent_bench_agent/configs/legal_agent_bench_native.yaml

legal_agent_bench_benchmark_resources_server:
_inherit_from: legal_agent_bench

legal_agent_bench_benchmark_harbor_agent:
_inherit_from: legal_agent_bench_harbor_agent
legal_agent_bench_benchmark_native_agent:
_inherit_from: legal_agent_bench_native_agent
responses_api_agents:
harbor_agent:
harbor_datasets:
legal_agent_bench:
local_dataset_path: ${legal_agent_bench_benchmark_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
harbor_agent_kwargs:
skills_dir: ${legal_agent_bench_benchmark_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
legal_agent_bench_agent:
resources_server:
type: resources_servers
name: legal_agent_bench_benchmark_resources_server
runtime_tasks_dir: ${legal_agent_bench_benchmark_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
skills_dir: ${legal_agent_bench_benchmark_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
datasets:
- name: legal_agent_bench
type: benchmark
Expand Down
25 changes: 25 additions & 0 deletions benchmarks/legal_agent_bench/config_claude_code.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
config_paths:
- responses_api_agents/legal_agent_bench_agent/configs/legal_agent_bench_claude_code.yaml

legal_agent_bench_benchmark_claude_code_resources_server:
_inherit_from: legal_agent_bench
resources_servers:
legal_agent_bench:
description: Configurable-agent integration of Legal Agent Benchmark (LAB) using Claude Code

legal_agent_bench_benchmark_claude_code_agent:
_inherit_from: legal_agent_bench_claude_code_agent
responses_api_agents:
legal_agent_bench_agent:
resources_server:
type: resources_servers
name: legal_agent_bench_benchmark_claude_code_resources_server
runtime_tasks_dir: ${legal_agent_bench_benchmark_claude_code_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
skills_dir: ${legal_agent_bench_benchmark_claude_code_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
datasets:
- name: legal_agent_bench
type: benchmark
jsonl_fpath: benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl
prompt_config: null
prepare_script: benchmarks/legal_agent_bench/prepare.py
num_repeats: 1
25 changes: 25 additions & 0 deletions benchmarks/legal_agent_bench/config_codex.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
config_paths:
- responses_api_agents/legal_agent_bench_agent/configs/legal_agent_bench_codex.yaml

legal_agent_bench_benchmark_codex_resources_server:
_inherit_from: legal_agent_bench
resources_servers:
legal_agent_bench:
description: Configurable-agent integration of Legal Agent Benchmark (LAB) using Codex

legal_agent_bench_benchmark_codex_agent:
_inherit_from: legal_agent_bench_codex_agent
responses_api_agents:
legal_agent_bench_agent:
resources_server:
type: resources_servers
name: legal_agent_bench_benchmark_codex_resources_server
runtime_tasks_dir: ${legal_agent_bench_benchmark_codex_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
skills_dir: ${legal_agent_bench_benchmark_codex_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
datasets:
- name: legal_agent_bench
type: benchmark
jsonl_fpath: benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl
prompt_config: null
prepare_script: benchmarks/legal_agent_bench/prepare.py
num_repeats: 1
25 changes: 25 additions & 0 deletions benchmarks/legal_agent_bench/config_harbor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

config_paths:
- resources_servers/legal_agent_bench/configs/legal_agent_bench.yaml

legal_agent_bench_benchmark_harbor_resources_server:
_inherit_from: legal_agent_bench

legal_agent_bench_benchmark_harbor_agent:
_inherit_from: legal_agent_bench_harbor_agent
responses_api_agents:
harbor_agent:
harbor_datasets:
legal_agent_bench:
local_dataset_path: ${legal_agent_bench_benchmark_harbor_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
harbor_agent_kwargs:
skills_dir: ${legal_agent_bench_benchmark_harbor_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
datasets:
- name: legal_agent_bench
type: benchmark
jsonl_fpath: benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl
prompt_config: null
prepare_script: benchmarks/legal_agent_bench/prepare.py
num_repeats: 1
25 changes: 25 additions & 0 deletions benchmarks/legal_agent_bench/config_hermes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
config_paths:
- responses_api_agents/legal_agent_bench_agent/configs/legal_agent_bench_hermes.yaml

legal_agent_bench_benchmark_hermes_resources_server:
_inherit_from: legal_agent_bench
resources_servers:
legal_agent_bench:
description: Configurable-agent integration of Legal Agent Benchmark (LAB) using Hermes

legal_agent_bench_benchmark_hermes_agent:
_inherit_from: legal_agent_bench_hermes_agent
responses_api_agents:
legal_agent_bench_agent:
resources_server:
type: resources_servers
name: legal_agent_bench_benchmark_hermes_resources_server
runtime_tasks_dir: ${legal_agent_bench_benchmark_hermes_resources_server.resources_servers.legal_agent_bench.harbor_tasks_dir}
skills_dir: ${legal_agent_bench_benchmark_hermes_resources_server.resources_servers.legal_agent_bench.harness_skills_dir}
datasets:
- name: legal_agent_bench
type: benchmark
jsonl_fpath: benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl
prompt_config: null
prepare_script: benchmarks/legal_agent_bench/prepare.py
num_repeats: 1
6 changes: 1 addition & 5 deletions benchmarks/legal_agent_bench/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
BENCHMARK_DIR = Path(__file__).resolve().parent
DATA_DIR = BENCHMARK_DIR / "data"
OUTPUT_FPATH = DATA_DIR / "legal_agent_bench_benchmark.jsonl"
BENCHMARK_AGENT_NAME = "legal_agent_bench_benchmark_harbor_agent"


def _render_benchmark_index(source_index: Path) -> str:
Expand All @@ -31,10 +30,7 @@ def _render_benchmark_index(source_index: Path) -> str:
if not isinstance(row, dict):
raise ValueError(f"LAB task index line {line_number} must contain a JSON object")

row["agent_ref"] = {
"name": BENCHMARK_AGENT_NAME,
"type": "responses_api_agents",
}
row.pop("agent_ref", None)
rendered_rows.append(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n")

if len(rendered_rows) != EXPECTED_TASK_COUNT:
Expand Down
181 changes: 165 additions & 16 deletions benchmarks/legal_agent_bench/tests/test_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
from pathlib import Path

import pytest
from omegaconf import OmegaConf
from omegaconf import DictConfig, OmegaConf

from benchmarks.legal_agent_bench import prepare as benchmark_prepare
from nemo_gym.benchmarks import BenchmarkConfig
from nemo_gym.config_types import ResponsesAPIAgentServerInstanceConfig
from nemo_gym.global_config import GlobalConfigDictParser, GlobalConfigDictParserConfig
from nemo_gym.train_data_utils import TrainDataProcessor
from resources_servers.legal_agent_bench.prepare import EXPECTED_TASK_COUNT, INDEX_FILENAME


Expand All @@ -28,10 +30,6 @@ def _write_task_index(parent: Path, count: int) -> tuple[Path, list[str]]:
for task_name in task_names:
rows.append(
{
"agent_ref": {
"name": "legal_agent_bench_harbor_agent",
"type": "responses_api_agents",
},
"instance_id": f"legal_agent_bench::{task_name}",
"responses_create_params": {
"input": [],
Expand Down Expand Up @@ -73,14 +71,7 @@ def test_prepare_writes_deterministic_complete_benchmark_index(monkeypatch, tmp_
rows = [json.loads(line) for line in output_path.read_text(encoding="utf-8").splitlines()]
assert len(rows) == EXPECTED_TASK_COUNT
assert [row["instance_id"].split("::", 1)[1] for row in rows] == task_names
assert all(
row["agent_ref"]
== {
"name": benchmark_prepare.BENCHMARK_AGENT_NAME,
"type": "responses_api_agents",
}
for row in rows
)
assert all("agent_ref" not in row for row in rows)


def test_wrong_row_count_does_not_replace_existing_output(monkeypatch, tmp_path) -> None:
Expand Down Expand Up @@ -129,7 +120,7 @@ def test_benchmark_config_is_isolated_and_resolves_shared_cache_paths() -> None:
benchmark = BenchmarkConfig.from_config_path(CONFIG_FPATH, strict=False)
assert benchmark is not None
assert benchmark.name == "legal_agent_bench"
assert benchmark.agent_name == "legal_agent_bench_benchmark_harbor_agent"
assert benchmark.agent_name == "legal_agent_bench_benchmark_native_agent"
assert benchmark.num_repeats == 1
assert benchmark.dataset.prompt_config is None
assert benchmark.dataset.jsonl_fpath == Path("benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl")
Expand All @@ -141,12 +132,170 @@ def test_benchmark_config_is_isolated_and_resolves_shared_cache_paths() -> None:
)
resolved = GlobalConfigDictParser().parse_no_environment(initial_global_config_dict=initial_config)
assert "legal_agent_bench" not in resolved
assert "legal_agent_bench_harbor_agent" not in resolved
assert "legal_agent_bench_native_agent" not in resolved

resource = resolved.legal_agent_bench_benchmark_resources_server.resources_servers.legal_agent_bench
agent = resolved.legal_agent_bench_benchmark_native_agent.responses_api_agents.legal_agent_bench_agent
assert agent.agent_server_module == "responses_api_agents.legal_agent_bench_native_agent.app"
assert agent.runtime_tasks_dir == resource.harbor_tasks_dir
assert agent.skills_dir == resource.harness_skills_dir
assert agent.runtime_builder_provider_options == {}
assert agent.agent_sandbox_provider_options == {}
assert agent.verifier_sandbox_provider_options == {}
assert agent.agent_kwargs.max_turns == 60
assert len(agent.datasets) == 1
assert agent.datasets[0].type == "benchmark"


def test_harbor_compatibility_variant_resolves() -> None:
config_path = BENCHMARK_DIR / "config_harbor.yaml"
benchmark = BenchmarkConfig.from_config_path(config_path, strict=False)
assert benchmark is not None
assert benchmark.name == "legal_agent_bench"
assert benchmark.agent_name == "legal_agent_bench_benchmark_harbor_agent"

initial_config = OmegaConf.merge(
OmegaConf.load(config_path),
GlobalConfigDictParserConfig.NO_MODEL_GLOBAL_CONFIG_DICT,
)
resolved = GlobalConfigDictParser().parse_no_environment(initial_global_config_dict=initial_config)
resource = resolved.legal_agent_bench_benchmark_harbor_resources_server.resources_servers.legal_agent_bench
agent = resolved.legal_agent_bench_benchmark_harbor_agent.responses_api_agents.harbor_agent
assert agent.harbor_datasets.legal_agent_bench.local_dataset_path == resource.harbor_tasks_dir
assert agent.harbor_agent_kwargs.skills_dir == resource.harness_skills_dir
assert agent.harbor_agent_kwargs.max_turns == 60
assert len(agent.datasets) == 1
assert agent.datasets[0].type == "benchmark"


@pytest.mark.parametrize(
("filename", "expected_agent", "expected_module"),
[
("config_hermes.yaml", "legal_agent_bench_benchmark_hermes_agent", "responses_api_agents.hermes_agent.app"),
(
"config_claude_code.yaml",
"legal_agent_bench_benchmark_claude_code_agent",
"responses_api_agents.claude_code_agent.app",
),
("config_codex.yaml", "legal_agent_bench_benchmark_codex_agent", "responses_api_agents.codex_agent.app"),
],
)
def test_configurable_benchmark_variants_resolve(filename, expected_agent, expected_module) -> None:
config_path = BENCHMARK_DIR / filename
benchmark = BenchmarkConfig.from_config_path(config_path, strict=False)
assert benchmark is not None
assert benchmark.name == "legal_agent_bench"
assert benchmark.agent_name == expected_agent
assert benchmark.dataset.jsonl_fpath == Path("benchmarks/legal_agent_bench/data/legal_agent_bench_benchmark.jsonl")

initial_config = OmegaConf.merge(
OmegaConf.load(config_path),
GlobalConfigDictParserConfig.NO_MODEL_GLOBAL_CONFIG_DICT,
)
resolved = GlobalConfigDictParser().parse_no_environment(initial_global_config_dict=initial_config)
agent_names = [
name for name, value in resolved.items() if isinstance(value, DictConfig) and "responses_api_agents" in value
]
resource_names = [
name for name, value in resolved.items() if isinstance(value, DictConfig) and "resources_servers" in value
]
assert agent_names == [expected_agent]
assert resource_names == [f"{expected_agent.removesuffix('_agent')}_resources_server"]
agent = resolved[expected_agent].responses_api_agents.legal_agent_bench_agent
assert agent.agent_server_module == expected_module
if expected_module == "responses_api_agents.claude_code_agent.app":
assert agent.agent_kwargs.claude_code_version == "2.1.211"
elif expected_module == "responses_api_agents.codex_agent.app":
assert agent.agent_kwargs.codex_version == "0.144.4"
assert agent.agent_kwargs.cwd == "/sandbox/nemo-gym-legal-agent-bench/workspace/output"
assert agent.datasets[0].type == "benchmark"
assert agent.runtime_tasks_dir.endswith("data/runtime/harbor_tasks/legal_agent_bench")
assert agent.runtime_builder_provider_options == {}
assert agent.agent_sandbox_provider_options == {}
assert agent.verifier_sandbox_provider_options == {}


@pytest.mark.parametrize(
("filename", "expected_agent"),
[
("config.yaml", "legal_agent_bench_benchmark_native_agent"),
("config_hermes.yaml", "legal_agent_bench_benchmark_hermes_agent"),
("config_claude_code.yaml", "legal_agent_bench_benchmark_claude_code_agent"),
("config_codex.yaml", "legal_agent_bench_benchmark_codex_agent"),
],
)
def test_configurable_variants_decode_phase_provider_options_from_environment(
monkeypatch, filename, expected_agent
) -> None:
monkeypatch.setenv(
"NEMO_GYM_LAB_RUNTIME_BUILDER_PROVIDER_OPTIONS",
"{policy: /tmp/lab-builder-policy.yaml}",
)
monkeypatch.setenv(
"NEMO_GYM_LAB_AGENT_SANDBOX_PROVIDER_OPTIONS",
"{policy: /tmp/lab-agent-policy.yaml}",
)
monkeypatch.setenv(
"NEMO_GYM_LAB_VERIFIER_SANDBOX_PROVIDER_OPTIONS",
"{policy: /tmp/lab-verifier-policy.yaml}",
)
initial_config = OmegaConf.merge(
OmegaConf.load(BENCHMARK_DIR / filename),
GlobalConfigDictParserConfig.NO_MODEL_GLOBAL_CONFIG_DICT,
)

resolved = GlobalConfigDictParser().parse_no_environment(initial_global_config_dict=initial_config)
agent = resolved[expected_agent].responses_api_agents.legal_agent_bench_agent

assert agent.runtime_builder_provider_options == {"policy": "/tmp/lab-builder-policy.yaml"}
assert agent.agent_sandbox_provider_options == {"policy": "/tmp/lab-agent-policy.yaml"}
assert agent.verifier_sandbox_provider_options == {"policy": "/tmp/lab-verifier-policy.yaml"}


@pytest.mark.parametrize(
"agent_name",
[
"legal_agent_bench_benchmark_native_agent",
"legal_agent_bench_benchmark_harbor_agent",
"legal_agent_bench_benchmark_hermes_agent",
"legal_agent_bench_benchmark_claude_code_agent",
"legal_agent_bench_benchmark_codex_agent",
],
)
def test_benchmark_collation_stamps_selected_agent_without_changing_source(tmp_path, agent_name) -> None:
source = tmp_path / "legal_agent_bench.jsonl"
neutral_row = {
"instance_id": "legal_agent_bench::corporate__task",
"responses_create_params": {"input": []},
}
source.write_text(json.dumps(neutral_row) + "\n", encoding="utf-8")
agent_config = {
"responses_api_agents": {
"agent": {
"host": "127.0.0.1",
"port": 12345,
"entrypoint": "app.py",
"resources_server": {"type": "resources_servers", "name": "legal_agent_bench"},
"model_server": {"type": "responses_api_models", "name": "policy_model"},
"datasets": [
{
"name": "legal_agent_bench",
"type": "benchmark",
"jsonl_fpath": str(source),
"prepare_script": "benchmarks/legal_agent_bench/prepare.py",
"num_repeats": 1,
}
],
}
}
}
instance = ResponsesAPIAgentServerInstanceConfig(
name=agent_name,
server_type_config_dict=DictConfig(agent_config),
responses_api_agents=agent_config["responses_api_agents"],
)

prepared = TrainDataProcessor()._collate_samples_single_type("benchmark", [instance])[0]
collated_row = json.loads(prepared.read_text(encoding="utf-8"))

assert json.loads(source.read_text(encoding="utf-8")) == neutral_row
assert collated_row["agent_ref"] == {"type": "responses_api_agents", "name": agent_name}
Loading
Loading