dsh-anywhere
Put a dsh (DeepSeek Harness) workspace on ANY machine — fleet enrollment for NAT'd off-site machines + SSH execution world, two modes in one plugin
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 18, 2026
- Updated
- Aug 18, 2026
Introduction
dsh-anywhere
Put your DeepSeek Harness workspace on any machine. Bring an off-site box online — even behind NAT — then let the agent
read/write/edit/bash/grepnatively on that machine, while the model, memory, and agent loop stay on your hub.
English · 简体中文
flowchart LR
subgraph HUB["🧠 dsh hub — your laptop / server"]
A["model · memory · agent loop"]
end
subgraph RELAY["☁️ your relay VPS"]
R["loopback rendezvous"]
end
subgraph REMOTE["💻 any machine — even behind NAT"]
F["files + processes"]
end
A -- "① fleet: one-time token + reverse tunnel" --> R
R -. "ProxyJump" .-> F
A == "② world: ctx.fs + ctx.subprocess<br/>read · write · edit · bash · grep land here" ==> F
Two composable modes in one plugin:
- ① fleet — reach it. Enroll a machine behind NAT into your dph with a one-time token and a reverse tunnel to your own relay VPS. It shows up as a
fleet-*dsh-sshhost, driven by the existingssh_exec/ssh_uploadtools. - ② world — make it your workspace. Turn any reachable machine into the session's
ctx.fs+ctx.subprocess, so the agent's native file & shell tools operate on it. Same shape as the E2B integration — but the remote is any SSH-reachable host.
Flagship combo (this is the "remote computer as workspace" story): use fleet to pull in the NAT'd box, then world (-o ProxyJump=<relay>) to promote it to the whole session's workspace. If the target is already reachable (a cloud VM, a LAN box), just use world and skip fleet.
Demo
A real agent session, driven through the published package, operating on a remote machine — it runs a shell command there and writes a file there:
$ dsh --profile headless --patch ./workspace-on-machine.patch.yml \
"run 'uname -n' and write MERGED-WORLD-OK to /tmp/selftest.txt"
▸ bash uname -n
VM-0-16-ubuntu ← the REMOTE host, not your laptop
▸ write /tmp/selftest.txt ← lands on the REMOTE machine
MERGED-WORLD-OK
$ ssh you@remote 'cat /tmp/selftest.txt' # verified independently
MERGED-WORLD-OK
📹 A screen recording of the
/fleetenroll-and-approve flow is welcome — see CONTRIBUTING.
Quick Start (~5 min)
Prerequisites: a working dsh install, and system ssh on the hub. For world mode you also need a machine you can already ssh into (LAN, a cloud VM, or a fleet-enrolled box).
1. Install (peers @deepseek-ai/dsh-fs / dsh-subprocess are provided by dsh):
dsh plugin --profile <your-profile> add dsh-anywhere
2a. World mode — put your workspace on a reachable machine. Copy examples/workspace-on-machine.patch.yml, fill in the target's login / sshArgs / cwd, then:
DSH_PERMISSION_MODE=danger-full-access \
dsh --profile <your-profile> --patch ./workspace-on-machine.patch.yml "work on this machine"
Now read / write / edit / bash / grep all land on that machine.
2b. Fleet mode — bring a NAT'd machine online. Installing already mounts the /fleet panel. Point it at your relay (run scripts/relay-init.sh on your VPS once, then register it), click Add machine, run the one-liner on the target, and approve it in the panel. See fleet mode below.
⚠️ Install properly — do not
link:. At boot, a--patchname:resolves relative to the profile directory. Alink:dev-install makes the world half fail withERR_MODULE_NOT_FOUND(its@deepseek-ai/dsh-fspeer can't be resolved from the repo realpath). Use a real install (dsh plugin add/npm i, peers provided by the runtime), or drop a realdsh-anywheredirectory into<profile>/node_modules/and symlink@deepseek-ai/dsh-fs+dsh-subprocessinto<profile>/node_modules/@deepseek-ai/(same realpath soextends FileSystemuses the same class). The fleet half is zero-dependency andlink:works.
The two modes
| fleet (reach layer) | world (execution-world layer) | |
|---|---|---|
| Job | make a NAT'd machine reachable | make a reachable machine the whole workspace |
| Machine appears as | a dsh-ssh host (fleet-*) | the session's ctx.fs + ctx.subprocess |
| You operate it with | ssh_exec / ssh_upload / ssh_download / ssh_tunnel | native read / write / edit / bash / grep, on the remote |
| Mounting | always-on once installed (dsh.bundle → cordis.patch.yml) | per session, --patch workspace-on-machine.patch.yml |
| Entry point | dsh-anywhere | dsh-anywhere/world |
- Zero external npm deps — files / processes / tunnels all go through the system
sshCLI (paths and contents are base64'd into the remote script, so no quoting or injection).@deepseek-ai/dsh-fsand@deepseek-ai/dsh-subprocessare peerDependencies, provided by the dsh runtime. - Fast — world reuses one OpenSSH ControlMaster connection, so fs micro-ops are ~40 ms (no re-handshake per op). This is what makes working over a tunnel actually pleasant.
- Cancellable — long commands use a dedicated connection;
terminate()drops it and sshd SIGHUPs the remote command.
Mode: fleet — bring a machine online
- Configure a relay (the plugin never hard-codes your VPS): run
scripts/relay-init.shon your relay VPS, then register it viaPOST /api/fleet/relay(or in the/fleetpanel). - Add a machine: the panel's Add machine gives a one-liner — run
curl <base>/join | bashon the target (Windows:irm '<base>/join?os=win' | iex). The machine prints a pairing code and waits. - Approve: verify the pairing code and click Approve in
/fleet(approval is accepted only from the local host). The machine's sshd is mapped to the relay's loopback via a reverse tunnel, and the hub reaches it over ProxyJump as afleet-*dsh-sshhost. - List / revoke:
GET /api/fleet/list,POST /api/fleet/approve|reject|revoke.
Security: one-time, expirable, per-machine-revocable tokens; a separate tunnel key per machine; the machine's sshd binds only the relay loopback and is never exposed publicly; approval is local-only.
Mode: world — make it your workspace
See Quick Start 2a. What world provides:
| Capability | Implementation |
|---|---|
ctx.fs (FileSystem) | fs-ssh: resolve / stat / lstat / readText / streamText / readBytes / listDir / writeText / editText — with version guards, atomic writes, binary/UTF-8 handling |
ctx.subprocess (SubprocessRuntime) | subprocess-ssh: resolveExecutable / spawn (stdin · collect · exit code · cwd · env · terminate) / spawnTerminal (basic PTY via ssh -tt) |
| bash / grep / terminal / LSP | no changes needed — they are provider-neutral consumers of ctx.fs + ctx.subprocess, so they land on the remote automatically |
One-world invariant: fs.cwd == subprocess.cwd (the patch's config.cwd) == sandbox-policy.workspaceRoot == the session workspace — they must all point at the same remote directory, or relative paths and command cwds land on a path that doesn't exist remotely and spawns fail.
Windows remotes: if the remote is Windows (OpenSSH + PowerShell), add platform: windows to the ssh-world config — fs/subprocess then run on a PowerShell backend (base64-framed, binary-safe) — and use Windows paths for cwd / workspaceRoot (e.g. C:\Users\you\work). Default is platform: posix (Linux/macOS). Validated end-to-end against a real Windows host. Note: Windows OpenSSH collapses non-zero exit codes, so the exact code is recovered out-of-band; a remote rg still helps grep speed.
Per-instance, not per-session: a dsh execution world is per-instance (ctx.fs/ctx.subprocess are not scope-aware). To use a local workspace and a remote one at the same time, run two dsh instances (one local profile, one world profile). fleet mode has no such limit — an enrolled machine is a dsh-ssh host that coexists with your local workspace.
Known limits (POC stage)
spawnTerminalis a basic PTY; precise remote foreground-process-group query/signalling and full TERM→KILL quiescing aren't implemented yet.- Each fs op is one remote round-trip (ControlMaster removes the handshake; huge bursts still pay per-op ssh process cost).
- The remote needs
rg(ripgrep) for the agent'sgrep/globto run at full speed; otherwise it falls back togrep/find. - world's
ctx.fsis not wired to dsh's sandbox fence (usedanger-full-access; the fence is delegated to the remote account itself).
Contributing
Issues and PRs welcome — see CONTRIBUTING.md. Tests: node --test (integration tests auto-skip unless you set DSH_ANYWHERE_RELAY=user@host).