Skip to content
Merged
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
18 changes: 16 additions & 2 deletions .agents/skills/openshell-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ This creates a sandbox whose canonical main process is `/bin/bash -l` and
attaches your terminal to that retained process. Add `--detach` to return after
the sandbox becomes ready without attaching.

An explicit trailing command is foreground even when stdin or stdout is not a
terminal. The CLI streams its stdout and stderr and returns its exact exit
status. Exit code 0 leaves a retained sandbox in `Completed`; nonzero leaves it
in `Error` with `MainProcessFailed`. Use `--no-keep` to delete either result
after output drains, or `--detach` for a long-running service.

When supplying `--name`, use a portable DNS-1123 label: at most 63 lowercase alphanumeric or `-` characters, beginning and ending with an alphanumeric character. The Kubernetes driver rejects uppercase letters, underscores, dots, and other names that cannot become Kubernetes resource labels.

**Shortcut for known tools**: When the trailing command is a recognized tool, the CLI auto-creates the required provider from local credentials:
Expand Down Expand Up @@ -248,11 +254,16 @@ Key flags:
- `--approval-mode manual|auto`: Control handling of agent-authored policy proposals; `manual` is the default
- `--upload <PATH>[:<DEST>]`: Upload local files into the container working directory or an explicit destination
- `--no-git-ignore`: Disable `.gitignore` filtering for uploads
- `--no-keep`: Delete the sandbox after the initial command or shell exits
- `--no-keep`: Delete the sandbox after main output and the exit result drain
- `--detach`: Start the canonical main process without attaching
- `--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

`--detach` adds no attachment grace period. When the canonical process exits,
its terminal phase is reported immediately. A foreground create declares one
expected main-process SSH attachment; cleanup finalizes after that connection
closes naturally.

Do not combine `--upload` with a trailing main command. Uploads currently finish
after the canonical process starts; create a scratch sandbox and use
`sandbox exec`, or build the files into the image.
Expand Down Expand Up @@ -363,7 +374,10 @@ openshell sandbox start [name]
Both commands default to the last-used sandbox. Stop stops background
forwards and waits for `Stopped`; start waits for `Ready`. Connect, exec,
file transfer, forwarding, and exposed services are unavailable while
stopped. Delete remains the operation that removes retained state.
stopped or completed. Starting a retained `Completed` or
`Error/MainProcessFailed` sandbox launches a fresh canonical-main instance and
invalidates SSH sessions from the previous runtime generation. Delete remains
the operation that removes retained state.

---

Expand Down
19 changes: 15 additions & 4 deletions .agents/skills/openshell-cli/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,18 +207,28 @@ identity provider. Requires an authenticated gateway connection.
Create a sandbox through the selected gateway and launch its canonical main
process. By default, the CLI attaches to that retained process after the
sandbox becomes ready. A trailing command defines the canonical main process;
without one, the default is `/bin/bash -l` with a PTY.
without one, the default is `/bin/bash -l` with a PTY. Explicit commands remain
foreground in non-interactive automation: stdout and stderr stream to the
caller and the CLI returns the command's exact status. Exit 0 leaves
`Completed`; nonzero leaves `Error/MainProcessFailed`.
Starting either retained terminal result invalidates SSH sessions from the
previous runtime generation.

| Flag | Description |
|------|-------------|
| `--name <NAME>` | Sandbox name (auto-generated if omitted) |
| `--from <SOURCE>` | Community name, Dockerfile path, directory, or image reference (BYOC) |
| `--no-keep` | Delete the sandbox after the initial command or shell exits |
| `--no-keep` | Delete the sandbox after main output and the result drain |
| `--detach` | Start the canonical main process without attaching |
| `--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`) |

`--detach` adds no attachment grace period: the sandbox reports the canonical
process result immediately when it exits. Foreground creation declares one
expected main-process SSH attachment; cleanup finalizes after that connection
drains and closes naturally.
| `--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` |
Expand Down Expand Up @@ -269,8 +279,9 @@ and waits for the `Stopped` phase.

### `openshell sandbox start [name]`

Start a stopped sandbox and wait for `Ready`. The name defaults to the
last-used sandbox.
Start a stopped, failed, or completed sandbox and wait for `Ready`. This
launches a fresh canonical-main instance. The name defaults to the last-used
sandbox.

### `openshell sandbox exec [OPTIONS] -- COMMAND...`

Expand Down
8 changes: 8 additions & 0 deletions architecture/compute-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ The gateway persists lifecycle intent before mutating compute:
Ready -> Stopping -> Stopped -> Starting -> Ready
```

A canonical main process that exits successfully follows `Ready -> Completed`.
A nonzero or signal-normalized result follows `Ready -> Error` with a
`MainProcessFailed` condition. Both retained results may be started explicitly,
which creates a fresh main-process instance. Drivers must not automatically
restart a completed or failed canonical process. Before an explicit restart,
the gateway disconnects the prior supervisor session and deletes its SSH
sessions so credentials cannot cross runtime generations.

`StopSandbox` and `StartSandbox` are idempotent driver operations. Stop
retains the driver resource and its persistent workspace boundary while making
exec, SSH, forwarding, and exposed services unavailable. Start reactivates the
Expand Down
16 changes: 13 additions & 3 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ workloads.
- Coordinate supervisor relay sessions for connect, exec, file sync, and
service forwarding.
- Persist the canonical main-process instance ID and normalized exit code on
sandbox status. Any main process exit transitions the sandbox to `Error`,
including exit code zero.
sandbox status. Exit code zero transitions the sandbox to `Completed`;
nonzero results transition it to `Error/MainProcessFailed`. Infrastructure
failures also use `Error`, with a distinct reason and no fabricated command
result.

The gateway does not enforce agent network policy at request time. That happens
inside each sandbox, where the supervisor and proxy can observe local process
Expand All @@ -26,7 +28,15 @@ identity.
The live supervisor session is the readiness authority for its main-process
instance. The supervisor reports its normalized result through the
sandbox-authenticated `ReportMainProcessExit` RPC, and the gateway rejects
results from stale instance IDs.
results from stale instance IDs. Foreground creation carries a one-shot
attachment intent to the process supervisor. The supervisor durably reports the
result immediately, accepts that declared SSH attachment even when the process
has already exited, sends the retained output and exit status, and waits for the
peer's channel close before finalizing the result for ephemeral cleanup.
Detached commands carry no attachment intent, so they finalize and exit
immediately without a grace period. Finalization is persisted separately from
the exit result; the gateway deletes an ephemeral sandbox only after the
finalized supervisor session disconnects.

## Protocol and Auth

Expand Down
16 changes: 12 additions & 4 deletions architecture/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,15 @@ engine with a gateway policy revision.
re-evaluate.
- If the supervisor relay drops, the sandbox can keep running, but connect and
exec operations fail until the supervisor registers again.
- If the canonical main process exits, including with code 0, the supervisor
reports its normalized exit code before shutdown. The gateway persists the
code on sandbox status, records `MainProcessExited`, and makes the sandbox
terminal `Error`; runtime restart policies must not replace the process.
- If the canonical main process exits, the supervisor durably reports the
normalized result immediately. A foreground create declares a one-shot main
attachment, so the supervisor accepts it even after a fast process exits,
sends the retained output and SSH exit status, waits for the peer's channel
close, and then finalizes ephemeral cleanup. With no declared or active
attachment, it finalizes and exits without a grace period. The gateway waits
for that finalized supervisor session to disconnect before deleting an
ephemeral sandbox. Exit code 0 records
`Completed/MainProcessCompleted`; nonzero and signal-normalized exits record
`Error/MainProcessFailed`. Infrastructure failures also use `Error`, with a
distinct condition reason and no fabricated canonical-process result. Runtime
restart policies must not replace the canonical process.
1 change: 1 addition & 0 deletions crates/openshell-cli/src/commands/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub fn phase_name(phase: i32) -> &'static str {
Ok(SandboxPhase::Stopping) => "Stopping",
Ok(SandboxPhase::Stopped) => "Stopped",
Ok(SandboxPhase::Starting) => "Starting",
Ok(SandboxPhase::Completed) => "Completed",
Ok(SandboxPhase::Unknown) | Err(_) => "Unknown",
}
}
Expand Down
12 changes: 10 additions & 2 deletions crates/openshell-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3071,7 +3071,7 @@ async fn run_async() -> Result<()> {
let endpoint = &ctx.endpoint;
let mut tls = tls.with_gateway_name(&ctx.name);
apply_auth(&mut tls, &ctx.name);
Box::pin(run::sandbox_create(
let exit_code = Box::pin(run::sandbox_create(
endpoint,
&ctx.name,
run::SandboxCreateConfig {
Expand Down Expand Up @@ -3100,6 +3100,9 @@ async fn run_async() -> Result<()> {
&tls,
))
.await?;
if exit_code != 0 {
std::process::exit(exit_code);
}
}
SandboxCommands::Upload {
name,
Expand Down Expand Up @@ -3225,7 +3228,12 @@ async fn run_async() -> Result<()> {
)
.await?;
} else {
run::sandbox_connect(endpoint, &name, &tls, &cli.workspace).await?;
let exit_code =
run::sandbox_connect(endpoint, &name, &tls, &cli.workspace)
.await?;
if exit_code != 0 {
std::process::exit(exit_code);
}
}
let _ = save_last_sandbox(&ctx.name, &cli.workspace, &name);
}
Expand Down
Loading
Loading