Skip to content

feat(nemo-agents): stage Fabric agent.yaml artifacts into Docker/K8s deployments - #1007

Open
tylersbray wants to merge 3 commits into
mainfrom
AIRCORE-966-fabric-artifact-staging/tbray
Open

feat(nemo-agents): stage Fabric agent.yaml artifacts into Docker/K8s deployments#1007
tylersbray wants to merge 3 commits into
mainfrom
AIRCORE-966-fabric-artifact-staging/tbray

Conversation

@tylersbray

@tylersbray tylersbray commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stage the canonical {agent}-spec fileset into multi-file config_files at Fabric Docker/K8s deploy time so relative paths (skills.paths, sibling prompt assets) resolve under /workspace.
  • Extend Docker materialization for multiple staged files via STAGED_CONFIG_FILES_B64_JSON; K8s continues to use ConfigMap multi-file mounts.
  • Fall back to inline-only agent.yaml when the fileset is missing; fail deploy early when referenced skills.paths entries are absent after staging.

Closes AIRCORE-966.

Test plan

  • uv run --frozen pytest plugins/nemo-agents/tests/unit/test_fabric_artifact_staging.py plugins/nemo-agents/tests/unit/test_runner_deployments.py -v (56 passed)
  • uv run ruff check / uv run --frozen ty check on runner package
  • Pre-commit hooks on commit
  • Live Fabric container deploy with skills fileset once AIRCORE-902 ships Fabric in the default image (full matrix: AIRCORE-960)

Summary by CodeRabbit

  • New Features
    • Fabric deployments now support staging multiple configuration and agent artifact files.
    • Docker and Kubernetes deployments can use bundled configuration files.
    • Agent creation and deletion now manage associated specification filesets.
    • Deployment creation supports creator identity for delegated authorization.
    • Missing artifact files gracefully fall back to inline configuration when possible.
  • Bug Fixes
    • Invalid, oversized, or missing skill references are rejected safely.
    • Deployment failures during artifact staging no longer create incomplete records.
  • Tests
    • Added coverage for multi-file deployments, artifact staging, fallback behavior, authorization, and validation.

@tylersbray
tylersbray requested review from a team as code owners July 30, 2026 23:05
@github-actions github-actions Bot added the feat label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Fabric deployments now propagate agent identifiers, stage and validate agent filesets, and materialize multiple configuration files in Docker and Kubernetes. The CLI uploads specification filesets, rolls back failed uploads, and removes filesets during agent deletion.

Changes

Fabric deployment artifacts

Layer / File(s) Summary
Deployment agent propagation
plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py, plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py, plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py, plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
Runner interfaces accept agent identifiers. The controller forwards the deployment agent.
Fabric fileset staging
plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py, plugins/nemo-agents/src/nemo_agents_plugin/entities.py, plugins/nemo-agents/tests/unit/test_fabric_artifact_staging.py
Filesets are downloaded, rewritten, validated, size-limited, and converted into staged configuration files. Unavailable filesets can fall back to inline YAML.
Multi-file deployment materialization
plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py, plugins/nemo-agents/tests/unit/test_runner_deployments.py
Docker materializes encoded files. Kubernetes mounts resolved files. Tests cover runtime wiring and staging failures.
Agent specification fileset lifecycle
plugins/nemo-agents/src/nemo_agents_plugin/cli.py, plugins/nemo-agents/tests/unit/test_cli.py, plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py
Fabric agent creation uploads a specification fileset and rolls back the agent on upload failure. Agent deletion removes the entity and best-effort removes its fileset.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant NeMoPlatform
  participant DeploymentsRunnerBackend
  participant FabricArtifactStaging
  participant DeploymentRuntime
  CLI->>NeMoPlatform: Create Fabric agent
  CLI->>NeMoPlatform: Upload agent specification fileset
  DeploymentsRunnerBackend->>FabricArtifactStaging: Stage fileset and rewritten configuration
  FabricArtifactStaging-->>DeploymentsRunnerBackend: Return resolved ConfigFile list
  DeploymentsRunnerBackend->>DeploymentRuntime: Configure Docker or Kubernetes files
  DeploymentRuntime-->>DeploymentsRunnerBackend: Materialize files and start agent
