Skip to content

feat(sandbox)!: introduce reusable sandbox templates - #2781

Open
grs wants to merge 5 commits into
NVIDIA:mainfrom
grs:create-sandbox-cleanup
Open

feat(sandbox)!: introduce reusable sandbox templates#2781
grs wants to merge 5 commits into
NVIDIA:mainfrom
grs:create-sandbox-cleanup

Conversation

@grs

@grs grs commented Aug 17, 2026

Copy link
Copy Markdown

Summary

Introduce workspace-scoped reusable sandbox templates and move driver-specific sandbox configuration behind those named templates. Direct sandbox creates now use a portable workload shape, while templates can own shared image, environment, resources, driver config, and startup service objectives.

Note: this is a breaking change!

openshell sandbox template create my-template --cpu 0.2 
openshell sandbox create my-sandbox --template my-template

Related Issue

Fixes #2680

Changes

  • Add sandbox template API methods for create, get, list, and delete, with workspace authorization and persistence.
  • Reshape CreateSandboxRequest to accept either an inline SandboxWorkloadConfig or a workload_template_name, with policy and providers promoted to top-level request fields.
  • Persist resolved sandbox workload, selected driver config, and template provenance on created sandboxes.
  • Add openshell sandbox template create|get|list|delete CLI commands.
  • Add openshell sandbox create --template <name> and reject conflicting inline workload flags such as --from, --cpu, --memory, --gpu, --env, and --driver-config-json.
  • Move direct driver config from sandbox create --driver-config-json to template creation.
  • Update Rust, Go, TypeScript, and Python SDK/client paths for the new create request shape and template-related types.
  • Update docs for sandbox templates, driver-owned config, GPU behavior, and compute-driver examples.

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

Signed-off-by: Gordon Sim <gsim@redhat.com>
@grs
grs requested review from a team, derekwaynecarr, mrunalp and sjenning as code owners August 17, 2026 19:13
@copy-pr-bot

copy-pr-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@drew drew left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

Validation: Project-valid implementation of linked, validated issue #2680.
Head SHA: 90410d1126c65b9c386d40c19311d8124336f3ec
Base SHA: 88cf35efe426024eb19fdc403f594a1602f44d49
Merge base SHA: 88cf35efe426024eb19fdc403f594a1602f44d49
Patch ID: 1509788c1e04fbf97b0964a4804cb5ec9eafa93e
Gator payload: 4
Review mode: initial
Previous reviewed SHA: none
Review budget exhausted: no
Maintainer decision required: no

Blocking findings:

  • GATOR-90410d11-01: A CLI --all-workspaces option must clear the workspace field before calling an API that defines all_workspaces and workspace as mutually exclusive.
  • GATOR-90410d11-02: Supported SDK create methods must reject provisioning fields that are no longer valid create inputs rather than silently discard them and create a differently configured sandbox.
  • GATOR-90410d11-03: Bundled agent command guidance must not recommend a workflow that a breaking CLI change now rejects, and must cover the replacement workflow identified by the skill-maintenance map.

Carried findings:

  • None

Non-blocking suggestions:

  • None

Docs: Relevant Fern docs under docs/ were updated; existing navigation does not need a new entry. Companion agent command guidance still needs the correction in GATOR-90410d11-03.

Next state: gator:in-review

Comment thread crates/openshell-cli/src/run.rs Outdated
.list_sandbox_templates(ListSandboxTemplatesRequest {
limit,
offset,
workspace: workspace.to_string(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-90410d11-01

Invariant: A CLI --all-workspaces option must clear the workspace field before calling an API that defines all_workspaces and workspace as mutually exclusive.

Prerequisite: A platform administrator invokes openshell sandbox template list --all-workspaces; the global workspace flag retains its default value of default even when the operator does not specify it.

Entry point → sink: openshell sandbox template list --all-workspaces. → The CLI sends ListSandboxTemplatesRequest to ListSandboxTemplates, whose server handler rejects requests containing both all_workspaces=true and a non-empty workspace.

Base → head: The base tree had no template-list command, while its existing sandbox, service, and provider all-workspace list paths explicitly serialized an empty workspace when all_workspaces was true. → The new template-list path always serializes workspace.to_string(); because the CLI workspace defaults to default, every normal --all-workspaces invocation reaches the server with both mutually exclusive fields set.

Impact: Cross-workspace template listing is deterministically unusable from the new CLI and returns INVALID_ARGUMENT instead of listing templates.

Reproducer: Call sandbox_template_list(server, 100, 0, "json", "default", true, tls) against the real handler or a validating test server. Observe a request with workspace="default" and all_workspaces=true, followed by all_workspaces and workspace are mutually exclusive.

PR ownership: This PR introduces the template list command, its request serialization, and the mutually-exclusive server validation; the mismatch exists only on the newly added path.

Requested change: Serialize workspace: String::new() when all_workspaces is true, matching the existing list implementations, and add an integration assertion covering the true case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @drew, I have fixed this and merged the fix into the cli focused commit.

Comment thread sdk/go/openshell/v1/sandbox_client.go Outdated
req.Annotations = converter.CopyStringMap(opts[0].Annotations)
}
if protoSpec != nil {
req.WorkloadSource = &pb.CreateSandboxRequest_Workload{Workload: protoSpec.GetWorkload()}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-90410d11-02

Invariant: Supported SDK create methods must reject provisioning fields that are no longer valid create inputs rather than silently discard them and create a differently configured sandbox.

Prerequisite: An application migrates to the new Go or Python SDK types and supplies non-empty SandboxSpec.driver_config, including security-sensitive settings such as runtime isolation, user namespaces, or mounts.

Entry point → sink: Go Sandboxes().Create(..., *SandboxSpec, ...) and Python SandboxClient.create(spec=SandboxSpec(...)). → The SDK request builders construct CreateSandboxRequest and invoke CreateSandbox; the gateway provisions the sandbox from the emitted workload without the caller's driver configuration.

Base → head: The base Go client sent the converted SandboxSpec wholesale and the Python client copied the supplied spec wholesale, so driver config placed in the old inline template reached CreateSandbox. → The new public Go SandboxSpec still exposes DriverConfig and its converter accepts it, but the client copies only workload, policy, and providers. Python performs the same selective copy. Both calls can succeed while silently omitting the requested driver configuration.

Impact: Applications can report successful sandbox creation while the runtime lacks the requested driver behavior, potentially omitting intended isolation controls or exposing different mounts. This is a fail-open configuration error for security-related settings (CWE-693).

Reproducer: In the Go mock-client test, call Create with SandboxSpec{Workload: ..., DriverConfig: map[string]any{"kubernetes": ...}} and capture the RPC; it succeeds with no driver config represented. Repeat with Python using SandboxSpec(driver_config=...); _create_sandbox_request returns a workload request and silently drops the field.

PR ownership: This PR removes driver config from direct create and rewrites both SDK request adapters, but leaves the resolved SandboxSpec type as their accepted input without validating the newly output-only field.

Requested change: Before issuing the RPC, return a clear invalid-argument error whenever create receives non-empty DriverConfig, directing callers to a named template. Alternatively, introduce a create-specific input that cannot express resolved-only fields. Add Go and Python tests proving the value is rejected rather than ignored.

Sibling sites: python/openshell/sandbox.py:1122

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @drew, I have improved all the SDKs and addressed these comments within those changes.

driver_config,
..SandboxTemplate::default()
})
if driver_config_json.is_some() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-90410d11-03

Invariant: Bundled agent command guidance must not recommend a workflow that a breaking CLI change now rejects, and must cover the replacement workflow identified by the skill-maintenance map.

Prerequisite: A user or agent follows the repository's openshell-cli skill to apply driver-specific sandbox configuration.

Entry point → sink: The openshell-cli skill's Sandbox Lifecycle workflow and its companion CLI reference. → sandbox_create rejects the skill-recommended direct --driver-config-json command before sending CreateSandbox.

Base → head: Direct openshell sandbox create --driver-config-json ... was supported and matched the bundled skill and CLI reference. → Direct create now always rejects that flag and requires named templates, but the skill still lists --driver-config-json as a direct-create flag and contains no template-management or --template workflow.

Impact: Agents following the maintained project skill emit commands that now fail and receive no skill-level guidance for creating, inspecting, using, or deleting the replacement template resource.

Reproducer: Follow .agents/skills/openshell-cli/SKILL.md Workflow 3 or the create flag table in cli-reference.md and invoke openshell sandbox create --driver-config-json '{"kubernetes":{}}'; observe the deterministic error that driver config is only supported through named templates.

PR ownership: The PR intentionally makes this CLI workflow breaking. The sync-agent-infra maintenance map routes CLI command and workflow changes to openshell-cli, but the changed-file set omits that skill.

Requested change: Update the skill and companion CLI reference to document template create/get/list/delete, sandbox create --template, template conflicts, and the removal of direct-create driver config.

Sibling sites: .agents/skills/openshell-cli/SKILL.md:205, .agents/skills/openshell-cli/cli-reference.md:218

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @drew, I have fixed this and merged the fix into the cli focused commit.

@drew drew added the gator:in-review Gator is reviewing or awaiting PR review feedback label Aug 18, 2026
Signed-off-by: Gordon Sim <gsim@redhat.com>
@grs
grs force-pushed the create-sandbox-cleanup branch from 90410d1 to 8b55925 Compare August 18, 2026 12:20

@drew drew left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

Validation: Project-valid implementation of linked, validated issue #2680.
Head SHA: 8b559258e1e0100bc0ecae441874cca4c3cfd118
Base SHA: dc374e88784a7e4a7366abd1fdb56c969efe6769
Merge base SHA: 88cf35efe426024eb19fdc403f594a1602f44d49
Patch ID: c9156e4e480bf533c7a8a1b351c96e6610cb0c14
Gator payload: 4
Review mode: follow_up
Previous reviewed SHA: 90410d1126c65b9c386d40c19311d8124336f3ec
Review budget exhausted: no
Maintainer decision required: no

Thanks @grs. I checked your notes that the CLI all-workspaces path and bundled CLI guidance were fixed in the CLI-focused commit. The list request now clears workspace and has an integration assertion, and the skill/reference now document template CRUD, --template, conflicts, and the removal of direct-create driver config. I also checked the remaining SDK obligation: the Go and Python create APIs now take create-specific workload/policy/provider inputs, so they can no longer silently accept and discard resolved driver configuration.

Resolved prior findings:

  • GATOR-90410d11-01: resolved by the corrected CLI request and integration coverage.
  • GATOR-90410d11-02: resolved by the create-specific Go and Python API inputs.
  • GATOR-90410d11-03: resolved by the updated bundled skill and CLI reference.

Blocking findings:

  • GATOR-8b559258-01: migrate the Python E2E harness to the breaking create API so required security-sensitive suites remain executable.
  • GATOR-8b559258-02: make the Go fake resolve workspace-scoped templates with production-equivalent NotFound, workload, driver-config, and provenance behavior.

Carried findings:

  • None.

Docs: Fern docs and companion agent guidance are updated; existing navigation does not need a new entry.

Next state: gator:in-review

sandbox: str | SandboxRef | None = None,
delete_on_exit: bool = True,
spec: openshell_pb2.SandboxSpec | None = None,
workload: openshell_pb2.SandboxWorkloadConfig | None = None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-8b559258-01

Invariant: Breaking changes to the Python sandbox creation surface must migrate the supported E2E harness so security-sensitive sandbox tests remain executable.

Prerequisite: An operator runs the Python E2E suite through its shared sandbox fixture or the direct provider-test client path.

Entry point → sink: e2e/python/conftest.py or test_sandbox_providers.py → Python argument binding for Sandbox.__init__ or SandboxClient.create.

Base → head: The prior API accepted spec, matching the shared E2E callers. This delta replaces it with workload, policy, and providers, while those callers still pass spec, so they fail before issuing an RPC.

Impact: Policy, Landlock, inference-routing, and provider E2E coverage aborts before reaching the gateway and cannot detect regressions in this breaking API.

Reproducer: Invoke the existing fixture as sandbox(spec=datamodel_pb2.SandboxSpec(policy=...)); it deterministically raises TypeError: unexpected keyword argument 'spec'. The direct call at e2e/python/test_sandbox_providers.py:423 fails likewise.

PR ownership: This delta changes the Python create signatures but leaves repository callers of the removed parameter unchanged.

Requested change: Update the shared fixture to unpack its SandboxSpec into workload, policy, and providers, and migrate the direct SandboxClient caller so existing tests exercise the new API.

Sibling sites: e2e/python/conftest.py:94, e2e/python/test_sandbox_providers.py:423.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again! I have fixed that and verified all python e2e tests pass.

if spec == nil {
spec = &types.SandboxSpec{}
// CreateFromTemplate creates a new sandbox from a named template with Provisioning phase.
func (c *fakeSandboxClient) CreateFromTemplate(_ context.Context, workspace, name, templateName string, policy *types.SandboxPolicy, providers []string, labels map[string]string, opts ...types.CreateOptions) (*types.Sandbox, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-8b559258-02

Invariant: The Go fake client's CreateFromTemplate path must model workspace-scoped resolution, including NotFound behavior and the resolved workload, driver configuration, and provenance returned by the gateway.

Prerequisite: An SDK consumer tests template-based creation with fake.NewClient.

Entry point → sink: fake.Client.Sandboxes().CreateFromTemplate → the fake sandbox store persists a sandbox without consulting the template store.

Base → head: The previous tree had no fake template-create path. The new method accepts any non-empty name and returns a sandbox with nil workload and driver config, even when the template is absent or contains those values.

Impact: Supported fake-client tests can pass when production returns NotFound and cannot verify application of security-sensitive driver configuration or workload settings.

Reproducer: Call CreateFromTemplate for missing and observe success; then create gpu-kata with an image and driver config and observe the returned sandbox has nil Spec.Workload and Spec.DriverConfig.

PR ownership: This delta introduces both the template store and fake CreateFromTemplate, but wires the sandbox and template clients to independent stores without resolution semantics.

Requested change: Pass the template store into the fake sandbox client; look up the template in the requested workspace, return NotFound when absent, deep-copy its workload and driver config, populate provenance with its resource version, and add tests for missing and resolved templates.

Sibling site: sdk/go/openshell/v1/fake/fake.go:82.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I have now fixed this.

grs added 3 commits August 18, 2026 13:57
Signed-off-by: Gordon Sim <gsim@redhat.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>
@grs
grs force-pushed the create-sandbox-cleanup branch from 8b55925 to 97e5fa4 Compare August 18, 2026 14:07

@drew drew left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

Validation: Project-valid implementation of linked, validated issue #2680.
Head SHA: 97e5fa442a59e19765f6549b9659719890192b99
Base SHA: dc374e88784a7e4a7366abd1fdb56c969efe6769
Merge base SHA: 88cf35efe426024eb19fdc403f594a1602f44d49
Patch ID: 2c1133ee75c560e59cac0e31c7d56eaf841f9cd5
Gator payload: 4
Review mode: follow_up
Previous reviewed SHA: 8b559258e1e0100bc0ecae441874cca4c3cfd118
Review budget exhausted: no
Maintainer decision required: no

I checked the latest author delta against the two outstanding obligations. The Python E2E fixture and direct provider-test client now adapt the old spec shape to the new create inputs, so the security-sensitive suites can reach the gateway. The Go fake now resolves templates through the workspace-scoped template store, returns NotFound for missing templates, deep-copies workload and driver configuration, and records template resource-version provenance with coverage for those behaviors.

Resolved prior findings:

  • GATOR-8b559258-01: resolved by the Python E2E harness migration.
  • GATOR-8b559258-02: resolved by production-equivalent fake template resolution and tests.

Blocking findings:

  • No blocking findings remain.

Carried findings:

  • None.

Docs: Fern docs and companion agent guidance are updated; existing navigation does not need a new entry.

Next state: gator:in-review pending required test dispatch confirmation.

@drew drew added test:e2e Requires end-to-end coverage test:e2e-gpu Requires GPU end-to-end coverage labels Aug 18, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied, but pull-request/2781 does not exist yet. A maintainer needs to comment /ok to test 97e5fa442a59e19765f6549b9659719890192b99 to mirror this PR. Once the mirror exists, re-apply the label or re-run Branch E2E Checks from the Actions tab.

@github-actions

Copy link
Copy Markdown

Label test:e2e-gpu applied, but pull-request/2781 does not exist yet. A maintainer needs to comment /ok to test 97e5fa442a59e19765f6549b9659719890192b99 to mirror this PR. Once the mirror exists, re-apply the label or re-run Branch E2E Checks from the Actions tab.

@drew

drew commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

/ok to test 97e5fa4

@drew drew added test:e2e Requires end-to-end coverage test:e2e-gpu Requires GPU end-to-end coverage and removed test:e2e Requires end-to-end coverage test:e2e-gpu Requires GPU end-to-end coverage labels Aug 18, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied for 97e5fa4. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@github-actions

Copy link
Copy Markdown

Label test:e2e-gpu applied for 97e5fa4. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute GPU E2E after building the required supervisor image once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@drew drew added gator:blocked Gator is blocked by process or repository gates and removed gator:in-review Gator is reviewing or awaiting PR review feedback labels Aug 18, 2026

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't disagree that we're mixing different concepts when creating a sandbox, I don't know whether exposing templates as a first-class concept is what we want here (or at least not at this stage).

We also haven't answered the question as to whether OpenShell should be the entity that manage the lifecycle of templates. Would storing these out of band (assuming something like #1520) not be a better solution to start with?

Let's discuss this further in #2680.

@drew drew added gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates and removed gator:blocked Gator is blocked by process or repository gates gator:in-review Gator is reviewing or awaiting PR review feedback labels Aug 18, 2026
@grs

grs commented Aug 18, 2026

Copy link
Copy Markdown
Author

While I don't disagree that we're mixing different concepts when creating a sandbox, I don't know whether exposing templates as a first-class concept is what we want here (or at least not at this stage).

We also haven't answered the question as to whether OpenShell should be the entity that manage the lifecycle of templates. Would storing these out of band (assuming something like #1520) not be a better solution to start with?

I very much like the idea behind #1520. To me though it is orthogonal to whether you have a first class template type. The value of a template is that it removes one set of concerns from those creating sandboxes. The workspace admin can setup up tested templates which can then be used by name without needing to know the details. The templates can be changed as needed, and use cases that reference them pick up those changes for any new sandboxes.

A specific benefit is warm pooling. Knowing up front what sorts of workloads are likely to be created means that warm pools can be created if desired. This is of course not the only way to do so, but I think it works well as a secondary motivation.

Being able to create both templates and sandboxes through some form of apply -f seems valuable to me. The ability to create sandboxes in that would would likewise in my view not remove the benefit of a named template. It would allow separation of different concerns.

As an example, lets say a particular use case has the need for two categories of sandbox, lets say 'codex small' and 'codex large' for example. By having templates to reflect these two categories, the definitions of the sandboxes remain simpler, and more focused on the providers. If it is decided that the memory needs need to be increased for one or other, you can do that in one place rather than in all the sandbox definitions.

@drew drew added gator:in-review Gator is reviewing or awaiting PR review feedback and removed gator:blocked Gator is blocked by process or repository gates labels Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gator:in-review Gator is reviewing or awaiting PR review feedback test:e2e Requires end-to-end coverage test:e2e-gpu Requires GPU end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CreateSandbox Template and API Cleanup Proposal

3 participants