Skip to content

chore: import the Python SDK history from NVIDIA/OpenShell - #2

Merged
pimlock merged 78 commits into
mainfrom
chore/import-history/pimlock
Nov 9, 2026
Merged

pimlock merged 78 commits into
mainfrom
chore/import-history/pimlock

Conversation

@pimlock

@pimlock pimlock commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Imports the history of the Python SDK from NVIDIA/OpenShell into this repository. This is the first step of the move, following the Go SDK's import (ai-openshell/sdk-go#3) and the migration notes in NVIDIA/OpenShell discussion 5273. Tracking: NVIDIA/OpenShell#5287.

  • Freeze point: the v0.1.7 release tag, 5d25211b2. Freezing at a tag means this repository starts from exactly the code the openshell==0.1.7 wheel was built from. sdk-go froze at a later main commit, and the Go notes list that as a lesson. No Python SDK file has changed in core since v0.1.7.
  • Authorship: authors, dates and sign-offs are unchanged. Bare #N references in commit messages now read NVIDIA/OpenShell#N, so they keep pointing at core.
  • README: the merge keeps this repository's initial README; the SDK had none in core.

What was extracted

The Python SDK isn't a single directory in core, so the history was filtered with git filter-repo --paths-from-file instead of --subdirectory-filter. The rules map core paths to this layout:

Core path Here
python/openshell/ src/openshell/
python/openshell/*_test.py, python/*_test.py (SDK tests) tests/
python/navigator/ (the package before its rename) src/navigator/, history only
pyproject.toml (the SDK's package definition in core) pyproject.toml
tasks/scripts/generate_python_proto.py, verify-python-wheel.py scripts/

Some tests exercise core packaging, release tooling or gateway config rather than the SDK. They stay in core: release_formula, rpm_gateway_config_migration, gateway_config_fixture, gateway_schema_v2_* and release_tooling.

Keeping python/navigator/ means git log --follow and blame reach back to the first commit.

The extraction scripts and rules (hack/migration/) come in the follow-up, #3, as in sdk-go.

Verification

verify-tree.sh applies the same rules to a one-commit copy of core's tree at the tag and compares tree hashes:

expected tree: 5619e0c472380eafb91a45567327bf5dc3bf6df2
actual tree:   5619e0c472380eafb91a45567327bf5dc3bf6df2
commits:       76
authors:       24
sign-offs:     317
bare #N refs:  0
OK

The merge's second parent has tree 5619e0c4…; the merge commit adds only the existing README.md.

This branch only imports history. It doesn't build on its own yet: pyproject.toml still points at python/, and proto generation still expects core's proto/. #3 makes it standalone.

DCO

34 of the 76 imported commits have no Signed-off-by trailer. They are core squash merges from January to August 2026 whose messages didn't carry the trailer. The required DCO check will flag them. Adding sign-offs now would mean signing on other people's behalf, so the history is imported unchanged. The check needs a maintainer override for this PR only; every commit written in this repository is signed off.

How to merge

Merge with a merge commit, not squash or rebase. The merge commit is what carries the 76 imported commits; a squash would drop the history this PR exists for.

Relation to #1

This keeps the move itself non-breaking: same package, same openshell._proto modules, same protobuf runtime. The Protobuf-Py switch in #1 can rebase onto the result as a separate, announced change.

drew and others added 30 commits January 29, 2026 17:33
…ons (!17)

Closes NVIDIA/OpenShell#24

## Problem

SSH sessions into the sandbox were **not entering the network namespace** or receiving proxy environment variables. This meant every command run via SSH (the only user-facing path) had unrestricted internet access, completely bypassing OPA network policy enforcement.

The root cause: the SSH server was started **before** the network namespace and proxy were created in `lib.rs`, so it never received the netns fd or proxy URL.

Additionally, **gRPC inference from within the sandbox did not work at all** — even after fixing the netns, multiple issues prevented the Python SDK from reaching the navigator server through the CONNECT proxy.

## Changes

### Sandbox binary

**Core fix — reorder startup + thread netns through SSH:**
- `lib.rs`: Move netns + proxy creation before SSH server start. Compute `ssh_netns_fd` and `ssh_proxy_url`, pass them to `run_ssh_server()`.
- `ssh.rs`: Thread `netns_fd` and `proxy_url` through the full SSH call chain into `spawn_pty_shell()`. Set proxy env vars on the shell command. Call `setns(fd, CLONE_NEWNET)` in `install_pre_exec()`.

**Proxy — control plane allowlist + IPv6 socket lookup:**
- `proxy.rs`: Connections to the navigator endpoint (derived from `NAVIGATOR_ENDPOINT`) are always allowed without OPA evaluation, logged with `engine=control_plane`. This is infrastructure the sandbox needs to function, not a user-configurable policy.
- `procfs.rs`: Extended `parse_proc_net_tcp` to also check `/proc/<pid>/net/tcp6`. gRPC C-core uses `AF_INET6` sockets even for IPv4 connections, so its TCP entries were invisible to the proxy's identity resolver. Also fixed port parsing to use `rsplit_once(':')` for correct IPv6 address handling.

**Proxy env vars — lowercase variants for gRPC C-core:**
- `process.rs` + `ssh.rs`: Added lowercase `http_proxy`, `https_proxy`, `grpc_proxy` alongside uppercase. gRPC C-core (libgrpc) checks lowercase first and was ignoring the uppercase-only vars.

### Server

- `sandbox/mod.rs`: Added `CAP_SYS_PTRACE` to sandbox pod security context. Required for the proxy (root) to read `/proc/<pid>/fd/` of sandbox-user processes for binary identity resolution.

### Python SDK

- `inference.py`: Strip `http://`/`https://` scheme from endpoint before passing to `grpc.insecure_channel()`, which expects `host:port`. Default `endpoint` and `sandbox_id` from `NAVIGATOR_ENDPOINT` / `NAVIGATOR_SANDBOX_ID` env vars so `Inference()` works inside sandboxes with no arguments.

### Build / infra

- `ci.toml`: Added `--cap-add=SYS_PTRACE` to `mise run sandbox` to mirror the k8s pod capabilities.

### Documentation

- `architecture/sandbox.md`: Documented `CAP_SYS_PTRACE` requirement and the full set of proxy env vars (uppercase + lowercase).

## Testing

All 29 sandbox unit tests pass. `mise run pre-commit` passes (fmt, clippy, all workspace tests, python lint).

E2E verified on a live cluster:

| Test | Result |
|------|--------|
| Proxy env vars (6 vars, upper+lowercase) | PASS |
| Blocked endpoints (google, anthropic via curl) | PASS — all denied |
| **gRPC inference from SSH session** (`Inference()` with no args, env var defaults) | **PASS** |
| Proxy log: navigator requests show `engine=control_plane` | PASS |
| Proxy log: blocked requests show `engine=opa` with correct deny reasons | PASS |
…(!19)

Closes NVIDIA/OpenShell#13

## Summary
- add Python sandbox execution APIs for command and callable workflows
- consolidate sandbox policy fixtures and expand e2e test coverage for policy and Python exec paths
- update CI/build config and images for sandbox e2e execution dependencies

## Test Plan
- mise run pre-commit
…as claude, outlook, etc (!23)

## Summary
- Add `Provider` entity for managing 3p deps from a sandbox
- Add provider CRUD API/server persistence and new CLI workflows (`nav provider create/get/list/update/delete`), including `--from-existing` laptop discovery.
- Integrate providers into sandbox create flow: infer from command (`-- claude`), support repeatable `--provider <type>`, prompt before auto-create, and allow manual in-sandbox setup.
- Add a dedicated `navigator-providers` crate with per-provider modules and mockable discovery test helpers.

## Key UX Changes
- `nav sandbox create --provider gitlab -- claude`
- Missing provider prompt now asks before creating from local state.
- `nav provider list --names` for scripting/cleanup.

## Test Plan
- `mise run cluster:deploy`
- `mise run test:e2e:sandbox`
- `mise run pre-commit`

Closes NVIDIA/OpenShell#19
Closes NVIDIA/OpenShell#22
Closes NVIDIA/OpenShell#11
Closes NVIDIA/OpenShell#67

## Summary

Implements transparent inference interception and routing for sandboxed AI agents. The sandbox proxy intercepts outbound AI SDK calls (OpenAI, Anthropic) and reroutes them through the gateway to policy-controlled backends — enabling organizations to redirect inference traffic to local or self-hosted models without modifying agent code.

**Decision model** — a tri-state OPA evaluation for every CONNECT request:
1. Binary + endpoint explicitly allowed in `network_policies` → **allow** (pass through)
2. Not explicitly allowed + `inference.allowed_routes` configured → **inspect for inference** (TLS intercept, detect API patterns, route through gateway)
3. Otherwise → **deny**

No endpoint declarations or binary lists needed for inference routing. Just configure `inference.allowed_routes`.

## Key Changes

### Sandbox (interception)
- **OPA policy**: New `network_action` Rego rule with three outcomes (`allow`, `inspect_for_inference`, `deny`). New `NetworkAction` enum replaces `PolicyDecision.allowed` bool for the proxy's main decision path.
- **Proxy**: New `InspectForInference` path — TLS-terminates client, parses HTTP, detects inference API patterns (`POST /v1/chat/completions`, `/v1/completions`, `/v1/messages`), strips auth headers, forwards via gRPC.
- **New module**: `l7/inference.rs` — `InferenceApiPattern`, `detect_inference_pattern()`, HTTP request/response parsing.
- **gRPC client**: New `proxy_inference()` for sandbox→gateway forwarding.
- **Sandbox init**: Creates OPA engine when inference is configured, even without `network_policies`.

### Gateway (dispatch)
- **InferenceService**: `ProxyInference` RPC loads sandbox policy, resolves allowed routes, dispatches to router. Full CRUD for inference routes.
- **Proto**: `InferenceRoute`, `InferenceRouteSpec`, `ProxyInferenceRequest/Response`, Inference gRPC service.

### Router (backend proxying)
- **New crate**: `navigator-router` with `Router`, `proxy_with_candidates()`, protocol-based route selection, backend HTTP proxying with auth header rewriting.
- **Mock support** for testing (`mock://` scheme).

### CLI
- `nav inference create/update/delete/list` commands for route management.

### Python SDK
- Updated protobuf bindings. Removed old `inference.py` client (replaced by transparent interception).

### Documentation
- New `architecture/inference-routing.md` — end-to-end system documentation.
- Updated `architecture/sandbox.md` — proxy, OPA, and source index sections.
- Updated `architecture/README.md` — new subsystem overview and diagram.

## Addendum: Chunked Transfer Compatibility

This branch now also fixes intercepted SDK requests that send chunked request bodies:

- `inspect_for_inference` now accepts `Transfer-Encoding: chunked` and decodes chunked request bodies before forwarding to the gateway
- Removed the prior `411 Length Required` response for chunked intercepted requests
- Added request/response header sanitization for framing and hop-by-hop headers (`content-length`, `transfer-encoding`, `connection`, etc.) to keep forwarded requests and returned responses valid
- Added unit tests for chunked parsing and header sanitization

Note: this improves compatibility for streaming-style SDK request patterns; true token-by-token passthrough response streaming is still a separate follow-up.

## Minimal Policy for Inference Routing

```yaml
inference:
  allowed_routes:
    - local
```

Any outgoing connection from a binary not explicitly allowed in `network_policies` will be intercepted and checked for inference API patterns.

## Test Plan

- [x] `cargo test --workspace` — all tests pass
- [x] `mise run pre-commit` — all checks pass
- [x] E2E: OpenAI chat completions routed through gateway
- [x] E2E: Anthropic messages routed through gateway
- [x] E2E: Python OpenAI SDK from sandbox (`examples/inference/inference.py`)
- [x] E2E test: `e2e/python/test_inference_routing.py`
…Shell#71)

Add Apache 2.0 licensing, SPDX copyright headers on all source files,
DCO enforcement, third-party notices, and CI enforcement.

- LICENSE: Apache License 2.0 full text
- DCO: Developer Certificate of Origin 1.1
- SPDX headers on all 176 source files (.rs, .py, .proto, .rego, .sh,
  .toml, .yaml, Dockerfiles)
- scripts/update_license_headers.py: header management with --check mode
- scripts/generate_third_party_notices.py: dependency license aggregation
- THIRD-PARTY-NOTICES: generated listing of all Rust and Python deps
- build/license.toml: mise tasks for license:check and license:update
- CI: license-headers job in checks.yml, DCO check workflow
- CONTRIBUTING.md: DCO sign-off requirement and license header docs
- Cargo.toml: license changed to Apache-2.0, repository URL updated
- pyproject.toml: license field added

Closes NVIDIA/OpenShell#58
…enShell#94)

* set up docs

* rm nv sphinx theme version

* rm v* trigger

* incorporate feedback with cursor

* minor updates

* doc docs

* more small tweaks

* clean contributing

---------

Co-authored-by: Drew Newberry <anewberry@nvidia.com>
…IDIA/OpenShell#102)

Closes NVIDIA/OpenShell#101

Add pytest-xdist for parallel e2e test execution with configurable
concurrency. Default to 5 workers; override via E2E_PARALLEL env var
(accepts a number or 'auto' for CPU-count matching). Make session-scoped
mock inference route fixtures worker-safe by incorporating the xdist
worker_id into route names and routing hints.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
…move implicit catch-all (NVIDIA/OpenShell#146)

Remove multi-route CRUD system and replace with single managed cluster
route (inference.local). Key changes:

- Remove inference route CRUD RPCs and CLI commands
- Remove InspectForInference OPA action; policy is binary allow/deny
- Introduce AuthHeader enum and InferenceProviderProfile in navigator-core
- Router is now provider-agnostic: auth style carried on ResolvedRoute
- Replace InferenceRouteSpec with ClusterInferenceConfig (2 fields vs 8)
- Rename proto: routing_hint->name, SandboxResolvedRoute->ResolvedRoute,
  GetSandboxInferenceBundle->GetInferenceBundle, drop sandbox_id param
- Rename RouteConfig.route -> RouteConfig.name; use inference.local
- Add 'nemoclaw cluster inference update' for partial config changes
- Delete stale navigator.inference.v1.rs checked-in proto file
- Update architecture docs, agent skills, and CLI reference

Closes NVIDIA/OpenShell#133
* feat(sandbox): add gpu sandbox scheduling support

Allow sandbox creation to request GPU resources explicitly or infer them from GPU image names. This wires GPU intent through bootstrap, validates gateway support, and adds dedicated GPU E2E coverage for follow-up cluster testing.
…ell#291)

Closes NVIDIA/OpenShell#273

Verify inference endpoints synchronously on the server during set/update, expose a --no-verify escape hatch in the CLI and Python helper, and return actionable failures when validation does not pass.
Remove the legacy Sphinx pipeline and make docs/ the single source of truth so the published site matches the repository layout.
…/OpenShell#919)

- adds filterable label selectors on resources

Closes NVIDIA/OpenShell#864

Signed-off-by: Derek Carr <decarr@redhat.com>
sjenning and others added 12 commits October 9, 2026 16:16
…/OpenShell#4357)

* feat(server): add operator-only provider credential retrieval

Authenticate operators exclusively through verified direct gateway mTLS. Export selected runtime credentials with freshness validation and all-or-nothing delivery.

Coordinate refresh across callers and replicas, preserve rotated grants across RPC cancellation, and fence credential delivery against reconfiguration. Add generated bindings, security and regression tests, and operator documentation.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(sdk): add curated operator credential retrieval

Expose provider credential clients in Python and TypeScript with explicit workspace and key selection, freshness options, optional expiration, and preserved gateway errors. Reuse existing mTLS transports without introducing retries or secret caching.

Add generated-wire and error-mapping tests, SDK package validation, and operator usage documentation.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(server): honor recovery state for queued automatic refreshes

Signed-off-by: Seth Jennings <sjenning@redhat.com>

---------

Signed-off-by: Seth Jennings <sjenning@redhat.com>
…nShell#4436)

* feat(python): expose sandbox status conditions in the SDK

Add SandboxConditionRef and surface status.conditions on SandboxStatusRef and SandboxRef. Callers need condition reasons and transition times to time terminal transitions and explain failures that happen before the main process starts, without reading the raw proto.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* test(python): use real gateway condition values in conditions fixtures

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

---------

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
…4367)

* fix(python): close client when Sandbox entry fails

Signed-off-by: Eric Curtin <eric.curtin@docker.com>

* test(python): share client patch helper

Signed-off-by: Eric Curtin <eric.curtin@docker.com>

---------

Signed-off-by: Eric Curtin <eric.curtin@docker.com>
…K module (NVIDIA/OpenShell#4439)

* fix(python): let proto stub generation skip .pyi for older protoc

Add a --no-pyi option to generate_python_proto.py for toolchains whose protoc predates --pyi_out, such as Fedora's grpcio-tools 1.48, and list python3-googleapis-common-protos and python3-httpx in the spec's Recommends and dist-info Requires-Dist, which the SDK needs to import. Default behavior is unchanged. Contributes to NVIDIA/OpenShell#3510.

Signed-off-by: politerealism <burdcat17@gmail.com>

* fix(rpm): install every SDK module and record the license accounting

The python3-openshell %install copied only __init__.py, sandbox.py and
_proto/, but the package imports .errors, .mutations and .providers and ships a
py.typed marker, so an installed RPM could not import openshell. Install the
missing files and add a %check that fails if any relative import in the
installed package has no matching installed module. Importing the package needs
grpcio, which the build environment lacks, so the check is static.

Set License to the minimal SPDX expression covering every crate linked into the
shipped binaries, keep the pure-Python subpackage at Apache-2.0, and note how
the non-crate content is covered.

Signed-off-by: politerealism <burdcat17@gmail.com>

---------

Signed-off-by: politerealism <burdcat17@gmail.com>
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* feat(sandbox): add suspend and resume lifecycle

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): preserve V8 state across failed resume launches

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): handle suspend and resume recovery races

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* refactor(example): move V8 suspend support to a dependent change

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): align suspend tests with current supervisor APIs

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): isolate suspend requests and resume compensation

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(supervisor): allow restoration to wait until agent admission

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
…penShell#4969)

* fix(deps): update setuptools to 84.1.0

Update the locked setuptools package and the build backend minimum to the patched release for GHSA-xmj3-9gf7-h52w.

Signed-off-by: Adrien Langou <alangou@nvidia.com>

* fix(deps): update vitest to 4.1.12

Update Vitest and V8 coverage together to resolve GHSA-qxg6-89mg-rqv8 and GHSA-xp26-3454-mjgp. Preserve unrelated locked dependencies.

Signed-off-by: Adrien Langou <alangou@nvidia.com>

---------

Signed-off-by: Adrien Langou <alangou@nvidia.com>
… breaks it (NVIDIA/OpenShell#5018)

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…5040)

Generate and package gRPC type stubs with concrete request and response types. Verify consumer typing, native async transport behavior, and wheel contents while preserving the existing synchronous APIs.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
…/OpenShell#4918)

* feat(sdk): route Python SDK exec to the sandbox owner replica

Ask GetSandbox for the owner replica, cache it for 30 seconds, and send
it as x-openshell-replica on ExecSandbox. Retry once without the hint
when the per-replica route has no healthy endpoint, forgetting the
cached owner.

Part of NVIDIA/OpenShell#4223

Signed-off-by: divesh <dgude@nvidia.com>

* refactor(sdk): share the Python GetSandbox request for owner lookup

Reuse get's request for the owner lookup, point the routing headers at
their documented contract, and accept only string owner metadata.

Part of NVIDIA/OpenShell#4223

Signed-off-by: divesh <dgude@nvidia.com>

---------

Signed-off-by: divesh <dgude@nvidia.com>
@pimlock

This comment was marked as outdated.

@pimlock
pimlock requested review from SDAChess and rhuss November 4, 2026 23:29
@pimlock
pimlock added this pull request to stack #4 November 4, 2026 23:31
@SDAChess

SDAChess commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

The reason I enforced the DCO is because the CNCF requires it. Let's sync.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
Filtered from NVIDIA/OpenShell at v0.1.7 (5d25211b25a8fc887e8d5c606d57d6ab22f1d76c)
with git filter-repo --paths-from-file. The tip tree
5619e0c equals the SDK paths at that
commit mapped to src/, tests/, and scripts/. 76 commits from 24 authors,
with bare #N references rewritten to NVIDIA/OpenShell#N.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
@pimlock
pimlock force-pushed the chore/import-history/pimlock branch from 82b8e14 to dd0bf72 Compare November 6, 2026 17:05
@pimlock

pimlock commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Warning

Merge this PR with "Create a merge commit". Do not use "Rebase and merge" or "Squash and merge".

This PR carries the Python SDK history as a separate line: 76 commits from 24 authors, filtered from NVIDIA/OpenShell at v0.1.7. The import merge (dd0bf72) joins that history to main. Only a merge commit keeps it as it is.

  • Squash collapses the 78 commits into one. The per-commit history, authors, and git blame on the SDK files are lost.
  • Rebase keeps the original authors, but it replays every commit as a new one. Each gets a new SHA and the merger as committer. The import merge is dropped, and the imported history is flattened on top of main. The SHAs then no longer match the filtered history the hack/ verification scripts reproduce. The replayed commits also still have no sign-off.

With a merge commit, git log --first-parent main reads Initial commit → LICENSE/README → import → this PR's merge, and the full SDK history stays reachable through the import merge's second parent.

After this merges, retarget #3 to main. Its commits are linear, so any merge method works there.

@pimlock

pimlock commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

@SDAChess After our discussion I added LICENSE file to this PR and it's standard Apache 2.0.

@pimlock
pimlock merged commit ad06256 into main Nov 9, 2026
1 check passed
@pimlock
pimlock deleted the chore/import-history/pimlock branch November 9, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.