fix(agents): resolve or reject a deployment image at submit time [ASTD-532] - #1727
Draft
marcusds wants to merge 1 commit into
Draft
fix(agents): resolve or reject a deployment image at submit time [ASTD-532]#1727marcusds wants to merge 1 commit into
marcusds wants to merge 1 commit into
Conversation
Contributor
|
marcusds
force-pushed
the
astd-532-reject-deployment-without-resolvable-image/mschwab
branch
from
September 2, 2026 18:44
0760592 to
219b6d7
Compare
15 tasks
The runner resolves 'image or deployments.default_image' and fails the
deployment when neither is set. The API did not look at the image at all, so
a docker or k8s deployment submitted without one returned 201 with status
pending and only failed on the next reconcile — a mistake that was knowable
at submit time, reported asynchronously.
Refuse the request instead. Nothing is created, and the message names both
ways to satisfy it. An operator who has configured default_image is
unaffected: the request stays empty and the runner still applies the default.
The server is the only place that knows this, so it stays the only place that
decides it; clients surface the rejection rather than predicting it.
Also type a pre-existing resources={...} literal in this test module; ty
rejects it and would otherwise block committing alongside it.
Signed-off-by: mschwab <mschwab@nvidia.com>
marcusds
force-pushed
the
astd-532-reject-deployment-without-resolvable-image/mschwab
branch
from
September 2, 2026 19:25
219b6d7 to
f972b57
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
agents.deployments.default_imagewas only ever read by the deployment runner, so nothing upstream knew whether an image was required. A docker or k8s deployment submitted without one came back 201 with statuspendingand then failed on the next reconcile — a mistake that was knowable at submit time, reported asynchronously.This refuses that request at the boundary, and exposes the resolved default so a client can tell "you must supply an image" from "one will be supplied for you".
Before and after, no
default_imageconfigured:POST /deploymentswith no image, modedockerpending, entity created, fails on reconcileGET /deployments/defaults{"default_image": ""}An operator who has configured
default_imageis unaffected: the request still succeeds with an emptyimage, and the runner still applies the default. That path now works from Studio too, which previously refused to send it.Related Issue
ASTD-532
Changes
api/v2/deployments.py: refuse a container-mode deployment when neitherimagenordeployments.default_imageresolves. Placed beside the existinguse_image_entrypointguard, which validates the same class of thing on the same request.api/v2/deployments.py:GET /deployments/defaults, returning the image a docker/k8s deployment gets when it omits one.schema.py:DeploymentDefaultsresponse model.plugins/nemo-agents/openapi/openapi.yaml: regenerated.Notes for reviewers
use_image_entrypoint requires deployment_mode 'docker' or 'k8s'— is a 400 for an equivalent mistake on the same endpoint. Two neighbouring guards disagreeing about the code for the same class of error is worse than either choice. Happy to move both to 422 if that is the preferred direction; I did not want to change existing behaviour by side effect./deployments/{name}already existed and matches"defaults", so declaring the new route after it would silently turn this into a lookup for a deployment nobody has. It is declared first, with a comment, andtest_defaults_is_not_swallowed_by_the_deployment_name_routepins it by asserting the entity client is never called. Moving the route below{name}fails all three defaults tests.nemo-iron-swarmalready servesGET /model-config-defaultsfor exactly this — server-side config a form needs in order to pre-fill.Not included
CreateDeploymentModal, "Container image is required for Docker and Kubernetes deployments"). Nothing consumes this endpoint yet; relaxing that rule to match the server is the client half and belongs on a Studio branch.make update-sdkproduces no change for this endpoint — see Verification.Type of Change
Quality Gates
default_imagetoday.Verification
Signed-off-by:traileruv run pre-commit run -apasses, or any blocked checks are identified belowTargeted validation:
uv run pytest plugins/nemo-agents/tests/unit/test_deployments_api.pyuv run pytest plugins/nemo-agents/tests/unittest_port_allocation.py, whichbind()s a socket and is blocked by the local sandbox — unrelated, and failing onmainuv run ruff check/ruff format --checkon changed filesuv run --frozen ty checkon changed filesmake refresh-openapiBoth new behaviours were checked against a deliberately broken build, so the tests are not decorative:
test_create_rejects_container_deployment_with_no_resolvable_image./deployments/defaultsbelow/deployments/{name}fails all three defaults tests.Why there is no SDK change.
AGENTS.mdasks formake update-sdkwhen endpoints change, so I ran it. It regenerated 31 files as 1 insertion and 94 deletions, none of them this endpoint — because Stainless generates the Python SDK from the rootopenapi/openapi.yaml, and agents plugin endpoints are not in it:The SDK has never covered these endpoints, including the five that already existed. That deletion-only diff is pre-existing drift between the checked-in SDK and current Stainless output, and committing it here would remove unrelated content while adding nothing. I reverted it rather than fold it into this PR. It looks worth its own issue.
Blocked pre-commit hooks, both environmental and neither related to this change:
uv-lock— requires exactly uv 0.9.14 onPATH; this machine has 0.9.30. No dependency files are touched here.helm-docs— the binary is not installed locally. No Helm files are touched.make refresh-openapialso needs to run outside the macOS sandbox:uvpanics there with the knownsystem_configuration::dynamic_storecrash noted inCLAUDE.md.