Skip to content

refactor(models): give token capture its own lazy public API - #5525

Merged
ananthsub merged 1 commit into
mainfrom
ananthsub/token-capture-public-api
Nov 3, 2026
Merged

ananthsub merged 1 commit into
mainfrom
ananthsub/token-capture-public-api

Conversation

@ananthsub

@ananthsub ananthsub commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

What changed and why

Token capture is a namespace of its own, the way a task format such as nemo_gym.data.tasks.harbor is, so it now has its own lazy public API. Code outside nemo_gym/ imports token capture names from nemo_gym.models.token_capture, not from nemo_gym.models or from a token capture module. Before this change, no package served the rollout finalizer, so the external agent harness guide had to import nemo_gym.models.token_capture.delivery directly. The package layout docs (#5385) ran into that.

  • nemo_gym.models.token_capture replaces its eager imports and __all__ with a _PUBLIC_API built with lazy_exports, plus a TYPE_CHECKING block, as in the Harbor package. It serves every name its old __all__ listed, and the protocol and record names that nemo_gym.models served. It also adds finalize_rollout_token_capture, retire_rollout_token_capture, ConformanceError, ConformanceWarning, TerminalAttribution, and resolve_terminal.
    • It also exports RolloutRemovalPayload and RolloutRetiredError. TokenSource and CaptureLedger return the first from retire and delete, and a CaptureLedger raises the second for a retired rollout. Neither the old __all__ nor nemo_gym.models exported them, so a framework implementing those protocols had to import token capture's protocols module.
    • The finalizer in delivery.py imports only token capture modules and nemo_gym.core.episode, which depends on Pydantic alone, so nothing keeps it out of the public API. The package docstring no longer says it needs Gym's server stack.
  • No server dependencies. Frameworks implement token capture's protocols, so every name either public API serves imports with only Pydantic and orjson from outside the standard library. Nothing loads Gym's server stack, an HTTP client, or the OpenAI SDK.
    • The external capture handlers (ExternalCaptureHandler, VLLMWorkerCaptureHandler, make_external_capture_handler, validate_framework_context_options) run inside the model server. Their module needs the OpenAI SDK through the chat completions converter. So nemo_gym.models, the model server's public API, serves them instead of token capture's.
  • nemo_gym.models.token_capture.staging, the hand-off to training frameworks, keeps a public API of its own, now lazy too. Its docstring already describes it as a namespace separate from token capture's records. It also serves the three staging names that nemo_gym.models served and that its old __all__ did not list.
    • The terminal resolver (resolve_terminal, TerminalAttribution) is defined in token capture, so token capture's API serves it. A lazy API serves only its own package's modules. The old path nemo_gym.token_id_capture.staging, which NeMo RL imports resolve_terminal from, still resolves through a new moved-name entry and warns.
    • The same applies to canonicalize_tool_arguments, which the old nemo_gym.token_id_capture package imported without exporting it.
  • nemo_gym.models no longer re-exports token capture's protocol and record names. No release shipped them: v0.7.0 has no nemo_gym.models package, and the moves ship in 0.8.0. So they need no moved-path entries.
  • Importers. The vLLM model server, its tests, the Claude Code agent tests, and the external agent harness guide's code samples import from the new public APIs. Code inside nemo_gym/ and its tests imports from the modules that define each name, so the library modules and tests that imported from the token capture packages now import from those modules.

Tracking issue: #5166.

Compatibility

  • from nemo_gym.models import <token capture protocol or record name> no longer works. That form never shipped in a release.
  • Importing nemo_gym.models.token_capture or its staging package no longer imports their modules eagerly.
  • Old nemo_gym.token_id_capture paths keep resolving with a deprecation warning. That includes nemo_gym.token_id_capture.staging.resolve_terminal, which NeMo RL imports, and nemo_gym.token_id_capture.canonicalize_tool_arguments.
  • scripts/migrate_imports.py reads public APIs only from top-level packages, so it does not write the token capture or Harbor subpackage form. That predates this change.

Validation

  • tests/unit_tests/test_public_api_facades.py now also covers the two new public APIs. It checks that each name is the object its module defines, that importing either package loads none of its modules, and that the TYPE_CHECKING imports match _PUBLIC_API. It also checks that code outside the library imports these names only from a public API, and that every name it imports resolves.
  • The token capture dependency check in tests/unit_tests/models/test_token_capture.py loads every name both public APIs serve in a fresh interpreter. It fails if Ray, FastAPI, Starlette, uvicorn, aiohttp, requests, httpx, the OpenAI SDK, or torch was imported, so an external capture handler added back to token capture's API fails it. Before this change the check imported only the package, which now loads none of its modules.
  • tests/unit_tests/test_moved_paths.py checks that every recorded old path and NeMo RL's imports still resolve.
  • pytest tests/unit_tests, compared with upstream/main in the same environment, to check that the change adds no failures.
  • pytest responses_api_models/vllm_model/tests responses_api_agents/claude_code_agent/tests, which run the vLLM model server and the Claude Code agent against the new imports.
  • lint-imports, which checks that each package imports only packages below it.
  • pre-commit run --files <changed files>.

Rollout evidence

N/A: this changes import paths only, with no change to agent, model server, or verifier behavior.

@copy-pr-bot

copy-pr-bot Bot commented Nov 3, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@ananthsub
ananthsub marked this pull request as ready for review November 3, 2026 19:16
@ananthsub
ananthsub requested a review from pthombre November 3, 2026 19:34
@ananthsub
ananthsub force-pushed the ananthsub/token-capture-public-api branch 2 times, most recently from 24c180f to a3e4e85 Compare November 3, 2026 19:44
Token capture is a namespace of its own, like a task format, so code
outside the library now imports its names from
nemo_gym.models.token_capture instead of nemo_gym.models or a submodule.
Frameworks implement its protocols, so every name it serves must import
without Gym's server stack or an OpenAI client.

- nemo_gym.models.token_capture serves a lazy _PUBLIC_API built with
  lazy_exports. It keeps every name its eager __all__ had and adds the
  rollout finalizer and retirement functions, the conformance check, and
  the protocol and record names nemo_gym.models served. Importing the
  package loads none of its modules, so the finalizer no longer needs to
  stay out of it.
- It also exports RolloutRemovalPayload, which TokenSource and
  CaptureLedger return from retire and delete, and RolloutRetiredError,
  which a CaptureLedger raises for a retired rollout. A framework can
  implement those protocols without importing the protocols module.
- nemo_gym.models.token_capture.staging, the framework hand-off, keeps
  a public API of its own, now lazy. The terminal resolver moves to token
  capture's API, where it is defined. The old token_id_capture paths for
  it and for canonicalize_tool_arguments resolve through moved-name
  entries.
- The external capture handlers run inside the model server, and their
  module needs the OpenAI SDK through the chat completions converter.
  nemo_gym.models, the model server's public API, serves them instead of
  token capture's.
- nemo_gym.models no longer re-exports token capture's protocol and
  record names. No release shipped them, so they need no moved-path
  entries.
- The dependency check imported only the package, which now loads none
  of its modules. It now loads every name both token capture public APIs
  serve, and also fails on the OpenAI SDK, httpx, and Starlette.
- The vLLM model server, its tests, the Claude Code agent tests, and the
  external agent harness guide's samples import from the new APIs. Code
  inside nemo_gym/ and its tests imports from the defining modules.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@ananthsub
ananthsub force-pushed the ananthsub/token-capture-public-api branch from a3e4e85 to e5e832a Compare November 3, 2026 19:53
@ananthsub
ananthsub merged commit 11b28ad into main Nov 3, 2026
13 checks passed
@ananthsub
ananthsub deleted the ananthsub/token-capture-public-api branch November 3, 2026 20:08
@nemo-automation-bot
nemo-automation-bot Bot requested review from a team November 3, 2026 21:15
zyzhou5 added a commit to zyzhou5/Gym that referenced this pull request Nov 4, 2026
…IA-NeMo#5525

main's token-capture public API (NVIDIA-NeMo#5525) no longer serves CaptureStore from nemo_gym.models.token_capture;
nemo_gym.models does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Zeyu Zhou <zezhou@nvidia.com>
adil-a added a commit that referenced this pull request Nov 7, 2026
Conflicts:
- nemo_gym/models/token_capture/sink.py: main renamed rollout_id to
  capture_key in the unresolved-parent warning; kept the rename and this
  branch's reason and request-item count.
- responses_api_agents/opencode_agent/configs/opencode_agent.yaml: kept
  main's new owner-extension keys and this branch's removal of the
  opencode_config.tools map.

Follow-ups for main's API changes: tests build TokenEntry with capture_key,
and the opencode_agent test imports the token-capture names from
nemo_gym.models.token_capture, their public API since #5525.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>

This branch was previously deployed

1 inactive deployment
public — e5e832a0 Deployed Nov 4, 2026 by copy-pr-bot[bot] via release / finalize / notify #9723
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant