Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6076,7 +6076,7 @@ nemo agents deploy [OPTIONS]
* `--mode`: Runtime backend: subprocess (default), docker, or k8s. [default: subprocess]
* `--image, -i`: Container image for docker/k8s modes (falls back to deployments.default_image).
* `--use-image-entrypoint`: For docker/k8s modes, preserve the image ENTRYPOINT/CMD instead of injecting the platform-owned agent server command.
* `--environment, -e`: AgentEnvironment to deploy under, as a 'workspace/name' ref (e.g. 'default/repo-research-ben'). Its EnvironmentSpec is merged into the agent config and its ComputeSpec/secret refs are snapshotted onto the deployment at create time.
* `--environment, -e`: AgentEnvironment for this deployment: a "workspace/name" ref, or an inline JSON object whose environment_spec / sandbox_spec / compute_spec are each a ref or inline spec (e.g. '`{"environment_spec": "default/prod-env", "sandbox_spec": {"provider": "openshell"}}`'). Resolved and snapshotted at create time.
* `--wait, --no-wait`: Wait for the deployment to reach a terminal status (running or failed) before returning. Exits 0 only on running; exits 1 with the failure reason if runtime startup fails or readiness times out. Pass --no-wait for fire-and-forget behaviour (the original default — returns the pending deployment immediately as JSON).
* `--timeout, -t <INTEGER>`: Maximum seconds to wait for a terminal status (only with --wait). [default: 300]
* `--workspace, -w`: [default: default]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class _AgentsMethods:
list_compute_specs = method(endpoints.list_compute_specs)
get_compute_spec = method(endpoints.get_compute_spec)
delete_compute_spec = method(endpoints.delete_compute_spec)
create_sandbox_spec = method(endpoints.create_sandbox_spec)
list_sandbox_specs = method(endpoints.list_sandbox_specs)
get_sandbox_spec = method(endpoints.get_sandbox_spec)
delete_sandbox_spec = method(endpoints.delete_sandbox_spec)
invoke_agent = method(endpoints.invoke_agent)
invoke_deployment = method(endpoints.invoke_deployment)
create_execute_job = method(endpoints.create_execute_job)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
AgentJobResult,
AgentJobResultListResponse,
AgentJobStatusResponse,
AgentSandboxSpec,
AgentSession,
CreateAgentDeploymentRequest,
CreateAgentRequest,
CreateComputeSpecRequest,
CreateEnvironmentRequest,
CreateEnvironmentSpecRequest,
CreateExecuteJobRequest,
CreateSandboxSpecRequest,
CreateSessionRequest,
DeploymentLogsQueryParams,
DeploymentLogsResponse,
Expand All @@ -52,6 +54,7 @@
_ENVIRONMENT_SPECS = f"{_PREFIX}/environment-specs"
_ENVIRONMENTS = f"{_PREFIX}/environments"
_COMPUTE_SPECS = f"{_PREFIX}/compute-specs"
_SANDBOX_SPECS = f"{_PREFIX}/sandbox-specs"
_EXECUTE_JOBS = f"{_PREFIX}/jobs/execute"
_JOBS = f"{_PREFIX}/jobs/{{collection}}"

Expand Down Expand Up @@ -232,6 +235,28 @@ def get_compute_spec(*, workspace: str | None = None, name: str) -> AgentCompute
def delete_compute_spec(*, workspace: str | None = None, name: str) -> None: ...


@post(_SANDBOX_SPECS)
@abstractmethod
def create_sandbox_spec(*, workspace: str | None = None, body: CreateSandboxSpecRequest) -> AgentSandboxSpec: ...


@get(_SANDBOX_SPECS)
@abstractmethod
def list_sandbox_specs(
*, workspace: str | None = None, query_params: ListEnvironmentResourcesQueryParams | None = None
) -> Paginated[AgentSandboxSpec]: ...


@get(f"{_SANDBOX_SPECS}/{{name}}")
@abstractmethod
def get_sandbox_spec(*, workspace: str | None = None, name: str) -> AgentSandboxSpec: ...


@delete(f"{_SANDBOX_SPECS}/{{name}}")
@abstractmethod
def delete_sandbox_spec(*, workspace: str | None = None, name: str) -> None: ...


# ---------------------------------------------------------------------------
# Gateway invocation
# ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,39 @@ class EnvironmentSpecInline(BaseModel):
)


class SandboxSpecInline(BaseModel):
"""Inline sandbox spec - the isolation posture around an agent run.

``provider`` names a deployments-substrate backend; the accepted set is
whatever that substrate declares. ``provider_config`` is validated by the
named backend, not interpreted by the agents plugin.
"""

description: str = Field(default="", description="Human-readable description.")
provider: str = Field(description="Deployments-substrate backend that isolates the run (e.g. 'openshell').")
provider_config: JsonMap = Field(
default_factory=dict,
description="Backend-specific sandbox configuration, validated by the named backend.",
)


class AgentEnvironmentInline(BaseModel):
"""Inline AgentEnvironment - a composition of environment + compute specs.
"""Inline AgentEnvironment - a composition of environment, sandbox, and compute specs.

Each part is a ``ref | inline | None`` union: a ``"workspace/name"`` string
references a stored spec entity, an object provides the spec inline, and
``None`` omits it. (A ``sandbox_spec`` is out of scope for now and omitted.)
``None`` omits it.
"""

description: str = Field(default="", description="Human-readable description.")
environment_spec: str | EnvironmentSpecInline | None = Field(
default=None,
description='"workspace/name" ref to an AgentEnvironmentSpec, an inline spec, or None.',
)
sandbox_spec: str | SandboxSpecInline | None = Field(
default=None,
description='"workspace/name" ref to an AgentSandboxSpec, an inline spec, or None.',
)
compute_spec: str | ComputeSpecInline | None = Field(
default=None,
description='"workspace/name" ref to an AgentComputeSpec, an inline spec, or None.',
Expand Down Expand Up @@ -230,8 +250,12 @@ class AgentEnvironmentSpec(EntityMetadata, EnvironmentSpecInline):
"""A reusable environment spec entity."""


class AgentSandboxSpec(EntityMetadata, SandboxSpecInline):
"""A reusable sandbox spec entity."""


class AgentEnvironment(EntityMetadata, AgentEnvironmentInline):
"""A reusable composition of environment and compute specs."""
"""A reusable composition of environment, sandbox, and compute specs."""


class AgentDeployment(EntityMetadata):
Expand All @@ -250,6 +274,10 @@ class AgentDeployment(EntityMetadata):
default=None,
description="Resolved compute snapshot from the referenced environment.",
)
sandbox: SandboxSpecInline | None = Field(
default=None,
description="Resolved sandbox spec snapshot (provider + provider_config) from the referenced environment.",
)
secrets: StringMap = Field(default_factory=dict, description="Resolved secret env references.")
status: DeploymentStatus = Field(default="pending", description="Deployment lifecycle status.")
deployment_mode: DeploymentMode = Field(default="subprocess", description="Runtime backend.")
Expand Down Expand Up @@ -355,6 +383,12 @@ class CreateComputeSpecRequest(ComputeSpecInline):
name: str = Field(description="Unique compute-spec name within the workspace.")


class CreateSandboxSpecRequest(SandboxSpecInline):
"""Request body for ``POST /v2/workspaces/{workspace}/sandbox-specs``."""

name: str = Field(description="Unique sandbox-spec name within the workspace.")


class InvokeAgentRequest(BaseModel):
"""OpenAI chat-completions request body for agent invocation."""

Expand Down Expand Up @@ -427,6 +461,7 @@ class AgentJob(BaseModel):
SessionPage = Page[AgentSession]
EnvironmentPage = Page[AgentEnvironment]
EnvironmentSpecPage = Page[AgentEnvironmentSpec]
SandboxSpecPage = Page[AgentSandboxSpec]
ComputeSpecPage = Page[AgentComputeSpec]
AgentJobPage = Page[AgentJob]
AgentJobResultListResponse = PlatformJobListResultResponse
Expand Down
16 changes: 16 additions & 0 deletions packages/nemo_platform_plugin/tests/agents/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
AgentJobRequest,
AgentJobResultListResponse,
AgentJobStatusResponse,
AgentSandboxSpec,
AgentSession,
CreateAgentRequest,
CreateComputeSpecRequest,
CreateDeploymentRequest,
CreateEnvironmentRequest,
CreateEnvironmentSpecRequest,
CreateExecuteJobRequest,
CreateSandboxSpecRequest,
CreateSessionRequest,
DeploymentLogsResponse,
InvokeAgentRequest,
Expand Down Expand Up @@ -135,6 +137,10 @@ def test_environment_resources() -> None:
workspace="default",
body=CreateComputeSpecRequest(name="small"),
)
sandbox_create = endpoints.create_sandbox_spec(
workspace="default",
body=CreateSandboxSpecRequest(name="isolated", provider="openshell", provider_config={"policy": "strict"}),
)

assert env_spec_create.path_template == f"{_PREFIX}/environment-specs"
assert env_spec_create.response_type is AgentEnvironmentSpec
Expand All @@ -149,6 +155,16 @@ def test_environment_resources() -> None:
assert get_origin(endpoints.list_compute_specs(workspace="default").response_type) is Paginated
assert endpoints.get_compute_spec(workspace="default", name="small").response_type is AgentComputeSpec
assert endpoints.delete_compute_spec(workspace="default", name="small").response_type is None
assert sandbox_create.path_template == f"{_PREFIX}/sandbox-specs"
assert sandbox_create.response_type is AgentSandboxSpec
assert _json_body(sandbox_create) == {
"name": "isolated",
"provider": "openshell",
"provider_config": {"policy": "strict"},
}
assert get_origin(endpoints.list_sandbox_specs(workspace="default").response_type) is Paginated
assert endpoints.get_sandbox_spec(workspace="default", name="isolated").response_type is AgentSandboxSpec
assert endpoints.delete_sandbox_spec(workspace="default", name="isolated").response_type is None


def test_gateway_invocation() -> None:
Expand Down
Loading
Loading