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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion .agents/skills/openshell-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Key flags:
- `--policy`: Custom policy YAML (otherwise uses built-in default or `OPENSHELL_SANDBOX_POLICY` env var)
- `--gpu [COUNT]`: Request the driver's default GPU selection or a specific GPU count
- `--cpu`, `--memory`: Set per-sandbox compute sizing. Docker/Podman apply limits; Kubernetes applies matching requests and limits.
- `--driver-config-json`: Pass experimental driver-specific sandbox configuration
- `--template NAME`: Create from a named sandbox template. Use templates for driver-specific configuration.
- `--label KEY=VALUE`: Add labels for later selection (repeatable)
- `--env KEY=VALUE`: Set non-secret sandbox environment variables (repeatable); use `--provider` for credentials
- `--approval-mode manual|auto`: Control handling of agent-authored policy proposals; `manual` is the default
Expand All @@ -212,6 +212,42 @@ Key flags:
- `--forward [BIND_ADDRESS:]PORT`: Forward a local port and keep the sandbox alive
- `--editor vscode|cursor`: Open a remote editor after creation and keep the sandbox alive

Direct `sandbox create --driver-config-json` is rejected. Driver-specific
settings belong to reusable sandbox templates:

```bash
openshell sandbox template create gpu-kata \
--from ghcr.io/nvidia/openshell-community/sandboxes/python:latest \
--driver-config-json '{"kubernetes":{"pod":{"node_selector":{"pool":"gpu"}}}}' \
--gpu 1

openshell sandbox create --name my-sandbox --template gpu-kata --provider my-github
```

`--template` uses the template workload, so it cannot be combined with inline
workload flags such as `--from`, `--gpu`, `--cpu`, `--memory`, `--env`, or
`--driver-config-json`. Keep per-run policy, providers, labels, uploads,
forwarding, editor launch, and the initial command on `sandbox create`.

### Manage sandbox templates

```bash
openshell sandbox template create gpu-kata \
--from ghcr.io/nvidia/openshell-community/sandboxes/python:latest \
--cpu 2 \
--memory 4Gi \
--gpu 1 \
--driver-config-json '{"kubernetes":{}}'
openshell sandbox template list
openshell sandbox template list --all-workspaces --output json
openshell sandbox template get gpu-kata
openshell sandbox template delete gpu-kata
```

Template `--from` accepts image references and community sandbox names in this
release. It does not build local Dockerfiles or directories; use direct
`sandbox create --from ./Dockerfile` for local BYOC builds.

### List and inspect sandboxes

