DeepSeek Harness + k8e-sandbox — Step-by-Step
DeepSeek Harness (dsh) is a plugin-based agent harness. The dsh-k8e-sandbox plugin family routes dsh's execution world — filesystem reads/writes and subprocess execution — into K8E sandboxes. After setup, dsh's built-in bash, file, terminal, and LSP tools all run inside gVisor / Kata / Firecracker pods without modifying dsh, and the k8e-sandbox skill is installed so the model knows how to use the sandbox.
KIP-20 · installs from the npm
latestdist-tag (no version pin needed) · estimated time ~10 minutes
Prerequisites
- A K8E cluster with the sandbox matrix deployed and the sandbox gRPC gateway listening on
:50051— or a remote node with a reachable gateway endpoint (see Sandbox CLI and Sandbox Runtime Setup). - Node.js 18+ —
dshruns vianpx @deepseek-ai/dsh, no global install needed.
Step 1 — Install and connect the sandbox CLI
The CLI is a single platform-suffixed binary; the plain k8e-sandbox-cli name is a symlink to it (do not rename the file).
# 1. Download your platform's binary
curl -sLO https://github.com/xiaods/k8e/releases/latest/download/k8e-sandbox-cli-darwin-arm64 # linux-amd64 / linux-arm64 / darwin-amd64 / windows-amd64.exe
chmod +x k8e-sandbox-cli-darwin-arm64
# 2. Symlink the plain command name and put it on PATH
ln -s k8e-sandbox-cli-darwin-arm64 k8e-sandbox-cli
mkdir -p ~/.local/bin && mv k8e-sandbox-cli* ~/.local/bin/
export PATH="$HOME/.local/bin:$PATH"
Connect the gateway and install the agent skill (this step also creates ~/.k8e/sandbox/profiles.yaml):
# Local node — auto-discovers the TLS certs:
k8e-sandbox-cli connect
# Remote — first create an API key on the server (default TTL 30 days):
k8e sandbox-apikey create my-agent # → {"name":"my-agent","key":"k8e-…","e2b_key":"e2b_…","ttl_days":30,...}
k8e-sandbox-cli connect --endpoint <host>:50051 --apikey k8e-...
connect also installs the k8e-sandbox skill. To install it for dsh as well, pass --agent dsh (or --agent all) — this writes the skill to ~/.dsh/skills and ~/.agents/skills:
k8e-sandbox-cli connect --agent dsh # or --agent all for every harness
Verify the gateway works:
k8e-sandbox-cli status # → {"available":true, ...}
k8e-sandbox-cli run 'echo hi' # → {"stdout":"hi\n","exit_code":0,...}
Step 2 — Install the dsh plugin bundle
npx @deepseek-ai/dsh plugin --profile web add @k8e-sandbox/dsh-k8e-sandbox-bundle
Installation notes
dsh plugin addforwards to pnpm. TheERR_PNPM_IGNORED_BUILDS/dsh: pnpm failedline is benign — pnpm skipped theprotobufjs/node-ptybuild scripts (not needed at runtime) and returned a non-zero exit code.- Because of that non-zero exit,
dsh plugin addcan skip appending the bundle todsh.profile.bundles. After installing, confirm~/.dsh/profiles/web/package.jsonlists@k8e-sandbox/dsh-k8e-sandbox-bundleunderdsh.profile.bundles; add it manually if missing — otherwise the plugin installs but never loads.- The unversioned install always resolves the
latestdist-tag (0.3.x). Only pin a version (...bundle@0.3.5) when you deliberately want to stay on an older release.
Step 3 — Confirm the skill is installed for dsh
The plugin mounts the sandbox seams, but the model still needs the skill to know how to work with the sandbox. If you connected with --agent dsh in Step 1, it is already in place:
ls ~/.dsh/skills/k8e-sandbox/SKILL.md # dsh user root (highest rank)
ls ~/.agents/skills/k8e-sandbox/SKILL.md # cross-harness fallback
Otherwise install it now (no gateway dial needed):
k8e-sandbox-cli connect --agent dsh --skill-only
dsh discovers the skill from ~/.dsh/skills and ~/.agents/skills. The skill carries a dsh-specific execution path: inside dsh the fs/subprocess seams already run in the sandbox, so the model uses the plugin's k8e_sandbox_exec / k8e_sandbox_run_background / k8e_sandbox_poll tools and file tools directly — it does not shell out to k8e-sandbox-cli.
Step 4 — Boot dsh and verify the connection
npx @deepseek-ai/dsh web
Open Settings → K8E Sandbox. The panel shows:
Tip: run
k8e-sandbox-cli doctorany time — it checks the CLI, profile, mTLS cert, gateway probe, dsh bundle registration (dsh.profile.bundles) and installation, and the skill install, printing the exact fix for each failure (exit code 1 when something is broken). Use--jsonfor scripts.
- Connection status —
● Connectedwhen the gateway handshake succeeds. - Connect address (auto-discovered) — the panel shows where the endpoint came from (labels follow your dsh UI language — English shown here):
Connect address auto-discovered from ~/.k8e/sandbox/profiles.yaml (profile: default)— from the CLI profile (typical case, no configuration needed)Connect address from env K8E_SANDBOX_ENDPOINTConnect address from the dsh-k8e-sandbox profile row— an explicit override- A yellow hint appears with a
k8e sandbox connect ...command when nothing is configured yet.
Endpoint resolution order (mirrors
k8e-sandbox-cli): explicit plugin config →K8E_SANDBOX_ENDPOINTenv →~/.k8e/sandbox/profiles.yaml→ CLI local auto-discovery. In the common single-cluster case you do not configure anything — the plugin finds the endpoint by itself and keeps one persistent gRPC connection for all fs/exec/session operations.
Step 5 — Experience the sandbox
Start a chat in dsh and try these, in order:
1. Plain chat request (files + bash in the sandbox).
"Write a Python script that computes the first 20 Fibonacci numbers, save it to
/workspace/fib.py, and run it."
dsh's file tools write into the sandbox's /workspace, and bash runs inside the pod. The model returns the output without touching your host filesystem.
2. Explicit sandbox tool.
"Use k8e_sandbox_exec to run
hostname && python3 --version."
k8e_sandbox_exec returns stdout / stderr / exit code from inside the pod.
3. Terminal panel. Click Open terminal in the Settings page (or the terminal panel that auto-opens on sandbox activity). Run tty — you should see /dev/pts/N, and ls /workspace shows files from step 1.
4. Skill-driven goal.
"Use k8e-sandbox to run this repository's unit tests."
The model loads the k8e-sandbox skill (its catalog name in the skill tool), follows its dsh execution path, and runs the tests inside the sandbox.
5. Prove the isolation. Ask the model to run ps aux | head inside the sandbox, then compare with ps aux on your host — different process sets. Back on the host, k8e-sandbox-cli sessions lists the live session and k8e-sandbox-cli log <sid> shows the transcript.
6. Expose a web service (KIP-24).
"Build a tiny Flask app in /workspace/app.py that returns 'hello from sandbox', run it in the background on port 8080, then expose port 8080 and give me the URL."
The model starts the server with k8e_sandbox_run_background, then calls k8e_sandbox_expose {port: 8080} — the k8e API Gateway proxies http(s)://<gateway>/k8e/expose/<sid>/8080/ into the pod. Open the URL in your browser; stop serving with k8e_sandbox_unexpose {port: 8080}.
7. Egress allowlist (KIP-24).
"Allow outbound access to registry.npmjs.org for this session."
The model calls k8e_sandbox_allow_hosts {hosts: [...]} — the session's egress allowlist is replaced live (network policy re-applied immediately). Pass an empty list to fall back to the cluster defaults.
Configuration (optional)
The defaults (cwd=/workspace, runtimeClass=gvisor, auto-discovered endpoint) work for most setups. To override, edit $DSH_HOME/profiles/web/cordis.patch.yml:
- id: k8e-sandbox
name: '@k8e-sandbox/dsh-k8e-sandbox'
config:
endpoint: 10.0.0.5:50051 # override auto-discovery (e.g. a different gateway)
cwd: /workspace
runtimeClass: kata # gvisor (default) | kata | firecracker
For multiple clusters, use CLI profiles (k8e-sandbox-cli --profile prod connect ...) — the plugin reads the active profile from ~/.k8e/sandbox/profiles.yaml.
Troubleshooting
| Symptom | Cause / Fix |
|---|---|
Panel shows No connect address found | No endpoint resolved. Run k8e-sandbox-cli connect --endpoint <host>:50051 --apikey <key> (remote) or k8e-sandbox-cli connect (local), then restart dsh. |
Terminal / tools fail with authentication handshake failed: EOF | Gateway unreachable, or the cached CA/client cert belongs to a rotated server CA. Restart the gateway and re-run connect (remove ~/.k8e/sandbox/ca.crt on trust mismatch). |
A run/terminal command hangs forever | Fixed in sandbox CLI (stale creating session state is auto-reclaimed; all RPCs carry deadlines). Update to the latest CLI; an old wedged state file self-heals on the next run. |
| Chat feels slow (older plugin) | Versions before 0.3.0 spawned a CLI process per fs op. 0.3.0+ uses one persistent gRPC connection with deadlines; update the bundle and restart dsh. |
Tool call fails with "value.durationMs" must be a number / is not a declared property | Fixed in a recent release (tool outputs match the strict schemas: numeric durationMs, no extra fields). Update the bundle and restart dsh. |
Reading a sandbox file fails with not a regular file | Fixed in a recent release (stat type bits are masked from the full st_mode). Update the bundle and restart dsh. |
dsh crashes at boot with ctx.k8eSandbox is not mounted | The bundle is not actually loaded — reinstall (latest), restart dsh, and run k8e-sandbox-cli doctor --fix. |
Tool call fails with reading 'getClient' | Fixed in a recent release (tools resolve the owner from ctx). Update the bundle and restart dsh. |
| Skill not available to the model | Restart the dsh session; confirm Step 3 (--agent dsh). The model loads it via the skill tool or by name. |