Skip to content

[GDPVal] Align the sandbox and prompt to GDPval-AA v2 - #3333

Merged
Kh4L merged 10 commits into
gdpval-develfrom
agronskiy/feat/gdpval-aa-align-v2
Sep 16, 2026
Merged

Kh4L merged 10 commits into
gdpval-develfrom
agronskiy/feat/gdpval-aa-align-v2

Conversation

@agronskiy

@agronskiy agronskiy commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Our GDPval prompt and container had drifted apart and neither matched the
published GDPval-AA v2 environment.

The prompt advertised torch 2.10.0, keras, jax, dlib, mtcnn, pygraphviz,
pdfkit, imgkit and fuzzywuzzy. gdpval.def installed none of them, and none
of them are in the v2 dependency set. That listing has been there since the
original Stirrup import (#1090); #1714 dropped torch from the container but
the prompt never got the matching edit. A model that believes it has PyTorch
plans a PyTorch solution and only finds out mid-trajectory.

The interpreter was also wrong: the published apt closure pins
python3.13=3.13.5, but basing on the docker python:3.13 image gave 3.13.15.

What this does

  • Vendors the two dependency snapshots Artificial Analysis publishes for
    GDPval-AA v2: 419 pinned Python packages and the 762-package apt closure.
  • Bases on debian:trixie and installs Debian's python3.13, asserting the
    micro version in %post. The closure is trixie-pinned (+deb13uN, t64
    names), so the old bookworm base also shipped older libreoffice/ffmpeg.
  • Installs Python from the pin file verbatim instead of resolving loose names,
    and checks afterwards that every pin landed at the published version.
  • Replaces the fabricated package listing in the prompt with the v2 stack
    summary, and says explicitly that there is no deep learning framework.
  • Installs the Office metric-substitute fonts (Carlito, Caladea) and Noto.
    --no-install-recommends had dropped them, which silently reflows every
    Office document written in Calibri or Cambria and renders CJK as tofu.
  • Adds verify_gdpval_sandbox.py, staged into the image at /opt/gdpval,
    which audits a built sif against both snapshots and then exercises the
    toolchain end to end.

Deliberate divergences

The only runtime divergence from the published prompt is the per-command time
limit: 5 minutes here against their 10, matching SHELL_TIMEOUT.

An earlier revision of this PR also claimed the working directory and
environment variables carry over between calls. That was wrong and is
fixed in 74b8765, thanks to @spanev. Three things in
apptainer_provider.py mean no shell state survives: every call is prefixed
with cd <working_dir>, the command runs inside bash -c, and that is
wrapped in a ( ... ) subshell. Files persist; directories, exported
variables, functions and aliases do not. So the published wording was
accurate for this backend all along, and the prompt now matches it.

The test for that claim originally asserted the prompt's wording, which
locked the error in rather than catching it. _build_command_script is now
extracted from _exec so the test asserts against the script the provider
actually builds.

arm64 builds drop five pins that publish x86_64 wheels with no sdist
(aspose-words, cadquery-ocp, cadquery_vtk, nlopt, and cadquery which depends
on the first). The list is closed and the verifier fails if a build drops
anything else.

Verification

Built and verified on SVG (x86_64) and HSG (aarch64). Verifier runs twice per
build, once with the production apptainer exec flags and once with a roomy
scratch dir.

Cluster Arch Interpreter Pins apt missing Failures
SVG x86_64 3.13.5 419/419 0 0
HSG aarch64 3.13.5 414/419 3 0

Images published to .../eval/gdpval/sif/python-3.13.gdpval.gym-80e4fc.sif on
both clusters with a BUILD_INFO file recording arch and deviations.

Stacked on #3319, so the first commit here is that PR's. It drops out once
#3319 merges.

agronskiy and others added 7 commits September 11, 2026 11:41
Mirrors the eval-factory refs.yaml change. The nine-ref ladder stopped at
1223 while the AA GDPval-AA v2 board runs to 1764, so a frontier candidate
had no anchor above it and its elo came out of a saturated win rate rather
than out of the fit.

  deepseek_v4_flash_0731  1468  new ceiling
  inkling_small           1191
  qwen35_122b              925  closes the 905-992 gap

Elos are the AA rows read 2026-09-11. Each resolves under GDPVAL_REFS_ROOT
like the existing nine, so a local run picks them up by pointing that env
var at a directory holding the same per-reference subdirectory names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
Our GDPval container and task prompt still described the GDPval-AA v1
environment, and they disagreed with each other. The prompt advertised
torch 2.10.0, keras, jax, dlib, mtcnn, pygraphviz, pdfkit, imgkit and
fuzzywuzzy; gdpval.def deliberately installed none of them. A model that
believes it has PyTorch plans a PyTorch solution, spends turns finding
out it does not, and submits worse work for reasons unrelated to its
ability.

Vendor the two dependency snapshots Artificial Analysis publishes for
GDPval-AA v2 and build against them:

  gdpval_aa_v2_python_requirements.txt  419 pinned Python packages
  gdpval_aa_v2_apt_closure.txt          762 pinned apt packages

Sandbox changes:

  * Base image moves from python:3.13-bookworm to python:3.13-trixie. The
    published apt closure is pinned to Debian 13 (`+deb13uN`, t64 library
    names), so a bookworm base shipped systematically older libreoffice,
    ffmpeg and chromium than the environment the reference scores came
    from.
  * Python packages install from the published pin file verbatim instead
    of resolving from loose top-level names, so the image reproduces the
    reference sandbox rather than drifting to whatever the resolver picks
    on build day. A post-install check asserts every pin landed at the
    published version, which catches a late pin quietly downgrading an
    earlier one.
  * Still no PyTorch: torch, torchvision, torchaudio, keras, jax, dlib
    and mtcnn are all absent from the published v2 snapshot.

Prompt changes: replace the fabricated version-by-version package listing
with the v2 stack summary, and state the absence of deep learning
frameworks explicitly. Runtime facts are described as our Apptainer
backend actually behaves, not as the published text reads: our backend
feeds commands to one long-lived bash, so shell state does carry over,
and the per-command limit is the harness's 5 minutes rather than E2B's
10. Copying the published wording verbatim would have misinformed the
model on both counts.

Add verify_gdpval_sandbox.py, staged into the image at /opt/gdpval, which
audits a built sif against both snapshots and then exercises the
toolchain end to end, because a tool that is present but cannot produce a
file is the failure that costs a whole run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
The build command in the README no longer works as written: gdpval.def now
stages the vendored GDPval-AA v2 manifests with %files, and apptainer
resolves those source paths relative to the build working directory, so
building from the repository root cannot find them. Say to build from
containers/, note that the image is x86_64 only, and document the staged
verifier that audits a finished sif against both manifests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
Verifying the first build against the published apt closure surfaced ten
behavioural gaps, all of them fonts plus locales and miller. Building with
--no-install-recommends drops what LibreOffice only recommends, so the
image had no fonts-crosextra-caladea/carlito and none of the Noto
families the closure carries.

Carlito and Caladea are the metric-compatible substitutes for Calibri and
Cambria. Without them LibreOffice silently falls back to a different
face, so every Office document written in the Microsoft defaults reflows:
page counts and layout drift from what the task author saw, and nothing
in the logs says why. Missing Noto means CJK text renders as tofu.

Install the fonts, locales and miller explicitly, and check the
substitution in %post so a future --no-install-recommends change fails
the build instead of quietly degrading deliverable fidelity. The verifier
grows a fonts section that asserts the same properties on a finished sif.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
Two gaps remained between the sif and the published GDPval-AA v2 sandbox.

The interpreter was wrong. The published apt closure pins
python3.13=3.13.5, but basing on the docker python:3.13 image gave 3.13.15,
so the eval ran on a different CPython patch release than the environment
the reference scores came from. Base on plain debian:trixie and install
Debian's python3.13, then assert the micro version in %post so a base
image bump cannot slide a different interpreter in unnoticed. pip installs
into the system interpreter with --break-system-packages, which is correct
for a single-purpose image whose entire point is this dependency set.

The manifest was x86_64 only. GDPval also has to run on the two arm64
clusters, and four of the 419 pins publish x86_64 wheels with no source
distribution, so they cannot be built there at all: aspose-words,
cadquery-ocp, cadquery_vtk and nlopt, plus cadquery itself which declares
a hard dependency on cadquery-ocp. That set was derived by querying the
PyPI JSON API for every pinned version rather than by trial and error;
406 of the 419 have aarch64 wheels and the remaining 9 build from sdist.

arm64 builds now drop exactly that closed list and nothing else. Any other
pin failing still aborts the build, and the verifier fails if the image
dropped something the list does not cover, so a thinner sandbox cannot
pass as a good one.

Also close the last two apt gaps the verifier reported, libopenblas0 and
libopenblas0-pthread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
The trixie base broke the build at the first pip call. Debian installs pip
without a RECORD file, so `pip install --upgrade pip` cannot uninstall the
existing copy and exits with uninstall-no-record-file, failing %post before
a single pin was installed.

Nothing needed the upgrade. Debian's pip resolves the pinned manifest fine,
and taking whatever trixie ships removes one more thing that can differ
between two builds of the same def.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
The trixie build died partway through the 419-pin install: pip tried to
uninstall tomlkit 0.13.2 to put the pinned copy on top and hit
uninstall-no-record-file, because that copy is python3-tomlkit from apt and
dpkg-managed distributions carry no RECORD file.

The overlap is not accidental. gdal-bin and yq pull python3-tomlkit,
python3-xmltodict, python3-yaml, python3-numpy and python3-argcomplete, and
the published apt closure carries exactly the same packages while the Python
manifest pins its own versions of several of them.

So install over them with --ignore-installed instead of fighting it. The
pinned copy lands in /usr/local/lib/python3/dist-packages, which Debian puts
ahead of /usr/lib/python3/dist-packages on sys.path, so the pinned version is
the one that imports. That reproduces the reference sandbox's layout, where
python3-numpy 2.2.4 and the numpy 2.4.4 pin coexist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
@yaoyu-33 yaoyu-33 added area:environment Individual environments, benchmarks, verifiers, and environment-specific resources servers bug Something isn't working needs-review PR is ready for code review and waiting on a reviewer labels Sep 11, 2026
Review catch from Serge Panev on #3333. The prompt told the model that the
working directory and environment variables carry over between tool calls.
They do not, and a prompt that is wrong about this is expensive: the model
`cd`s once and then silently operates in the wrong directory for the rest of
the task.

I reasoned from the provider keeping one long-lived bash process and never
checked how commands are fed into it. There are three independent reasons no
state survives, any one of which is on its own fatal to a `cd`:

  1. every call is prefixed with `cd <working_dir>`, overwriting any earlier
     `cd` before the command runs;
  2. the command runs inside `bash -c`;
  3. which is itself wrapped in a `( ... )` subshell.

Files persist, since it is the same filesystem. Directories, exported
variables, shell functions and aliases do not. That makes the published
Artificial Analysis wording correct for this backend too, so the prompt now
matches it.

Also his second point: the test asserted the wording rather than the
behaviour, so it locked the error in instead of catching it. Extracted
`_build_command_script` from `_exec` and assert against the script the
provider actually builds. The wording test stays, but only as a pair with the
behavioural one -- on its own it proves nothing, since wording can always be
made to agree with itself.

Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
@agronskiy

Copy link
Copy Markdown
Contributor Author

Good catch, and correct — thank you. Fixed in 74b8765; the PR description is updated too, since it advertised the same wrong claim.

You were right about the subshell, and there is a stronger reason next to it. Three independent things in apptainer_provider.py mean no shell state survives, any one of which alone kills a cd:

script = (
    f"cd {self._working_dir} && "     # 1. the directory is reset before EVERY call
    f"( {wrapped_cmd} ) 2>..."        # 3. outer subshell
)
# wrapped_cmd = f"timeout -k 10 {timeout} bash -c {shlex.quote(cmd)}"   # 2. inner subshell

Line 360 is the one I should have seen: an earlier cd is overwritten before the next command runs, independently of the subshells. Files persist, since it is the same filesystem; directories, exported variables, functions and aliases do not — exactly as you describe.

How I got it wrong: I saw the provider keeps one long-lived bash process and concluded state persists, without reading how commands are fed into it. I then treated the published Artificial Analysis wording as E2B-specific and "corrected" it, when it was accurate for this backend all along. The prompt now matches it again.

Your second point is the sharper one and I have acted on it. The test asserted the prompt's wording, so it locked the error in instead of catching it — wording can always be made to agree with itself. _build_command_script is now extracted from _exec, and the test asserts against the script the provider actually builds:

assert script.startswith("cd /workspace &&")
assert "bash -c " in script

The wording test stays, but only as a pair with the behavioural one.

Two consequences worth flagging, since they reach past this PR. The Super-3.5 evidence run (be3caf0d493d5f6b) ran on df3e201d9 and so used the bad prompt, and EFB main currently pins that SHA. It may also explain the 31-point Elo drop I flagged against the previous run — a model told it can cd, then silently working in the wrong directory, loses deliverables without crashing. I will move the pin to a tag on the fixed commit and repeat the run.

Second instance of the same mistake in one paragraph: text copied from the
published Artificial Analysis prompt without checking it against this
backend. Theirs runs as a non-root user in /home/user. GDPValTask builds the
provider with working_dir="/root" and prefixes every command with a `cd` to
it, so an example rooted at /home/user pointed the model at a directory that
does not exist here.

The prompt now names /root explicitly and uses it in the example. A test
reads working_dir straight out of tasks/gdpval.py and fails if the prompt
stops matching it, or if /home/user reappears.

Signed-off-by: Alex Gronskiy <agronskiy@nvidia.com>
@github-actions github-actions Bot added the sla:triage-overdue Review assignment is over the one-business-day SLA label Sep 14, 2026
@yaoyu-33 yaoyu-33 added the complexity:medium Single-domain change with interacting parts or a moderate review surface label Sep 15, 2026
Signed-off-by: Serge Panev <spanev@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 16, 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.

@Kh4L
Kh4L merged commit eda0a31 into gdpval-devel Sep 16, 2026
5 checks passed
@Kh4L
Kh4L deleted the agronskiy/feat/gdpval-aa-align-v2 branch September 16, 2026 22:39
khazic pushed a commit to khazic/Gym-lao that referenced this pull request Oct 1, 2026
…nt runtime (NVIDIA-NeMo#3685)

## What does this PR do?

This PR moves the Stirrup sandbox and runtime work from `gdpval-devel`
to main. On devel it landed as NVIDIA-NeMo#2338, NVIDIA-NeMo#2395, NVIDIA-NeMo#2588, NVIDIA-NeMo#3086, NVIDIA-NeMo#3121 and
NVIDIA-NeMo#3333 (sandbox part). It is one of a series of PRs cut by file from a
single tested devel snapshot. It touches
`responses_api_agents/stirrup_agent/` and the GDPVal sandbox build
script in `nemotron_recipes/`. The GDPVal prompt rewrite that describes
this sandbox is in NVIDIA-NeMo#3686, not here.

**Sandbox (NVIDIA-NeMo#3333)**
- `containers/gdpval.def` now builds the published GDPval-AA v2
environment:
- `debian:trixie` with Debian's Python 3.13.5. The build fails on any
other interpreter version.
- The 419 published Python pins, installed verbatim and checked for
exact versions afterwards.
- Carlito/Caladea (metric-compatible Calibri/Cambria) and the Noto
fonts.
- Both published manifests are vendored as plain text: 419 Python pins
and the 762-package apt closure. On arm64 the build drops exactly the 5
pins in `gdpval_aa_v2_arm64_exclusions.txt`. Any other pin that fails to
install fails the build.
- `verify_gdpval_sandbox.py` is staged into the image. It checks a built
`.sif` against both manifests, then runs the toolchain end to end.
- The definition stages its manifests with relative `%files` paths, so
it has to be built from `containers/`. The README command is updated for
that. `nemotron_recipes/.../build-gdpval-sif.sh` now builds from the
definition's directory, where before it built from the repo root, which
fails with this definition.
- `_build_command_script` is pulled out of
`ApptainerCodeExecToolProvider._exec` with no behaviour change. A test
runs two provider-built commands in one bash and checks that only files
persist between calls.

**Model client and agent (NVIDIA-NeMo#2338, NVIDIA-NeMo#3121)**
- `DynamicMaxTokensChatCompletionsClient`:
- When the tokenizer renders the complete prompt, the remaining context
also caps `max_completion_tokens`. A prompt with no room left raises
Stirrup's `ContextOverflowError` before the request is sent.
- For token counting only, tool-call arguments are JSON-decoded before
template rendering, as vLLM does.
- Without a tokenizer, or when tokenization fails, the character
estimate counts the whole JSON-serialized history, tool calls included.
  - New arguments:
    - `min_completion_tokens`, default 1024 (the old constant).
- `prompt_estimator_truncate_history_thinking`, default `None`. It only
affects the estimate.
- `truncation_recovery`, default `False`. When on, a turn that uses its
whole budget without a schema-valid tool call is followed by one turn
that requests thinking off and adds a one-time instruction. The
instruction is not recorded in the trajectory.
- `NeMoAgent.summarize_messages`:
  - Strips a leading `<think>` block from compaction summaries.
- Makes up to 3 summary attempts. Retries add a stricter text-only
instruction, and the last attempt runs without tools. If all 3 fail it
raises instead of wiping the history.
  - New `min_compaction_summary_words` argument, default 1.

**Provider payload and trajectories (NVIDIA-NeMo#2395, NVIDIA-NeMo#2588, NVIDIA-NeMo#3086)**
- `to_provider_openai_messages` sends only the standard OpenAI message
keys:
- Stirrup's serializer also emits `reasoning_content`, `thinking_blocks`
and `metadata` on assistant messages, flat
`tool_call_id`/`name`/`arguments`/`signature` fields on tool calls, and
`name` on tool messages.
- Main's strict chat schema rejects those fields. So any multi-turn
request whose history has an assistant turn with tool calls or reasoning
currently fails against a Gym model server.
- Unparsed `<tool_call>` / `<function=...>` blocks are removed from the
provider copy of assistant text. This applies only to assistant turns
with no parsed `tool_calls`, and an unclosed block is stripped to the
end of the message. The recorded history keeps them.
- `convert_stirrup_history_to_output_items` makes `call_id` unique
within a response when the server reuses ids across turns (e.g.
`code_exec:0`). Call/output pairing is preserved, and trajectories
without repeated ids are unchanged.
- Tavily `web_fetch`:
- If markdown extraction is empty, it tries a second plain-text pass
that includes comments. If that is also empty it returns an explicit
error.
- An extraction under 1% of a page of 20,000+ characters gets a "likely
a fragment" note at the start.

The new client and agent options are documented in the Stirrup README as
constructor arguments. The agent config keys that set them come with
NVIDIA-NeMo#3686, which owns `stirrup_agent/app.py`.

**Issue:** none. This promotes work that was already reviewed and merged
on `gdpval-devel`.

**Dependencies:**
- CI: none. This PR passes the Stirrup and GDPVal suites on main by
itself.
- Runtime, already true on main before this PR: main's strict chat
schema returns 422 for the `chat_template_kwargs` that Stirrup's client
sends on every call. Running Stirrup against a Gym vLLM model server
therefore needs NVIDIA-NeMo#3682.
- Thinking control: under NVIDIA-NeMo#3682's current default
(`forward_request_chat_template_kwargs: false`), the model server
accepts request `chat_template_kwargs` and drops them. That means
Stirrup's `enable_thinking`, and the thinking-off half of
`truncation_recovery`, reach vLLM only when the policy model server sets
`forward_request_chat_template_kwargs: true`. The GDPVal config in NVIDIA-NeMo#3686
doesn't set it, so there only the recovery instruction takes effect. The
default is still open on NVIDIA-NeMo#3682.
- NVIDIA-NeMo#3686 (lands after this one):
- It brings the GDPVal prompt rewrite (`code_exec`, no shell state
between calls, the 5-minute limit, the AA v2 package summary). It also
brings the reference-file path fix that prompt relies on, and the tests
that tie the prompt to this sandbox's manifests.
- It adds the agent config keys `context_window_tokens`,
`min_completion_tokens`, `prompt_estimator_truncate_history_thinking`,
`min_compaction_summary_words` and `truncation_recovery`.
- Until it lands, main's prompt keeps advertising `run_shell` and
packages the image doesn't have (torch, keras, jax, dlib). Main's
current image doesn't have them either. The new options keep their
defaults, and the context window still comes from the request's
`max_output_tokens` (fallback 262144).

## Validation

Main `1c8261080` + only this PR's 17 files, macOS, Python 3.13,
per-server venvs:

- `cd responses_api_agents/stirrup_agent && python -m pytest tests -q`:
**224 passed, 2 skipped**. The skips need GNU `timeout` or a real
Apptainer image (`GDPVAL_CONTAINER_PATH`).
- `cd resources_servers/gdpval && python -m pytest tests -q` (gdpval
imports Stirrup): **156 passed**.
- With the rest of the series applied: Stirrup 319 passed / 2 skipped,
GDPVal 564 passed. The 12 prompt-content tests that moved to NVIDIA-NeMo#3686 fail
against main's current prompt, which is why they live there.
- `pre-commit run --all-files`: all hooks pass, no files modified.
- Schema probe against main's
`NeMoGymChatCompletionCreateParamsNonStreaming`:

| History | Stirrup's `to_openai_messages` |
`to_provider_openai_messages` |
  |---|---|---|
| Assistant tool call + tool result | rejected (44 errors) | validates |
| Assistant reasoning, no tool calls | rejected (14 errors) | validates
|
  | Text only | validates | validates |

  Adding `chat_template_kwargs` is still rejected until NVIDIA-NeMo#3682.
- Build script: I ran it with a stub `apptainer` that checks every
`%files` source resolves from the build directory.
- The new script passes for the default, a relative and an absolute
output path.
- Main's script fails: `gdpval_aa_v2_python_requirements.txt` is not
found from the repo root.
- Container: built and verified with `verify_gdpval_sandbox.py` in
NVIDIA-NeMo#3333. The image was not rebuilt for this PR, and only comment lines
under `containers/` have changed since.

| Arch | Interpreter | Pins | apt-closure entries missing | Verifier
failures |
  |---|---|---|---|---|
  | x86_64 | 3.13.5 | 419/419 | 0 | 0 |
  | aarch64 | 3.13.5 | 414/419 (the 5 listed exclusions) | 3 | 0 |
- Core unit tests: not run. This PR changes nothing under `nemo_gym/`,
`scripts/` or `tests/`.

## Rollouts

**Live GDPVal smoke (final code):** all seven PRs of the series applied
together on main `1c8261080`, run with the devel-only SLURM launcher on
a 4-task AA-v2 comparison smoke (one 4-GPU policy node, CPU judging):
rollout 4/4 tasks saved and finished (1h04m, 0 failure rows), Office
preparation 7/7 converted, comparison judging 4/4 tasks judged with 0
imputed and 0 excluded, 3 wins / 1 loss. Per-task outcomes match an
earlier gdpval-devel run on the same checkpoint, tasks and references.
No rate limiting occurred, so the judge retry budget was not exercised
live.

## Compatibility and benchmark impact

- **Sandbox image** (takes effect only after a rebuild):
  - Existing `.sif` images keep working.
- Build from `containers/` (`cd
responses_api_agents/stirrup_agent/containers && apptainer build
gdpval.sif gdpval.def`) or with the recipes script. A build started from
the repo root cannot find the `%files` inputs.
  - Python packages:
- The base moves from `python:3.13-bookworm` to `debian:trixie`, with
Python fixed at 3.13.5.
- Python packages become exact pins. Every Python package the old
definition installed is still in the pin set.
- apt: `unar` is dropped (it isn't in the AA v2 closure), and three
packages move to their trixie names.
- Deliberate differences from AA v2: we keep a compiler toolchain, we
add `espeak-ng`, and the per-command limit is 5 minutes (AA uses 10).
- **GDPVal results:** runs on a rebuilt image are not comparable with
runs on an image built from the old definition. Package versions, fonts
and LibreOffice all change. The prompt change in NVIDIA-NeMo#3686 moves scores
further.
- **Token budgeting (all Stirrup runs):**
- With `model_id` set and a template that renders,
`max_completion_tokens` can drop below 1024 near the context limit, and
a full context raises `ContextOverflowError` before the request is sent.
- Without `model_id`, or when tokenization fails, the estimate is larger
for every history, most for tool-heavy ones. Budgets shrink only near
the context limit, and the 1024 floor still applies.
- **Constructor validation:** `max_completion_tokens_cap`,
`min_completion_tokens` and `min_compaction_summary_words` below 1 now
raise `ValueError`.
- **Logging:** a WARNING is logged every time a call uses its whole
completion budget without a usable tool call, even when
`truncation_recovery` is off.
- **Compaction:** empty or think-only summaries now get up to 3 attempts
and then fail the rollout. Before, the run continued with an empty
summary.
- The override uses private Stirrup internals
(`_unwind_context_overflow`, `_active_tools`, `_logger`). They are
stable under the existing `stirrup>=0.1.12,<0.2` pin, but a Stirrup
minor release may need a follow-up.
- **Provider requests:**
- The non-standard message keys listed above are no longer sent, so
earlier-turn reasoning is not replayed.
- Unparsed tool-call blocks are removed from the provider copy of
assistant text.
- **Trajectories:** a repeated tool `call_id` gets a `#n` suffix from
its second occurrence onward.
- **`web_fetch`:**
- A page with no extractable text now returns `success=False` with an
error. Before, it returned an empty body.
  - A fragmentary extraction gets a note at the start.
- **Opt-in behaviour:** `truncation_recovery` is off by default. When
on, the recovery instruction is appended as a user message, so the
provider can see two user messages in a row.
- NVIDIA-NeMo#3686 turns it on for GDPVal and also sets `min_completion_tokens:
8192` and `min_compaction_summary_words: 50`. Other Stirrup users are
unaffected.
- Until NVIDIA-NeMo#3686 lands, those keys in agent YAML have no effect:
`StirrupAgentWrapperConfig` accepts extra keys and ignores them.

## Checklist

- [ ] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

Signed-off-by: Serge Panev <spanev@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:environment Individual environments, benchmarks, verifiers, and environment-specific resources servers bug Something isn't working complexity:medium Single-domain change with interacting parts or a moderate review surface needs-review PR is ready for code review and waiting on a reviewer sla:triage-overdue Review assignment is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants