Skip to content

feat(deployments): OpenShell sandboxed deployment backend (AIRCORE-872) - #662

Merged
maxdubrinsky merged 6 commits into
mainfrom
aircore-872-openshell-deployment-target/md
Jul 30, 2026
Merged

feat(deployments): OpenShell sandboxed deployment backend (AIRCORE-872)#662
maxdubrinsky merged 6 commits into
mainfrom
aircore-872-openshell-deployment-target/md

Conversation

@maxdubrinsky

@maxdubrinsky maxdubrinsky commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an OpenShell deployment backend to the nemo-deployments plugin, so an agent deployed through the standard deployments API runs inside an OpenShell sandbox. The backend generates a Landlock filesystem/process policy and a default-deny egress rule that only permits traffic back to the platform Inference Gateway, so the agent's model calls go through the platform and other egress is blocked at the sandbox boundary.

The create/package/deploy/invoke flow works end to end. The demo routes model traffic through gateway-managed inference.local, which drops the docker-specific host and proxy workarounds the earlier version needed. Getting-started notebook: plugins/nemo-deployments/examples/openshell/DEMO.ipynb.

What's here (AIRCORE-872)

Twenty commits, ordered so each builds on the last. The first six:

  • consume the published openshell SDK (892). Depends on the openshell PyPI package as the plugin's optional [openshell] extra and dev group. No vendored gRPC stubs and no wheel pin. Pinned >=0.0.92, with the exact version in uv.lock. The floor matters: the gateway started enforcing a 19-char routable-name limit in v0.0.88, so an older client contract is not compatible with a current gateway.
  • generate SandboxPolicy (891). Default-deny filesystem plus the always-injected platform-egress rule. A static YAML policy can override it.
  • OpenShell deployment backend (887). OpenShellDeploymentBackend implements the DeploymentBackend ABC (create, read_status, delete, expose) over the OpenShell gRPC gateway and applies the policy and serve workdir. Covered by mocked unit tests, an executor-config test, and a live-gateway integration test.
  • sandbox-runtime image profiles (888). A --sandbox-runtime openshell profile for nemo agents package, registered through the nemo.sandbox_profiles entry point.
  • wire the local openshell executor (889). Adds the openshell-local executor to local.yaml for the local end-to-end path.
  • deploy-sandbox skill and inference.local demo. The deploy-sandbox skill plus the getting-started notebook.

Review round (28 Jul)

Addressing @matthewgrossman's findings:

  • sandbox names were rejected by any gateway from v0.0.88 on. _sandbox_name emitted 20 chars against a 19-char limit, so every deployment failed with INVALID_ARGUMENT. Verified live: a 0.0.92 gateway rejects the old name and accepts the new 18-char one. Port names are clamped to the same limit, since ExposeService caps service names identically.
  • a dead workload reported READY. The detached serve command is unsupervised, so a process that exited (unresolvable model, rejected config) left an exposed port answering 502 behind a READY deployment. The reconciler now probes the workload's pid before exposing ports and on every poll, and reports FAILED with the tail of the serve log.
  • get_logs returned supervisor logs, not the agent's. GetSandboxLogs is fed by supervisor tracing events, never by the workload's redirected stdout, so the serve log is tailed over ExecSandbox instead.
  • static policy typos failed open. A policy mapping is now parsed with json_format.ParseDict against SandboxPolicy rather than rebuilt field by field, so an unknown key raises instead of vanishing, and every field OpenShell supports is accepted rather than the subset this backend writes. The two free-form strings the proto cannot type (landlock.compatibility, enforcement) are checked separately, because the supervisor reads anything unrecognised as the weaker setting.
  • the live integration test could not pass. It asserted created.endpoints after create went asynchronous; it now polls read_status to READY.

Status

In review. openshell is a normal PyPI dependency (>=0.0.92, resolved to 0.0.92 in the lock), so CI can resolve it and uv sync picks it up like any other dependency. The example compose pins the gateway image to the matching tag instead of :latest, overridable through IMAGE_TAG, so client and gateway cannot drift apart. Local checks pass: openshell unit tests (69), ruff check and format, ty. The live integration test passes against a 0.0.92 gateway.

Linear: AIRCORE-872

Summary by CodeRabbit

  • New Features
    • Added OpenShell deployment backend with default-deny sandbox policies and configurable platform egress controls.
    • Added an openshell-local executor for sandboxed local deployments.
    • Added --sandbox-runtime to nemo agents package, enabling sandbox-runtime-aware Dockerfile generation.
  • Documentation
    • Updated the OpenShell demo notebook and refreshed the deploy-sandbox runbook with governed sandbox guidance and troubleshooting notes.
  • Tests / CI
    • Added path-based CI execution plus a dedicated OpenShell test job and make test-deployments-openshell target.

@github-actions github-actions Bot added the feat label Jul 13, 2026
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 28200/36034 78.3% 62.7%
Integration Tests 16779/34752 48.3% 20.9%

@maxdubrinsky
maxdubrinsky force-pushed the aircore-872-openshell-deployment-target/md branch from 5b25cc0 to 3c9f550 Compare July 15, 2026 17:17
@maxdubrinsky
maxdubrinsky force-pushed the aircore-872-openshell-deployment-target/md branch 2 times, most recently from 04b9b31 to 0f88d5f Compare July 23, 2026 22:36
@maxdubrinsky
maxdubrinsky marked this pull request as ready for review July 27, 2026 17:35
@maxdubrinsky
maxdubrinsky requested review from a team as code owners July 27, 2026 17:35
@coderabbitai

coderabbitai Bot commented Jul 27, 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

Adds OpenShell sandbox deployment support, including runtime image packaging, policy generation, gateway-backed deployment lifecycle operations, local configuration, discovery, runbooks, examples, and targeted CI coverage.

Changes

OpenShell sandbox deployment

Layer / File(s) Summary
Change detection and image packaging
.github/actions/..., .github/workflows/ci.yaml, Makefile, packages/nemo_platform_plugin/..., plugins/nemo-agents/..., plugins/nemo-deployments/.../sandbox_profile.py
Adds path-filtered CI execution, discoverable sandbox image profiles, and validated --sandbox-runtime Dockerfile/image packaging.
Policy and deployment backend
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/..., plugins/nemo-deployments/tests/...
Adds OpenShell executor configuration, policy conversion, gRPC sandbox lifecycle management, endpoint exposure, logging, status handling, optional dependency behavior, and tests.
Runtime configuration and runbooks
packages/nmp_platform/config/local.yaml, plugins/nemo-deployments/examples/openshell/*, plugins/nemo-deployments/src/.../skills/*, packages/nemo_platform_ext/...
Adds the opt-in executor, gateway and policy examples, agent configuration, deployment skill, notebook, skill tests, and build-agent guidance.

Suggested labels: test

Suggested reviewers: tylersbray, crookedstorm, benmccown, svvarom, a2bondar

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.57% 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 Title clearly matches the main change: adding the OpenShell sandboxed deployment backend.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch aircore-872-openshell-deployment-target/md
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aircore-872-openshell-deployment-target/md

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: 6

🧹 Nitpick comments (4)
plugins/nemo-deployments/src/nemo_deployments_plugin/skills/deploy-sandbox/SKILL.md (1)

80-83: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the generate-certs image instead of :latest.

This skill is agent-executed (allowed-tools: [Bash, ...]); an unpinned mutable tag on ghcr.io/nvidia/openshell/gateway:latest is a rug-pull vector if the tag is ever repointed to a malicious image.

🔒 Pin the image
-  docker run --rm --user 0 -v /var/lib/openshell:/var/lib/openshell \
-    ghcr.io/nvidia/openshell/gateway:latest generate-certs \
+  docker run --rm --user 0 -v /var/lib/openshell:/var/lib/openshell \
+    ghcr.io/nvidia/openshell/gateway@sha256:<digest> generate-certs \

Based on the SkillSpector hint: "Docker image references without a specific tag (:latest is implicit) or digest ... can be silently replaced by a malicious image."

🤖 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-deployments/src/nemo_deployments_plugin/skills/deploy-sandbox/SKILL.md`
around lines 80 - 83, Update the docker run command for generate-certs to
replace the mutable ghcr.io/nvidia/openshell/gateway:latest reference with a
specific immutable image tag or digest. Keep the existing generate-certs
arguments and volume configuration unchanged.

Source: Linters/SAST tools

plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/config.py (1)

53-113: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider extra="forbid" and Literal for the enum-ish fields.

A typo in a config key (e.g. platform_egres:) is silently ignored today and the sandbox gets the default egress instead of the intended one — a silent security-relevant divergence. Same for protocol/access/tls, which document fixed value sets but accept anything; landlock_compatibility already uses Literal.

♻️ Proposed change
 class OpenShellExecutorConfig(BaseModel):
     """Knobs for a named openshell executor instance."""
 
+    model_config = ConfigDict(extra="forbid")
+
     gateway_endpoint: str = Field(
 class PlatformEgressConfig(BaseModel):
-    protocol: str = Field(default="rest", description="OpenShell endpoint protocol: rest | websocket | sql.")
+    protocol: Literal["rest", "websocket", "sql"] = Field(default="rest", description="OpenShell endpoint protocol.")
...
-    access: str = Field(default="full", description="OpenShell access level: read-only | read-write | full.")
+    access: Literal["read-only", "read-write", "full"] = Field(default="full", description="OpenShell access level.")
🤖 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-deployments/src/nemo_deployments_plugin/backends/openshell/config.py`
around lines 53 - 113, Update the relevant Pydantic configuration models,
including OpenShellExecutorConfig and the models defining protocol/access/tls
fields, to reject unknown configuration keys with extra="forbid". Replace
enum-like string annotations for protocol, access, and tls with
Literal-constrained values matching their documented supported options, while
preserving the existing landlock_compatibility Literal behavior.
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py (1)

462-486: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

touch always succeeds, so a serve command that dies instantly is still marked launched.

setsid <serve> ... & touch marker returns the exit status of touch, not the workload; only cd/shell errors surface here. The docstring's "catches launch-time failures" overstates it. Consider marking after a short liveness check (e.g. sleep 1; kill -0 $!) or reword the docstring.

🤖 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-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py`
around lines 462 - 486, The _launch_serve implementation marks the launch
successful based on touch alone, so immediately failing serve processes are not
detected. Add a short post-launch liveness check using the background process
PID before creating _LAUNCH_MARKER, ensuring the marker is written only when the
process remains alive; update the _launch_serve docstring to describe this
behavior accurately.
plugins/nemo-deployments/examples/openshell/DEMO.ipynb (1)

452-457: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale step reference. MODEL is set in the "Pick a model" cell, not Step 1.

🤖 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-deployments/examples/openshell/DEMO.ipynb` around lines 452 -
457, The troubleshooting text in the notebook incorrectly refers to Step 1 for
the MODEL configuration. Update that reference to identify the “Pick a model”
cell, leaving the surrounding troubleshooting guidance unchanged.
🤖 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 349-357: Prevent sandbox compatibility from being silently
bypassed: in plugins/nemo-agents/src/nemo_agents_plugin/cli.py lines 349-357,
reject --sandbox-runtime combined with --dockerfile before packaging; in
plugins/nemo-agents/src/nemo_agents_plugin/container/builder.py line 102, add
the same guard for direct callers using sandbox_runtime and dockerfile; in
plugins/nemo-agents/src/nemo_agents_plugin/container/template.py lines 329-349,
reject sandbox_runtime with external templates unless the template explicitly
enforces the required sandbox fragments.

In `@plugins/nemo-deployments/examples/openshell/DEMO.ipynb`:
- Line 355: Update the Step 3 description in the demo documentation to match the
zero-egress configuration: remove claims that a platform_egress or mandatory
nemo_platform egress rule is generated, and state that the policy contains no
network rules when platform_egress is null. Preserve the filesystem permissions
and run_as_user details.

In `@plugins/nemo-deployments/examples/openshell/docker-compose.yml`:
- Around line 41-63: The docker-compose gateway configuration exposes a root
Docker-socket service on all host interfaces without an explicit local-only
warning. Update the comments near user and ports to clearly state that this
setup is for local development only and must not run on shared or untrusted
networks, or change the port binding to the Docker bridge host IP while
preserving sandbox bridge reachability.

In `@plugins/nemo-deployments/examples/openshell/local-sandbox-policy.yaml`:
- Around line 46-49: Update the binaries section to document that the pinned
/opt/uv/python/cpython-3.13.7-linux-aarch64-gnu/bin/python3.13 entry is
host-specific and must be replaced for each architecture and Python version;
alternatively, verify policy.py supports globbing before using a portable
pattern.

In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py`:
- Around line 440-460: Update the serve-status flow around _serve_launched so
grpc.RpcError from its marker probe is caught and converted into the same
appropriate UNKNOWN/STARTING BackendStatusUpdate used for transient RPC
failures, rather than escaping read_status. Preserve the existing launch,
port-exposure, and READY behavior when the probe succeeds.

In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/sandbox_profile.py`:
- Around line 21-23: Update the OpenShell sandbox profile in sandbox_profile.py
to explicitly require glibc >= 2.39 rather than only documenting it. Ensure the
profile rendering includes a build-time preflight check that validates the
selected base image’s glibc version and fails image creation when it is below
2.39, including for arbitrary base-image overrides.

---

Nitpick comments:
In `@plugins/nemo-deployments/examples/openshell/DEMO.ipynb`:
- Around line 452-457: The troubleshooting text in the notebook incorrectly
refers to Step 1 for the MODEL configuration. Update that reference to identify
the “Pick a model” cell, leaving the surrounding troubleshooting guidance
unchanged.

In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py`:
- Around line 462-486: The _launch_serve implementation marks the launch
successful based on touch alone, so immediately failing serve processes are not
detected. Add a short post-launch liveness check using the background process
PID before creating _LAUNCH_MARKER, ensuring the marker is written only when the
process remains alive; update the _launch_serve docstring to describe this
behavior accurately.

In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/config.py`:
- Around line 53-113: Update the relevant Pydantic configuration models,
including OpenShellExecutorConfig and the models defining protocol/access/tls
fields, to reject unknown configuration keys with extra="forbid". Replace
enum-like string annotations for protocol, access, and tls with
Literal-constrained values matching their documented supported options, while
preserving the existing landlock_compatibility Literal behavior.

In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/skills/deploy-sandbox/SKILL.md`:
- Around line 80-83: Update the docker run command for generate-certs to replace
the mutable ghcr.io/nvidia/openshell/gateway:latest reference with a specific
immutable image tag or digest. Keep the existing generate-certs arguments and
volume configuration unchanged.
🪄 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: 1f9a5d1f-9916-4e3b-a259-2c8199c1f4f3

📥 Commits

Reviewing files that changed from the base of the PR and between 82cf2c1 and c8c6cfb.

⛔ Files ignored due to path filters (2)
  • sdk/python/nemo-platform/src/nemo_platform/skills/nemo-build-agent/SKILL.md is excluded by !sdk/**
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • .github/actions/changes/action.yaml
  • .github/workflows/ci.yaml
  • Makefile
  • packages/nemo_platform_ext/src/nemo_platform_ext/skills/nemo-build-agent/SKILL.md
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/discovery.py
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/sandbox.py
  • packages/nmp_platform/config/local.yaml
  • plugins/nemo-agents/src/nemo_agents_plugin/cli.py
  • plugins/nemo-agents/src/nemo_agents_plugin/container/builder.py
  • plugins/nemo-agents/src/nemo_agents_plugin/container/sandbox.py
  • plugins/nemo-agents/src/nemo_agents_plugin/container/template.py
  • plugins/nemo-agents/tests/unit/test_container.py
  • plugins/nemo-deployments/examples/openshell/DEMO.ipynb
  • plugins/nemo-deployments/examples/openshell/agent/config.yaml
  • plugins/nemo-deployments/examples/openshell/docker-compose.yml
  • plugins/nemo-deployments/examples/openshell/gateway.toml
  • plugins/nemo-deployments/examples/openshell/local-sandbox-policy.yaml
  • plugins/nemo-deployments/pyproject.toml
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/base.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/config.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/policy.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/sandbox_profile.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/registry.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/skills.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/skills/deploy-sandbox/SKILL.md
  • plugins/nemo-deployments/src/nemo_deployments_plugin/skills/deploy-sandbox/tests.json
  • plugins/nemo-deployments/tests/integration/backends/openshell/test_openshell_backend.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/conftest.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_executor_config.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_policy.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_sandbox_profile.py
  • plugins/nemo-deployments/tests/unit/test_registry.py

Comment thread plugins/nemo-agents/src/nemo_agents_plugin/cli.py
Comment thread plugins/nemo-deployments/examples/openshell/DEMO.ipynb Outdated
Comment thread plugins/nemo-deployments/examples/openshell/docker-compose.yml
@maxdubrinsky

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai. Triaged everything against the code and openshell/proto/sandbox.proto (the source of truth for the egress value sets). Disposition:

Addressing now (pushed to this branch):

  • docs(guardrails): Focus skills on content-safety flow; docs updates #5 (marker-probe RpcError escapes read_status): correct. Wrapped the provisioning call so a transient exec RPC error maps to UNKNOWN and retries on the next poll instead of propagating into the reconciler. The deliberate launch/expose failure paths already return FAILED and clean up, so this only catches unhandled transients.
  • Nitpick: config extra="forbid" + Literal: adopted extra="forbid" on all three models (the real win: a typo'd platform_egres no longer silently falls back to the default egress). Added Literal constraints, but using the proto value sets, which are broader than the field docstrings: protocol also allows graphql and "" (L4-only), and tls allows passthrough. Literals built from the docstrings would have rejected valid configs.

Acknowledged, deferred to follow-up work (not this PR):

No redesign required. Thanks for the review.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

backend=$(fd -a '^backend\.py$' plugins/nemo-deployments/src | rg '/openshell/')
config=$(fd -a '^config\.py$' plugins/nemo-deployments/src | rg '/openshell/')
tests=$(fd -a -t f . plugins/nemo-deployments/tests/unit/backends/openshell | tr '\n' ' ')

printf '%s\n' '--- backend outline ---'
ast-grep outline "$backend" --items all --type method,function --match 'read_status|_provision|_launch|_marker' --view expanded

printf '%s\n' '--- config outline ---'
ast-grep outline "$config" --items all --type class,method --match 'OpenShell|PlatformEgress|_validate' --view expanded

printf '%s\n' '--- implementation evidence ---'
rg -n -C 8 'RpcError|_provision|read_status|ConfigDict|extra\s*=|Literal\[|protocol:|tls:' \
  "$backend" "$config"

printf '%s\n' '--- relevant tests ---'
rg -n -C 4 'RpcError|UNKNOWN|extra|platform_egres|graphql|passthrough|protocol|tls' $tests

Length of output: 90125


@maxdubrinsky Verified:

  • #5 now maps unhandled provisioning RpcErrors to UNKNOWN; the marker-probe regression test covers retry behavior without cleanup.
  • All config models forbid unknown fields.
  • Egress Literals retain proto-valid graphql, "", and passthrough values, with tests.

Would you like follow-up issues for the deferred valid gaps?


✏️ Learnings added
Learnt from: maxdubrinsky
URL: https://github.com/NVIDIA-NeMo/nemo-platform/pull/662

Timestamp: 2026-07-27T18:26:45.732Z
Learning: For the OpenShell deployment backend, `openshell/proto/sandbox.proto` is authoritative for platform-egress enum-like values. `PlatformEgressConfig.protocol` supports `"rest"`, `"websocket"`, `"graphql"`, `"sql"`, and `""`; `PlatformEgressConfig.tls` supports `"terminate"`, `"passthrough"`, and `""`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@matthewgrossman

Copy link
Copy Markdown
Contributor

A few more things from my agent:


Findings

  1. [P1] Generated sandbox names are rejected by OpenShell 0.0.90. [_sandbox_name](https://github.com/NVIDIA-NeMo/nemo-platform/blob/680a1c346d1ff08d2e4bc8a3052d3d1e4e3b139e/plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py#L561-L569) always produces 20 characters (nmp- plus 16 hex digits), but [OpenShell v0.0.90 limits routable names to 19](https://github.com/NVIDIA/OpenShell/blob/v0.0.90/crates/openshell-server/src/grpc/mod.rs#L108-L113) and rejects longer names during creation. Every deployment against that release fails with INVALID_ARGUMENT. Reduce the generated name and update the unit test’s obsolete <=28 assertion.

  2. [P1] A dead workload is reported as READY. The launcher backgrounds the command and immediately touches the marker, so shell success does not prove the serve process started. The next poll exposes the port and returns READY; afterward, [existing service metadata is treated as proof of health](

    endpoints = await self._list_endpoints(sandbox_nm)
    if endpoints:
    return BackendStatusUpdate(status="READY", status_message="Sandbox serving", endpoints=endpoints)
    ), even after the process exits. The runbook confirms bad configs/models produce READY plus persistent 502s. This needs supervised execution or an actual readiness/liveness check before and after reaching READY.

  3. [P2] [get_logs](https://github.com/NVIDIA-NeMo/nemo-platform/blob/680a1c346d1ff08d2e4bc8a3052d3d1e4e3b139e/plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py#L366-L380) cannot return the agent’s logs. The workload redirects stdout/stderr to /tmp/nemo-serve.log, while get_logs calls GetSandboxLogs. OpenShell populates that API from supervisor tracing events, not the redirected workload file ([implementation](https://github.com/NVIDIA/OpenShell/blob/v0.0.90/crates/openshell-supervisor-process/src/log_push.rs#L4-L20)). Tail _SERVE_LOG through ExecSandbox, or launch the workload through a mechanism whose output OpenShell captures.

  4. [P2] Static policy typos can silently weaken confinement. [normalize_loaded_policy](https://github.com/NVIDIA-NeMo/nemo-platform/blob/680a1c346d1ff08d2e4bc8a3052d3d1e4e3b139e/plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/policy.py#L101-L124) and the proto builder accept arbitrary mappings and ignore unknown keys. For example, compatibilty: hard_requirement silently becomes compatibility: best_effort, allowing filesystem confinement to fail open on unsupported kernels. Validate override YAML with strict typed models before constructing the security policy.

  5. [P2] The live integration test cannot pass after the asynchronous-create refactor. create_deployment now returns STARTING without endpoints, but the live test immediately requires created.endpoints. It should poll read_status until READY, then obtain the endpoint.

The 57 targeted OpenShell unit tests pass, and current CI is green; the live gateway path is skipped without a running gateway. I did not repeat the already-open CodeRabbit findings or post comments to GitHub.

@mckornfield mckornfield 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.

hehe

Comment thread .github/workflows/ci.yaml
Comment thread packages/nemo_platform_plugin/src/nemo_platform_plugin/sandbox.py
Comment thread packages/nmp_platform/config/local.yaml Outdated
Comment thread packages/nmp_platform/config/local.yaml Outdated

@tylersbray tylersbray 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.

Nonblocking approval, modulo conflicts, happy CI, stripping ticket numbers out, etc etc. Looks good.

Comment thread plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/config.py Outdated
Add the `openshell` Python SDK as the nemo-deployments plugin's optional
`[openshell]` extra, alongside grpcio/protobuf. The SDK is on PyPI and
resolves like any other dependency; no vendored gRPC stubs and no local
wheel pin. It is kept out of the dev dependency-group because the wheel
is platform-restricted (manylinux_2_39 / macOS 13 arm64, no sdist) and a
dev-group entry would break `uv sync --all-packages` on older hosts.

Pin as `openshell>=0.0.83` and let `uv.lock` hold the exact resolved
version so the lockfile carries the pin without churning pyproject on
every SDK bump.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Build the OpenShell SandboxPolicy for a deployment: a default-deny
filesystem/process allowlist plus a mandatory, always-injected
platform-egress rule so the sandbox's only outbound path is back to the
Inference Gateway. A static-YAML policy override is supported for
callers that need to hand-tune the allowlist, validated against the
policy proto.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Implement OpenShellDeploymentBackend against the DeploymentBackend ABC
(create / read_status / delete / expose) over the OpenShell gRPC
gateway, and register it in the backend registry. The backend applies
the generated SandboxPolicy and serve workdir when it stands a
deployment up, drives provisioning from read_status, resolves secrets,
and only exposes ports once a live serve pid is present, so an agent
deployed through the standard deployments API runs inside a
policy-governed sandbox. Covered by mocked unit tests, an
executor-config test, and a live-gateway integration test.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Add a provider-neutral `--sandbox-runtime` image profile to
`nemo agents package` so an agent can be packaged for a sandbox runtime
(openshell) without hand-editing the container build. The
nemo-deployments plugin contributes the `openshell` profile via the
`nemo.sandbox_profiles` entry point; the agents container
builder/template consume the selected profile. build-agent skill docs
updated to mention the flag.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Register an `openshell-local` executor in the local platform config so
the OpenShell backend is reachable end-to-end through the deployments
API on a developer machine.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
@maxdubrinsky
maxdubrinsky force-pushed the aircore-872-openshell-deployment-target/md branch from 21ff025 to a013991 Compare July 30, 2026 17:16
Add the `deploy-sandbox` skill (with its test manifest) that walks the
create -> package -> deploy -> invoke flow for a policy-governed
OpenShell sandbox, and a self-contained getting-started notebook under
examples/openshell. The demo routes the agent's model traffic through
gateway-managed `inference.local`, so egress governance holds with zero
sandbox egress rules and no docker-specific host/proxy hacks. A
dedicated CI job installs the `[openshell]` extra and covers the
backend.

AIRCORE-872

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
@maxdubrinsky
maxdubrinsky force-pushed the aircore-872-openshell-deployment-target/md branch from a013991 to d99e6e0 Compare July 30, 2026 17:37
@maxdubrinsky
maxdubrinsky added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit d870f93 Jul 30, 2026
58 checks passed
@maxdubrinsky
maxdubrinsky deleted the aircore-872-openshell-deployment-target/md branch July 30, 2026 18:23
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.

5 participants