Loading

Possibly related PRs

Suggested reviewers: mmogallapalli, mckornfield, stefan-kickoff

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.99% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: staging Fabric agent.yaml artifacts for Docker and Kubernetes deployments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch AIRCORE-966-fabric-artifact-staging/tbray

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py (1)

384-404: 🚀 Performance & Scalability | 🔵 Trivial

Multi-file payload transported entirely via one env var.

All staged file contents are base64-encoded into a single JSON env var. Docker/OS environment-size limits could be hit for filesets with many or large skill/prompt files, causing container start failures that are hard to diagnose from the deployment status alone. Worth a sanity check on typical fileset sizes, or a size guard/clear error before this path is hit at scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py`
around lines 384 - 404, The multi-file branch in the Docker backend can exceed
environment-size limits by placing the entire staged fileset in one
`_STAGED_CONFIG_FILES_ENV` variable. Add a preflight size check before appending
that env var, using the serialized base64 payload size and an appropriate
documented limit; raise a clear deployment error when exceeded, while preserving
the existing materialization flow for payloads within the limit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py`:
- Around line 247-256: Update _materialize_staged_config_files_and_exec to
invoke the staging inline script with python instead of python3, keeping the
existing exec {quoted_argv} behavior unchanged so Fabric Docker deployments
consistently use the available interpreter.

In
`@plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py`:
- Around line 96-102: Guard the file read in the staging loop around
path.read_text within the artifact staging function, handling UnicodeDecodeError
alongside the existing file-related exceptions. Convert it into a clear staging
error that identifies the unreadable file, while preserving the special
rewritten_agent_yaml path and normal UTF-8 content behavior.
- Around line 70-77: Ensure the fileset-missing fallback in the staging flow
still invokes _validate_referenced_skill_paths before returning the inline
agent.yaml configuration, so configured skills.paths entries fail deployment
immediately when unavailable. Add coverage in test_fabric_artifact_staging.py
for a missing fileset with skills.paths configured, asserting validation raises
or otherwise reports the missing skill rather than allowing staging to succeed.

---

Nitpick comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py`:
- Around line 384-404: The multi-file branch in the Docker backend can exceed
environment-size limits by placing the entire staged fileset in one
`_STAGED_CONFIG_FILES_ENV` variable. Add a preflight size check before appending
that env var, using the serialized base64 payload size and an appropriate
documented limit; raise a clear deployment error when exceeded, while preserving
the existing materialization flow for payloads within the limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: db4c4926-279e-40cf-9861-3dea1c445f9e

📥 Commits

Reviewing files that changed from the base of the PR and between cac16db and 1d5cdc2.

📒 Files selected for processing (7)
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
  • plugins/nemo-agents/tests/unit/test_fabric_artifact_staging.py
  • plugins/nemo-agents/tests/unit/test_runner_deployments.py

Comment thread plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py Outdated
Comment thread plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py Outdated
@tylersbray
tylersbray force-pushed the AIRCORE-966-fabric-artifact-staging/tbray branch from 1d5cdc2 to e982a03 Compare July 31, 2026 00:10
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 29440/37448 78.6% 63.2%
Integration Tests 17381/36166 48.1% 20.5%

@tylersbray
tylersbray requested a review from mmogallapalli July 31, 2026 18:15
@mmogallapalli

mmogallapalli commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

The existing changes look good to me, one thing I noticed is that we don't do the fileset creation and upload of artifacts on the producer/cli side. This wasn't an issue for local/subprocess deployment since the agent directly had access to any referenced filepaths. (sorry for not flagging this earlier in that ticket/last PR)

So for docker/k8s the {agent-name}-spec fileset being referenced won't exist, I think we should add that logic in this PR itself, i'm thinking something like below, where we essentially pair Agent creation and fileset creation and Agent deletion and fileset deletion. This will create filesets for local deployments that won't be used but I think thats fine since we will delete them as well when the agents deleted. Let me know what you think, including rough psuedocode of what I'm suggesting

below changes are for cli.py

Create

def create(...):
    config_dict = _load_yaml(agent_config)
    config_format = config_dict.get(
        "config_format",
        NAT_WORKFLOW_CONFIG_FORMAT,
    )

    if config_format == NEMO_AGENTS_SPEC_CONFIG_FORMAT:
        config_dict = _validate_platform_agent_config_for_cli(
            config_dict,
            base_dir=agent_config.parent,
        )
    elif config_format == NAT_WORKFLOW_CONFIG_FORMAT:
        config_dict = inject_default_model(config_dict)
        ...
    else:
        typer.echo(f"Unsupported config format: {config_format}", err=True)
        raise typer.Exit(code=1)

    payload = {
        "name": name,
        "description": description,
        "config": config_dict,
        "config_format": config_format,
    }

    agent_created = False

    try:
        response = _api_request(
            "POST",
            base_url,
            f"/apis/agents/v2/workspaces/{workspace}/agents",
            json_body=payload,
        )
        agent_created = True

        if config_format == NEMO_AGENTS_SPEC_CONFIG_FORMAT:
            _upload_agent_spec_fileset(
                agent_name=name,
                workspace=workspace,
                agent_root=agent_config.parent,
                base_url=base_url,
            )
    except Exception:
        if agent_created:
            _delete_agent_and_spec_fileset(
                agent_name=name,
                workspace=workspace,
                base_url=base_url,
            )
        raise

    typer.echo(json.dumps(response, indent=2))

Delete

def delete(...):
    base_url = _resolve_base_url(base_url)

    if not yes:
        typer.confirm(f"Delete agent '{name}'?", abort=True)

    _delete_agent_and_spec_fileset(
        agent_name=name,
        workspace=workspace,
        base_url=base_url,
    )

    typer.echo(f"Agent '{name}' deleted.")

Fileset Upload

def _upload_agent_spec_fileset(...):
    _upload_directory_to_fileset(
        local_path=agent_root,
        fileset_name=agent_spec_fileset_name(agent_name),
        workspace=workspace,
        base_url=base_url,
    )

Agent And Fileset Deletion

def _delete_agent_and_spec_fileset(...):
    _api_request(
        "DELETE",
        base_url,
        f"/apis/agents/v2/workspaces/{workspace}/agents/{agent_name}",
    )

    try:
        _delete_fileset(
            fileset_name=agent_spec_fileset_name(agent_name),
            workspace=workspace,
            base_url=base_url,
        )
    except FilesetNotFound:
        pass

tylersbray added a commit that referenced this pull request Jul 31, 2026
Pair Fabric agent create/delete with the conventional {agent}-spec
fileset so Docker/K8s staging has artifacts to download. Roll back the
agent entity if upload fails; best-effort delete the fileset on agent
delete. Closes the producer gap flagged on #1007.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
@tylersbray

Copy link
Copy Markdown
Contributor Author

Good catch — agreed this belonged with the consumer staging work.

Landed on this branch: Fabric nemo agents create now uploads {agent}-spec from agent.yaml's parent directory (Fabric base_dir contract), with compensating rollback if upload fails after the agent entity is created. nemo agents delete removes the agent then best-effort deletes the fileset (missing fileset is fine). Reuses upload_to_fileset / sdk.files.filesets.delete rather than inventing parallel helpers.

Out of scope for this PR (happy to file follow-ups if useful):

  • Studio / raw API create paths still don't produce the fileset (server can't see local disk)
  • Agent update / re-upload when skills change

Thanks for the sketch — the create/delete pairing and rollback shape matched what we needed.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
plugins/nemo-agents/src/nemo_agents_plugin/cli.py (1)

1512-1519: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return the concrete NeMoPlatform type.

_platform_sdk returns Any, so callers lose type checking on .files.filesets. Import NeMoPlatform normally and annotate the return type.

♻️ Proposed refactor
-def _platform_sdk(base_url: str) -> Any:
+def _platform_sdk(base_url: str) -> NeMoPlatform:
     """Return an auth-aware platform SDK client for fileset upload/delete."""
-    from nemo_platform import NeMoPlatform
-
     headers = _resolve_context_headers()
     if headers:
         return NeMoPlatform(base_url=base_url, default_headers=headers)
     return NeMoPlatform(base_url=base_url)

Add the import at module level:

from nemo_platform import NeMoPlatform

If the lazy import exists to protect CLI startup latency, keep the local import and annotate with the concrete type instead.

As per coding guidelines: "In Python code, prefer concrete type hints over string-based type hints, and do not import those types only under TYPE_CHECKING; import them normally when possible."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py` around lines 1512 - 1519,
Update _platform_sdk to return the concrete NeMoPlatform type instead of Any,
and make NeMoPlatform a normal module-level import when possible. If the lazy
local import is required for startup behavior, retain it while annotating the
function with the concrete type so callers receive type checking for
.files.filesets.

Source: Coding guidelines

plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py (2)

104-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a non-NotFoundError fileset failure.

cli.py tolerates NotFoundError inside _delete_agent_spec_fileset and tolerates every other exception inside _delete_agent_and_spec_fileset. Only the first branch is tested. A regression that lets a generic fileset error fail the delete command stays undetected.

💚 Proposed test
def test_delete_succeeds_when_fileset_delete_errors(self, app) -> None:
    def handler(req: httpx.Request) -> httpx.Response:
        assert req.method == "DELETE"
        return httpx.Response(204)

    filesets = MagicMock()
    filesets.delete.side_effect = RuntimeError("boom")
    sdk = MagicMock()
    sdk.files.filesets = filesets

    with (
        _install_mock_transport(handler),
        patch(f"{_PATCH_PREFIX}._platform_sdk", return_value=sdk),
    ):
        result = runner.invoke(app, ["delete", "my-agent", "--yes", "--base-url", "http://test"])

    assert result.exit_code == 0, result.output
    assert "deleted" in result.output.lower()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py` around lines 104
- 123, Add a unit test alongside
test_delete_succeeds_when_fileset_already_absent covering a generic fileset
deletion failure: configure filesets.delete to raise RuntimeError, invoke the
same confirmed delete command with the mocked SDK and transport, then assert a
successful exit and “deleted” in the output. Preserve the existing NotFoundError
test unchanged.

34-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate _install_mock_transport in a shared test helper.

plugins/nemo-agents/tests/unit contains multiple copies with divergent patch targets and signatures. Move the helper to a shared module, add typed parameters, and allow the patch target to be specified where needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py` around lines 34 -
42, Move _install_mock_transport from the individual unit test module into a
shared helper under the unit test utilities, then update all callers to import
and reuse it. Give the helper typed handler and patch-target parameters,
defaulting the patch target to the existing httpx.Client path while allowing
callers with different targets to override it; preserve the current
MockTransport and real-client construction behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py`:
- Around line 1522-1542: Update _upload_agent_spec_fileset to stage only the
intended agent files from agent_root before calling upload_to_fileset, excluding
unrelated checkout content. Validate the staged files against the applicable
file-count and total-size limits, and abort before upload when either limit is
exceeded; preserve the existing fileset name, workspace, and SDK arguments.

In `@plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py`:
- Around line 57-61: Update the test assertion around
mock_delete.assert_called_once_with to use unittest.mock.ANY for the base_url
argument instead of reading mock_delete.call_args.kwargs["base_url"], and add
ANY to the existing unittest.mock imports.

In `@plugins/nemo-agents/tests/unit/test_cli.py`:
- Around line 265-287: Extend the assertions in the CLI create test to verify
that the captured uploaded["sdk_base_url"] equals "http://test". Keep the
existing assertions and fake_upload capture behavior unchanged.

---

Nitpick comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py`:
- Around line 1512-1519: Update _platform_sdk to return the concrete
NeMoPlatform type instead of Any, and make NeMoPlatform a normal module-level
import when possible. If the lazy local import is required for startup behavior,
retain it while annotating the function with the concrete type so callers
receive type checking for .files.filesets.

