Skip to content

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

Draft
maxdubrinsky wants to merge 14 commits into
mainfrom
aircore-872-openshell-deployment-target/md
Draft

feat(deployments): OpenShell sandboxed deployment backend (AIRCORE-872)#662
maxdubrinsky wants to merge 14 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)

Six commits, ordered so each builds on the last:

  • 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.83, with the exact version in uv.lock.
  • 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.

Status

Still a draft, pending review. openshell is a normal PyPI dependency (>=0.0.83, resolved to 0.0.90 in the lock), so CI can resolve it and uv sync picks it up like any other dependency. Local checks pass: openshell unit tests (35), agents container tests (102), ruff check and format.

Linear: AIRCORE-872

@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 27260/34983 77.9% 62.2%
Integration Tests 16018/33695 47.5% 19.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 from a3e43ad to 04b9b31 Compare July 22, 2026 18:57
Add the `openshell` Python SDK as the nemo-deployments plugin's optional
`[openshell]` extra and dev dependency-group, alongside grpcio/protobuf.
The SDK is on PyPI and resolves like any other dependency; no vendored
gRPC stubs and no local wheel pin.

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 (892)

Signed-off-by: Max Dubrinsky <[email protected]>
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.

AIRCORE-872 (891)

Signed-off-by: Max Dubrinsky <[email protected]>
Implement OpenShellDeploymentBackend against the DeploymentBackend ABC
(create / read_status / delete / expose) over the OpenShell gRPC
gateway, and register it in BACKEND_CLASSES. The backend applies the
generated SandboxPolicy and serve workdir when it stands a deployment
up, 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 (887)

Signed-off-by: Max Dubrinsky <[email protected]>
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 (888)

Signed-off-by: Max Dubrinsky <[email protected]>
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 (889)

Signed-off-by: Max Dubrinsky <[email protected]>
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.

AIRCORE-872

Signed-off-by: Max Dubrinsky <[email protected]>
@maxdubrinsky
maxdubrinsky force-pushed the aircore-872-openshell-deployment-target/md branch from 04b9b31 to 0f88d5f Compare July 23, 2026 22:36
openshell is a platform-restricted wheel present only in the plugin dev group and its [openshell] extra, so CI's root-level uv run never installs it (unlike docker/kubernetes, which are main deps of core packages). Five CI checks failed on its absence.

Handle it gracefully instead of requiring it:
- add MissingBackendDependencyError; the executor registry skips a missing-dep executor with a warning rather than crashing the whole deployments service at startup (the default local.yaml now registers an opt-in openshell executor, which broke e2e service boot).
- importorskip / skipif the openshell backend tests so they skip where the extra is not installed and still run where it is.
- mark the optional openshell imports with ty: ignore[unresolved-import].
- reformat the demo notebook (ruff).

Add registry tests for the skip path and for a missing-dep default_executor still failing fast.

Signed-off-by: Max Dubrinsky <[email protected]>
The graceful-degradation change means the shared unit-test job skips the OpenShell backend tests (the platform-restricted openshell extra is not in the default sync). Add an isolated, path-filtered job that installs the extra and runs the backend tests for real, so the backend is not left to local-only testing. Kept separate so a wheel-install failure fails only this small job, not the whole unit suite (mirrors the nemo-guardrails extra job).

Signed-off-by: Max Dubrinsky <[email protected]>
The PR added a second top-level deployments: key, so YAML last-wins dropped the local-docker executor and default_executor from the deployments registry while models.controller still referenced local-docker. Merge into one block: both executors plus default_executor: local-docker, sandbox stays opt-in per deployment.

Signed-off-by: Max Dubrinsky <[email protected]>
openshell is a platform-restricted manylinux_2_39 wheel with no sdist; keeping it in the dev group made uv sync --all-packages (bootstrap) hard-fail on glibc < 2.39 / older macOS, since the lock marker gates arch/OS but not glibc. Rely on the [openshell] extra; the dedicated CI job installs it to cover the backend.

Signed-off-by: Max Dubrinsky <[email protected]>
… read_status

create_deployment now resolves DeploymentConfig secrets (matching docker/k8s) and fails with named vars on unresolved secretRef/valueFrom instead of silently dropping them.

Provisioning (wait for READY, launch the serve command, expose ports) moves out of the blocking create path into a read_status state machine, so a slow sandbox no longer stalls the serial reconcile loop. The serve launcher writes a marker file so read_status (stateless across polls) does not relaunch it. A non-zero launcher exit fails the deployment, and any provisioning failure best-effort deletes the sandbox so it does not leak behind a terminal FAILED deployment.

Also drops house-style em-dashes from the module docstring/comments.

Signed-off-by: Max Dubrinsky <[email protected]>
A loaded/override policy YAML that omitted enforcement ran egress rules in audit (observe-but-allow) mode, and one omitting process/landlock ran as the image-default user with no filesystem confinement. Default network enforcement to enforce and normalize loaded policies to inject the sandbox process block and a landlock block, matching the generated path.

Add a validated landlock_compatibility knob (default best_effort to keep the local-dev docker path working; opt into hard_requirement to fail closed on kernels without Landlock).

Nits: fix the tls-passthrough comment, align the gateway_endpoint default to the documented :17670, drop em-dashes.
Signed-off-by: Max Dubrinsky <[email protected]>
…cal design

The skill documented the old direct-egress model (platform_egress present, agent base_url at host.docker.internal:8080), but the repo ships gateway-managed inference.local with platform_egress: null, so following the skill literally produced a blocked-egress 502. Rewrite onto the shipped design: add the one-time provider create / inference set wiring and the mandatory generate-certs and telemetry-trim preflight steps, keep direct-egress as an explicit alternate branch, and fix the version-pin and unicode nits.

Signed-off-by: Max Dubrinsky <[email protected]>
An unrelated edit renumbered the 'Verify the wire' step from 5. to 1., restarting the ordered list mid-sequence. Restore 5. in both skill copies.

Signed-off-by: Max Dubrinsky <[email protected]>
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.

1 participant