Skip to content

feat(eval): export Gym trajectories as ATIF - #2767

Open
afourniernv wants to merge 17 commits into
NVIDIA-NeMo:mainfrom
afourniernv:feat/atif-export
Open

afourniernv wants to merge 17 commits into
NVIDIA-NeMo:mainfrom
afourniernv:feat/atif-export

Conversation

@afourniernv

@afourniernv afourniernv commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a strict offline exporter from completed Gym ng_trajectory v1.0 rollouts to ATIF v1.7. It writes one ATIF file per rollout plus a SHA-256-pinned manifest that Gym's ATIF reverification path can consume.

Together, #2737 and #2767 establish a file-based interoperability path between NeMo Relay and NeMo Gym. Relay-exported ATIF trajectories can be evaluated in Gym, while Gym-native trajectories can be exported to the same ATIF format for downstream consumers. This does not couple the Relay and Gym runtimes or add Relay as a Gym dependency.

This is PR 2 of a two-PR sequence. #2737 owns ATIF ingress and the shared ATIF schema/JSON boundary; this PR owns Gym-to-ATIF egress. Related to #2468.

Before

Gym could store normalized rollout trajectories but could not export them in a portable trajectory format.

After

gym eval export \
  --format atif \
  --rollouts results/rollouts.jsonl \
  --output-dir results/atif \
  --session-id eval-2026-08-25 \
  --agent-version 1.2.3

The exporter maps Gym's normalized trajectory, not provider-native payloads. Raw provider request and response copies are retained opaquely in ATIF extension metadata; they are never parsed to fill gaps in the normalized record.

Initial contract

Supported:

  • one completed root invocation and one initial system/user phase;
  • exactly one captured model call per agent turn;
  • text, reasoning, generic function calls, and correlated text results;
  • normalized turn/model/token/timing/status evidence; and
  • task/rollout identity plus a hash-pinned manifest.

Rejected rather than guessed or dropped:

  • evidence gaps, subagents, later user/system turns, and multimodal content;
  • incomplete, failed, or ambiguously correlated model/tool calls;
  • unsupported Responses item families or namespaced calls;
  • training token IDs, log probabilities, and routed-expert metadata; and
  • non-standard JSON.

The exporter writes each known per-step usage field without inventing missing values. reasoning_tokens and total_tokens use the same step-level ATIF extension shape as Relay. A final prompt-, completion-, or cached-token aggregate is emitted only when every captured model call reports that field; an unavailable value is never counted as zero.

An export with partial usage can therefore be valid ATIF for another consumer while remaining ineligible for Gym's strict reverify path. Gym reverify requires complete per-step prompt/completion counts whenever supported usage exists because one verifier request cannot represent partial standard usage. This asymmetry is deliberate.

The exported extra.nemo_gym.conversion.status=complete means every field present in the supported normalized ng_trajectory profile was represented without guessing. It does not certify opaque provider request or response copies retained as provenance.

Out of scope

No ATOF parsing, Relay/NAT dependency, live instrumentation, provider-payload reconstruction, or best-effort export of unsupported structural or content fields is included. Preserving known step-level usage while omitting an unprovable aggregate is exact representation, not best-effort conversion.

Review order

This is the egress follow-on to #2737 and can be read while that PR is open. After #2737 lands, main will be merged into this branch again and the shared ATIF boundary will be reconciled. atif_v1_7.py should then leave this diff after preserving #2737's final_metrics.total_steps validation. atif_json.py will retain the small strict JSON equality helper used by the exporter, so reviewers should inspect that egress-specific delta rather than assume every shared-file change disappears.

Review area Files What to verify
Export contract nemo_gym/atif_export.py Exact normalized-trajectory mapping, rejection boundaries, batch atomicity, and manifest hashes
Shared boundary delta nemo_gym/atif_json.py Strict JSON equality used by export validation
Wiring nemo_gym/cli/eval.py, nemo_gym/cli/main.py, docs One-command offline export and documented scope
Evidence tests/unit_tests/test_atif_export.py, CLI tests Contract, failure, hash, and round-trip coverage

Suggested pass: review the exporter first, then its small shared-boundary delta and wiring. The test file is intentionally large because unsupported shapes fail closed.

Validation

  • Focused exporter, rollout-reverification, and CLI suites: 499 passed, including 179 exporter contract cases.
  • Changed-file pre-commit and git diff --check: passed.
  • The exact cross-PR test passed from Gym ng_trajectory through this exporter and feat(eval): reverify Relay ATIF trajectories #2737's importer.
  • Manifest SHA verification succeeded, tampering was rejected, parallel tool results were correlated by call ID rather than arrival order, usage survived, and native versus round-tripped MCQA verification both returned reward 1.0.

The broader local unit run reported the same five non-ATIF failures listed on #2737. The affected exporter, importer, rollout, and CLI suites pass.

The earlier Codex → Relay → ATIF → Gym live qualification remains evidence for #2737; this offline exporter PR does not claim another provider run.

Checklist

  • I have read the contributing guidelines.
  • Tests were added and pass locally.
  • Commits have DCO sign-off.

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 25, 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.

