From ebeaa96cdf2085a408ebeb79cd1538aa3042a948 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Sun, 18 Oct 2026 13:28:05 -0700 Subject: [PATCH 1/9] feat(kubernetes): bootstrap experimental gateway supervisor topology Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 17 ++++ .../openshell-driver-kubernetes/src/config.rs | 16 ++++ crates/openshell-driver-kubernetes/src/lib.rs | 6 +- .../openshell-driver-kubernetes/src/main.rs | 1 + crates/openshell-supervisor/README.md | 8 ++ .../openshell-supervisor/src/experimental.rs | 68 ++++++++++++++++ crates/openshell-supervisor/src/main.rs | 5 ++ deploy/helm/openshell/templates/_helpers.tpl | 14 ++++ .../templates/experimental-supervisor.yaml | 80 +++++++++++++++++++ .../tests/experimental_supervisor_test.yaml | 80 +++++++++++++++++++ docs/how-it-works/gateways/configuration.mdx | 17 ++++ skills/debug-openshell-cluster/SKILL.md | 11 +++ 12 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 crates/openshell-supervisor/src/experimental.rs create mode 100644 deploy/helm/openshell/templates/experimental-supervisor.yaml create mode 100644 deploy/helm/openshell/tests/experimental_supervisor_test.yaml diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 94826b0412..51af096d82 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -57,6 +57,23 @@ Override with env vars before running `helm:k3s:create`: `mcr.microsoft.com/dotnet/aspire-dashboard:latest`) - `HELM_K3S_COLLECTOR_HEALTH_TIMEOUT` (default: `120` seconds) +### Experimental supervisor plumbing + +Set `gatewayConfig.openshell.drivers.kubernetes.experimental_supervisor_topology` +using a values file (the table key is literally `openshell.drivers.kubernetes`): + +```yaml +gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway +``` + +The gateway chart creates a separate, single-replica idle supervisor Deployment +in the release namespace, regardless of shared/managed/operator workspace mode. +Use a supervisor image built from this branch: the bootstrap subcommand is new. +Check its rollout and exec health probes separately; no sandbox sessions route +through it. `workspace` is reserved and rejected. Omit the field to disable it. + ### 2. Deploy OpenShell **Iterative dev** (rebuilds on file changes, recommended during active development): diff --git a/crates/openshell-driver-kubernetes/src/config.rs b/crates/openshell-driver-kubernetes/src/config.rs index 14adb9ee01..6062aa0dff 100644 --- a/crates/openshell-driver-kubernetes/src/config.rs +++ b/crates/openshell-driver-kubernetes/src/config.rs @@ -194,9 +194,20 @@ where Ok(value) } +/// Experimental scope of a shared supervisor deployment. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ExperimentalSupervisorTopology { + Gateway, + Workspace, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct KubernetesComputeConfig { + /// Deploy an idle experimental supervisor through Helm. Does not route sessions. + #[serde(skip_serializing_if = "Option::is_none")] + pub experimental_supervisor_topology: Option, /// Permit caller-supplied driver JSON. Does not waive resource admission. pub allow_driver_config: bool, /// Operator-owned external attachment approval policy. @@ -389,6 +400,7 @@ pub const ANNOTATION_SCC_SUPPLEMENTAL_GROUPS: &str = "openshift.io/sa.scc.supple impl Default for KubernetesComputeConfig { fn default() -> Self { Self { + experimental_supervisor_topology: None, workspace_mode: WorkspaceMode::default(), allow_driver_config: false, resource_admission: @@ -442,6 +454,10 @@ impl Default for KubernetesComputeConfig { impl KubernetesComputeConfig { /// Validate startup configuration without connecting to Kubernetes. pub fn validate_configuration(&self) -> Result<(), String> { + if self.experimental_supervisor_topology == Some(ExperimentalSupervisorTopology::Workspace) + { + return Err("experimental_supervisor_topology=workspace is not implemented".into()); + } self.validate_workspace_mode()?; self.validate_provider_spiffe_workload_api_socket_path()?; self.validate_sandbox_identity_config()?; diff --git a/crates/openshell-driver-kubernetes/src/lib.rs b/crates/openshell-driver-kubernetes/src/lib.rs index da17212a39..aa18fe0506 100644 --- a/crates/openshell-driver-kubernetes/src/lib.rs +++ b/crates/openshell-driver-kubernetes/src/lib.rs @@ -12,9 +12,9 @@ mod sandbox_runtime; pub use config::{ DEFAULT_GATEWAY_ID, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME, DEFAULT_WORKSPACE_STORAGE_SIZE, - KubernetesComputeConfig, KubernetesControlConnection, KubernetesImagePullPolicy, - KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, WorkspaceMode, - managed_namespace_prefix, + ExperimentalSupervisorTopology, KubernetesComputeConfig, KubernetesControlConnection, + KubernetesImagePullPolicy, KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, + WorkspaceMode, managed_namespace_prefix, }; pub use driver::{KubernetesComputeDriver, KubernetesDriverError}; pub use grpc::ComputeDriverService; diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index ab6e0bf1b8..c0c3b46436 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -260,6 +260,7 @@ async fn main() -> Result<()> { let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); let driver = KubernetesComputeDriver::new( KubernetesComputeConfig { + experimental_supervisor_topology: None, allow_driver_config: args.admission_config_json.allow_driver_config, resource_admission: args.admission_config_json.resource_admission.clone(), workspace_mode: args.workspace_mode, diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index 753e381423..17fedfac2c 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -60,3 +60,11 @@ belong to the sandbox or isolation backend. Signals use `BoundarySignal`. These portable control-plane foundations do not qualify a platform isolation backend or enable a Windows workload runtime. + +## Experimental deployment bootstrap + +`openshell-supervisor experimental-bootstrap --health-socket /run/openshell/health.sock` +starts an idle process for the Kubernetes gateway topology experiment. The private +Unix socket supports the existing `health --socket` command. No sandbox session +listener, gateway authentication, or tenant routing is started. SIGTERM and SIGINT +close the listener and remove the socket. This subcommand is Unix-only. diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/experimental.rs new file mode 100644 index 0000000000..05bb90bae3 --- /dev/null +++ b/crates/openshell-supervisor/src/experimental.rs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Deployment bootstrap only: no sandbox admission, credentials, or session listener. + +use clap::Parser; +#[cfg(unix)] +use miette::IntoDiagnostic; +use miette::Result; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +#[command(name = "openshell-supervisor experimental-bootstrap")] +#[command(about = "Start an idle experimental supervisor; does not accept sandbox sessions")] +struct Args { + /// Private socket reporting process health, not sandbox readiness. + #[arg(long)] + health_socket: PathBuf, +} + +pub fn run(raw_args: &[String]) -> Result<()> { + let args = Args::parse_from(raw_args); + run_platform(args) +} + +#[cfg(unix)] +fn run_platform(args: Args) -> Result<()> { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .into_diagnostic()?; + runtime.block_on(async { + // Install shutdown handlers before publishing health. + let mut terminate = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .into_diagnostic()?; + let mut interrupt = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) + .into_diagnostic()?; + // The private emptyDir survives a container restart after an unclean exit. + match std::fs::remove_file(&args.health_socket) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error).into_diagnostic(), + } + let listener = tokio::net::UnixListener::bind(&args.health_socket).into_diagnostic()?; + eprintln!("Experimental supervisor bootstrap started; sandbox sessions are disabled"); + let result = loop { + tokio::select! { + result = listener.accept() => { + if let Err(error) = result { + break Err(error).into_diagnostic(); + } + } + _ = terminate.recv() => break Ok(()), + _ = interrupt.recv() => break Ok(()), + } + }; + drop(listener); + std::fs::remove_file(&args.health_socket).into_diagnostic()?; + result + }) +} + +#[cfg(not(unix))] +fn run_platform(_args: Args) -> Result<()> { + Err(miette::miette!("experimental-bootstrap requires Unix")) +} diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index 7e98eb3e94..b83495641c 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -17,6 +17,7 @@ use tracing_subscriber::EnvFilter; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::{Layer as _, layer::SubscriberExt as _, util::SubscriberInitExt as _}; +mod experimental; mod logging; const DEBUG_RPC_SUBCOMMAND: &str = "debug-rpc"; @@ -365,6 +366,10 @@ fn main() -> Result<()> { return openshell_supervisor::check_control_readiness(&args.socket); } + if raw_args.get(1).map(String::as_str) == Some("experimental-bootstrap") { + return experimental::run(&raw_args[1..]); + } + let args = Args::parse(); validate_role_arguments(&args)?; arm_parent_liveness(args.parent_liveness_stdin)?; diff --git a/deploy/helm/openshell/templates/_helpers.tpl b/deploy/helm/openshell/templates/_helpers.tpl index 27ef9f273a..d0ebc75836 100644 --- a/deploy/helm/openshell/templates/_helpers.tpl +++ b/deploy/helm/openshell/templates/_helpers.tpl @@ -568,6 +568,20 @@ like user-supplied schema-v2 fields during compatibility resolution. */}} Validate chart values that Helm would otherwise accept silently. */}} {{- define "openshell.validateValues" -}} +{{- $kubernetes := include "openshell.effectiveKubernetesConfig" . | fromYaml -}} +{{- if hasKey $kubernetes "experimental_supervisor_topology" -}} +{{- $topology := get $kubernetes "experimental_supervisor_topology" -}} +{{- if not (has $topology (list "gateway" "workspace")) -}} +{{- fail "experimental_supervisor_topology must be gateway or workspace; omit it to disable the experiment" -}} +{{- end -}} +{{- if eq $topology "workspace" -}} +{{- fail "experimental_supervisor_topology=workspace is not implemented" -}} +{{- end -}} +{{- $gateway := get (.Values.gatewayConfig | default dict) "openshell.gateway" | default dict -}} +{{- if ne (get $gateway "compute_driver" | default "kubernetes") "kubernetes" -}} +{{- fail "experimental_supervisor_topology requires the kubernetes compute driver" -}} +{{- end -}} +{{- end -}} {{- $workloadKind := include "openshell.workloadKind" . -}} {{- $workload := .Values.workload | default dict -}} {{- $maxReplicas := int (include "openshell.maxReplicas" .) -}} diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/experimental-supervisor.yaml new file mode 100644 index 0000000000..b638589227 --- /dev/null +++ b/deploy/helm/openshell/templates/experimental-supervisor.yaml @@ -0,0 +1,80 @@ +{{- include "openshell.validateValues" . }} +{{- $kubernetes := include "openshell.effectiveKubernetesConfig" . | fromYaml -}} +{{- $imagePullSecrets := get $kubernetes "image_pull_secrets" | default list -}} +{{- range .Values.imagePullSecrets -}} +{{- $imagePullSecrets = append $imagePullSecrets .name -}} +{{- end -}} +{{- if eq (get $kubernetes "experimental_supervisor_topology" | default "") "gateway" }} +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "openshell.fullname" . }}-experimental-supervisor + namespace: {{ .Release.Namespace }} + labels: + {{- include "openshell.labels" . | nindent 4 }} + app.kubernetes.io/component: experimental-supervisor +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: openshell-experimental-supervisor + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: experimental-supervisor + template: + metadata: + labels: + app.kubernetes.io/name: openshell-experimental-supervisor + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: experimental-supervisor + spec: + automountServiceAccountToken: false + terminationGracePeriodSeconds: 30 + {{- with ($imagePullSecrets | uniq) }} + imagePullSecrets: + {{- range . }} + - name: {{ . | quote }} + {{- end }} + {{- end }} + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + fsGroup: 65534 + seccompProfile: + type: RuntimeDefault + containers: + - name: supervisor + image: {{ get $kubernetes "supervisor_image" | quote }} + imagePullPolicy: {{ get (dict "always" "Always" "if_not_present" "IfNotPresent" "never" "Never") (get $kubernetes "supervisor_image_pull_policy" | default "if_not_present") | quote }} + args: + - experimental-bootstrap + - --health-socket + - /run/openshell/health.sock + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + memory: 128Mi + readinessProbe: + exec: + command: [/openshell-supervisor, health, --socket, /run/openshell/health.sock] + livenessProbe: + exec: + command: [/openshell-supervisor, health, --socket, /run/openshell/health.sock] + volumeMounts: + - name: health + mountPath: /run/openshell + volumes: + - name: health + emptyDir: {} +{{- end }} diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml new file mode 100644 index 0000000000..f32c05feaf --- /dev/null +++ b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +suite: experimental supervisor deployment +templates: + - templates/experimental-supervisor.yaml + - templates/gateway-config.yaml +release: + name: openshell + namespace: gateway +tests: + - it: adds no supervisor by default + template: templates/experimental-supervisor.yaml + asserts: + - hasDocuments: + count: 0 + - it: deploys one isolated idle process in the gateway namespace + template: templates/experimental-supervisor.yaml + set: + gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway + supervisor_image: example.com/supervisor:experiment + asserts: + - equal: + path: metadata.namespace + value: gateway + - equal: + path: spec.replicas + value: 1 + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/name"] + value: openshell-experimental-supervisor + - equal: + path: spec.template.spec.containers[0].image + value: example.com/supervisor:experiment + - equal: + path: spec.template.spec.containers[0].args[0] + value: experimental-bootstrap + - equal: + path: spec.template.spec.automountServiceAccountToken + value: false + - it: renders the same setting into gateway TOML + template: templates/gateway-config.yaml + set: + gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: 'experimental_supervisor_topology = "gateway"' + - it: rejects workspace until implemented + template: templates/experimental-supervisor.yaml + set: + gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: workspace + asserts: + - failedTemplate: + errorMessage: experimental_supervisor_topology=workspace is not implemented + - it: rejects invalid topology + template: templates/experimental-supervisor.yaml + set: + gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: invalid + asserts: + - failedTemplate: + errorMessage: experimental_supervisor_topology must be gateway or workspace; omit it to disable the experiment + - it: rejects enabled topology for another compute driver + template: templates/experimental-supervisor.yaml + set: + gatewayConfig: + openshell.gateway: + compute_driver: docker + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway + asserts: + - failedTemplate: + errorMessage: experimental_supervisor_topology requires the kubernetes compute driver diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 7f16929759..9c8a15437a 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -854,6 +854,23 @@ Kubernetes configurations set `namespace`, `service_account_name`, and `enable_u ### Kubernetes +For experimental deployment plumbing, set +`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`. +With Helm, use the existing gateway configuration: + +```yaml +gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway +``` + +The chart deploys one idle supervisor in the gateway namespace for all workspace +modes (`shared`, `managed`, and `operator`). Its health checks report process +health only. It does not accept sandbox sessions; sandboxes retain their dedicated +supervisors. Omit the field to disable the experiment. The `workspace` value is +reserved and currently returns an unsupported error. Helm owns this Deployment; +setting the field in a standalone TOML file does not provision infrastructure. + The gateway runs as a Pod and creates paired workload and supervisor Pods in another namespace. The gateway CA is projected from a Kubernetes Secret into supervisor Pods; user client certificate and key entries in that Secret are not exposed to either Pod. ```toml diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 0c0a39ee24..19c7b46227 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -49,6 +49,17 @@ For local evaluation only, TLS may be disabled and the gateway can be reached th Use `openshell --help` and nested `--help` output as the authority for the installed CLI version. Use the published [installation guide](https://docs.nvidia.com/openshell/latest/about/installation.md), [compute-driver reference](https://docs.nvidia.com/openshell/latest/how-it-works/sandboxes/runtimes), [gateway configuration reference](https://docs.nvidia.com/openshell/latest/how-it-works/gateways/configuration), and [Kubernetes setup guide](https://docs.nvidia.com/openshell/latest/kubernetes/setup.md) as the authority for deployment and configuration behavior. +## Experimental supervisor deployment + +When the installed gateway configuration enables +`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`, +Helm creates `-experimental-supervisor` in the gateway namespace. +Inspect that Deployment separately from per-sandbox supervisor Pods. This applies +to shared, managed, and operator workspace modes. Its probes report idle process +health, not sandbox session readiness; it does not serve sandboxes yet. Consult +the published gateway configuration reference for the installed version. Omit +the setting and upgrade the release to remove the experimental Deployment. + ## Workflow Run diagnostics in order and stop once the root cause is clear. From 15a84e85b49e399fc3a54aa8f298521d6dc857f4 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 11:30:59 -0700 Subject: [PATCH 2/9] docs: move the experimental supervisor topology under an Experimental section Signed-off-by: Drew Newberry --- docs/how-it-works/gateways/configuration.mdx | 38 +++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 9c8a15437a..68720eee6c 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -854,23 +854,6 @@ Kubernetes configurations set `namespace`, `service_account_name`, and `enable_u ### Kubernetes -For experimental deployment plumbing, set -`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`. -With Helm, use the existing gateway configuration: - -```yaml -gatewayConfig: - openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway -``` - -The chart deploys one idle supervisor in the gateway namespace for all workspace -modes (`shared`, `managed`, and `operator`). Its health checks report process -health only. It does not accept sandbox sessions; sandboxes retain their dedicated -supervisors. Omit the field to disable the experiment. The `workspace` value is -reserved and currently returns an unsupported error. Helm owns this Deployment; -setting the field in a standalone TOML file does not provision infrastructure. - The gateway runs as a Pod and creates paired workload and supervisor Pods in another namespace. The gateway CA is projected from a Kubernetes Secret into supervisor Pods; user client certificate and key entries in that Secret are not exposed to either Pod. ```toml @@ -1543,3 +1526,24 @@ Replace the executable and configuration paths with your installation's paths, a The command reports each selected executable path and version. Each tool receives only `-V`, with a five-second deadline and an 8 KiB output limit per stream. Missing, non-executable, outdated, or failing tools return a nonzero exit status with corrective guidance and the selected tool's error. The checks create no gateway state or images and do not start a driver or VM. Version checks confirm the installation; they do not test image creation or filesystem recovery. Other drivers do not require these VM tools. A VM configuration table alone does not select the VM driver. When a remote driver socket is configured, preflight reports that host tool checks were not performed; run the check on that host in the driver service's environment with a local VM configuration. + +## Experimental + +### Kubernetes supervisor topology + +For experimental deployment plumbing, set +`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`. +With Helm, use the existing gateway configuration: + +```yaml +gatewayConfig: + openshell.drivers.kubernetes: + experimental_supervisor_topology: gateway +``` + +The chart deploys one idle supervisor in the gateway namespace for all workspace +modes (`shared`, `managed`, and `operator`). Its health checks report process +health only. It does not accept sandbox sessions; sandboxes retain their dedicated +supervisors. Omit the field to disable the experiment. The `workspace` value is +reserved and currently returns an unsupported error. Helm owns this Deployment; +setting the field in a standalone TOML file does not provision infrastructure. From 2003165049e4748396c60f902e0f0c832770919d Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 15:18:22 -0700 Subject: [PATCH 3/9] feat(supervisor): report experimental bootstrap health over TCP experimental-bootstrap now takes --health-port instead of a Unix health socket. It binds the dedicated supervisor's wildcard readiness listener, reachable at the Pod IP, and accepts and closes connections while the process runs. The chart probes the named container port health (5502) with native kubelet tcpSocket checks and drops the health-only emptyDir. No filesystem state remains to clean up after an unclean exit. Dedicated supervisors keep --health-socket-path and health --socket. Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 4 +-- crates/openshell-supervisor/README.md | 11 +++--- .../openshell-supervisor/src/experimental.rs | 34 ++++++++----------- crates/openshell-supervisor/src/lib.rs | 5 +-- .../templates/experimental-supervisor.yaml | 22 ++++++------ .../tests/experimental_supervisor_test.yaml | 24 +++++++++++-- skills/debug-openshell-cluster/SKILL.md | 4 +-- 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 51af096d82..1e4f58e62f 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -71,8 +71,8 @@ gatewayConfig: The gateway chart creates a separate, single-replica idle supervisor Deployment in the release namespace, regardless of shared/managed/operator workspace mode. Use a supervisor image built from this branch: the bootstrap subcommand is new. -Check its rollout and exec health probes separately; no sandbox sessions route -through it. `workspace` is reserved and rejected. Omit the field to disable it. +Check its rollout and TCP health probes (container port `health`, 5502) +separately; no sandbox sessions route through it. `workspace` is reserved and rejected. Omit the field to disable it. ### 2. Deploy OpenShell diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index 17fedfac2c..283a2e40aa 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -63,8 +63,9 @@ backend or enable a Windows workload runtime. ## Experimental deployment bootstrap -`openshell-supervisor experimental-bootstrap --health-socket /run/openshell/health.sock` -starts an idle process for the Kubernetes gateway topology experiment. The private -Unix socket supports the existing `health --socket` command. No sandbox session -listener, gateway authentication, or tenant routing is started. SIGTERM and SIGINT -close the listener and remove the socket. This subcommand is Unix-only. +`openshell-supervisor experimental-bootstrap --health-port 5502` starts an idle +process for the Kubernetes gateway topology experiment. The wildcard TCP health +port accepts and closes connections for kubelet `tcpSocket` probes and reports +process health only; it keeps no filesystem state. No sandbox session listener, +gateway authentication, or tenant routing is started. SIGTERM and SIGINT close +the listener. This subcommand is Unix-only. diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/experimental.rs index 05bb90bae3..5535d67302 100644 --- a/crates/openshell-supervisor/src/experimental.rs +++ b/crates/openshell-supervisor/src/experimental.rs @@ -7,15 +7,15 @@ use clap::Parser; #[cfg(unix)] use miette::IntoDiagnostic; use miette::Result; -use std::path::PathBuf; #[derive(Debug, Parser)] #[command(name = "openshell-supervisor experimental-bootstrap")] #[command(about = "Start an idle experimental supervisor; does not accept sandbox sessions")] struct Args { - /// Private socket reporting process health, not sandbox readiness. + /// TCP port accepting connections while the process runs; reports process + /// health, not sandbox readiness. #[arg(long)] - health_socket: PathBuf, + health_port: u16, } pub fn run(raw_args: &[String]) -> Result<()> { @@ -37,28 +37,24 @@ fn run_platform(args: Args) -> Result<()> { let mut interrupt = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) .into_diagnostic()?; - // The private emptyDir survives a container restart after an unclean exit. - match std::fs::remove_file(&args.health_socket) { - Ok(()) => {} - Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} - Err(error) => return Err(error).into_diagnostic(), - } - let listener = tokio::net::UnixListener::bind(&args.health_socket).into_diagnostic()?; + let health = openshell_supervisor::bind_readiness_tcp(args.health_port) + .and_then(tokio::net::TcpListener::from_std) + .into_diagnostic()?; eprintln!("Experimental supervisor bootstrap started; sandbox sessions are disabled"); - let result = loop { + loop { tokio::select! { - result = listener.accept() => { + // A connection is the whole check; nothing is exchanged. + result = health.accept() => { if let Err(error) = result { - break Err(error).into_diagnostic(); + eprintln!("experimental supervisor health accept failed: {error}"); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; } } - _ = terminate.recv() => break Ok(()), - _ = interrupt.recv() => break Ok(()), + _ = terminate.recv() => break, + _ = interrupt.recv() => break, } - }; - drop(listener); - std::fs::remove_file(&args.health_socket).into_diagnostic()?; - result + } + Ok(()) }) } diff --git a/crates/openshell-supervisor/src/lib.rs b/crates/openshell-supervisor/src/lib.rs index 9628d811a0..999bb5d739 100644 --- a/crates/openshell-supervisor/src/lib.rs +++ b/crates/openshell-supervisor/src/lib.rs @@ -175,8 +175,9 @@ impl ReadinessEndpoint { } } -/// Falls back to IPv4 when the network namespace has IPv6 disabled. -fn bind_readiness_tcp(port: u16) -> std::io::Result { +/// Binds a nonblocking wildcard listener reachable at the Pod IP. Falls back to +/// IPv4 when the network namespace has IPv6 disabled. +pub fn bind_readiness_tcp(port: u16) -> std::io::Result { use socket2::{Domain, Socket, Type}; let bind = |domain: Domain, address: std::net::SocketAddr| { diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/experimental-supervisor.yaml index b638589227..7cd70e74ce 100644 --- a/deploy/helm/openshell/templates/experimental-supervisor.yaml +++ b/deploy/helm/openshell/templates/experimental-supervisor.yaml @@ -52,8 +52,12 @@ spec: imagePullPolicy: {{ get (dict "always" "Always" "if_not_present" "IfNotPresent" "never" "Never") (get $kubernetes "supervisor_image_pull_policy" | default "if_not_present") | quote }} args: - experimental-bootstrap - - --health-socket - - /run/openshell/health.sock + - --health-port + - "5502" + ports: + - name: health + containerPort: 5502 + protocol: TCP securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true @@ -66,15 +70,9 @@ spec: limits: memory: 128Mi readinessProbe: - exec: - command: [/openshell-supervisor, health, --socket, /run/openshell/health.sock] + tcpSocket: + port: health livenessProbe: - exec: - command: [/openshell-supervisor, health, --socket, /run/openshell/health.sock] - volumeMounts: - - name: health - mountPath: /run/openshell - volumes: - - name: health - emptyDir: {} + tcpSocket: + port: health {{- end }} diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml index f32c05feaf..8dc5fdc9d6 100644 --- a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml +++ b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml @@ -34,8 +34,28 @@ tests: path: spec.template.spec.containers[0].image value: example.com/supervisor:experiment - equal: - path: spec.template.spec.containers[0].args[0] - value: experimental-bootstrap + path: spec.template.spec.containers[0].args + value: [experimental-bootstrap, --health-port, "5502"] + - equal: + path: spec.template.spec.containers[0].ports + value: + - name: health + containerPort: 5502 + protocol: TCP + - equal: + path: spec.template.spec.containers[0].readinessProbe + value: + tcpSocket: + port: health + - equal: + path: spec.template.spec.containers[0].livenessProbe + value: + tcpSocket: + port: health + - notExists: + path: spec.template.spec.containers[0].volumeMounts + - notExists: + path: spec.template.spec.volumes - equal: path: spec.template.spec.automountServiceAccountToken value: false diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 19c7b46227..800a00a887 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -55,8 +55,8 @@ When the installed gateway configuration enables `[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`, Helm creates `-experimental-supervisor` in the gateway namespace. Inspect that Deployment separately from per-sandbox supervisor Pods. This applies -to shared, managed, and operator workspace modes. Its probes report idle process -health, not sandbox session readiness; it does not serve sandboxes yet. Consult +to shared, managed, and operator workspace modes. Its TCP probes on container +port `health` (5502) report idle process health, not sandbox session readiness; it does not serve sandboxes yet. Consult the published gateway configuration reference for the installed version. Omit the setting and upgrade the release to remove the experimental Deployment. From f828fe0b220b0141b2e463b020a0451bb5dfa24d Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 15:40:28 -0700 Subject: [PATCH 4/9] docs: defer public experimental supervisor docs Remove the experimental supervisor topology additions from the published gateway configuration page and the public debug-openshell-cluster skill. Public documentation will be added once the experimental stack settles. Signed-off-by: Drew Newberry --- docs/how-it-works/gateways/configuration.mdx | 21 -------------------- skills/debug-openshell-cluster/SKILL.md | 11 ---------- 2 files changed, 32 deletions(-) diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 68720eee6c..7f16929759 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -1526,24 +1526,3 @@ Replace the executable and configuration paths with your installation's paths, a The command reports each selected executable path and version. Each tool receives only `-V`, with a five-second deadline and an 8 KiB output limit per stream. Missing, non-executable, outdated, or failing tools return a nonzero exit status with corrective guidance and the selected tool's error. The checks create no gateway state or images and do not start a driver or VM. Version checks confirm the installation; they do not test image creation or filesystem recovery. Other drivers do not require these VM tools. A VM configuration table alone does not select the VM driver. When a remote driver socket is configured, preflight reports that host tool checks were not performed; run the check on that host in the driver service's environment with a local VM configuration. - -## Experimental - -### Kubernetes supervisor topology - -For experimental deployment plumbing, set -`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`. -With Helm, use the existing gateway configuration: - -```yaml -gatewayConfig: - openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway -``` - -The chart deploys one idle supervisor in the gateway namespace for all workspace -modes (`shared`, `managed`, and `operator`). Its health checks report process -health only. It does not accept sandbox sessions; sandboxes retain their dedicated -supervisors. Omit the field to disable the experiment. The `workspace` value is -reserved and currently returns an unsupported error. Helm owns this Deployment; -setting the field in a standalone TOML file does not provision infrastructure. diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 800a00a887..0c0a39ee24 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -49,17 +49,6 @@ For local evaluation only, TLS may be disabled and the gateway can be reached th Use `openshell --help` and nested `--help` output as the authority for the installed CLI version. Use the published [installation guide](https://docs.nvidia.com/openshell/latest/about/installation.md), [compute-driver reference](https://docs.nvidia.com/openshell/latest/how-it-works/sandboxes/runtimes), [gateway configuration reference](https://docs.nvidia.com/openshell/latest/how-it-works/gateways/configuration), and [Kubernetes setup guide](https://docs.nvidia.com/openshell/latest/kubernetes/setup.md) as the authority for deployment and configuration behavior. -## Experimental supervisor deployment - -When the installed gateway configuration enables -`[openshell.drivers.kubernetes].experimental_supervisor_topology = "gateway"`, -Helm creates `-experimental-supervisor` in the gateway namespace. -Inspect that Deployment separately from per-sandbox supervisor Pods. This applies -to shared, managed, and operator workspace modes. Its TCP probes on container -port `health` (5502) report idle process health, not sandbox session readiness; it does not serve sandboxes yet. Consult -the published gateway configuration reference for the installed version. Omit -the setting and upgrade the release to remove the experimental Deployment. - ## Workflow Run diagnostics in order and stop once the root cause is clear. From 4d44dd8ca86796296222fd5dae075c5fb3ed19e6 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 15:56:42 -0700 Subject: [PATCH 5/9] refactor(supervisor): replace bootstrap with shared host stub Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 10 ++++--- crates/openshell-supervisor/README.md | 18 +++++++------ .../openshell-supervisor/src/experimental.rs | 27 ++++++++++--------- crates/openshell-supervisor/src/lib.rs | 5 ++-- crates/openshell-supervisor/src/main.rs | 2 +- .../templates/experimental-supervisor.yaml | 14 +++++----- .../tests/experimental_supervisor_test.yaml | 10 +++---- 7 files changed, 45 insertions(+), 41 deletions(-) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 1e4f58e62f..32c66daeab 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -68,11 +68,13 @@ gatewayConfig: experimental_supervisor_topology: gateway ``` -The gateway chart creates a separate, single-replica idle supervisor Deployment +The gateway chart creates a separate, single-replica shared-host placeholder Deployment in the release namespace, regardless of shared/managed/operator workspace mode. -Use a supervisor image built from this branch: the bootstrap subcommand is new. -Check its rollout and TCP health probes (container port `health`, 5502) -separately; no sandbox sessions route through it. `workspace` is reserved and rejected. Omit the field to disable it. +Use a supervisor image built from this branch: the `experimental-host` subcommand is new. +Check its rollout and TCP health probes on the placeholder control listener +(container port `session-control`, 5501). It implements no session RPCs; sandboxes +still use dedicated supervisors. `workspace` is reserved and rejected. Omit the +field to disable it. ### 2. Deploy OpenShell diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index 283a2e40aa..a618366d58 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -61,11 +61,13 @@ belong to the sandbox or isolation backend. Signals use `BoundarySignal`. These portable control-plane foundations do not qualify a platform isolation backend or enable a Windows workload runtime. -## Experimental deployment bootstrap - -`openshell-supervisor experimental-bootstrap --health-port 5502` starts an idle -process for the Kubernetes gateway topology experiment. The wildcard TCP health -port accepts and closes connections for kubelet `tcpSocket` probes and reports -process health only; it keeps no filesystem state. No sandbox session listener, -gateway authentication, or tenant routing is started. SIGTERM and SIGINT close -the listener. This subcommand is Unix-only. +## Experimental shared host + +`openshell-supervisor experimental-host --session-control-listen 0.0.0.0:5501` +starts a shared-host placeholder for the Kubernetes gateway topology experiment. +Its future control listener accepts and closes connections, including kubelet +`tcpSocket` probes, and reports process health only. No control RPCs, sandbox +sessions, gateway authentication, or tenant routing are implemented yet. +`experimental_supervisor_topology = "gateway"` deploys this placeholder; sandboxes +continue using dedicated supervisors. SIGTERM and SIGINT close the listener. +This subcommand is Unix-only. diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/experimental.rs index 5535d67302..17971c5a71 100644 --- a/crates/openshell-supervisor/src/experimental.rs +++ b/crates/openshell-supervisor/src/experimental.rs @@ -1,21 +1,21 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! Deployment bootstrap only: no sandbox admission, credentials, or session listener. +//! Shared-host placeholder: no sandbox admission, credentials, or session RPCs. use clap::Parser; #[cfg(unix)] use miette::IntoDiagnostic; use miette::Result; +use std::net::SocketAddr; #[derive(Debug, Parser)] -#[command(name = "openshell-supervisor experimental-bootstrap")] -#[command(about = "Start an idle experimental supervisor; does not accept sandbox sessions")] +#[command(name = "openshell-supervisor experimental-host")] +#[command(about = "Start a shared supervisor placeholder; sandbox sessions are not implemented")] struct Args { - /// TCP port accepting connections while the process runs; reports process - /// health, not sandbox readiness. + /// Placeholder control listener; also accepts TCP process-health probes. #[arg(long)] - health_port: u16, + session_control_listen: SocketAddr, } pub fn run(raw_args: &[String]) -> Result<()> { @@ -37,16 +37,17 @@ fn run_platform(args: Args) -> Result<()> { let mut interrupt = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) .into_diagnostic()?; - let health = openshell_supervisor::bind_readiness_tcp(args.health_port) - .and_then(tokio::net::TcpListener::from_std) + let control = tokio::net::TcpListener::bind(args.session_control_listen) + .await .into_diagnostic()?; - eprintln!("Experimental supervisor bootstrap started; sandbox sessions are disabled"); + eprintln!("Shared supervisor started; sandbox sessions are not implemented"); loop { tokio::select! { - // A connection is the whole check; nothing is exchanged. - result = health.accept() => { + // This placeholder only accepts and closes connections. The same + // listener will serve session-control RPCs in the next stage. + result = control.accept() => { if let Err(error) = result { - eprintln!("experimental supervisor health accept failed: {error}"); + eprintln!("shared supervisor control accept failed: {error}"); tokio::time::sleep(std::time::Duration::from_millis(100)).await; } } @@ -60,5 +61,5 @@ fn run_platform(args: Args) -> Result<()> { #[cfg(not(unix))] fn run_platform(_args: Args) -> Result<()> { - Err(miette::miette!("experimental-bootstrap requires Unix")) + Err(miette::miette!("experimental-host requires Unix")) } diff --git a/crates/openshell-supervisor/src/lib.rs b/crates/openshell-supervisor/src/lib.rs index 999bb5d739..9628d811a0 100644 --- a/crates/openshell-supervisor/src/lib.rs +++ b/crates/openshell-supervisor/src/lib.rs @@ -175,9 +175,8 @@ impl ReadinessEndpoint { } } -/// Binds a nonblocking wildcard listener reachable at the Pod IP. Falls back to -/// IPv4 when the network namespace has IPv6 disabled. -pub fn bind_readiness_tcp(port: u16) -> std::io::Result { +/// Falls back to IPv4 when the network namespace has IPv6 disabled. +fn bind_readiness_tcp(port: u16) -> std::io::Result { use socket2::{Domain, Socket, Type}; let bind = |domain: Domain, address: std::net::SocketAddr| { diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index b83495641c..86c269c83a 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -366,7 +366,7 @@ fn main() -> Result<()> { return openshell_supervisor::check_control_readiness(&args.socket); } - if raw_args.get(1).map(String::as_str) == Some("experimental-bootstrap") { + if raw_args.get(1).map(String::as_str) == Some("experimental-host") { return experimental::run(&raw_args[1..]); } diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/experimental-supervisor.yaml index 7cd70e74ce..99f4fccf96 100644 --- a/deploy/helm/openshell/templates/experimental-supervisor.yaml +++ b/deploy/helm/openshell/templates/experimental-supervisor.yaml @@ -51,12 +51,12 @@ spec: image: {{ get $kubernetes "supervisor_image" | quote }} imagePullPolicy: {{ get (dict "always" "Always" "if_not_present" "IfNotPresent" "never" "Never") (get $kubernetes "supervisor_image_pull_policy" | default "if_not_present") | quote }} args: - - experimental-bootstrap - - --health-port - - "5502" + - experimental-host + - --session-control-listen + - 0.0.0.0:5501 ports: - - name: health - containerPort: 5502 + - name: session-control + containerPort: 5501 protocol: TCP securityContext: allowPrivilegeEscalation: false @@ -71,8 +71,8 @@ spec: memory: 128Mi readinessProbe: tcpSocket: - port: health + port: session-control livenessProbe: tcpSocket: - port: health + port: session-control {{- end }} diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml index 8dc5fdc9d6..8441e303ac 100644 --- a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml +++ b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml @@ -35,23 +35,23 @@ tests: value: example.com/supervisor:experiment - equal: path: spec.template.spec.containers[0].args - value: [experimental-bootstrap, --health-port, "5502"] + value: [experimental-host, --session-control-listen, "0.0.0.0:5501"] - equal: path: spec.template.spec.containers[0].ports value: - - name: health - containerPort: 5502 + - name: session-control + containerPort: 5501 protocol: TCP - equal: path: spec.template.spec.containers[0].readinessProbe value: tcpSocket: - port: health + port: session-control - equal: path: spec.template.spec.containers[0].livenessProbe value: tcpSocket: - port: health + port: session-control - notExists: path: spec.template.spec.containers[0].volumeMounts - notExists: From 7b180d7dc296114b6fe625d57b50c9b7532dfee7 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 15:58:54 -0700 Subject: [PATCH 6/9] refactor(supervisor): name shared supervisor subcommand shared Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 2 +- crates/openshell-supervisor/README.md | 2 +- crates/openshell-supervisor/src/experimental.rs | 4 ++-- crates/openshell-supervisor/src/main.rs | 2 +- deploy/helm/openshell/templates/experimental-supervisor.yaml | 2 +- deploy/helm/openshell/tests/experimental_supervisor_test.yaml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 32c66daeab..5fe9f55c92 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -70,7 +70,7 @@ gatewayConfig: The gateway chart creates a separate, single-replica shared-host placeholder Deployment in the release namespace, regardless of shared/managed/operator workspace mode. -Use a supervisor image built from this branch: the `experimental-host` subcommand is new. +Use a supervisor image built from this branch: the `shared` subcommand is new. Check its rollout and TCP health probes on the placeholder control listener (container port `session-control`, 5501). It implements no session RPCs; sandboxes still use dedicated supervisors. `workspace` is reserved and rejected. Omit the diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index a618366d58..5835853442 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -63,7 +63,7 @@ backend or enable a Windows workload runtime. ## Experimental shared host -`openshell-supervisor experimental-host --session-control-listen 0.0.0.0:5501` +`openshell-supervisor shared --session-control-listen 0.0.0.0:5501` starts a shared-host placeholder for the Kubernetes gateway topology experiment. Its future control listener accepts and closes connections, including kubelet `tcpSocket` probes, and reports process health only. No control RPCs, sandbox diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/experimental.rs index 17971c5a71..026d1de868 100644 --- a/crates/openshell-supervisor/src/experimental.rs +++ b/crates/openshell-supervisor/src/experimental.rs @@ -10,7 +10,7 @@ use miette::Result; use std::net::SocketAddr; #[derive(Debug, Parser)] -#[command(name = "openshell-supervisor experimental-host")] +#[command(name = "openshell-supervisor shared")] #[command(about = "Start a shared supervisor placeholder; sandbox sessions are not implemented")] struct Args { /// Placeholder control listener; also accepts TCP process-health probes. @@ -61,5 +61,5 @@ fn run_platform(args: Args) -> Result<()> { #[cfg(not(unix))] fn run_platform(_args: Args) -> Result<()> { - Err(miette::miette!("experimental-host requires Unix")) + Err(miette::miette!("shared requires Unix")) } diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index 86c269c83a..dd1aa923b6 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -366,7 +366,7 @@ fn main() -> Result<()> { return openshell_supervisor::check_control_readiness(&args.socket); } - if raw_args.get(1).map(String::as_str) == Some("experimental-host") { + if raw_args.get(1).map(String::as_str) == Some("shared") { return experimental::run(&raw_args[1..]); } diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/experimental-supervisor.yaml index 99f4fccf96..f582fd16d2 100644 --- a/deploy/helm/openshell/templates/experimental-supervisor.yaml +++ b/deploy/helm/openshell/templates/experimental-supervisor.yaml @@ -51,7 +51,7 @@ spec: image: {{ get $kubernetes "supervisor_image" | quote }} imagePullPolicy: {{ get (dict "always" "Always" "if_not_present" "IfNotPresent" "never" "Never") (get $kubernetes "supervisor_image_pull_policy" | default "if_not_present") | quote }} args: - - experimental-host + - shared - --session-control-listen - 0.0.0.0:5501 ports: diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml index 8441e303ac..61d7247599 100644 --- a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml +++ b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml @@ -35,7 +35,7 @@ tests: value: example.com/supervisor:experiment - equal: path: spec.template.spec.containers[0].args - value: [experimental-host, --session-control-listen, "0.0.0.0:5501"] + value: [shared, --session-control-listen, "0.0.0.0:5501"] - equal: path: spec.template.spec.containers[0].ports value: From 7de49653d72db743ca08b95a6ec53a59622d4b06 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 16:26:17 -0700 Subject: [PATCH 7/9] refactor(supervisor): select shared mode with explicit endpoint flags Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 8 ++-- crates/openshell-supervisor/README.md | 12 +++--- .../openshell-supervisor/src/experimental.rs | 37 +++++++---------- crates/openshell-supervisor/src/main.rs | 40 +++++++++++++++++-- .../templates/experimental-supervisor.yaml | 12 +++--- .../tests/experimental_supervisor_test.yaml | 13 ++++-- 6 files changed, 79 insertions(+), 43 deletions(-) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 5fe9f55c92..dd161a459f 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -70,9 +70,11 @@ gatewayConfig: The gateway chart creates a separate, single-replica shared-host placeholder Deployment in the release namespace, regardless of shared/managed/operator workspace mode. -Use a supervisor image built from this branch: the `shared` subcommand is new. -Check its rollout and TCP health probes on the placeholder control listener -(container port `session-control`, 5501). It implements no session RPCs; sandboxes +Use a supervisor image built from this branch: `--mode=shared` is new. +The chart sets `--gateway-endpoint` from the gateway's gRPC endpoint and +`--address` to `0.0.0.0:5501`. Check its rollout and TCP health probes on this +listener (container port `supervisor`, 5501). The outbound gateway connection and +session RPCs are not implemented yet; sandboxes still use dedicated supervisors. `workspace` is reserved and rejected. Omit the field to disable it. diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index 5835853442..9af1dd1f16 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -63,11 +63,13 @@ backend or enable a Windows workload runtime. ## Experimental shared host -`openshell-supervisor shared --session-control-listen 0.0.0.0:5501` +`openshell-supervisor --mode=shared --gateway-endpoint https://gateway:8080 --address 0.0.0.0:5501` starts a shared-host placeholder for the Kubernetes gateway topology experiment. -Its future control listener accepts and closes connections, including kubelet -`tcpSocket` probes, and reports process health only. No control RPCs, sandbox -sessions, gateway authentication, or tenant routing are implemented yet. +The listener at `--address` accepts and closes connections, including kubelet +`tcpSocket` probes, and reports process health only. `--gateway-endpoint` selects +the gateway the supervisor will dial for session control. The outbound connection, +control RPCs, sandbox sessions, gateway authentication, and tenant routing are not +implemented yet. Omitting `--mode` keeps the existing dedicated behavior. `experimental_supervisor_topology = "gateway"` deploys this placeholder; sandboxes continue using dedicated supervisors. SIGTERM and SIGINT close the listener. -This subcommand is Unix-only. +Shared mode is Unix-only. diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/experimental.rs index 026d1de868..bc67386fd4 100644 --- a/crates/openshell-supervisor/src/experimental.rs +++ b/crates/openshell-supervisor/src/experimental.rs @@ -3,28 +3,19 @@ //! Shared-host placeholder: no sandbox admission, credentials, or session RPCs. -use clap::Parser; #[cfg(unix)] use miette::IntoDiagnostic; use miette::Result; use std::net::SocketAddr; -#[derive(Debug, Parser)] -#[command(name = "openshell-supervisor shared")] -#[command(about = "Start a shared supervisor placeholder; sandbox sessions are not implemented")] -struct Args { - /// Placeholder control listener; also accepts TCP process-health probes. - #[arg(long)] - session_control_listen: SocketAddr, -} - -pub fn run(raw_args: &[String]) -> Result<()> { - let args = Args::parse_from(raw_args); - run_platform(args) +// The gateway endpoint is reserved for the shared supervisor's outbound session. +// This plumbing stage only starts the local listener. +pub fn run(address: SocketAddr, _gateway_endpoint: &str) -> Result<()> { + run_platform(address) } #[cfg(unix)] -fn run_platform(args: Args) -> Result<()> { +fn run_platform(address: SocketAddr) -> Result<()> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -37,17 +28,19 @@ fn run_platform(args: Args) -> Result<()> { let mut interrupt = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) .into_diagnostic()?; - let control = tokio::net::TcpListener::bind(args.session_control_listen) + let listener = tokio::net::TcpListener::bind(address) .await .into_diagnostic()?; - eprintln!("Shared supervisor started; sandbox sessions are not implemented"); + eprintln!( + "Shared supervisor started; gateway connection and sandbox sessions are not implemented" + ); loop { tokio::select! { - // This placeholder only accepts and closes connections. The same - // listener will serve session-control RPCs in the next stage. - result = control.accept() => { + // This placeholder only accepts and closes connections, including + // health probes. Session control will use an outbound gateway stream. + result = listener.accept() => { if let Err(error) = result { - eprintln!("shared supervisor control accept failed: {error}"); + eprintln!("shared supervisor listener accept failed: {error}"); tokio::time::sleep(std::time::Duration::from_millis(100)).await; } } @@ -60,6 +53,6 @@ fn run_platform(args: Args) -> Result<()> { } #[cfg(not(unix))] -fn run_platform(_args: Args) -> Result<()> { - Err(miette::miette!("shared requires Unix")) +fn run_platform(_address: SocketAddr) -> Result<()> { + Err(miette::miette!("--mode=shared requires Unix")) } diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index dd1aa923b6..489ac6b8c3 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -24,6 +24,15 @@ const DEBUG_RPC_SUBCOMMAND: &str = "debug-rpc"; const HEALTH_SUBCOMMAND: &str = "health"; const SERVICE_NAME: &str = "openshell-supervisor"; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)] +enum SupervisorMode { + /// Supervise a single sandbox generation. + #[default] + Dedicated, + /// Host sandbox supervision sessions in a shared process. + Shared, +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)] enum SupervisorRole { /// Attach an Isolation Backend and supervise one sandbox generation. @@ -47,6 +56,18 @@ struct HealthArgs { #[command(about = "OpenShell policy and workload supervisor")] #[allow(clippy::struct_excessive_bools)] struct Args { + /// Supervisor deployment mode. + #[arg(long, value_enum, default_value_t)] + mode: SupervisorMode, + + /// Gateway endpoint for the shared supervisor's outbound connection. + #[arg(long, required_if_eq("mode", "shared"))] + gateway_endpoint: Option, + + /// Shared supervisor listen address, also used for TCP health probes. + #[arg(long, required_if_eq("mode", "shared"))] + address: Option, + /// Supervisor responsibility to run. #[arg(long, value_enum, default_value_t)] role: SupervisorRole, @@ -366,11 +387,22 @@ fn main() -> Result<()> { return openshell_supervisor::check_control_readiness(&args.socket); } - if raw_args.get(1).map(String::as_str) == Some("shared") { - return experimental::run(&raw_args[1..]); - } - let args = Args::parse(); + if args.mode == SupervisorMode::Shared { + let address = args + .address + .ok_or_else(|| miette::miette!("--address is required for --mode=shared"))?; + let gateway_endpoint = args + .gateway_endpoint + .as_deref() + .ok_or_else(|| miette::miette!("--gateway-endpoint is required for --mode=shared"))?; + return experimental::run(address, gateway_endpoint); + } + if args.gateway_endpoint.is_some() || args.address.is_some() { + return Err(miette::miette!( + "--gateway-endpoint and --address require --mode=shared" + )); + } validate_role_arguments(&args)?; arm_parent_liveness(args.parent_liveness_stdin)?; validate_main_exit_marker(args.main_exit_marker.as_deref())?; diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/experimental-supervisor.yaml index f582fd16d2..786a31bfe6 100644 --- a/deploy/helm/openshell/templates/experimental-supervisor.yaml +++ b/deploy/helm/openshell/templates/experimental-supervisor.yaml @@ -51,11 +51,13 @@ spec: image: {{ get $kubernetes "supervisor_image" | quote }} imagePullPolicy: {{ get (dict "always" "Always" "if_not_present" "IfNotPresent" "never" "Never") (get $kubernetes "supervisor_image_pull_policy" | default "if_not_present") | quote }} args: - - shared - - --session-control-listen + - --mode=shared + - --gateway-endpoint + - {{ include "openshell.grpcEndpoint" . | quote }} + - --address - 0.0.0.0:5501 ports: - - name: session-control + - name: supervisor containerPort: 5501 protocol: TCP securityContext: @@ -71,8 +73,8 @@ spec: memory: 128Mi readinessProbe: tcpSocket: - port: session-control + port: supervisor livenessProbe: tcpSocket: - port: session-control + port: supervisor {{- end }} diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml index 61d7247599..52c64a9099 100644 --- a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml +++ b/deploy/helm/openshell/tests/experimental_supervisor_test.yaml @@ -35,23 +35,28 @@ tests: value: example.com/supervisor:experiment - equal: path: spec.template.spec.containers[0].args - value: [shared, --session-control-listen, "0.0.0.0:5501"] + value: + - --mode=shared + - --gateway-endpoint + - https://openshell.gateway.svc.cluster.local:8080 + - --address + - 0.0.0.0:5501 - equal: path: spec.template.spec.containers[0].ports value: - - name: session-control + - name: supervisor containerPort: 5501 protocol: TCP - equal: path: spec.template.spec.containers[0].readinessProbe value: tcpSocket: - port: session-control + port: supervisor - equal: path: spec.template.spec.containers[0].livenessProbe value: tcpSocket: - port: session-control + port: supervisor - notExists: path: spec.template.spec.containers[0].volumeMounts - notExists: From 8d61a29fbf37b374a07912fd911a9802366f30ac Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 17:17:25 -0700 Subject: [PATCH 8/9] refactor(supervisor): name the shared host module after its mode Rename the shared-mode host module from experimental to shared to match the --mode=shared flag. No behavior changes. Signed-off-by: Drew Newberry --- crates/openshell-supervisor/src/main.rs | 4 ++-- .../openshell-supervisor/src/{experimental.rs => shared.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/openshell-supervisor/src/{experimental.rs => shared.rs} (100%) diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index 489ac6b8c3..0442fd10a2 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -17,8 +17,8 @@ use tracing_subscriber::EnvFilter; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::{Layer as _, layer::SubscriberExt as _, util::SubscriberInitExt as _}; -mod experimental; mod logging; +mod shared; const DEBUG_RPC_SUBCOMMAND: &str = "debug-rpc"; const HEALTH_SUBCOMMAND: &str = "health"; @@ -396,7 +396,7 @@ fn main() -> Result<()> { .gateway_endpoint .as_deref() .ok_or_else(|| miette::miette!("--gateway-endpoint is required for --mode=shared"))?; - return experimental::run(address, gateway_endpoint); + return shared::run(address, gateway_endpoint); } if args.gateway_endpoint.is_some() || args.address.is_some() { return Err(miette::miette!( diff --git a/crates/openshell-supervisor/src/experimental.rs b/crates/openshell-supervisor/src/shared.rs similarity index 100% rename from crates/openshell-supervisor/src/experimental.rs rename to crates/openshell-supervisor/src/shared.rs From 90537efa8c78aca77b862f09f782621bab787cc3 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 20 Oct 2026 17:41:54 -0700 Subject: [PATCH 9/9] refactor(kubernetes): drop the experimental prefix from the supervisor topology Rename the Helm template to supervisor.yaml, its Kubernetes names and labels to the -supervisor suffix and supervisor component, and the driver setting to supervisor_topology. The setting remains opt-in and workspace remains reserved. Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 6 +-- .../openshell-driver-kubernetes/src/config.rs | 15 ++++---- crates/openshell-driver-kubernetes/src/lib.rs | 6 +-- .../openshell-driver-kubernetes/src/main.rs | 2 +- crates/openshell-supervisor/README.md | 6 +-- deploy/helm/openshell/templates/_helpers.tpl | 10 ++--- ...mental-supervisor.yaml => supervisor.yaml} | 14 +++---- ...ervisor_test.yaml => supervisor_test.yaml} | 38 +++++++++---------- 8 files changed, 48 insertions(+), 49 deletions(-) rename deploy/helm/openshell/templates/{experimental-supervisor.yaml => supervisor.yaml} (83%) rename deploy/helm/openshell/tests/{experimental_supervisor_test.yaml => supervisor_test.yaml} (68%) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index dd161a459f..d924bf1ece 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -57,15 +57,15 @@ Override with env vars before running `helm:k3s:create`: `mcr.microsoft.com/dotnet/aspire-dashboard:latest`) - `HELM_K3S_COLLECTOR_HEALTH_TIMEOUT` (default: `120` seconds) -### Experimental supervisor plumbing +### Shared supervisor plumbing -Set `gatewayConfig.openshell.drivers.kubernetes.experimental_supervisor_topology` +Set `gatewayConfig.openshell.drivers.kubernetes.supervisor_topology` using a values file (the table key is literally `openshell.drivers.kubernetes`): ```yaml gatewayConfig: openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway + supervisor_topology: gateway ``` The gateway chart creates a separate, single-replica shared-host placeholder Deployment diff --git a/crates/openshell-driver-kubernetes/src/config.rs b/crates/openshell-driver-kubernetes/src/config.rs index 6062aa0dff..ccf130fab6 100644 --- a/crates/openshell-driver-kubernetes/src/config.rs +++ b/crates/openshell-driver-kubernetes/src/config.rs @@ -194,10 +194,10 @@ where Ok(value) } -/// Experimental scope of a shared supervisor deployment. +/// Scope of a shared supervisor deployment. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum ExperimentalSupervisorTopology { +pub enum SupervisorTopology { Gateway, Workspace, } @@ -205,9 +205,9 @@ pub enum ExperimentalSupervisorTopology { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct KubernetesComputeConfig { - /// Deploy an idle experimental supervisor through Helm. Does not route sessions. + /// Deploy an idle shared supervisor through Helm. Does not route sessions. #[serde(skip_serializing_if = "Option::is_none")] - pub experimental_supervisor_topology: Option, + pub supervisor_topology: Option, /// Permit caller-supplied driver JSON. Does not waive resource admission. pub allow_driver_config: bool, /// Operator-owned external attachment approval policy. @@ -400,7 +400,7 @@ pub const ANNOTATION_SCC_SUPPLEMENTAL_GROUPS: &str = "openshift.io/sa.scc.supple impl Default for KubernetesComputeConfig { fn default() -> Self { Self { - experimental_supervisor_topology: None, + supervisor_topology: None, workspace_mode: WorkspaceMode::default(), allow_driver_config: false, resource_admission: @@ -454,9 +454,8 @@ impl Default for KubernetesComputeConfig { impl KubernetesComputeConfig { /// Validate startup configuration without connecting to Kubernetes. pub fn validate_configuration(&self) -> Result<(), String> { - if self.experimental_supervisor_topology == Some(ExperimentalSupervisorTopology::Workspace) - { - return Err("experimental_supervisor_topology=workspace is not implemented".into()); + if self.supervisor_topology == Some(SupervisorTopology::Workspace) { + return Err("supervisor_topology=workspace is not implemented".into()); } self.validate_workspace_mode()?; self.validate_provider_spiffe_workload_api_socket_path()?; diff --git a/crates/openshell-driver-kubernetes/src/lib.rs b/crates/openshell-driver-kubernetes/src/lib.rs index aa18fe0506..963935abef 100644 --- a/crates/openshell-driver-kubernetes/src/lib.rs +++ b/crates/openshell-driver-kubernetes/src/lib.rs @@ -12,9 +12,9 @@ mod sandbox_runtime; pub use config::{ DEFAULT_GATEWAY_ID, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME, DEFAULT_WORKSPACE_STORAGE_SIZE, - ExperimentalSupervisorTopology, KubernetesComputeConfig, KubernetesControlConnection, - KubernetesImagePullPolicy, KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, - WorkspaceMode, managed_namespace_prefix, + KubernetesComputeConfig, KubernetesControlConnection, KubernetesImagePullPolicy, + KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, SupervisorTopology, WorkspaceMode, + managed_namespace_prefix, }; pub use driver::{KubernetesComputeDriver, KubernetesDriverError}; pub use grpc::ComputeDriverService; diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index c0c3b46436..30c7f899e7 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -260,7 +260,7 @@ async fn main() -> Result<()> { let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); let driver = KubernetesComputeDriver::new( KubernetesComputeConfig { - experimental_supervisor_topology: None, + supervisor_topology: None, allow_driver_config: args.admission_config_json.allow_driver_config, resource_admission: args.admission_config_json.resource_admission.clone(), workspace_mode: args.workspace_mode, diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index 9af1dd1f16..8108c6ea03 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -61,15 +61,15 @@ belong to the sandbox or isolation backend. Signals use `BoundarySignal`. These portable control-plane foundations do not qualify a platform isolation backend or enable a Windows workload runtime. -## Experimental shared host +## Shared host `openshell-supervisor --mode=shared --gateway-endpoint https://gateway:8080 --address 0.0.0.0:5501` -starts a shared-host placeholder for the Kubernetes gateway topology experiment. +starts a shared-host placeholder for the Kubernetes gateway supervisor topology. The listener at `--address` accepts and closes connections, including kubelet `tcpSocket` probes, and reports process health only. `--gateway-endpoint` selects the gateway the supervisor will dial for session control. The outbound connection, control RPCs, sandbox sessions, gateway authentication, and tenant routing are not implemented yet. Omitting `--mode` keeps the existing dedicated behavior. -`experimental_supervisor_topology = "gateway"` deploys this placeholder; sandboxes +`supervisor_topology = "gateway"` deploys this placeholder; sandboxes continue using dedicated supervisors. SIGTERM and SIGINT close the listener. Shared mode is Unix-only. diff --git a/deploy/helm/openshell/templates/_helpers.tpl b/deploy/helm/openshell/templates/_helpers.tpl index d0ebc75836..2421463d3d 100644 --- a/deploy/helm/openshell/templates/_helpers.tpl +++ b/deploy/helm/openshell/templates/_helpers.tpl @@ -569,17 +569,17 @@ Validate chart values that Helm would otherwise accept silently. */}} {{- define "openshell.validateValues" -}} {{- $kubernetes := include "openshell.effectiveKubernetesConfig" . | fromYaml -}} -{{- if hasKey $kubernetes "experimental_supervisor_topology" -}} -{{- $topology := get $kubernetes "experimental_supervisor_topology" -}} +{{- if hasKey $kubernetes "supervisor_topology" -}} +{{- $topology := get $kubernetes "supervisor_topology" -}} {{- if not (has $topology (list "gateway" "workspace")) -}} -{{- fail "experimental_supervisor_topology must be gateway or workspace; omit it to disable the experiment" -}} +{{- fail "supervisor_topology must be gateway or workspace; omit it to disable the shared supervisor" -}} {{- end -}} {{- if eq $topology "workspace" -}} -{{- fail "experimental_supervisor_topology=workspace is not implemented" -}} +{{- fail "supervisor_topology=workspace is not implemented" -}} {{- end -}} {{- $gateway := get (.Values.gatewayConfig | default dict) "openshell.gateway" | default dict -}} {{- if ne (get $gateway "compute_driver" | default "kubernetes") "kubernetes" -}} -{{- fail "experimental_supervisor_topology requires the kubernetes compute driver" -}} +{{- fail "supervisor_topology requires the kubernetes compute driver" -}} {{- end -}} {{- end -}} {{- $workloadKind := include "openshell.workloadKind" . -}} diff --git a/deploy/helm/openshell/templates/experimental-supervisor.yaml b/deploy/helm/openshell/templates/supervisor.yaml similarity index 83% rename from deploy/helm/openshell/templates/experimental-supervisor.yaml rename to deploy/helm/openshell/templates/supervisor.yaml index 786a31bfe6..9d5c695cfd 100644 --- a/deploy/helm/openshell/templates/experimental-supervisor.yaml +++ b/deploy/helm/openshell/templates/supervisor.yaml @@ -4,32 +4,32 @@ {{- range .Values.imagePullSecrets -}} {{- $imagePullSecrets = append $imagePullSecrets .name -}} {{- end -}} -{{- if eq (get $kubernetes "experimental_supervisor_topology" | default "") "gateway" }} +{{- if eq (get $kubernetes "supervisor_topology" | default "") "gateway" }} # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "openshell.fullname" . }}-experimental-supervisor + name: {{ include "openshell.fullname" . }}-supervisor namespace: {{ .Release.Namespace }} labels: {{- include "openshell.labels" . | nindent 4 }} - app.kubernetes.io/component: experimental-supervisor + app.kubernetes.io/component: supervisor spec: replicas: 1 strategy: type: Recreate selector: matchLabels: - app.kubernetes.io/name: openshell-experimental-supervisor + app.kubernetes.io/name: openshell-supervisor app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/component: experimental-supervisor + app.kubernetes.io/component: supervisor template: metadata: labels: - app.kubernetes.io/name: openshell-experimental-supervisor + app.kubernetes.io/name: openshell-supervisor app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/component: experimental-supervisor + app.kubernetes.io/component: supervisor spec: automountServiceAccountToken: false terminationGracePeriodSeconds: 30 diff --git a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml b/deploy/helm/openshell/tests/supervisor_test.yaml similarity index 68% rename from deploy/helm/openshell/tests/experimental_supervisor_test.yaml rename to deploy/helm/openshell/tests/supervisor_test.yaml index 52c64a9099..2276b3469b 100644 --- a/deploy/helm/openshell/tests/experimental_supervisor_test.yaml +++ b/deploy/helm/openshell/tests/supervisor_test.yaml @@ -1,25 +1,25 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -suite: experimental supervisor deployment +suite: supervisor deployment templates: - - templates/experimental-supervisor.yaml + - templates/supervisor.yaml - templates/gateway-config.yaml release: name: openshell namespace: gateway tests: - it: adds no supervisor by default - template: templates/experimental-supervisor.yaml + template: templates/supervisor.yaml asserts: - hasDocuments: count: 0 - it: deploys one isolated idle process in the gateway namespace - template: templates/experimental-supervisor.yaml + template: templates/supervisor.yaml set: gatewayConfig: openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway - supervisor_image: example.com/supervisor:experiment + supervisor_topology: gateway + supervisor_image: example.com/supervisor:shared asserts: - equal: path: metadata.namespace @@ -29,10 +29,10 @@ tests: value: 1 - equal: path: spec.template.metadata.labels["app.kubernetes.io/name"] - value: openshell-experimental-supervisor + value: openshell-supervisor - equal: path: spec.template.spec.containers[0].image - value: example.com/supervisor:experiment + value: example.com/supervisor:shared - equal: path: spec.template.spec.containers[0].args value: @@ -69,37 +69,37 @@ tests: set: gatewayConfig: openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway + supervisor_topology: gateway asserts: - matchRegex: path: data["gateway.toml"] - pattern: 'experimental_supervisor_topology = "gateway"' + pattern: 'supervisor_topology = "gateway"' - it: rejects workspace until implemented - template: templates/experimental-supervisor.yaml + template: templates/supervisor.yaml set: gatewayConfig: openshell.drivers.kubernetes: - experimental_supervisor_topology: workspace + supervisor_topology: workspace asserts: - failedTemplate: - errorMessage: experimental_supervisor_topology=workspace is not implemented + errorMessage: supervisor_topology=workspace is not implemented - it: rejects invalid topology - template: templates/experimental-supervisor.yaml + template: templates/supervisor.yaml set: gatewayConfig: openshell.drivers.kubernetes: - experimental_supervisor_topology: invalid + supervisor_topology: invalid asserts: - failedTemplate: - errorMessage: experimental_supervisor_topology must be gateway or workspace; omit it to disable the experiment + errorMessage: supervisor_topology must be gateway or workspace; omit it to disable the shared supervisor - it: rejects enabled topology for another compute driver - template: templates/experimental-supervisor.yaml + template: templates/supervisor.yaml set: gatewayConfig: openshell.gateway: compute_driver: docker openshell.drivers.kubernetes: - experimental_supervisor_topology: gateway + supervisor_topology: gateway asserts: - failedTemplate: - errorMessage: experimental_supervisor_topology requires the kubernetes compute driver + errorMessage: supervisor_topology requires the kubernetes compute driver