Skip to content

Restore Python lint checks and extend them to the verl subtree - #553

Merged
Yuge Zhang (ultmaster) merged 8 commits into
microsoft:mainfrom
ultmaster:chore/restore-python-docs-lints
Aug 22, 2026
Merged

Restore Python lint checks and extend them to the verl subtree#553
Yuge Zhang (ultmaster) merged 8 commits into
microsoft:mainfrom
ultmaster:chore/restore-python-docs-lints

Conversation

@ultmaster

Copy link
Copy Markdown
Contributor

Restores Python lint checks in CI, then extends the same coverage to the
parts of the tree that were still exempt.

Lint and formatting (4e59e0fb, 3dcb86b7, 447de0f0, d4b7f391)

Adds a lint job running pre-commit, Ruff (check + format), Pyright and a
copyright-header check, plus a .pre-commit-config.yaml and
scripts/check_headers.py. The bulk of the diff is the resulting
normalization pass.

Behavior-preserving guarantee (766e7cb9)

A normalization pass should not change what the code does, so the diff was
audited for exactly that. Two changes did, and are reverted:

  • k8s_reconcilerjob.raw had become
    job if isinstance(job, dict) else job.raw. kr8s types
    Job.async_list as AsyncGenerator[Self | dict] and its body really can
    yield a dict, so the added branch was reachable.
  • server/app — the OmegaConf.is_config(config) guard had been
    narrowed to isinstance(config, DictConfig). is_config is also true for
    ListConfig, so the replacement re-routed that input and changed the
    error raised for a non-mapping container.

Both use typing.cast instead, which returns its argument unchanged at
runtime. Both blocks are byte-identical to their pre-cleanup form once the
cast wrappers are normalized away.