@nemo-automation-bot nemo-automation-bot Bot added the community-request Issue reported or requested by someone from the community label Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

Copy link
Copy Markdown
Contributor Author

Review cleanup and the completed hardening commits are now pushed through ce3a7d2. This PR remains draft.

Suggested review order after #2737:

  1. atif_provider_reconciliation.py: provider identity, termination, usage, and output fidelity.
  2. atif_export.py: Gym trajectory to ATIF conversion and atomic output.
  3. CLI registration and docs.
  4. Provider tests, now colocated and parameterized separately from exporter orchestration tests.

No supported behavior was removed. The shared ATIF schema and strict JSON helpers now match #2737 exactly. The PR is 193 lines smaller overall.

Validation on the exact pushed tree: 2,875 unit tests passed, 2 skipped, 336 deselected, 15 subtests passed; 666 focused tests passed; all pre-commit hooks passed; and the real Gym trajectory to ATIF to Gym verifier-payload round trip passed.

@nemo-automation-bot nemo-automation-bot Bot removed the community-request Issue reported or requested by someone from the community label Aug 27, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>

# Conflicts:
#	fern/versions/latest/pages/reference/cli-commands.mdx
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

Copy link
Copy Markdown
Contributor Author

The latest egress hardening is pushed in 16bd3a4, and the PR description now records the current usage contract and round-trip evidence.

  • Focused exporter, rollout-reverification, and CLI suites: 499 passed, including 179 exporter contract cases.
  • Changed-file pre-commit and git diff --check: passed.
  • Exact-head DCO, lint, docs collection, copyright, and secret-scanning checks are green.

This PR remains draft until #2737 lands. After that, main should be merged into this branch so the shared ATIF files leave the review diff.

@afourniernv
afourniernv marked this pull request as ready for review August 27, 2026 17:05
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@github-actions github-actions Bot added the sla:triage-overdue Review assignment is over the one-business-day SLA label Aug 28, 2026
@svcnvidia-nemo-ci svcnvidia-nemo-ci added the waiting-on-maintainers Waiting on maintainers to respond label Aug 29, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

Copy link
Copy Markdown
Contributor Author

Review-order note: this PR currently duplicates atif_v1_7.py and atif_json.py from #2737, so the shared boundary makes the diff harder to review while #2737 is still open. After #2737 lands, I will merge current main into this branch and reconcile those shared files so this PR's remaining diff is focused on Gym-to-ATIF export, CLI wiring, documentation, and its tests.

@svcnvidia-nemo-ci svcnvidia-nemo-ci added waiting-on-maintainers Waiting on maintainers to respond and removed waiting-on-maintainers Waiting on maintainers to respond labels Sep 1, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Comment thread nemo_gym/atif_v1_7.py
Comment thread nemo_gym/atif_export.py Outdated
Comment thread nemo_gym/atif_export.py Outdated
@github-actions github-actions Bot removed the sla:triage-overdue Review assignment is over the one-business-day SLA label Sep 14, 2026
Comment thread nemo_gym/atif_export.py
)


def test_gym_rollout_to_atif_preserves_provenance_turns_metrics_and_tool_identity() -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think there's any test here or in #2737 that tests the combination of both PRs (eg feeds an exporter-produced file through the reverify projection logic). It's hard to test while the 2 PRs are separate branches, but I think this should be added once the first one is merged in before merging the other one

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I am keeping this as a merge-order gate rather than copying the ingress implementation into this sibling branch. After #2737 lands, I will merge main into #2767, retain the canonical shared modules, and add one exporter-output to manifest-load to reverify-projection test before #2767 merges.

@svcnvidia-nemo-ci svcnvidia-nemo-ci added waiting-on-customer Waiting on the original author to respond and removed waiting-on-maintainers Waiting on maintainers to respond labels Sep 14, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@svcnvidia-nemo-ci svcnvidia-nemo-ci removed the waiting-on-customer Waiting on the original author to respond label Sep 16, 2026
svcnemo-autobot pushed a commit to svcnemo-autobot/Gym that referenced this pull request Sep 18, 2026
## What does this PR do?

Adds a bounded ATIF v1.7 input path to `gym eval reverify`, allowing
completed Relay trajectories to be scored by Gym's existing stateless
verifier flow without rerunning the agent or policy model.

Together, NVIDIA-NeMo#2737 and NVIDIA-NeMo#2767 establish a file-based interoperability path
between NeMo Relay and NeMo Gym. Relay-exported ATIF trajectories can be
evaluated in Gym, while Gym-native trajectories can be exported to the
same ATIF format for downstream consumers. This does not couple the
Relay and Gym runtimes or add Relay as a Gym dependency.

Relates to NVIDIA-NeMo#2468.

## Before

`gym eval reverify` only accepted native Gym rollout JSONL.

ATIF contains the external agent trajectory, but not the Gym task,
expected answer, verifier configuration, or resources-server routing
needed to calculate a reward.

## After

```bash
gym eval reverify \
  --input-format atif \
  --config my_resources_server.yaml \
  --inputs materialized-inputs.jsonl \
  --atif-manifest relay-trajectories.jsonl \
  --output rewards.jsonl
```

