Omnigent on NVIDIA OpenShell
NVIDIA OpenShell is a self-hosted sandbox
provider. Omnigent connects to an OpenShell gateway with the official
openshell Python SDK and asks that
gateway to create, execute in, and delete sandboxes on the gateway's configured
compute driver.
This guide covers the Omnigent-specific OpenShell setup:
- install the
openshellextra; - select a working OpenShell gateway;
- use an OpenShell-compatible Omnigent host image;
- configure CLI-launched or server-managed sandboxes.
pip install 'omnigent[openshell]'
Omnigent uses OpenShell two ways:
- CLI-launched:
omnigent sandbox create/connectprovisions a sandbox from your terminal, ships your local checkout into it, and registers it as a host with your server. - Server-managed: the server provisions a sandbox automatically when a
session is created with
"host_type": "managed"and terminates it when the session is deleted.
This is a sandbox-provider guide, not a server deploy target.
Two traits shape the rest of this guide:
- gRPC, and a gateway you select — not an API key. Omnigent connects through
the OpenShell gateway you've made active with
openshell gateway select. The SDK'sfrom_active_cluster()resolves that gateway's endpoint, TLS material, and OIDC token from$OPENSHELL_GATEWAY/~/.config/openshell/active_gateway. There is no base-URL or token knob in Omnigent — gateway setup and auth are an OpenShell concern. - No local port forward. OpenShell has no sandbox→laptop callback path, so
the interactive in-sandbox
omnigent login/ App OAuth step is skipped automatically (as on Modal, Daytona, and CoreWeave) — fine for token/OIDC-auth servers.
Prerequisites
You need a running OpenShell gateway with a compute driver, made active on the machine the launcher runs on. Installing and operating the gateway is an OpenShell concern — follow the OpenShell docs. Install the runtime + CLI:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
(Apple Silicon macOS installs the Homebrew formula; Linux installs the deb/rpm.)
Important
The gateway host must be amd64 Linux. OpenShell's supervisor (Landlock/seccomp/netns) does not run reliably under emulation — on an arm64 host (e.g. Apple Silicon via colima) the sandbox never reaches READY. The official host image now publishes multi-arch (amd64 + arm64), but its arm64 variant omits
cel-expr-python(no linux-arm64 wheel — CEL policies degrade to unavailable there), so the amd64 variant is the one to run with OpenShell. On an Apple-Silicon laptop, point the gateway at a remote amd64 Linux box (and the server at that gateway) rather than the local Docker VM.
Minimal local Docker gateway (for trying it out)
For a quick local test, run one OpenShell gateway backed by your local Docker
daemon. The gateway needs a signing key so sandbox containers can authenticate
back to it; the helper script creates that key, writes the gateway config, starts
the gateway, registers it with the OpenShell CLI, and waits for openshell status
to report Connected.
Make sure Docker is running first. If you use colima, set DOCKER_HOST before
running the script:
export DOCKER_HOST=unix://$HOME/.colima/default/docker.sock
Then start and register the gateway:
deploy/openshell/start-local-docker-gateway.sh
The script writes local development state under ~/.openshell-local and leaves
gateway logs at ~/.openshell-local/gateway.log.
For a real deployment, run the gateway behind TLS with OIDC or mTLS (see the
OpenShell docs), then openshell gateway add <https-url> and openshell gateway login; the SDK picks up the TLS/OIDC material from the gateway metadata
automatically — Omnigent needs no extra configuration.
Warning
allow_unauthenticated_users = trueand--disable-tlsare local-development conveniences. Don't expose such a gateway on a network.
The host image
Sandboxes boot from ghcr.io/omnigent-ai/omnigent-host:latest, published by CI
from the host target of deploy/docker/Dockerfile with
Omnigent and its dependencies preinstalled — including the coding-harness CLIs
(claude, codex, pi, kiro-cli), so agents on any harness run without an in-sandbox
install. OpenShell injects its own supervisor as the container entrypoint.
The host target also carries the two things OpenShell's image contract requires
(and which are inert for the root-based providers): a non-root sandbox
user/group and iproute2/nftables for the per-sandbox network namespace.
A custom image used with OpenShell must include both, or the supervisor refuses to
start. (The launcher handles the remaining non-root detail — pinning each exec's
cwd and $HOME to /home/sandbox — so the image's /root default still works
for the other providers.)
Before using an image with OpenShell, smoke-test that contract from the same Docker daemon the gateway uses:
docker run --rm --entrypoint sh ghcr.io/omnigent-ai/omnigent-host:latest \
-lc 'id sandbox && command -v ip && command -v nft'
To use a different image (a fork, or extra tooling baked in), run the build from an Omnigent repository checkout on an amd64 Docker-capable machine, then push it where the gateway's driver can pull from:
docker build -f deploy/docker/Dockerfile --target host \
--platform linux/amd64 \
-t docker.io/<you>/omnigent-host:latest .
docker push docker.io/<you>/omnigent-host:latest
Then point Omnigent at it with OMNIGENT_OPENSHELL_HOST_IMAGE.
Note
Air-gapped? Pre-load the host image (and OpenShell's supervisor image) into the registry or host the gateway pulls from — the first launch from an uncached image otherwise waits on a registry pull.
CLI-launched sandboxes
With a gateway selected, provision a sandbox and ship your local checkout into it:
omnigent sandbox create --provider openshell --server https://your-host
This creates a sandbox from the host image, builds wheels from your local checkout, and overlays them on top — so the sandbox runs your code, not whatever the image was built from. Then register it as a host with your server:
omnigent sandbox connect --provider openshell \
--sandbox-id <id-printed-by-create> \
--server https://your-host
connect runs omnigent host inside the sandbox and holds the connection open
in your terminal — Ctrl-C tears it down (stopping the in-sandbox host). New
sessions targeting that host now run in the sandbox. Pass a unique --host-name <label> per sandbox when connecting several to one server (the server keys hosts
on (owner, name)). Sandboxes are disposable; when your code changes, create a new
one.
To inject LLM/git credentials into the sandbox, set OMNIGENT_OPENSHELL_SANDBOX_ENV
in your shell to a comma-separated list of variable names before running
create — the named variables are copied from your environment into the sandbox
at provision time. A listed name that is not set fails the launch loudly (it
would otherwise surface much later as an opaque harness auth failure inside the
sandbox):
export OMNIGENT_OPENSHELL_SANDBOX_ENV=ANTHROPIC_API_KEY,GIT_TOKEN
omnigent sandbox create --provider openshell --server https://your-host
Server-managed sandboxes
Add a sandbox: section to the server config (omnigent server -c config.yaml,
or <data_dir>/config.yaml):
sandbox:
provider: openshell
server_url: https://your-host # public URL sandboxes dial back to
provider + server_url is a complete config. Sessions created with
host_type: "managed" (the API call or the Web UI's New Sandbox option) then run
on a fresh OpenShell sandbox; the create returns immediately and provisioning
happens in the background, exactly like the Modal managed
flow. Each managed sandbox
authenticates back with a server-minted, per-launch token — no user credentials
enter the sandbox for the server connection.
curl -X POST https://your-host/v1/sessions \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent_...", "host_type": "managed"}'
Unlike the cloud providers, OpenShell needs no API key in the server environment —
the server process must instead have OpenShell gateway access: it connects
with the same from_active_cluster() resolution as the CLI, so select a gateway
with openshell gateway select (or set OPENSHELL_GATEWAY /
sandbox.openshell.cluster) where the server runs. server_url must be reachable
from the sandbox — and because OpenShell is deny-by-default on egress, that
reachability is not automatic; see Network egress policy.
Optional openshell: settings:
sandbox:
provider: openshell
server_url: https://your-host
openshell:
image: docker.io/<you>/omnigent-host:latest # default: official image
env: [OPENAI_API_KEY, ANTHROPIC_API_KEY, GIT_TOKEN] # server env var NAMES to inject
cluster: my-gateway # default: active gateway
How the managed dial-back interacts with the server's auth mode is a
framework-level behavior shared by all providers; see
deploy/cwsandbox/README.md.
Network egress policy
This is the part of an OpenShell deployment most likely to trip you up. OpenShell
is deny-by-default: every sandbox runs in its own network namespace with all
egress forced through a policy proxy, and anything not explicitly allowed is
blocked (the in-sandbox https_proxy returns 403). The agent and host run with
no outbound access until the sandbox policy grants it. The policy is resolved
from /etc/openshell/policy.yaml baked into the image, or set per-sandbox; see
the OpenShell policy schema. A managed host
needs egress to:
- the server URL (
server_url) — the host and runner dial it back over a WebSocket tunnel; without it the host can connect but the runner never registers; - the LLM provider host — the agent's model calls originate inside the
sandbox (e.g.
*.googleapis.comfor Gemini,api.anthropic.comfor Claude,api.openai.comfor OpenAI); - tokenizer/asset hosts some harnesses fetch on first use, e.g.
*.blob.core.windows.net(the openai-agents harness downloads thetiktokenencoding).
A minimal network_policies block (in the image's policy.yaml) looks like:
network_policies:
server:
endpoints: [{ host: "your-host.example.com", port: 443, tls: skip }]
binaries: [{ path: /** }]
llm:
endpoints: [{ host: "*.googleapis.com", port: 443, tls: skip }]
binaries: [{ path: /** }]
Important
Forward the proxy vars to the runner. The host inherits the sandbox's
https_proxy/http_proxy, but the runner subprocess it spawns does not — so the runner fails withTemporary failure in name resolutioneven though the host connected. InjectOMNIGENT_RUNNER_ENV_PASSTHROUGHnaming the proxy vars so the host forwards them:sandbox: openshell: env: [OMNIGENT_RUNNER_ENV_PASSTHROUGH, …] # value set in the server env: # OMNIGENT_RUNNER_ENV_PASSTHROUGH=https_proxy,http_proxy,HTTPS_PROXY,HTTP_PROXY,NO_PROXY,no_proxy
Tip
For LLM traffic specifically, OpenShell recommends its inference routing over allow-listing the provider host directly, so a stolen key can't be used to reach the provider from inside the sandbox. The allow-list above is the simplest path to get a turn working; inference routing is the hardened one.
Model credentials (LLM keys)
A fresh sandbox has no model credentials. Name the variables to inject in
OMNIGENT_OPENSHELL_SANDBOX_ENV; the launcher copies the value from your
environment into the sandbox, and the in-sandbox host forwards the standard
harness credential vars (ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN,
OPENAI_API_KEY, OPENAI_BASE_URL, GEMINI_API_KEY, …) to its runners.
export ANTHROPIC_API_KEY=sk-ant-…
export OMNIGENT_OPENSHELL_SANDBOX_ENV=ANTHROPIC_API_KEY
Which variables to inject — providers, gateways, subscriptions — is identical to
the other providers; see the Modal variable table and per-plan
recipes. For a Claude
subscription, run claude setup-token on your own machine (one-time browser
auth) and inject the resulting CLAUDE_CODE_OAUTH_TOKEN. For env vars beyond the
standard set, inject OMNIGENT_RUNNER_ENV_PASSTHROUGH=NAME1,NAME2.
Tip
OpenShell can also enforce credential and egress policy at the sandbox boundary via its declarative YAML policy (a gateway-side feature, independent of Omnigent). See the OpenShell policy docs.
Git credentials (private repositories)
Inject an HTTPS token as GIT_TOKEN (GitLab: add GIT_USERNAME=oauth2) via
OMNIGENT_OPENSHELL_SANDBOX_ENV. The host image's git credential helper answers
HTTPS auth from it for both the launch-time clone and the agent's later fetch /
push, writing nothing to disk. Use HTTPS repository URLs. Details by provider
match the Modal git guide.
How it works
- Connection.
OpenShellSandboxLauncherbuilds aSandboxClientviafrom_active_cluster()and calls the gateway over gRPC:CreateSandbox+wait_readyto provision,ExecSandboxto run commands,DeleteSandboxto terminate. - File shipping. OpenShell exposes command execution but no upload RPC, so
putstreams the file's bytes tocatover the exec channel's stdin (the same approach NVIDIA's own LangChain backend uses). Wheels are shipped this way, then installed with the shared host-image overlay command. - Sandbox identity. OpenShell assigns each sandbox a petname (e.g.
touched-urial); that name is the handle Omnigent prints and reuses. The requested--nameis advisory. - Non-root execution. OpenShell runs the agent as the
sandboxuser, so the launcher pins every exec's cwd and$HOMEto/home/sandbox(the image keeps/rootas its default for the root-based providers). - Long-lived host. OpenShell terminates an exec's process tree the moment the
exec returns, so the in-sandbox host can't be detached with the usual
setsid nohup … &(it gets reaped instantly). The launcher instead runs it as a foreground exec held open on a daemon thread for the session's lifetime.
Troubleshooting
docker sandboxes require gateway JWT auth; configure [openshell.gateway.gateway_jwt]— the Docker driver needs a gateway-minted sandbox JWT. Generate the Ed25519 key material and add the[openshell.gateway.gateway_jwt]block as shown in Minimal local Docker gateway, then restart the gateway.No OpenShell server configured/Could not connect to an OpenShell gateway— no gateway is active. Runopenshell gateway select <name>(or setOPENSHELL_GATEWAY), and confirm withopenshell status.- Sandbox stuck in
Provisioning— usually a slow first image pull. Confirm the gateway's Docker daemon can pull the host image (docker pull <image>from the sameDOCKER_HOST); pre-pull it to cache. On colima, make sure the gateway was started withDOCKER_HOSTpointed at colima's socket —/var/run/docker.sockmay point at a different (stopped) Docker. - Agent has no credentials — verify the injected var names match the forwarded
set (or are named in
OMNIGENT_RUNNER_ENV_PASSTHROUGH), and that each name was actually set in the launching environment. - Host registers but the runner never comes online / runner log shows
Temporary failure in name resolution— the runner subprocess isn't getting the sandbox's proxy vars. Forward them withOMNIGENT_RUNNER_ENV_PASSTHROUGH(see Network egress policy). - Turn fails reaching the model, or proxy returns
403— the destination isn't in the sandbox's egress allow-list. Add the LLM host (and any tokenizer/asset host) tonetwork_policies(see Network egress policy). - Sandbox container restarts /
sandbox user 'sandbox' not foundortrusted ip helper not found— the image isn't OpenShell-compatible. Use the official host image (or include thesandboxuser +iproute2in your custom one); see The host image.
Environment variable reference
| Variable | Where it's read | Purpose |
|---|---|---|
OPENSHELL_GATEWAY |
CLI machine / server | Gateway name to use; overrides ~/.config/openshell/active_gateway (read by the SDK). sandbox.openshell.cluster takes precedence for managed. |
OMNIGENT_OPENSHELL_HOST_IMAGE |
CLI machine | Override the host image ref (default ghcr.io/omnigent-ai/omnigent-host:latest); sandbox.openshell.image is the managed equivalent |
OMNIGENT_OPENSHELL_SANDBOX_ENV |
CLI machine | Comma-separated launcher-side env var names to inject into the sandbox; sandbox.openshell.env is the managed equivalent |
OMNIGENT_RUNNER_ENV_PASSTHROUGH |
inside the sandbox (injected) | Extra env var names the host forwards to runners |
GIT_TOKEN / GIT_USERNAME |
inside the sandbox (injected) | HTTPS credentials for private repository clone / fetch / push |
Validation
Exercised end-to-end against a live OpenShell gateway on an amd64 Linux host (Docker driver, the official host image):
- Launcher primitives — provision → run (
echo/uname) → put (file upload over exec stdin) → verify → terminate, plusexec_foreground(theconnectprimitive) streaming output and propagating exit codes; the gateway logs the matchingCreateSandbox/ExecSandbox/DeleteSandboxRPCs. - Full server-managed session — a
host_type:"managed"session drove the server to provision a sandbox on the gateway, startomnigent hostin it (held foreground exec), dial back over the tunnel, register, spawn the runner, and complete a real agent turn (a Gemini model via the openai-agents harness) — the agent's reply came back from inside the sandbox.
Unit tests (a faked SDK / launcher, no gateway needed) cover provision, run, file upload, foreground streaming, attach, terminate, env passthrough, error handling, and the managed-config parsing:
pip install -e '.[openshell,dev]'
pytest tests/onboarding/sandboxes/test_openshell.py tests/server/test_managed_hosts.py