```bash
Expand Down Expand Up @@ -706,6 +742,11 @@ $ openshell sandbox upload --help
| Create sandbox (interactive) | `openshell sandbox create` |
| Create sandbox with tool | `openshell sandbox create -- claude` |
| Create sandbox with GPUs | `openshell sandbox create --gpu 1` |
| Create sandbox from template | `openshell sandbox create --template <template>` |
| Create sandbox template | `openshell sandbox template create <name> --from <image> --driver-config-json '{"kubernetes":{}}'` |
| List sandbox templates | `openshell sandbox template list` |
| Inspect sandbox template | `openshell sandbox template get <name>` |
| Delete sandbox template | `openshell sandbox template delete <name>` |
| Create with custom policy | `openshell sandbox create --policy ./p.yaml` |
| Connect to sandbox | `openshell sandbox connect <name>` |
| Stop sandbox compute | `openshell sandbox stop [name]` |
Expand Down
49 changes: 48 additions & 1 deletion .agents/skills/openshell-cli/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@ Create a sandbox through the selected gateway, wait for readiness, then connect,
| Flag | Description |
|------|-------------|
| `--name <NAME>` | Sandbox name (auto-generated if omitted) |
| `--template <NAME>` | Create from a named sandbox template |
| `--from <SOURCE>` | Community name, Dockerfile path, directory, or image reference (BYOC) |
| `--no-keep` | Delete the sandbox after the initial command or shell exits |
| `--editor vscode|cursor` | Launch a remote editor and keep the sandbox alive |
| `--gpu [COUNT]` | Request the driver's default GPU selection or a specific count |
| `--cpu <QUANTITY>` | CPU limit (for example: `500m`, `1`, `2.5`) |
| `--memory <QUANTITY>` | Memory limit (for example: `512Mi`, `4Gi`, `8G`) |
| `--driver-config-json <JSON>` | Experimental driver-keyed configuration object |
| `--provider <NAME>` | Provider to attach (repeatable) |
| `--policy <PATH>` | Custom policy YAML; overrides the built-in default and `OPENSHELL_SANDBOX_POLICY` |
| `--forward <[BIND:]PORT>` | Start a local port forward and keep the sandbox alive |
Expand All @@ -229,6 +229,53 @@ Create a sandbox through the selected gateway, wait for readiness, then connect,
| `--no-git-ignore` | Disable `.gitignore` filtering for `--upload` |
| `[-- COMMAND...]` | Initial command (defaults to an interactive shell) |

Direct sandbox create rejects `--driver-config-json`; use `sandbox template
create --driver-config-json` and then `sandbox create --template <NAME>`.
`--template` conflicts with inline workload flags: `--from`, `--gpu`, `--cpu`,
`--memory`, `--env`, and `--driver-config-json`.

### `openshell sandbox template create NAME [OPTIONS]`

Create a reusable sandbox template. Templates hold workload defaults and
driver-specific configuration for later `sandbox create --template NAME` calls.

| Flag | Description |
|------|-------------|
| `--from <SOURCE>` | Image reference or community sandbox name; local Dockerfiles/directories are not supported for templates |
| `--env <KEY=VALUE>` | Set a non-secret template workload environment variable (repeatable) |
| `--cpu <QUANTITY>` | CPU limit for sandboxes created from the template |
| `--memory <QUANTITY>` | Memory limit for sandboxes created from the template |
| `--gpu [COUNT]` | Request GPU resources for sandboxes created from the template |
| `--driver-config-json <JSON>` | Driver-keyed configuration object owned by the template |
| `--ready-within <DURATION>` | Desired time for template-created sandboxes to become ready |
| `--max-burst <N>` | Maximum expected startup burst for this template |
| `--label <KEY=VALUE>` | Attach a template label (repeatable) |
| `--annotation <KEY=VALUE>` | Attach a template annotation (repeatable) |
| `--output table|yaml|json` | Output format |

### `openshell sandbox template get NAME`

Show a sandbox template.

| Flag | Description |
|------|-------------|
| `--output table|yaml|json` | Output format |

### `openshell sandbox template list`

List sandbox templates.

| Flag | Default | Description |
|------|---------|-------------|
| `--limit <N>` | 100 | Maximum templates |
| `--offset <N>` | 0 | Pagination offset |
| `--all-workspaces` | false | List templates across all workspaces; requires platform-admin permissions |
| `--output table|yaml|json` | `table` | Output format |

### `openshell sandbox template delete NAME...`

Delete one or more sandbox templates by name.

### `openshell sandbox get [name]`

Show sandbox details and the active policy. Metadata identifies sandbox or global policy source and the corresponding revision. The name defaults to the last-used sandbox.
Expand Down
8 changes: 5 additions & 3 deletions architecture/compute-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,11 @@ through the driver configuration. The Helm chart defaults sandbox agents to
`Unconfined` so runtime/default AppArmor profiles do not block supervisor
network namespace setup on AppArmor-enabled nodes.

Resource requirements enter the driver layer through `SandboxSpec.resource_requirements`. This includes a set of GPU requirements, where a user
can request a specific number of GPUs or the driver-specific default behaviour.
For all in-tree drivers, this is equivalent to selecting a single GPU.
Resource requirements enter the driver layer through
`SandboxSpec.workload.resources`. CPU and memory use portable quantity strings,
and GPU requests use an optional `gpu_count`. For all in-tree drivers, omitting
the GPU count while requesting GPU access is equivalent to selecting a single
GPU.

VM runtime state paths are derived only from driver-validated sandbox IDs
matching `[A-Za-z0-9._-]{1,128}`. The gateway-owned VM driver socket uses a
Expand Down
17 changes: 12 additions & 5 deletions crates/openshell-cli/src/commands/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ pub fn ready_false_condition_message(

pub fn provisioning_timeout_message(
timeout_secs: u64,
resource_requirements: Option<&openshell_core::proto::ResourceRequirements>,
resources: Option<&openshell_core::proto::SandboxResources>,
condition_message: Option<&str>,
) -> String {
let mut message = format!("sandbox provisioning timed out after {timeout_secs}s");
Expand All @@ -602,7 +602,7 @@ pub fn provisioning_timeout_message(
message.push_str(condition_message);
}

if resource_requirements.is_some_and(|requirements| requirements.gpu.is_some()) {
if resources.is_some_and(|resources| resources.gpu_count.is_some()) {
message.push_str(
". Hint: this may be because the available GPU is already in use by another sandbox.",
);
Expand Down Expand Up @@ -727,18 +727,24 @@ pub fn parse_duration_to_ms(s: &str) -> Result<i64> {
}
// Split off the last character by its UTF-8 length: indexing by byte
// length would panic on multi-byte units (e.g. "5\u{20ac}").
let last_len = s.chars().last().map_or(0, char::len_utf8);
let (num_str, unit) = s.split_at(s.len() - last_len);
let (num_str, unit) = s.strip_suffix("ms").map_or_else(
|| {
let last_len = s.chars().last().map_or(0, char::len_utf8);
s.split_at(s.len() - last_len)
},
|num| (num, "ms"),
);
let num: i64 = num_str
.parse()
.map_err(|_| miette::miette!("invalid duration: {s} (expected e.g. 5m, 1h, 30s)"))?;
let multiplier = match unit {
"ms" => 1,
"s" => 1_000,
"m" => 60_000,
"h" => 3_600_000,
_ => {
return Err(miette::miette!(
"unknown duration unit: {unit} (use s, m, or h)"
"unknown duration unit: {unit} (use ms, s, m, or h)"
));
}
};
Expand Down Expand Up @@ -1059,6 +1065,7 @@ mod tests {
#[test]
fn parse_duration_to_ms_parses_supported_units() {
assert_eq!(parse_duration_to_ms("30s").expect("parse"), 30_000);
assert_eq!(parse_duration_to_ms("500ms").expect("parse"), 500);
assert_eq!(parse_duration_to_ms("5m").expect("parse"), 300_000);
assert_eq!(parse_duration_to_ms("1h").expect("parse"), 3_600_000);
}
Expand Down
Loading
Loading