feat(agents): RemoteAgent, thin proxy server for user-hosted remote agent services - #2163
Merged
Conversation
…agent services
Adds responses_api_agents/remote_agent/: a thin agent server that brokers
rollouts to an agent service running outside Gym's process tree. The remote
service implements one endpoint (POST {agent_base_url}/v1/responses),
receives only responses_create_params (verifier_metadata never leaves Gym),
runs its own loop with its own model and tools, and returns one finished
Responses API trajectory. The server owns seed_session, session cookies,
strict trajectory validation, and /verify, and returns the verify response
from /run.
- failures never raise: every failure (remote endpoint down, timeout,
malformed or interrupted reply, seed/verify errors, internal bugs)
becomes a reward-0 sentinel verify-response routed to the failures
sidecar and retried on resume; reused rollout/failures JSONL rows with
stale result keys are sanitized rather than crashing or leaking routing
- bounded outbound retries (3x, connect/disconnect only), per-request
ClientTimeout, whole-run wallclock applied after semaphore acquire,
redirects rejected, per-worker concurrency semaphore
- optional forward_session: resources-server URL + session cookie sent as
X-NeMo-Gym-* headers so the remote service can call Gym-hosted tools
(stateful environments); declared-tools guard refuses silent-zero
configurations; advertised_resources_url for off-host services
- aggregate_metrics proxied to the resources server with a wallclock bound
- 48 offline tests incl. an in-process stateful counter E2E (forwarded
session cookie, real verify) and a collector round-trip pinning sidecar
routing
Part of the external-agent-integration epic #1396; supersedes the
collector-side agent_url approach in #2006 (see PR description).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: adil-a <adil.asif2000@hotmail.com>
adil-a
marked this pull request as ready for review
July 28, 2026 15:15
- replace forward_session/assume_remote_tools with a single tools_mode: refuse|forward|remote enum, each choice documented - remove the speculative non-terminal-trajectory warning (and its test) - document the body/record duality on _run_once, the per-request URL forwarding rationale (random ports), and advertised_resources_url's bind-vs-advertise role directly in the code - README: off-host checklist (bind, route, verify from the remote machine, advertise) and the remote-side reachability self-check recommendation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Usage guide for the remote_agent server: the one-endpoint contract with request/response expectations, a minimal FastAPI quickstart, every config knob with defaults, the tools_mode choices with the off-host checklist for forwarded sessions, failure/resume semantics, and the gotchas list (redirects, missing usage, timeout retry semantics, skills, reused rollout files, stacked concurrency bounds). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Contributor
|
🌿 Preview your docs: https://nvidia-preview-remote-agent.docs.buildwithfern.com/nemo/gym Here are the markdown pages you've updated: |
ffrujeri
reviewed
Jul 28, 2026
Docs: quickstart usage example and contract table now show the full usage shape strict validation requires; placeholder/ref-name clarified; retry-cap and terminal-failure semantics stated; MCP forward-mode limitation and internet-exposure (no outbound auth, no HTTP_PROXY) notes added; dead README cross-reference removed. Code: the unroutable-advertise warning now also covers 0.0.0.0 (a bind address resolves to the remote machine's own loopback — confirmed live in an off-host container E2E where it produced silent zero rewards with no warning); remote-hop failures honor global_aiohttp_client_request_debug with full tracebacks/bodies, matching core request()/raise_for_status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
…model Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
…-reasons-to-return contract The quickstart service now uses the Claude Code CLI as the agent brain — render conversation, let Claude decide, translate to Responses format (the CLI envelope note added where the translation happens). Verified by execution: the doc's literal block driven by the real RemoteAgent against the real counter server with real haiku scored 5/5 reward 1.0, including batched-vs-incremental ask patterns and fenced-JSON model output. The contract docs now state explicitly that the service runs its own tools freely and returns only to ask for a Gym tool or to finish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
…ckstart Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
…the intro Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
Contributor
I think here we don't necessarily need to say that it is called like a model, because it is more like a composition of agents all compliant with the OpenAI Responses contract. And with the possibility of calling Gym's resources servers tools |
ffrujeri
reviewed
Jul 30, 2026
ffrujeri
reviewed
Jul 30, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: adil-a <adil.asif2000@hotmail.com>
ffrujeri
approved these changes
Jul 30, 2026
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the external-agent-integration epic #1396. Supersedes the collector-side
agent_urlapproach proposed in #2006 (closed unmerged in favor of this PR).What
An agent server,
responses_api_agents/remote_agent/, that drives an agent service running outside Gym's process tree (your own repo, your own infrastructure) through Gym's tool loop. The remote service implements one endpoint compliant with the OpenAI/v1/responsescontract, and the two servers compose as Responses-speaking agents:POST {agent_base_url}/v1/responses— receives the conversation so far (the row'sresponses_create_paramswith accumulated output and tool results appended toinput) and returns a Responses API object: unpairedfunction_callitems to ask Gym to execute environment tools, paired call+output items as records of its own internal tools (passed through untouched), or a final assistant message to finish.Gym owns everything else: it seeds the session and holds its cookies, executes the asked-for tools against the resources server, appends results and calls the service again, validates each reply against the Responses API schema (failing the rollout on mismatch), verifies, and returns the verify response from
/run. The resources server is never exposed to the service — tool execution, session cookies, andverifier_metadataall stay inside Gym. The service's own cookies are round-tripped per call so it can keep per-rollout state; the only network direction is Gym → service. To the collector it is a normal named agent — resume, aggregation, and profiling work unchanged.The loop is
simple_agent'sresponses()with two marked in-loop divergences — the model hop is a hardened POST toagent_base_url, and only unpaired function_calls are executed (a call the service already answered itself is its own record, not an ask) — plus the never-raise/runcontract that turns transport/validation errors into failure rows, and one deliberate cookie difference: the service's own cookies are round-tripped to it but stay out of the outgoing Set-Cookie (they are its private session, not Gym's).run()mirrorssimple_agent's seed → self-post/v1/responses→ verify.Why a server instead of collector-side
agent_url(#2006)/runimplementation plus the shared SimpleServer/ServerClient plumbing — instead of being reimplemented inside the shared collection loop./runresponsibilities stay in the agent-server layer; the collector stays agent-agnostic.Failure contract
Failures never raise out of
/run: remote endpoint down (3 total connection attempts,ClientOSError/ServerDisconnectedErroronly; timeouts and all other errors fail after a single attempt), per-call and whole-rollout timeouts, malformed/interrupted replies, seed/verify errors, and internal bugs all become reward-0 sentinel verify-responses (_ng_failure_class="remote_agent_error") that rollout collection routes to the failures sidecar and retries on resume — non-terminal failures only, up toNEMO_GYM_MAX_ROLLOUT_ATTEMPTS(default 3). An invalid Responses object from the service is terminal (a schema bug will not fix itself on retry); the terminal flag crosses the HTTP self-post boundary by exception name. Reused rollout/failures JSONL rows carrying stale result keys are sanitized rather than crashing or leaking routing flags. Tool-level errors are NOT rollout failures: an unknown tool name or malformed arguments come back to the service as that call'sfunction_call_output, matchingsimple_agent's semantics.Testing
/v1/responsesself-post is routed into the realresponses()with the exception middleware emulated): config validation, loop mechanics (multi-turn tool execution, paired-call pass-through, unknown-tool feedback, malformed-arguments feedback, max_steps, service-cookie round-trip, usage accumulation), every transport failure mode → sentinel, terminal classification across the route boundary, semaphore bounds incl. release-on-failure, run-wallclock-after-semaphore semantics, aggregate proxy + bound, route-level serialization (HTTP 200, never 500).example_session_state_mgmtcounter server in-process; the service returns unpaired tool asks, GYM executes them on the seeded session, reward 1.0 through the real verifier — and the test asserts the service was fed the counter value Gym read back.RolloutCollectionHelper.run_from_configdriving this agent — successes to the main JSONL, sentinel rows to the failures sidecar.agent_base_urlpointing at the container's bridge IP; the counter resources server stayed on loopback with a random port — unreachable from the container by construction. 5/5 rollouts reward 1.0, trajectories readingfunction_call → function_call_output → … → message, real token usage accumulated across loop turns (~8.3k mean/rollout). The only network path was Gym → container.Follow-ups
/run-exposing self-scoring services) as a config mode on this serverrequest()(then this server's transport collapses onto core), and two additive core helpers with existing adopters — a failure-row builder (stirrup/pinchbench hand-roll the same) and an aggregate-metrics proxy helper (eight agents hand-roll it)🤖 Generated with Claude Code