The inputs have separate responsibilities:

- Materialized Gym inputs provide the task, verifier metadata, request
context, and routing.
- ATIF provides the completed external-agent output.
- The manifest explicitly joins each trajectory to `(_ng_task_index,
_ng_rollout_index)` and may pin its SHA-256.

Gym projects the supported ATIF output into its existing
Responses-shaped verifier payload, calls the normal stateless `/verify`
path, and computes aggregate metrics.

## Integration contract

Gym consumes the canonical ATIF fields needed to build that bounded
scoring payload. It does not reconstruct provider-native payloads stored
in optional `extra` metadata; the ATIF producer owns that conversion.

`projection_status=complete` means Gym constructed the supported
verifier payload without guessing or dropping a field the adapter maps.
It does not claim full-document ATIF fidelity, certify the producer's
provider-to-ATIF conversion, or interpret arbitrary producer metadata.
Persisted source and trajectory hashes retain that provenance.

Supported in this initial path:

- exact ATIF v1.7 schema and text-only trajectories;
- canonical agent text and `reasoning_content`;
- generic function calls with exactly one correlated result per call;
- standard token usage and supported Relay usage details;
- explicit one-to-one trajectory/task joins with optional source-hash
verification; and
- persisted trajectory ID, session ID, schema version, source hash, and
projection status.

Standard usage is strict. If any model step reports supported usage,
every model-backed step must provide `prompt_tokens` and
`completion_tokens`, and `final_metrics` must provide matching totals.
Optional cached, reasoning, and reported-total details are projected
only when their aggregate can be proven. Partially reported optional
details remain unknown rather than being counted as zero. Equivalent
Relay reasoning-token aliases are normalized, conflicting aliases are
rejected, and cost metadata remains provenance rather than verifier
input.

The path fails closed on shapes that cannot be mapped without changing
what the verifier scores, including:

- multimodal, continued, copied-context, or subagent trajectories;
- later user or system input after agent output;
- aggregated LLM calls or ambiguous message/tool ordering;
- incomplete, duplicated, or uncorrelated tool calls and results; and
- training token IDs or log probabilities.

Optional producer-specific status or provider payloads in `extra` do not
change the canonical projection. A trajectory may end after a tool call
when every call has a correlated observation result.

Tool-bearing ATIF is rejected when routed to a resources server with
`expose_tools_over_mcp: true`. ATIF proves generic call/result
correlation, but does not carry Gym's canonical MCP `(server_name,
tool_name)` identity.

ATIF mode requires the verifiers selected by the batch to be stateless
and does not support `--force`, `--resume`, `--judge-failed-only`, or
`--append`.

Native Gym reverification remains the default. This adds no Relay, NeMo
Agent Toolkit, or Harbor runtime dependency.

## Suggested review order

Most of the added lines are qualification tests and fixtures. The
maintainer-facing behavior is concentrated in these areas:

| Review area | Files | What to verify |
| --- | --- | --- |
| Strict ATIF boundary | `nemo_gym/atif_v1_7.py`,
`nemo_gym/atif_json.py` | Exact v1.7/schema and lossless JSON rules |
| Projection | `nemo_gym/atif_reverification.py` | Supported trajectory
mapping, usage semantics, identity, and fail-closed boundaries |
| Gym integration | `nemo_gym/rollout_reverification.py` |
Selected-route preflight and reuse of the existing stateless verifier
path |
| Wiring and evidence | CLI, docs, fixtures, and tests | User-facing
contract and regression coverage |

Suggested pass: review the boundary first, then projection, then the
small Gym integration. The remaining files document and prove that
contract.

## Validation

- Focused ATIF projection, rollout-reverification, and CLI suites: **491
passed**.
- Changed-file pre-commit and `git diff --check`: passed.
- Fixture tests prove reward equivalence between native and projected
responses and show that the same ATIF trajectory can be rescored with a
second stateless verifier.
- The live Codex CLI → Relay → ATIF → Gym check executed a real tool
call, preserved `function_call → function_call_output → message`, and
returned reward `1.0` with aggregate accuracy `100%`.

The broader local unit run reported the same five failures in both ATIF
worktrees, all outside the affected paths: sandbox port binding,
existing `aime24` metadata, macOS `/proc` behavior, system Python
missing `aiohttp`, and sandbox semaphore permissions. The affected
suites pass.

## Related work

- NVIDIA-NeMo#2468 tracks ATIF input for Gym reverification.
- NVIDIA/NeMo-Relay#887 fixes Responses tool-call correlation.
- NVIDIA-NeMo#2673 covers Gym-native structured MCP tool identity and is
complementary to this PR.

## Checklist

- [x] I have read the contributing guidelines.
- [x] The change is focused and adds no unrelated runtime dependency.
- [x] Tests and documentation were added.
- [x] Changed-file pre-commit checks pass.
- [x] Commits include DCO sign-off.

---------

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@svcnvidia-nemo-ci svcnvidia-nemo-ci added the waiting-on-maintainers Waiting on maintainers to respond label Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-maintainers Waiting on maintainers to respond

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants