Skip to content

Modularize the agent inspector and pin routing behavior - #4974

Draft
chesterxgchen wants to merge 58 commits into
NVIDIA:mainfrom
chesterxgchen:agent-inspector-modularization
Draft

Modularize the agent inspector and pin routing behavior#4974
chesterxgchen wants to merge 58 commits into
NVIDIA:mainfrom
chesterxgchen:agent-inspector-modularization

Conversation

@chesterxgchen

Copy link
Copy Markdown
Collaborator

Description

Modularize the agent inspector that selects conversion skills, and add an
executable characterization suite that pins its routing behavior.

Depends on #4967. That PR adds the Hugging Face conversion skill and the
framework detection this refactor reorganizes. Until it merges, the diff here
also shows its commits.

Production code is a net reduction. No routing behavior changes: inspector
output is byte-identical to the pre-refactor commit across the whole
characterization corpus.

What Changed

Inspector modularization

  • Consolidate skill selection and recommended-command generation into one
    routing decision so the two outputs cannot silently disagree.
  • Replace the shared mutable inspection state at module boundaries with frozen
    inspection facts and narrower project/routing projections.
  • Build the project import graph and framework-family resolver once per
    inspection.
  • Reduce nvflare/tool/agent/inspector.py from 2,543 lines to a 198-line
    orchestrator, moving cohesive responsibilities into seven modules under
    nvflare/tool/agent/inspection/ (models, scanner, python_scanner, project,
    routing, skill_discovery).
  • Split the former monolithic inspector test file across focused test modules
    for project analysis, routing, scanning, and skill discovery.
  • Deduplicate the redaction helpers that the module split had forked into two
    copies, inline a single-use scoring pass-through, and drop an unreachable
    branch in installed-skill discovery.

Behavior contract suite

Static routing analysis fails silently: a wrong answer is a plausible-looking
recommendation, not a crash. This adds an executable gate so future
consolidation can be attempted safely.

  • tests/unit_test/tool/agent/fixtures/inspection_behavior/ — 35 unconverted
    source cases covering baseline ownership, mixed Lightning/Hugging Face
    projects, trainer identity across global/nonlocal/closure scopes,
    assignment and unpacking provenance, every flare.patch call form, nine
    shadowing cases that must not produce an owner, and evaluation-only,
    unused-trainer, and inference-only suppression cases.
  • cases.json declares the expected framework, conversion state, ownership,
    and recommended skills per case.
  • expected.json is a normalized full-envelope golden generated from the
    parent commit and treated as immutable.
  • inspection_behavior_contract_test.py compares the full envelope, pins the
    normalizer's exclusion list, and asserts the normalizer's own digest so the
    gate cannot be weakened by narrowing what it inspects.
  • inspection_behavior_normalizer.py --source-root strips editable-install
    finders and verifies the resolved package, so the golden cannot be silently
    generated from the working tree instead of the reference.
  • lexical_scope_bindings_test.py covers candidate_scopes(),
    binding_scope(), enclosing-scope resolution, class-scope skipping,
    global/nonlocal, and identity invalidation after rebinding.

Why The Lexical Analysis Stays

Per-file import matching is not sufficient to choose a converter. A project can
import transformers for a model while Lightning owns the training loop, rebind
a trainer name after construction, or build the trainer in a global,
nonlocal, or closure scope. Replacing lexical identity resolution,
source-order handling, and assignment RHS provenance with file-level matching
changes observable output in 35 covered cases: converted jobs report
partial_client_api instead of client_api_converted, Hugging Face
recommendations are lost or redirected to nvflare-orient, and
PyTorch/Lightning evidence disappears from class bodies, definition-time
expressions, and unpacking targets. The corpus above pins each of those.

Non-Goals

  • No routing policy changes. Every skill recommendation, conversion state, and
    ownership result is unchanged.
  • No deduplication of the parallel Hugging Face and Lightning detector paths.
    They contain similar-looking import, alias, pending-call, and finalization
    machinery, but patch() has exactly two entry points in the codebase
    (app_opt/lightning/api.py, app_opt/hf/api.py) and no third can join —
    manual PyTorch has no Trainer to wrap. Consolidating two closed members adds
    indirection without a payoff.

Validation

  • python -m pytest tests/unit_test/tool/agent tests/unit_test/tool/agent_skill_checks -q
  • Equivalence: expected.json regenerated from the pre-refactor commit in a
    detached worktree matches the checked-in golden byte for byte.
  • Gate liveness: mutating one golden routing value fails the contract test;
    adding a field to the normalizer's exclusion list fails both the semantic and
    golden tests.
  • ./runtest.sh -s and ./runtest.sh -l

The agentskills.io spec allows six top-level SKILL.md fields and defines
`metadata` as a map of string keys to string values. `skills/README.md`
documents the same six, but eight skills declared `version` at the top level
and used YAML lists for `tags`/`languages`/`frameworks`, so the published
frontmatter did not match the convention the repo documents.
`nvflare-convert-huggingface` was already conformant; the rest now match it.

Nothing enforces this: the lint permits top-level `version` as an explicit
NVIDIA-skills-catalog carve-out and never checks metadata value types, so the
two forms could coexist indefinitely.

`min_flare_version` moves to 2.9.0 across all skills because 2.9.0 is the first
FLARE release that ships agent skills; the 2.8.0 values predate that decision.

The string form also matters for the 200-line SKILL.md limit: list-form
metadata costs a dozen lines in files already sitting at exactly 200.
Static routing analysis fails silently: a wrong answer is a plausible
recommendation, not a crash. A file-level simplification of the lexical
identity model looked correct and regressed 35 routing cases, so pin the
behavior before any future consolidation is attempted.

Adds 35 unconverted source cases covering baseline ownership, mixed
Lightning/Hugging Face projects, trainer identity across global, nonlocal,
and closure scopes, assignment and unpacking provenance, every flare.patch
call form, nine shadowing cases that must not yield an owner, and
evaluation-only, unused-trainer, and inference-only suppression.

The contract compares the full normalized envelope against a golden
generated from the parent commit, and pins the normalizer's own digest and
exclusion list so the gate cannot be weakened by narrowing what it inspects.
Golden generation strips editable-install finders and verifies the resolved
package, so it cannot be silently produced from the working tree.

Also deduplicates the redaction helpers the module split had forked into two
copies, inlines a single-use scoring pass-through, and drops an unreachable
branch in installed-skill discovery.
@chesterxgchen
chesterxgchen force-pushed the agent-inspector-modularization branch from 1363de9 to 8340803 Compare July 28, 2026 05:58
@codecov-commenter

codecov-commenter commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.78902% with 151 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.53%. Comparing base (63ae589) to head (f1a82fb).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
nvflare/tool/agent/inspection/scanner.py 80.41% 56 Missing ⚠️
nvflare/tool/agent/inspection/python_scanner.py 86.04% 55 Missing ⚠️
nvflare/tool/agent/inspection/skill_discovery.py 85.71% 13 Missing ⚠️
nvflare/tool/agent/frameworks/huggingface.py 95.43% 11 Missing ⚠️
nvflare/tool/agent/frameworks/pytorch.py 93.90% 5 Missing ⚠️
nvflare/tool/agent/frameworks/lightning.py 97.00% 3 Missing ⚠️
nvflare/tool/agent/frameworks/registry.py 96.34% 3 Missing ⚠️
nvflare/tool/agent/inspection/models.py 94.33% 3 Missing ⚠️
nvflare/tool/agent/inspection/project.py 99.08% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4974      +/-   ##
==========================================
+ Coverage   64.28%   64.53%   +0.25%     
==========================================
  Files        1030     1037       +7     
  Lines      103733   104450     +717     
==========================================
+ Hits        66681    67411     +730     
+ Misses      37052    37039      -13     
Flag Coverage Δ
unit-tests 64.53% <91.78%> (+0.25%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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