Execute a goal inside the K8E sandbox (gVisor/Kata/Firecracker). Use when the user runs /k8e-sandbox <goal>, $k8e-sandbox <goal>, or asks to run/execute/test code safely off the host.
Execute a goal inside the K8E sandbox (gVisor/Kata/Firecracker). Use when the user runs /k8e-sandbox <goal>, $k8e-sandbox <goal>, or asks to run/execute/test code safely off the host.
argument-hint
<goal>
user-invocable
true
/k8e-sandbox
Treat this as the k8e-sandbox skill command.
Invocation (same skill, different harness prefixes):
Claude Code: /k8e-sandbox <goal>
Codex: $k8e-sandbox <goal> (or pick from /skills)
Pi: /skill:k8e-sandbox <goal> (or /k8e-sandbox when skill commands are enabled)
dsh (DeepSeek Harness): the model loads this skill via the skill tool (catalog name k8e-sandbox), or the user names it directly in chat — see dsh execution path below
Goal from invocation arguments:
$ARGUMENTS
If $ARGUMENTS is empty and no goal is otherwise provided, ask the user for a sandbox goal and stop (do not invent work).
dsh (DeepSeek Harness) execution path
In dsh, decide your execution mode by checking the current session's tool
list first — everything else in this skill branches on it.
A. Plugin mounted — k8e_sandbox_session_status IS in your tool list
The dsh-k8e-sandbox plugin replaced the harness's execution seams:
bash (subprocess seam) → runs inside the sandbox pod
Do NOT run k8e-sandbox-cli here: it is not present inside the
sandbox, and invoking it from the sandboxed bash would recursively dial
the gateway from inside the pod. Use the tools + sandboxed seams instead.
Prefer plain bash for commands — it lands in the sandbox. Use
k8e_sandbox_exec when you want structured stdout/stderr/exitCode, and
k8e_sandbox_run_background + k8e_sandbox_poll for long/streaming tasks.
First action in a session: k8e_sandbox_session_status — it lazily
creates the session shared by fs/subprocess/exec, and reports
available, sessionId, tenantId.
Tool reference (exact argument shapes — do not guess):
{hosts: string[] (required)} — full replacement list; [] clears (falls back to cluster defaults)
hosts[]
Service exposure in dsh: after starting a long-running service with
k8e_sandbox_run_background, call k8e_sandbox_expose {port: 8080} and hand
the returned URL to the user — same gateway-proxied URL the CLI's expose
prints. Teardown with k8e_sandbox_unexpose {port: 8080}.
Session, connection, and mTLS are owned by the plugin: it resolves the
gateway from config → env → ~/.k8e/sandbox/profiles.yaml (KIP-17) and
reuses one persistent gRPC connection. If a tool errors with gateway
unreachable / mTLS / deadline, tell the user to run k8e-sandbox-cli connect
(local) or k8e-sandbox-cli connect --endpoint <host>:50051 --apikey <key>
(remote) outside dsh, then restart the dsh session.
B. Plugin NOT mounted — k8e_sandbox_* tools are NOT in your tool list
The plugin bundle is not installed for this dsh profile. dsh's bash still
runs on the HOST here (no seam replacement), so the CLI-first flow below
works normally: execute everything via k8e-sandbox-cli run ... exactly as
the CLI examples describe. Do not pretend the k8e_sandbox_* tools exist —
calling a nonexistent tool errors.
To enable the full plugin experience, ask the user to install the bundle once
(from a k8e checkout), then restart dsh:
k8e-sandbox-cli connect (or with --endpoint/--apikey) outside dsh, restart dsh
bash/read error with connection refused
session pod not ready
k8e_sandbox_session_status; wait and retry
k8e_sandbox_expose returns 503 "no pod IP"
old server: session status.podIP empty
upgrade k8e server (rc7+), or k8e-sandbox-cli connect then retry
The CLI-first flow below (k8e-sandbox-cli run ...) is for harnesses where the sandbox is not mounted (Claude Code / Codex / Pi / dsh without the plugin).
Binary naming (read this first)
The downloaded file name carries a platform suffix — pick the one for the user's machine:
Platform
Download name
Linux amd64
k8e-sandbox-cli-linux-amd64
Linux arm64
k8e-sandbox-cli-linux-arm64
macOS amd64
k8e-sandbox-cli-darwin-amd64
macOS arm64
k8e-sandbox-cli-darwin-arm64
Windows amd64
k8e-sandbox-cli-windows-amd64.exe
It is the same binary this skill invokes as k8e-sandbox-cli — just under the platform-suffixed name. To make the plain name work without renaming, create a symlink (do not rename the file):
# Example for Linux amd64 — substitute the platform name for other OS/arch
curl -sLO https://github.com/xiaods/k8e/releases/latest/download/k8e-sandbox-cli-linux-amd64
chmod +x k8e-sandbox-cli-linux-amd64
ln -s k8e-sandbox-cli-linux-amd64 k8e-sandbox-cli # symlink, original file stays# optionally move both into a PATH dir, e.g. ~/.local/bin/
./k8e-sandbox-cli ... connect # connect installs this skill + ensures PATH
(Windows: use mklink k8e-sandbox-cli.exe k8e-sandbox-cli-windows-amd64.exe in cmd.)
From then on, this skill and all examples use the plain name k8e-sandbox-cli — same binary.
If you only see a platform-suffixed name in the user's environment (no symlink yet), use that file directly: ./k8e-sandbox-cli-linux-amd64 status etc. All spellings are interchangeable; never tell the user they are missing a second binary.
Hard rules
All code and shell execution goes through k8e-sandbox-cli — never run python3, node, pip, npm, curl, compilers, or tests on the host for this goal.
Prefer auto session mode: k8e-sandbox-cli run "..." (creates/reuses session).
Parse JSON with jq unless --raw is used.
If the gateway is unreachable, tell the user to run k8e-sandbox-cli connect (local) or k8e-sandbox-cli connect --endpoint <host>:50051 --apikey <key> (remote). Multi-cluster: --profile <name> / ~/.k8e/sandbox/profiles.yaml (KIP-17).
Auth & multi-profile (KIP-14 / KIP-17 / #538)
Do not confuse these files:
Path
Who
What
/etc/k8e/config.yaml
k8e server/agent
Daemon flags only
~/.k8e/sandbox/profiles.yaml
k8e-sandbox-cli
Named gateways / cert dirs
~/.k8e/sandbox/config.json
k8e-sandbox-cli
Last connect stamp
mTLS bootstrap: first remote connect/login uses an API key once; CLI stores ca.crt + client.crt + client.key (private key never leaves the machine). Client certs last 90 days and auto-renew when <30 days remain. API keys default to 30-day TTL (k8e sandbox-apikey create name, override with --ttl 90d|never).
Profiles (~/.k8e/sandbox/profiles.yaml, override with K8E_SANDBOX_CONFIG):
# ~/.k8e/sandbox/profiles.yaml — NOT /etc/k8e/config.yamlversion:1current_profile:defaultprofiles:default:endpoint:10.0.0.1:50051prod:endpoint:sandbox.prod.example:50051cert_dir:~/.k8e/sandbox-proddevice_name:laptop-prod
Show stdout/stderr and exit codes from the CLI JSON. Do not claim host-side execution.
One-time setup (if not connected)
# Local K8E node
k8e-sandbox-cli connect
# Remote — API key from server (default TTL 30d)
k8e sandbox-apikey create my-agent
# → {"name":"my-agent","key":"k8e-…","ttl_days":30,"expires_at":"…"}# k8e sandbox-apikey create my-agent --ttl never # optional non-expiring
k8e-sandbox-cli connect --endpoint <server-ip>:50051 --apikey k8e-...
# Multi-cluster: k8e-sandbox-cli --profile prod connect --apikey k8e-...
connect authenticates (mTLS), verifies the gateway, puts k8e-sandbox-cli on PATH when needed (symlink to ~/.local/bin/k8e-sandbox-cli), and installs this skill into Claude / Codex / Pi / dsh discovery paths (--agent dsh or --agent all; dsh reads it from ~/.dsh/skills or ~/.agents/skills).
Command reference
Command
Purpose
k8e-sandbox-cli --profile <name> …
Use named profile from ~/.k8e/sandbox/profiles.yaml
k8e-sandbox-cli connect
Local/remote auth + install this skill into agent harnesses
k8e-sandbox-cli connect --skill-only
Re-install this skill only (no gateway dial)
k8e-sandbox-cli login
Remote mTLS only (no skill install); optional --device-name
Default run output is JSON: stdout, stderr, exit_code, session_id. Use --raw to stream text.
Service exposure (KIP-24)
When the agent builds a long-running service inside the sandbox (web app,
API server), expose it through the k8e API Gateway so the gateway/other hosts
can reach it — no port-forward, no inbound pod exposure:
k8e-sandbox-cli run "python3 -m http.server 8080 --bind 127.0.0.1" --background
k8e-sandbox-cli expose 8080 # -> {"url":"http://<gateway>/k8e/expose/<sid>/8080/",...}
curl http://<gateway>/k8e/expose/<sid>/8080/ # reachable via the gateway (VPC/LB)
k8e-sandbox-cli exposed # list live exposures
k8e-sandbox-cli unexpose 8080 # tear down
The exposed URL routes: Cilium Gateway API (:80/:443) -> embedded e2b HTTP
server -> reverse proxy to http://<podIP>:<port>. The gateway base is
configured server-side (--sandbox-expose-base-url, default
http://<advertise-hostname>). The CNP is re-applied automatically so only
the gateway/e2b-server can reach the exposed port.
Egress allowlist is freely configurable — when the sandbox needs outbound
access to domains (package registries, tunnel endpoints), update it live:
k8e-sandbox-cli allow-hosts --add pypi.org,registry.npmjs.org
k8e-sandbox-cli allow-hosts --remove pypi.org
k8e-sandbox-cli allow-hosts --clear # fall back to cluster defaults
At session creation: create --allowed-hosts a.com,b.com (or run --allowed-hosts for auto-created sessions).
Live, any time (KIP-24): allow-hosts --add a.com,b.com / --remove a.com / --clear (fall back to cluster defaults). Applies immediately via CNP re-apply; in dsh use k8e_sandbox_allow_hosts {hosts: [...]}.
Security red lines
--env is for non-sensitive config only (stored on CRD). Use --secret ENV=secret:key for secrets.
Never pass host secrets into sandbox flags in chat logs if avoidable.
Never sudo via sandbox CLI.
Destructive sandbox actions require confirm → approve (human in the loop); don't skip it.
Error quick reference
Exit
Meaning
Action
2
TLS / cert / unreachable
Server reinstalled or CA rotated? Re-run connect --reset-certs --apikey <key> (clears the cached CA and re-bootstraps trust); otherwise check profile cert_dir
1
Command/session error
Read JSON error; recreate session if gone; re-create API key if TTL expired
8
ResourceExhausted
Wait or free warm pool capacity
Your role when this skill is active
Do:
dsh + plugin mounted (section A): run everything through the sandboxed
bash/read/write seams and the k8e_sandbox_* tools; start long-running
services with k8e_sandbox_run_background, hand the user a reachable URL
via k8e_sandbox_expose.
Everywhere else (CLI-first flow): execute $ARGUMENTS entirely via
k8e-sandbox-cli; prefer run; use --lang python for Python; use --raw
for long streams; show real CLI output. When the goal builds a long-running
service (web app, API), start it with run --background and hand the user a
reachable URL via expose <port>.
Don't: run the goal on the host; skip pre-flight; invent successful output
without actually running a tool/CLI; call k8e_sandbox_* tools that are not
in your current tool list (plugin not mounted — use section B instead).