In `@plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py`:
- Around line 104-123: Add a unit test alongside
test_delete_succeeds_when_fileset_already_absent covering a generic fileset
deletion failure: configure filesets.delete to raise RuntimeError, invoke the
same confirmed delete command with the mocked SDK and transport, then assert a
successful exit and “deleted” in the output. Preserve the existing NotFoundError
test unchanged.
- Around line 34-42: Move _install_mock_transport from the individual unit test
module into a shared helper under the unit test utilities, then update all
callers to import and reuse it. Give the helper typed handler and patch-target
parameters, defaulting the patch target to the existing httpx.Client path while
allowing callers with different targets to override it; preserve the current
MockTransport and real-client construction behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6ddef55a-1b4c-4c1d-9f9b-4c4a3ec77ebf

📥 Commits

Reviewing files that changed from the base of the PR and between e982a03 and 8a45419.

📒 Files selected for processing (3)
  • plugins/nemo-agents/src/nemo_agents_plugin/cli.py
  • plugins/nemo-agents/tests/unit/test_cli.py
  • plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py

Comment thread plugins/nemo-agents/src/nemo_agents_plugin/cli.py
Comment thread plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py
Comment thread plugins/nemo-agents/tests/unit/test_cli.py
tylersbray added a commit that referenced this pull request Jul 31, 2026
Pair Fabric agent create/delete with the conventional {agent}-spec
fileset so Docker/K8s staging has artifacts to download. Roll back the
agent entity if upload fails; best-effort delete the fileset on agent
delete. Closes the producer gap flagged on #1007.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
@tylersbray
tylersbray force-pushed the AIRCORE-966-fabric-artifact-staging/tbray branch from 8a45419 to 2a54a1c Compare July 31, 2026 21:00

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py`:
- Around line 733-762: Update the rollback exception branches around
_delete_agent_and_spec_fileset in the config_format upload flow to call
typer.echo(..., err=True) for both typer.Exit and generic Exception failures.
Clearly tell the user that rollback failed and the agent may still exist,
indicating manual cleanup may be required, while preserving the existing
logger.error/logger.exception behavior and final exit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 58213727-0c31-4377-9d9b-559ad912581d

📥 Commits

Reviewing files that changed from the base of the PR and between 8a45419 and 2a54a1c.

📒 Files selected for processing (11)
  • plugins/nemo-agents/src/nemo_agents_plugin/cli.py
  • plugins/nemo-agents/src/nemo_agents_plugin/entities.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
  • plugins/nemo-agents/tests/unit/test_cli.py
  • plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py
  • plugins/nemo-agents/tests/unit/test_fabric_artifact_staging.py
  • plugins/nemo-agents/tests/unit/test_runner_deployments.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py

Comment on lines +733 to +762
if config_format == NEMO_AGENTS_SPEC_CONFIG_FORMAT:
try:
_upload_agent_spec_fileset(
agent_name=name,
workspace=workspace,
agent_root=agent_config.parent,
base_url=base_url,
)
except Exception as exc:
typer.echo(
f"Error: failed to upload agent spec fileset for {name!r}: {exc}",
err=True,
)
try:
_delete_agent_and_spec_fileset(
agent_name=name,
workspace=workspace,
base_url=base_url,
)
except typer.Exit:
logger.error(
"Failed to roll back agent %r after fileset upload failure",
name,
)
except Exception:
logger.exception(
"Failed to roll back agent %r after fileset upload failure",
name,
)
raise typer.Exit(code=1) from exc

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Surface rollback failures to the user.

When _upload_agent_spec_fileset fails, the code attempts to roll back the just-created agent through _delete_agent_and_spec_fileset. If that rollback itself fails (typer.Exit or another exception), the code only calls logger.error/logger.exception. It never calls typer.echo(err=True).

The user sees only the upload-failure message and exit code 1. The user does not learn that the agent may still exist on the platform. Add a typer.echo call in both except branches so the CLI tells the user when manual cleanup is needed.

🔧 Proposed fix
                 try:
                     _delete_agent_and_spec_fileset(
                         agent_name=name,
                         workspace=workspace,
                         base_url=base_url,
                     )
                 except typer.Exit:
                     logger.error(
                         "Failed to roll back agent %r after fileset upload failure",
                         name,
                     )
+                    typer.echo(
+                        f"Error: failed to roll back agent {name!r}; it may still exist on the platform.",
+                        err=True,
+                    )
                 except Exception:
                     logger.exception(
                         "Failed to roll back agent %r after fileset upload failure",
                         name,
                     )
+                    typer.echo(
+                        f"Error: failed to roll back agent {name!r}; it may still exist on the platform.",
+                        err=True,
+                    )
                 raise typer.Exit(code=1) from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if config_format == NEMO_AGENTS_SPEC_CONFIG_FORMAT:
try:
_upload_agent_spec_fileset(
agent_name=name,
workspace=workspace,
agent_root=agent_config.parent,
base_url=base_url,
)
except Exception as exc:
typer.echo(
f"Error: failed to upload agent spec fileset for {name!r}: {exc}",
err=True,
)
try:
_delete_agent_and_spec_fileset(
agent_name=name,
workspace=workspace,
base_url=base_url,
)
except typer.Exit:
logger.error(
"Failed to roll back agent %r after fileset upload failure",
name,
)
except Exception:
logger.exception(
"Failed to roll back agent %r after fileset upload failure",
name,
)
raise typer.Exit(code=1) from exc
if config_format == NEMO_AGENTS_SPEC_CONFIG_FORMAT:
try:
_upload_agent_spec_fileset(
agent_name=name,
workspace=workspace,
agent_root=agent_config.parent,
base_url=base_url,
)
except Exception as exc:
typer.echo(
f"Error: failed to upload agent spec fileset for {name!r}: {exc}",
err=True,
)
try:
_delete_agent_and_spec_fileset(
agent_name=name,
workspace=workspace,
base_url=base_url,
)
except typer.Exit:
logger.error(
"Failed to roll back agent %r after fileset upload failure",
name,
)
typer.echo(
f"Error: failed to roll back agent {name!r}; it may still exist on the platform.",
err=True,
)
except Exception:
logger.exception(
"Failed to roll back agent %r after fileset upload failure",
name,
)
typer.echo(
f"Error: failed to roll back agent {name!r}; it may still exist on the platform.",
err=True,
)
raise typer.Exit(code=1) from exc
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 762-762: use jsonify instead of json.dumps for JSON output
Context: json.dumps(resp, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py` around lines 733 - 762,
Update the rollback exception branches around _delete_agent_and_spec_fileset in
the config_format upload flow to call typer.echo(..., err=True) for both
typer.Exit and generic Exception failures. Clearly tell the user that rollback
failed and the agent may still exist, indicating manual cleanup may be required,
while preserving the existing logger.error/logger.exception behavior and final
exit.

…deployments

Download the agent-spec fileset at deploy time and materialize sibling files
(skills, prompts, etc.) under /workspace so Fabric relative paths resolve in
containers the same way they do locally. Closes AIRCORE-966.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
Pair Fabric agent create/delete with the conventional {agent}-spec
fileset so Docker/K8s staging has artifacts to download. Roll back the
agent entity if upload fails; best-effort delete the fileset on agent
delete. Closes the producer gap flagged on #1007.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
The spec fileset upload is recursive with no server-side filtering, so an
agent.yaml sitting in a source checkout would ship the whole tree and only
fail later when the ConfigMap/env payload is assembled. Check file count and
total size up front against limits shared with the staging consumer.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
@tylersbray
tylersbray force-pushed the AIRCORE-966-fabric-artifact-staging/tbray branch from 2a54a1c to a62bb57 Compare July 31, 2026 22:25

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
plugins/nemo-agents/tests/unit/test_cli.py (1)

264-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert _platform_sdk receives the correct base_url.

mock_sdk.return_value is fixed regardless of the call arguments. The test does not verify that _upload_agent_spec_fileset passes the correct base_url into _platform_sdk. A regression that hardcodes or drops base_url before calling _platform_sdk would not fail this test.

♻️ Proposed fix
     assert uploaded["local_dir"] == tmp_path
     assert uploaded["fileset"] == "fabric-agent-spec"
     assert uploaded["workspace"] == "default"
     assert uploaded["sdk_base_url"] == "http://test"
+    mock_sdk.assert_called_once_with("http://test")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/tests/unit/test_cli.py` around lines 264 - 293, Update
the test around the patched cli._platform_sdk mock so it asserts the mock was
called with base_url="http://test" during the create command. Keep the existing
return-value setup and upload assertions, ensuring the test verifies the
base_url propagated into _platform_sdk rather than only checking the SDK
instance afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py`:
- Around line 1577-1601: Add a user-visible typer.echo(..., err=True) call in
the exception handler of _delete_agent_and_spec_fileset, stating that the agent
was deleted but cleanup of its spec fileset failed and may require manual
removal. Keep the existing logger.warning behavior and successful deletion
output unchanged.

---

Nitpick comments:
In `@plugins/nemo-agents/tests/unit/test_cli.py`:
- Around line 264-293: Update the test around the patched cli._platform_sdk mock
so it asserts the mock was called with base_url="http://test" during the create
command. Keep the existing return-value setup and upload assertions, ensuring
the test verifies the base_url propagated into _platform_sdk rather than only
checking the SDK instance afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d7a5198c-3e17-4c0a-a796-0be8f69b32e3

📥 Commits

Reviewing files that changed from the base of the PR and between 2a54a1c and a62bb57.

📒 Files selected for processing (11)
  • plugins/nemo-agents/src/nemo_agents_plugin/cli.py
  • plugins/nemo-agents/src/nemo_agents_plugin/entities.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/deployments_backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
  • plugins/nemo-agents/tests/unit/test_cli.py
  • plugins/nemo-agents/tests/unit/test_cli_delete_undeploy.py
  • plugins/nemo-agents/tests/unit/test_fabric_artifact_staging.py
  • plugins/nemo-agents/tests/unit/test_runner_deployments.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/controller.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/backend.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/in_memory.py
  • plugins/nemo-agents/src/nemo_agents_plugin/entities.py
  • plugins/nemo-agents/src/nemo_agents_plugin/runner/fabric_artifact_staging.py

Comment on lines +1577 to +1601
def _delete_agent_spec_fileset(*, agent_name: str, workspace: str, base_url: str) -> None:
"""Best-effort delete of the conventional ``{agent}-spec`` fileset."""
from nemo_platform import NotFoundError

fileset_name = agent_spec_fileset_name(agent_name)
try:
_platform_sdk(base_url).files.filesets.delete(name=fileset_name, workspace=workspace)
except NotFoundError:
logger.info("Agent spec fileset %s/%s already absent", workspace, fileset_name)


def _delete_agent_and_spec_fileset(*, agent_name: str, workspace: str, base_url: str) -> None:
"""Delete the agent entity, then best-effort remove its spec fileset."""
_api_request("DELETE", base_url, f"/apis/agents/v2/workspaces/{workspace}/agents/{agent_name}")
try:
_delete_agent_spec_fileset(agent_name=agent_name, workspace=workspace, base_url=base_url)
except Exception:
logger.warning(
"Agent %r deleted but failed to remove spec fileset %r",
agent_name,
agent_spec_fileset_name(agent_name),
exc_info=True,
)


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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fileset cleanup failure is silent to the CLI user.

In _delete_agent_and_spec_fileset, if _delete_agent_spec_fileset raises, the code only calls logger.warning (Line 1594). The delete command then still prints "Agent 'name' deleted." The user has no way to know the {agent}-spec fileset may be orphaned and needs manual cleanup.

Add a typer.echo(..., err=True) in the except branch, mirroring the visibility gap already flagged for the create-rollback path.

🔧 Proposed fix
     try:
         _delete_agent_spec_fileset(agent_name=agent_name, workspace=workspace, base_url=base_url)
     except Exception:
         logger.warning(
             "Agent %r deleted but failed to remove spec fileset %r",
             agent_name,
             agent_spec_fileset_name(agent_name),
             exc_info=True,
         )
+        typer.echo(
+            f"Warning: agent {agent_name!r} deleted but failed to remove its spec fileset "
+            f"{agent_spec_fileset_name(agent_name)!r}; manual cleanup may be required.",
+            err=True,
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/src/nemo_agents_plugin/cli.py` around lines 1577 - 1601,
Add a user-visible typer.echo(..., err=True) call in the exception handler of
_delete_agent_and_spec_fileset, stating that the agent was deleted but cleanup
of its spec fileset failed and may require manual removal. Keep the existing
logger.warning behavior and successful deletion output unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants