NVIDIA OpenShell
NVIDIA OpenShell runs the published AGRO
image as a non-root workload under a default-deny policy. The openshell
runtime is provisionable.
Experimental. The
openshellruntime is experimental in v1. The runtime is interactive-only: no unattended schedule runs in an OpenShell sandbox. The runtime supports Claude Code only: the policy gives network access to Anthropic hosts and to no other model provider.
V1 proves one loop: install, shell, list, and destroy. Every other lifecycle
verb prints the equivalent openshell command and exits 1.
Host prerequisites
The operator prepares the host before the first agro sandbox install openshell.
Every openshell command runs on the host. agro never installs OpenShell.
| Prerequisite | Value |
|---|---|
| OpenShell CLI | v0.1.2 or later |
| Docker | 28.0 or later |
| Linux kernel | Landlock ABI 3 (Linux 6.2 or later), with seccomp user notification |
| Gateway | the user service openshell-gateway on https://127.0.0.1:17670, with status connected |
| WSL 2 | upstream marks WSL 2 with Docker Desktop as Experimental; see Known host limits |
If the openshell binary is not on PATH, agro sandbox install openshell
exits 1 and prints this text:
agro sandbox install: install the OpenShell CLI on the host, then run the command again:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | OPENSHELL_VERSION=v0.1.2 sh
review-first alternative — download the script, inspect it, then run it:
curl -LsSf -o openshell-install.sh https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh
less openshell-install.sh
OPENSHELL_VERSION=v0.1.2 sh openshell-install.sh
Use the review-first alternative. Read openshell-install.sh before you run the script.
If openshell status -o json exits non-zero or reports a status other than
connected, the command exits 1 and prints this text:
agro sandbox install: the OpenShell gateway is not connected; start the local gateway and register it:
systemctl --user restart openshell-gateway
openshell gateway add https://127.0.0.1:17670 --local --name openshell
then confirm with: openshell status
Create a sandbox
Run agro sandbox install openshell on the host:
agro sandbox install openshell [--name <name>] [--image=<ref>] [--print-argv] [--yes]
The command never prompts. The name, the timezone, and the git identity come
from the flags and the defaults. The default name is the lowest free
agro-sbx-<n>. The command runs the official release image, or the
--image=<ref> value.
--print-argv prints the openshell sandbox create argv and writes no registry
entry:
$ agro sandbox install openshell --name os-demo --print-argv
openshell sandbox create --name os-demo --from ghcr.io/mifunedev/agro:0.15.0 --policy /home/<user>/.agro/sandboxes/os-demo/openshell-policy.yaml --env AGRO_EXECUTION_TARGET=local --env SANDBOX_NAME=os-demo --env HOME=/home/sandbox --detach -- /usr/local/bin/openshell-main.sh
A successful run writes agro.json with runtime: "openshell" and
image.mode: "image", then prints next: agro shell <name>. If
openshell sandbox create exits non-zero, the command exits with the same code
and removes the new registry entry. The OpenShell gateway can keep the failed
sandbox record in the Error phase, so the command also prints this text:
agro sandbox install: the OpenShell gateway can keep a failed sandbox record in the Error phase; if <name> is not another sandbox, remove it with: openshell sandbox delete <name>
The command does not run openshell sandbox delete. A create can fail because
another sandbox already has the name. Before you run the delete, make sure that
the record is not another sandbox.
The command refuses three options. Each refusal exits 1 and names the
openshell runtime:
| Option | Message |
|---|---|
--checkout <dir> | agro sandbox install: --checkout is not supported on the openshell runtime |
--home-mount <dir> | agro sandbox install: --home-mount is not supported on the openshell runtime |
image.mode: "build" in agro.json, without --image | agro sandbox install: image.mode "build" is not supported on the openshell runtime |
agro sandbox --help lists the runtime:
Runtimes:
docker provisionable
microsandbox planned
openshell provisionable
The verb table
Each agro verb routes to OpenShell or refuses. A refusal exits 1 and prints
the openshell command to run.
| agro verb | V1 behavior |
|---|---|
agro shell <name> | runs openshell sandbox exec -n <name> --tty --workdir /home/sandbox/harness -- zsh -l |
agro sandbox list | reads the status from openshell sandbox get <name> -o json |
agro destroy <name> | asks for confirmation, runs openshell sandbox delete <name>, then removes the registry entry |
agro stop <name> | refuses; prints openshell sandbox stop <name> |
agro restart <name> | refuses; prints openshell sandbox stop <name> && openshell sandbox start <name> |
agro logs <name> | refuses; prints openshell logs <name> |
agro ps <name> | refuses; prints openshell sandbox get <name> |
agro compose config | refuses; prints openshell policy get <name> |
agro sandbox upgrade <name> | refuses; prints agro destroy <name>, then agro sandbox install openshell --name <name> --image=<ref> |
This example shows one refusal:
$ agro stop os-demo
agro stop: the openshell runtime does not support stop; run: openshell sandbox stop os-demo
agro sandbox list maps the OpenShell phase to a status:
| OpenShell phase | Status |
|---|---|
Ready | ready |
Provisioning, Starting | starting |
Stopping, Stopped, Deleting, Completed | stopped |
Error, Unknown, Unspecified, or an unlisted value | failed |
sandbox not found | absent |
Name resolution
An OpenShell entry takes the sandbox name from the name field in the entry
agro.json. If the field is empty, the entry takes the name of the entry
directory. The CLI never reads SANDBOX_NAME for an OpenShell entry.
The policy
The canonical policy is .devcontainer/openshell-policy.yaml. The CLI writes a
copy to <entry>/openshell-policy.yaml on every lifecycle call. Edit the
canonical file, not the entry copy. The next lifecycle call replaces the entry
copy.
The policy sets process.run_as_user: sandbox and
process.run_as_group: sandbox. The policy denies all egress by default. The
policy holds three network rules. Each rule names the binaries that the rule
lets through:
| Rule | Hosts | Binaries | Access |
|---|---|---|---|
github | github.com, api.github.com, release-assets.githubusercontent.com | /usr/bin/git, /usr/bin/gh, /usr/bin/curl | read and write on github.com and api.github.com; read-only on release-assets.githubusercontent.com |
npm | registry.npmjs.org | /usr/local/bin/node | read-only |
anthropic | api.anthropic.com, platform.claude.com | /home/sandbox/.local/lib/node_modules/@anthropic-ai/claude-code/bin/* | read and write |
The github rule permits git push over HTTPS. The operator approved agent
git push on 2026-09-28. The github rule also permits the release downloads
that agro tool install herdr uses.
Claude Code tools, for example WebFetch, reach only the anthropic hosts. A
Claude Code tool cannot fetch a page from any other host.
The Claude Code sign-in needs platform.claude.com in addition to
api.anthropic.com. If the sign-in fails on a denied host, add the host to
the anthropic rule in the live policy.
The policy denies Claude Code telemetry to http-intake.logs.us5.datadoghq.com.
openshell logs <name> then shows a NET:OPEN DENIED line for that host. This
denial does not stop Claude Code. Expect this line.
The default policy also denies claude.exe requests to
mcp-proxy.anthropic.com (claude.ai connectors) and to
raw.githubusercontent.com. Connectors need an added endpoint in the
anthropic rule.
Network rules reload while the sandbox runs. Filesystem and process fields apply only when OpenShell creates the sandbox. To change the live network policy, run this command on the host:
openshell policy set <name> --policy <file> --wait
Process model
- OpenShell replaces the image
ENTRYPOINT,CMD, andUSERwith its supervisor. - The supervisor starts
/usr/local/bin/openshell-main.shas the main process. openshell-main.shloads the image environment and runssandbox-main.sh.sandbox-main.shruns the non-root steps ofentrypoint.sh: home seed, workspace seed, provider links, and git identity.sandbox-main.shthen runsexec sleep infinity.
No systemd runs in an OpenShell sandbox.
OpenShell gives each openshell sandbox exec session a clean environment and
runs the command in a bash login shell. The CLI sets HOME=/home/sandbox at
create, and /etc/profile.d/agro-image-env.sh exports the image ENV values
from /etc/agro/image-environment, so login shells get the image PATH.
Warning. A gateway restart or a host reboot stops every process in the sandbox.
openshell sandbox start <name>starts a new main process in the kept container. Processes from before the restart do not come back.
Run interactive agents in Herdr. Inside the sandbox, run
agro tool install herdr, then herdr. To sign in to GitHub, run
gh auth login && gh auth setup-git inside the sandbox.
Storage
The registry entry stays at ${AGRO_HOME:-~/.agro}/sandboxes/<name>/. The entry
holds agro.json, .env, and openshell-policy.yaml. The entry holds no
compose file.
The OpenShell gateway keeps the sandbox record. The Docker driver keeps the
container and its writable layer. The home directory and the workspace are in
that layer. agro destroy <name> deletes both through
openshell sandbox delete <name>.
Known host limits
WSL 2 with Docker Desktop
On a WSL 2 distro with Docker Desktop, agro sandbox install openshell can fail
after OpenShell prints Created sandbox: <name>. The error text includes these
lines:
Error: × sandbox entered error phase while provisioning: ControlSupervisorStartFailed:
│ Docker supervisor exited before becoming ready; log tail: openshell: log push
│ connect failed: failed to connect to OpenShell server
│ Error: × Startup configuration fetch failed after 5 attempts: failed to
│ connect to OpenShell server
Cause: the Docker driver runs the supervisor with host networking and points it
at the gateway loopback endpoint. On Docker Desktop, the host network is the
Docker Desktop VM, not the WSL 2 distro. The gateway user service listens on
127.0.0.1 in the WSL 2 distro, so the supervisor cannot reach the gateway.
Upstream marks WSL 2 with Docker Desktop as Experimental. Upstream gives one
setting for this case: grpc_endpoint in the [openshell.drivers.docker] table
of ~/.config/openshell/gateway.toml. If the sandbox cannot reach the gateway
on host loopback, set the value:
[openshell.drivers.docker]
grpc_endpoint = "<gateway endpoint reachable from Docker Desktop>"
AGRO has no tested value for this setting. Use a Linux host or a Linux VM with
Docker Engine 28.0 or later for the openshell runtime.
After a failed create, remove the Error phase record on the host:
openshell sandbox delete <name>
Out of scope in v1
V1 does not include these items. Each item gets one follow-up issue.
- Every unattended schedule. A gateway restart stops every process, so the design waits for live evidence.
- Routed
stop,restart,logs, andpsverbs. V1 prints theopenshellcommand instead. - The interactive install wizard and a minimum-version check.
- Network rules for PyPI and OpenAI, and support for Python tooling and Codex.
- The
--checkoutbind mount. Bind mounts bypass the OpenShell filesystem policy and need three gateway settings. - The Docker socket, sshd, and Docker inside the sandbox. The workload has no capabilities and no inbound network.
- Root tool installs inside the sandbox.
no_new_privsblockssudo, so eachinstallUser: "root"tool fails. - OpenShell provider credential injection and the upstream provider profiles.
- The MicroVM and Kubernetes drivers. The manual review covers the Docker driver only.
- Host install of the
openshellbinary byagro. - The public page in
mifunedev/agro-web.