Resources Server APIs
Resources Server APIs
A Resources Server implements the Environment interface related to: its tools, state, and reward logic.
Agent Server context
The Agent is the behavior that decides what to do next, typically with different components like a Harness and a policy-model. The Agent Server is the HTTP transport for two internal methods:
run()is exposed as/runand implements part of the episode orchestration associated with the Resources Server seed session, delegation of the Agent loop to responses() and call to the verifier.responses()is exposed as/v1/responsesand implements Agent behavior. Depending on the Agent Server, this can be a complete agent harness or one policy-model call.
In simple_agent, run() delegates the Agent loop to responses() before requesting final verification. In gymnasium_agent, run() owns the model-and-environment loop and does not invoke responses() to the tool calling.
Resources Server Protocol Landscape
NeMo Gym ships two framework-standard base protocols for coordinating an episode between the Agent and the Environment. Agent behavior and rollout orchestration are implemented by responses() and run() behind the Agent Server’s HTTP endpoints; the Environment is implemented by the Resources Server.
Each protocol has a matching built-in Agent Server: simple_agent for the /seed_session and /verify interface, and gymnasium_agent for the /reset and /step interface.
Pick one base protocol and build the Resources Server and Agent Server to match. GymnasiumServer exposes /reset and /step and does not expose /verify. SimpleResourcesServer defines /seed_session and /verify but can be extended with additional environment-specific routes.
The two base protocols do not describe every concrete Agent–Environment interface in the repository. Some environments extend the seed/verify protocol with custom transition endpoints. Other environments are embedded inside the Agent and do not use a Resources Server during /run.
Seed/Verify Protocol Extensions
Several environments keep the /seed_session and /verify lifecycle while adding bespoke transition APIs. These extensions require a matching Agent Server; sharing an endpoint name such as /step does not make their request and response contracts interchangeable.
Some Agent Servers also use variants of the seed/verify sequence—for example, skipping a no-op /seed_session, treating it as optional, or calling /verify without tool execution. These are Agent Server behaviors built around the seed/verify API rather than new Resources Server base classes.
Agent-Embedded Environments
Some Agent Servers embed the environment or an external benchmark harness in their own process. Examples include OSWorld, Tau2, SWE wrappers, Harbor, PinchBench, and Verifiers. Their /run implementation computes reward without calling a Resources Server, so they do not implement a Resources Server episode protocol.
Action and Tool Transports
The episode protocol defines initialization, transitions, and scoring. The action transport separately defines how the Agent affects the Environment during the episode.
Seed/Verify Protocol: seed_session() and verify()
The seed/verify protocol brackets the Agent harness with environment initialization and final verification:
For the built-in simple_agent, run() performs the lifecycle orchestration and responses() implements the Agent loop. The Agent Server exposes those methods as /run and /v1/responses, respectively.
Lifecycle API
SimpleResourcesServer exposes:
bodyinseed_session()contains fields used to initialize an episode. Define aBaseSeedSessionRequestsubclass for fields such as an initial board, task ID, or sandbox image. The default implementation is a no-op.body.responses_create_paramsinverify()is the original model request.body.responseinverify()is the completedNeMoGymResponse, including the trajectory assembled by the Agent Server.- Custom task metadata requires a
BaseVerifyRequestsubclass. Declare fields such asanswerorexpected_state; undeclared fields are not retained by the base Pydantic model. request: Request, when added to either method signature, provides access to the per-rollout session ID.
Initial Observation for a Single Agent Turn
A Resources Server used by environment_servers/single_agent_turn can return an
optional observation string in its ResourcesSeedSessionResponse when the task
prompt becomes available during seeding or an environment reset:
The single_agent_turn Environment Server appends a nonempty observation
verbatim as one user message after a copy of the authored
responses_create_params.input. A nonempty string input becomes the first user
message; an empty string contributes no message. List inputs keep their items
and order. Model settings and tools stay as authored, and /verify receives the
original authored request. Omitting observation, returning null, or returning
an empty string leaves the agent request unchanged.
When a server does not set observation, serialization omits the field to preserve
existing seed responses. An explicitly supplied null or empty string remains in the response.
The observation is retained with the episode’s session handles, so restoring at
agent invocation still supplies it once without seeding again. This consumption
behavior belongs to single_agent_turn; other Environment Servers and Agent
Servers must define how they use a seed observation.
When Resources supplies the prompt, leave the authored user input empty for an
agent that accepts only one user message. For example, use
responses_create_params: {input: ""}. Adding an authored user prompt as well
would send two user messages.
Direct HTTP Tool-Calling Protocol
The seed/verify episode protocol does not itself define how the Agent acts on the environment. simple_agent supplies a direct HTTP function-tool protocol inside responses():
- The input row declares function schemas in
responses_create_params.tools. - The Agent Server sends those schemas to the Model Server.
- The model returns a structured
function_callwithname,arguments, andcall_id. simple_agentparsesargumentsand sendsPOST /{name}to the Resources Server.- The Resources Server’s response becomes a
function_call_outputassociated with the samecall_id. responses()sends the updated trajectory back to the model and repeats until the model returns a final response or the step limit is reached.
The tool name in the input schema must match the Resources Server route:
For a model response such as:
simple_agent calls POST /square with {"value": 4}. The Agent Server performs this dispatch; the Resources Server only implements the endpoint.
Resources Server tools can also be exposed over MCP. MCP changes the tool transport, not the episode protocol: the rollout can still use /seed_session and /verify. See MCP Resources Server.
Minimal Seed/Verify Environment
The complete server below combines the tool route with final trajectory verification:
Choose the seed/verify protocol when:
- The Agent harness should own model calls, tool dispatch, retries, or other orchestration.
- Resources Server routes should be exposed directly as model tools.
- One final reward should be computed from the completed trajectory or final environment state.
- You are pairing the environment with
simple_agentor a custom Agent Server that implements/seed_session, an Agent harness, and/verify.
Gymnasium Protocol: reset() and step()
The Gymnasium protocol models the environment as a transition function. The Agent Server sends every model response to the Resources Server:
gymnasium_agent never calls /verify. GymnasiumServer does not register a /verify endpoint; /step provides both transitions and rewards.
Transition API
Subclass GymnasiumServer and implement step(). Override reset() when the environment needs initialization or an initial observation.
actionis the complete structured model response for the current step. It can contain assistant messages, reasoning items, or function calls.metadatacontains extra top-level fields from the input row, such asanswer,category, or task settings. It does not includeresponses_create_params.session_ididentifies the rollout and keys per-episode state. Stateless environments can ignore it.
reset() returns (observation, info). A non-None observation is appended as a user message before the first model call.
step() returns (observation, reward, terminated, truncated, info):
observation: the next user message, orNonewhen no message is needed.reward: reward for this step.gymnasium_agentsums rewards across the episode.terminated:Truewhen the episode reaches a natural terminal state.truncated:Truewhen a limit or timeout cuts the episode short.info: diagnostic metadata and optionaltool_outputsfor the Agent Server. Three keys are reserved for episodes the environment itself could not finish (an unavailable user simulator or judge, a lost session):mask_sample(Trueexcludes the reward from quality metrics and training data),failure_kind(a registered name fromnemo_gym.core.failure_kinds, or<server>:<kind>) andfailure_reason(free text)./steplifts them out ofinfointo the step response, andgymnasium_agentreports the values from the step that ends the episode on the rollout; a mask set on an earlier step is dropped.
Tool Calls as Gymnasium Actions
gymnasium_agent does not dispatch a function call to POST /{name}. Instead:
- The model returns a
NeMoGymResponse. - The Agent Server sends that response to
/stepasaction. step()interprets text or structured function calls and executes the corresponding environment operation.- For function calls,
step()returns results ininfo["tool_outputs"]. - The Agent Server converts those results into
function_call_outputitems for the next model call.
Tool schemas still come from responses_create_params.tools, but they describe the actions the model can emit. The Resources Server does not need matching HTTP tool routes because step() performs the dispatch.
Choose the Gymnasium protocol when:
- The Resources Server must process every model response before the episode continues.
- The environment decides the next observation and stopping condition.
- Rewards are naturally assigned per transition.
- The model response itself is the environment action.
- You are pairing the environment with
gymnasium_agentor a custom Agent Server that implements the/resetand/steploop.
State Management
Both episode protocols use the Resources Server’s session middleware. It assigns a unique session_id cookie, and the Agent Server preserves that cookie across Resources Server calls in the same rollout.
Store rollout-specific state under the session ID so concurrent episodes remain isolated. State can range from:
- Stateless: grade an answer directly from the response and task metadata.
- Lightweight: store a counter, game board, or conversation state in memory.
- Heavyweight: associate each session with a browser, database, or Docker container.
Seed/Verify State
SimpleResourcesServer does not define a state container. Add one and read the session ID from the FastAPI request:
Tool endpoints can read the same request.session[SESSION_ID_KEY], so /seed_session, tool calls, and /verify operate on the same environment instance.
Gymnasium State
GymnasiumServer provides self.session_state and passes the session ID to reset() and step():
Do not put rollout-specific mutable data in an unkeyed server attribute.
Session Cleanup
- Seed/verify protocol: remove custom state in
verify(), preferably in afinallyblock.SimpleResourcesServerdoes not automatically clear custom state. - Gymnasium protocol: when
step()returnsterminated=Trueortruncated=True,GymnasiumServercallsclose_session()and removes the entry fromself.session_state. - Heavyweight state: override
close_session(), release the external resource, and callawait super().close_session(session_id). - Episode sessions: an environment server seeds with a
ResourcesSeedSessionRequestand closes through/close_session.SimpleResourcesServeranswers both with no-op defaults, so a server without per-rollout state needs no changes. A server with state overridesseed_session()andclose_resources_session(), which is named differently from the Gymnasiumclose_session()helper. Release state byresources_session_id, not by the cookie: after a lost seed reply the close arrives without one. Refuse a seed that no retry can fix withResourcesSeedRejectedError(422) for invalid input orResourcesSessionConflictError(409) for an ID bound to another episode, and answer a verify for a session the server no longer holds with the masked sample fromlost_session_verification().ResourcesSessionLocksmakes each session’s seed and close run one at a time, andResourcesSessionFencesfences each closed session so a seed after its close is refused until the fence expires; check these rules from the server’s tests withcheck_resources_session_contract(app, seed, keeps_state=True, holds_state=..., verify_body=...)fromnemo_gym.server_runtime.testing.session_conformance.
Ensure a Gymnasium environment returns a terminal or truncated result before the Agent Server’s max_steps is exhausted. Also use timeouts or stale-session reclamation so client failures cannot leak external resources indefinitely.
State With Several Workers
With num_workers above 1, each Uvicorn worker is a separate process with its own memory, so a session’s state lives only in its current owner. Gym routes every request by one rule, so the in-memory patterns above keep working: it finds the request’s session ID, looks up which worker owns that session, and forwards the request to that worker. If this worker owns it, or nobody does, the request is handled where it arrived.
The session ID comes from the request itself:
An episode seed’s resources_session_id becomes its cookie’s SESSION_ID_KEY, so the seed, its close, and every request in between name the session by the same ID. If a request’s cookie and body name different sessions, it gets HTTP 409. A seed, or another route in checkpoint_session_starts, takes ownership of its session even if its handler never reads it. Any other request whose handler never reads or writes the session, such as a health check or /aggregate_metrics, takes no ownership and gets no session cookie. If a cookie names a session whose ownership has ended, the worker handling the request takes ownership of the session if the handler uses it.
The worker that creates a session owns it, including a seed that failed and the close of an ID nobody has seeded, which keeps a delayed seed from opening it on another worker. Ownership lasts while the session is open, from a successful /seed_session (or another route in checkpoint_session_starts) until its /close_session, its /verify, or checkpoint_session_ended; a /verify that says a close follows, as single_agent_turn’s does, leaves it open for that close. After that, and for a session that never opened, ownership expires session_close_retry_window_seconds (default 300) after the last request for the session, so a retried close or a delayed seed still reaches the worker that saw it. Fence closed sessions with ResourcesSessionFences from nemo_gym.server_runtime.base_resources_server, constructed with the same setting: a fence then expires no later than the ownership, so every worker gives a late seed the same answer.
If the worker that holds a session is no longer running, for example because it crashed and Uvicorn replaced it, the session’s requests get HTTP 410 instead of running against empty state. State keyed only by some other ID in the request body is not routed; key state by the session ID as shown above. Agent Server sessions follow the same rule, with agent_session_id as the session ID, in the cookie and in the seed and close bodies.
Uvicorn starts several workers by importing the server’s entrypoint module in each of them and serving its module-level app. A server that supports several workers ends its entrypoint with:
A server whose state routing cannot reach, such as a registry keyed by another ID in the request body, overrides multiple_workers_unsupported_reason() instead and returns why it needs one worker. run_webserver then refuses num_workers above 1 with that reason before Uvicorn starts, and a dry run reports it too.
A server that keeps no per-session state, or shares it outside the process, can opt out of routing with routes_sessions_to_owner = False on its class.
Concurrency limits such as judge semaphores apply per worker, so the server as a whole allows num_workers times the configured limit.
YAML Configuration
The Resources Server definition has the same YAML shape for both episode protocols. The paired Agent Server determines which protocol is used.
Seed/verify
Gymnasium
The Agent Server’s resources_server.name must match the top-level Resources Server instance name.
For an Environment Server, add taskset: <name> to the dataset
declaration and map that name in environment_server_routes. The normal
gym dataset collate and gym eval run --benchmark ... paths then convert prepared
flat rows to task_id and task_input; no separate conversion command is needed.
Collation leaves task-owned fields flat inside task_input; each Environment Server
validates its own input when dispatched. The built-in single_agent_turn server
also accepts the existing {responses_create_params, task_data} shape. Its
task_data key is reserved as that container; additional flat fields are merged
into it, with conflicting values rejected. Dataset metrics count custom rows
without requiring Responses API fields; response-specific metrics are collected
only when those fields validate.
The source JSONL stays unchanged and can still be shared with flat-row recipes.
Omit taskset to keep the current flat-row format. This option accepts flat input
only; already materialized input should be passed directly with --input.
For example, add these keys to a config whose swebench_pro_hermes Environment
Server binds the swebench_pro_hermes_resources_server instance:
environment_routing_mode does not need to change: converted rows route by their
taskset in every mode. They reject run-level +responses_create_params.*
overrides, num_repeats_add_seed, and run-level skills. Set response parameters
in the source row before collation; benchmark prompt configs still apply to input messages.
The task’s qualified ID is <taskset>/<id>. The ID is taken from task_id,
problem_id, or instance_id, in that order. Without an explicit ID, an existing
_ng_task_index takes precedence; otherwise collation uses a zero-based source-row
index across all datasets of the same type, before repeats. Adding or reordering
other datasets in that collation can shift these positional IDs.
These tasksets route to an Environment Server, not to an agent, so their datasets
do not set agent. Benchmark discovery, gym eval prepare, and collation check the
route instead: every agent the Environment Server references must exist, a dataset
declared on an agent must be one of them, and a dataset declared on a resources
server must be the one the Environment Server binds. The agent does not need its
own resources_server binding. An Environment Server can front several agents
through fields other than agent_server, such as a user_agent and an
assistant_agent. When it does and names no agent_server, gym eval list shows
the Environment Server instead of an agent. Collation preserves existing collector
task indices, rollout IDs, and retry attempts outside task_input.
Next Steps
- Build a seed/verify environment in Single-Step Environment.
- Add per-rollout state in Stateful Environment.
- Expose Resources Server tools through MCP Resources Server.
- Review common reward designs in Verification Patterns.
- See a multi-step Gymnasium implementation in
resources_servers/blackjack.