Skip to main content

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 openshell runtime 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.

PrerequisiteValue
OpenShell CLIv0.1.2 or later
Docker28.0 or later
Linux kernelLandlock ABI 3 (Linux 6.2 or later), with seccomp user notification
Gatewaythe user service openshell-gateway on https://127.0.0.1:17670, with status connected
WSL 2upstream 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:

OptionMessage
--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 --imageagro 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 verbV1 behavior
agro shell <name>runs openshell sandbox exec -n <name> --tty --workdir /home/sandbox/harness -- zsh -l
agro sandbox listreads 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 configrefuses; 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 phaseStatus
Readyready
Provisioning, Startingstarting
Stopping, Stopped, Deleting, Completedstopped
Error, Unknown, Unspecified, or an unlisted valuefailed
sandbox not foundabsent

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:

RuleHostsBinariesAccess
githubgithub.com, api.github.com, release-assets.githubusercontent.com/usr/bin/git, /usr/bin/gh, /usr/bin/curlread and write on github.com and api.github.com; read-only on release-assets.githubusercontent.com
npmregistry.npmjs.org/usr/local/bin/noderead-only
anthropicapi.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​

  1. OpenShell replaces the image ENTRYPOINT, CMD, and USER with its supervisor.
  2. The supervisor starts /usr/local/bin/openshell-main.sh as the main process.
  3. openshell-main.sh loads the image environment and runs sandbox-main.sh.
  4. sandbox-main.sh runs the non-root steps of entrypoint.sh: home seed, workspace seed, provider links, and git identity.
  5. sandbox-main.sh then runs exec 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, and ps verbs. V1 prints the openshell command instead.
  • The interactive install wizard and a minimum-version check.
  • Network rules for PyPI and OpenAI, and support for Python tooling and Codex.
  • The --checkout bind 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_privs blocks sudo, so each installUser: "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 openshell binary by agro.
  • The public page in mifunedev/agro-web.