Changes that looked risky were verified equivalent rather than assumed:
the httpx.QueryParams rewrite produces the identical query string; the
RolloutStatusPatch rewrite produces identical exclude_unset payloads
across every field combination; and the reformatted Jinja chat template —
where whitespace is tokenizer-visible — renders byte-for-byte identically
across six message shapes, because each stripped line sits next to a {{-
/ {%- tag that already consumed it.

Type-checking the verl subtree (e2d702e9)

agentlightning/verl and tests/verl were excluded from Pyright, so that
subtree had Ruff coverage only. The excludes are dropped and a verl-cpu
dependency group makes verl importable for the checker.

Torch comes from the PyTorch CPU index, scoped to the group via
tool.uv.sources, following the convention already on v0.x. Pyright only
reads .py/.pyi, so this drops 15 nvidia-* packages plus triton and
halves the synced environment (3.5GB → 1.6GB). The group is dev-only — the
built wheel still advertises just Provides-Extra: dev, so the CPU pin
never reaches anyone installing the package.

verl is pinned >=0.7.1,<0.9.0. This is load-bearing: 0.9.0 renamed
main_ppo.TaskRunner to TaskRunnerV1 and dropped create_rl_sampler,
both imported by verl/entrypoint.py, and auto_await landed in 0.7.1.

The 23 resulting errors are fixed type-only. Three were ours (wandb Table
columns is invariant over list; RolloutCreate.config receives a dict
that pydantic coerces). The other 20 trace to verl's own annotations:
auto_await is unannotated, so functools.wraps leaves the checker seeing
a coroutine where verl itself calls the same methods synchronously, and
register_policy_loss retypes the decorated function to a positional-only
Callable. Two of those surfaced as reportUnusedCoroutine and looked
like missing awaits — they are not, and adding await would have been a
real behavior change.

Pyright moved to its own job so the fast checks do not wait on the torch
download.

Test scope (0f80433a)

The test job enumerated four paths, which left tests/verl and
tests/examples/test_swe_smith_agent.py uncollected. It now runs tests,
so new files are picked up without a workflow edit. Coverage goes from 24
to 62 tests; the suite still runs in about four seconds.

Verification

Run against an environment synced with the exact CI command:

  • Pyright: 0 errors, with the verl subtree included
  • pytest tests: 62 passed
  • Ruff check + format, header check, uv lock --check, all 7 pre-commit hooks: pass
  • uv build --no-sources succeeds; wheel metadata confirmed free of the group and the CPU index

Trade-off worth a look

The test job now syncs verl-cpu (1.6GB) where it previously needed none,
to collect tests/verl. It shares the setup-uv cache with typecheck,
but the sync step will be slower than before. Splitting verl into its own
job would restore a fast core-tests signal if that matters.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JzYGoMiLFttovPT9XS7LFU

Yuge Zhang (ultmaster) and others added 7 commits August 21, 2026 17:05
The Pyright cleanup changed behavior in two places instead of only
satisfying the type checker. Restore both and use typing.cast, which
returns its argument unchanged at runtime.

k8s_reconciler: `job.raw` had become `job if isinstance(job, dict) else
job.raw`. kr8s types Job.async_list as AsyncGenerator[Self | dict] and can
genuinely yield a dict, so the new branch was reachable and changed what
the reconciler does with such an item.

server/app: the `OmegaConf.is_config(config)` guard had been narrowed to
`isinstance(config, DictConfig)`. is_config is also true for ListConfig, so
the replacement routed ListConfig down the `dict(config)` path and changed
the error raised for a non-mapping container.

Both blocks are now byte-identical to their pre-cleanup form once the cast
wrappers are normalized away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzYGoMiLFttovPT9XS7LFU
agentlightning/verl and tests/verl were excluded from Pyright, so that
subtree was covered by Ruff only. Drop the excludes and add a verl-cpu
dependency group that makes verl and its peers importable for the checker.

Torch comes from the PyTorch CPU index, scoped to the group via
tool.uv.sources, following the convention already used on v0.x. Pyright
only reads .py/.pyi, so this drops 15 nvidia-* packages plus triton and
halves the synced environment (3.5GB -> 1.6GB). The group is dev-only: the
built wheel still advertises just `Provides-Extra: dev`, so nothing about
the CPU pin reaches users installing the package.

verl is pinned >=0.7.1,<0.9.0. 0.9.0 renamed main_ppo.TaskRunner to
TaskRunnerV1 and dropped create_rl_sampler, both imported by
verl/entrypoint.py; auto_await landed in 0.7.1.

The 23 resulting Pyright errors are fixed type-only, with no runtime
change. Three were ours (wandb Table columns is invariant over list, and
RolloutCreate.config receives a dict that pydantic coerces). The other 20
come from verl's own annotations: auto_await is unannotated, so functools
.wraps leaves the checker seeing a coroutine where verl itself calls the
same methods synchronously, and register_policy_loss retypes the decorated
function to a positional-only Callable.

Pyright now runs in its own job so the fast lint checks do not wait on the
torch download.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzYGoMiLFttovPT9XS7LFU
The test job enumerated four paths, which left tests/verl and
tests/examples/test_swe_smith_agent.py uncollected. Run `tests` instead so
new test files are picked up without editing the workflow.

tests/verl needs verl importable, so the job now syncs the verl-cpu group
alongside dev. Coverage goes from 24 to 62 tests; the suite still runs in
about four seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzYGoMiLFttovPT9XS7LFU
Copilot AI lite review requested due to automatic review settings August 21, 2026 12:18

Copilot AI left a comment

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.

Pull request overview

Restores and expands Python CI quality gates (linting, formatting, headers, and type-checking) across the repository, including the previously exempt agentlightning/verl and tests/verl subtrees, and broadens test collection to run the full tests/ suite.

Changes:

  • Adds CI jobs for linting (pre-commit + Ruff + header check) and type-checking (Pyright), and updates the test job to run all tests.
  • Introduces a Python copyright-header checker and repository pre-commit configuration.
  • Updates pyproject.toml to support Pyright coverage of the verl subtree via a verl-cpu dependency group and CPU-only torch index source.

Reviewed changes

Copilot reviewed 36 out of 46 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/verl/test_rollout_adapter.py Formatting-only normalization in rollout adapter tests.
tests/verl/test_per_rollout_loss.py Adds copyright header; adds a Pyright suppression on a test call.
tests/server/test_endpoints.py Formatting changes to assertions and request construction.
tests/server/conftest.py Adds richer type annotations to fixtures and imports FastAPI.
tests/examples/test_swe_smith_images.py Formatting-only change to a string assert.
tests/examples/test_swe_smith_agent.py Formatting-only changes to helper function and argument layout.
scripts/check_headers.py New script to enforce required Python copyright headers.
pyproject.toml Adds pre-commit + OpenAI dev deps; configures uv sources/indexes; expands Pyright include; adds verl-cpu group.
examples/swe_smith/train_smith_agent.py Import/type cleanup and file-reading normalization; formatting adjustments.
examples/swe_smith/train_smith_agent_megatron.py Import/type cleanup and formatting adjustments.
examples/swe_smith/swe_smith_chat_template.jinja Whitespace normalization around Jinja blocks.
examples/swe_smith/pull_images.py Uses contextlib.suppress for exception swallowing; formatting updates.
examples/swe_smith/agents/smith_agent.py Broad formatting normalization and minor refactors; adds/adjusts ignores for type-checking.
examples/search_r1/train_search_r1_agent.py Formatting-only change to indexing layout.
examples/search_r1/retrieval_launch.sh Removes trailing blank line.
examples/search_r1/data/.gitkeep Removes the placeholder file.
examples/science_world/train_sw_agent.py Formatting-only changes to error message and f-string quoting.
examples/llm-in-sandbox/train_llm_in_sandbox.py Import/type cleanup and f-string quoting normalization.
examples/llm-in-sandbox/run.sh Formatting-only change to line indentation.
examples/llm-in-sandbox/job-template.yaml Formatting-only change to line indentation.
examples/llm-in-sandbox/Dockerfile.agent Formatting-only change to line indentation.
examples/llm-in-sandbox/agents/runner.py Formatting-only change to env-var selection expression.
examples/llm-in-sandbox/.gitignore Formatting-only change to indentation.
examples/gsm8k/train_gsm8k_agent.py f-string quoting normalization.
examples/gsm8k/run_local.sh Formatting-only change to indentation.
examples/gsm8k/gsm8k_agent.py Removes trailing blank lines.
examples/calc_x/train_calc_agent.py Adds a blank line for formatting normalization.
examples/calc_x/Dockerfile Formatting-only change to line indentation.
examples/calc_x/calc_agent.py Formatting-only whitespace removal and indentation normalization.
docs/macros/source_links.py Type modernization (dict[...]) and formatting simplification.
agentlightning/verl/trainer.py Adds Pyright suppressions and formatting changes for verl integration types.
agentlightning/verl/rollout_level_advantage.py Formatting-only change to exception formatting.
agentlightning/verl/rollout_adapter.py Adds/adjusts type annotations and formatting normalizations.
agentlightning/verl/per_rollout_loss.py Adds required copyright header.
agentlightning/verl/entrypoint.py Removes stray blank line.
agentlightning/verl/dataset.py Type import cleanup and minor formatting normalization.
agentlightning/verl/agl_rollout_manager.py Uses cast for pydantic-coerced config; typing-only changes.
agentlightning/server/routes/rollouts.py Removes trailing blank lines.
agentlightning/server/routes/events.py Formatting-only change to list comprehension layout.
agentlightning/server/app.py Uses cast to preserve runtime behavior while satisfying typing.
agentlightning/controller/local_reconciler.py Switches query params to httpx.QueryParams; moves status patch to typed model.
agentlightning/controller/k8s_reconciler.py Switches query params to httpx.QueryParams; adjusts job handling and typed status patching.
agentlightning/config/server.yaml Formatting-only indentation normalization.
.pre-commit-config.yaml Adds pre-commit hooks and repository-wide excludes.
.github/workflows/tests.yml Adds lint/typecheck jobs and broadens test collection to tests/.
Suppressed comments (1)

agentlightning/verl/rollout_adapter.py:37

  • These constants are lists of column names (strings). Annotating them as list[Any] weakens type checking and makes it easier to accidentally insert non-string values without tooling catching it.
_ROLLOUT_TRAJECTORY_COLUMNS: list[Any] = [

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 165 to 167
jobs = [
job.raw
cast(k8s_objects.Job, job).raw
async for job in k8s_objects.Job.async_list(

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.

Declining this one deliberately — it asks to re-introduce the exact change this PR reverts in 766e7cb.

The diagnosis is correct: Job.async_list is typed AsyncGenerator[Self | dict] and its body really can yield a bare dict, in which case .raw raises AttributeError. But that is the behavior on main today, and it is unchanged by this PR.

An earlier commit on this branch had quietly turned job.raw into job if isinstance(job, dict) else job.raw while resolving Pyright errors. That is a live behavior change — the added branch is reachable — and it landed in a commit labelled as a type-checker cleanup, with no test covering it (tests/controller/test_k8s_reconciler.py only exercises build_job_spec). This PR is a lint/normalization pass whose contract is that runtime behavior is preserved, so it was reverted to job.raw with a cast, which is a no-op at runtime.

Handling the dict case may well be worth doing, but it is a behavior change that deserves its own PR with test coverage for the dict path, not a silent rider on a formatting sweep. Happy to open a follow-up issue.

Comment thread agentlightning/verl/rollout_adapter.py Outdated
Annotating the column constants as list[Any] silenced the checker but
weakened every use of them, including the row lookup that iterates them.

Restore both to their original inferred list[str] and move the widening to
the two wandb.Table call sites instead. wandb types columns as
list[ColumnKey] where ColumnKey = str | int, and list is invariant, so a
list[str] is rejected there specifically. cast is a runtime no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzYGoMiLFttovPT9XS7LFU
@ultmaster
Yuge Zhang (ultmaster) merged commit c632d8d into microsoft:main Aug 22, 2026
6 checks passed
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.

2 participants