Skip to content

feat(agents): add pool sandboxed agent for SWE-bench Verified - #3614

Open
martin-nv wants to merge 15 commits into
codex/benchmark-fast-runsfrom
mmuller/pool-harness
Open

martin-nv wants to merge 15 commits into
codex/benchmark-fast-runsfrom
mmuller/pool-harness

Conversation

@martin-nv

@martin-nv martin-nv commented Sep 22, 2026 •

Copy link
Copy Markdown

What does this PR do?

Adds responses_api_agents/pool_sandboxed_agent, a harness that runs Poolside's pool CLI coding agent headlessly (pool exec) inside the resources-server-owned task sandbox, following the OpenCode sandboxed agent pattern: the SWE-bench resources server creates the task sandbox, the agent installs pool into it, points it at Gym's per-rollout model URL, runs the task, and leaves the edits in the workdir for the resources server to diff and grade.

  • pool is configured through a generated agent config file (--agent-config-file) mirroring its CLI defaults, with an OpenAI-compatible provider aimed at the Gym proxy, use_streaming: false (token ids and logprobs are only returned on non-streaming responses), and compaction thresholds derived from pool_max_context_window. pool_agent_config deep-merges overrides.
  • HOME and the XDG dirs are redirected under /tmp so pool's config, state and trajectories never land in the repo workdir that gets diffed. Pool's own container sandboxing is disabled and tool approvals are auto-allowed for the non-interactive run.
  • The pool exec -o json event stream is converted into Responses output items (messages with <think> reasoning, function_call / function_call_output pairs). Usage is not part of the stream, so usage is empty for now.
  • Benchmark wiring for SWE-bench Verified, Multilingual, Pro, and Terminal Bench 2.1. Includes the Lightning SWE Verified profile and Laguna XS profiles for all four benchmarks. XS SWE Pro defaults to two nodes with concurrency 128 * NUM_NODES (256 by default); XS Multilingual and Terminal Bench use concurrency 128.
  • nemo_gym/openai_utils.py: pool attaches Anthropic-style cache_control: {type: ephemeral} hints to the last content part and tool spec of every chat request, which the strict params models rejected with 422. The hint is now typed (NotRequired[CacheControlEphemeralParam]) on the chat text content part and tool param.

Stacked on #3602 (codex/benchmark-fast-runs); review this diff against that branch.

Validation

  • New XS Pool profiles: Gym offline config resolution, agent/resource references, Bash syntax, and SWE Pro concurrency checks passed. Full pre-commit and secret scans passed. No additional live rollout was launched for Multilingual, Pro, or Terminal Bench; those combinations still need end-to-end evaluation.

  • pytest responses_api_agents/pool_sandboxed_agent/tests tests/unit_tests/test_openai_utils.py and pre-commit run on the changed files.

  • Two one-task smoke runs of SWE-bench Verified (astropy__astropy-12907) on OpenSandbox via gym eval run ... --limit 1, with the policy model served through an OpenAI-compatible endpoint (nemotron-3-ultra). Both resolved with reward 1.0: pool installed from the public installer inside the sandbox, reached the Gym proxy, produced a 13-line patch, and the resources server graded it with 15/15 tests passing. A third one-task run through the Slurm launcher on Laguna XS 2.1 (vLLM, DFlash) also resolved; a full 1500-rollout SWE-bench Verified run on that model is in progress.

Rollouts

Smoke rollout transcript (streaming run) had 150 output items; the non-streaming run 132. Available on request; not attached since they contain the full task prompt and model reasoning.

Compatibility and benchmark impact

No change to existing harnesses. Typing cache_control widens the accepted chat schema; the field is forwarded to the backend, which vLLM accepts (extra="allow"). No anti-cheat tool restrictions are configured for pool yet, unlike the OpenCode deny-list; SWE-bench Verified repos are already clean, and pool exposes no equivalent permission config today.

Checklist

  • I have read the contributing guidelines.
  • The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs.
  • Tests added or updated and pass locally.
  • Documentation added or updated (responses_api_agents/pool_sandboxed_agent/README.md).
  • Pre-commit checks pass locally (pre-commit run on the changed files).
  • New source files include the required Apache-2.0 SPDX header.
  • All commits have DCO sign-off (git commit -s).

🤖 Generated with Claude Code

@copy-pr-bot

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

Comment thread nemo_gym/base_responses_api_model.py Outdated
(e.g. ``"false"`` or ``1``) stays on the strict non-streaming path, which rejects the
malformed ``stream`` with the same 422 as before.
"""
body = strip_prompt_cache_hints(body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It seems that pool is sending non standard cache_control. Instead of stripping I would rather add it to the type, wdyt?

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.

Agreed. I guess it would crash on a vllm endpoint that doesn't support the extra field, but maybe better like this

@martin-nv martin-nv Sep 22, 2026 •

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 in f801bc9: dropped the strip and typed cache_control as NotRequired[CacheControlEphemeralParam] on NeMoGymChatCompletionContentPartTextParam and NeMoGymChatCompletionToolParam (the tool-message part reuses the text part). Note the field now flows through to the backend via model_dump(exclude_unset=True); vLLM's request models are extra="allow" so that's fine there, but the OpenAI backend would reject it inside content parts.

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 back and forth on this with Martin. We'd rather not widen the shared chat schema in openai_utils.py for an Anthropic-only hint no Gym backend acts on, so the latest commit drops it at the chat dispatch instead, now targeted to messages[].content[].cache_control and tools[].cache_control only (a cache_control key inside a tool's JSON schema is untouched). Happy to revisit if you feel strongly.

@christian-muertz
christian-muertz force-pushed the mmuller/pool-harness branch 2 times, most recently from b0bea0a to cf8af0f Compare September 22, 2026 14:38
@yaoyu-33 yaoyu-33 added area:agent Agent harnesses and Responses API agent behavior complexity:high Cross-component or shared-contract change with a large review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer labels Sep 22, 2026
martin-nv and others added 15 commits September 23, 2026 03:51
Adds a harness that runs Poolside's pool CLI headlessly (pool exec) inside
the resources-server-owned task sandbox, mirroring the OpenCode sandboxed
agent. pool runs in standalone mode driven by environment variables
(POOLSIDE_STANDALONE_BASE_URL / POOLSIDE_API_KEY / POOLSIDE_STANDALONE_MODEL)
pointed at Gym's per-rollout model proxy, with its config, state and
trajectories redirected under /tmp so nothing lands in the repo workdir the
resources server diffs. The NLJSON event stream is converted into Responses
output items.

Includes the SWE-bench Verified benchmark wiring and a Lightning launcher
profile.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
Release assets live under downloads.poolside.ai/pool/v<version>/, so the
installer needs the tag form (v1.0.16), not the bare version.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…equests

Clients like Poolside's pool CLI attach Anthropic-style
cache_control: {type: ephemeral} to the last content part and tool spec of
every /v1/chat/completions request. The strict params models forbid the
field and Gym backends cannot act on it, so the shared chat dispatch now
strips it from messages and tools before validation.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…fig file

pool exec --agent-config-file does not merge with pool's defaults, so the
agent generates the full config, mirroring the CLI defaults, with an
OpenAI-compatible provider pointed at Gym's per-rollout URL and
use_streaming disabled: token ids and logprobs only come back on
non-streaming responses. pool_agent_config deep-merges overrides.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…l harness

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…nCode wiring

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…ing path, match dispatch

Per review, accept pool's Anthropic-style cache_control hint by typing it on
the chat text content part and tool param instead of stripping it in the
dispatch. The harness now has one configuration path: the generated
non-streaming agent config, with compaction thresholds derived from
pool_max_context_window. Event parsing and sandbox outcome selection use
match statements; comments and docstrings trimmed.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
Signed-off-by: Christian Muertz <cmrtz@nvidia.com>
…ng it

pool emits reasoning as its own event before the tool call it precedes, so
attaching it to the next assistant message shifted it by a step. Each
reasoning event now becomes a think-wrapped message where it occurs, like
the OpenCode sandboxed agent's reasoning parts.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
(cherry picked from commit 3a317dcb569bc8d46d52aa59fc992226a717067d)
…nk-tagged messages

Signed-off-by: Martin Muller <mmuller@nvidia.com>
…results in order

Signed-off-by: Martin Muller <mmuller@nvidia.com>
Signed-off-by: Martin Muller <mmuller@nvidia.com>
…and tool specs

Revert the openai_utils typing in favor of a targeted sanitizer next to the
existing streaming one: only messages[].content[].cache_control and
tools[].cache_control are dropped, so a legitimately named key inside a
tool's JSON schema is left alone.

Signed-off-by: Martin Muller <mmuller@nvidia.com>
Signed-off-by: Christian Muertz <cmrtz@nvidia.com>
Signed-off-by: Christian Muertz <cmrtz@nvidia.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Agent harnesses and Responses API agent behavior complexity:high Cross-component or shared-contract change with a large review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants