DeepSeek Harness Remote Workspace
Work on a remote server as if it were local
Connect a host over SSH, pick a folder there, and work in it. The agent reads, writes, edits, lists, searches, and runs shell commands on the far side with every tool it already has. The connection is shared by every session, several servers can be connected at once, and the whole thing is switched on from Settings → Plugins.
What it does
- A remote folder becomes a workspace. It appears in the sidebar like any other, and a session opened there works on the server.
- Every tool, not a subset. File reads and writes, literal edits, listings,
glob/grep, and the shell all run on the far side. - Several hosts at once. Profiles are durable and shared by every session, so a server connected once is available everywhere.
- OpenSSH, or Tailscale SSH. Your own keys, agent, and
~/.ssh/configare the authority; a tailnet can add identity-based access.
Requirements
- dsh installed —
npm install -g @deepseek-ai/dsh, any profile. - Node 22.19+ or 24+ — the range the harness itself requires.
- An OpenSSH client on the machine running dsh (
ssh -V). Windows OpenSSH works as a client, without connection multiplexing. - A POSIX SSH server (Linux, macOS, BSD). A Windows OpenSSH server is refused with an explicit message.
- Key-based authentication recommended.
sshpassis needed only for a password. - Optional:
ripgrepon the server, forglobandgrep.
Install
curl -fsSL https://raw.githubusercontent.com/cmukanisa/dsh-remote-ssh/main/install.sh | sh
Or from a clone: git clone https://github.com/cmukanisa/dsh-remote-ssh && cd dsh-remote-ssh && node install.mjs
Installing activates the plugin, and the run is a transaction
It checks every requirement first, installs, verifies, and rolls back to the exact previous state if anything does not pass — a half-installed plugin would leave a harness booting a composition that disables the shipped providers without registering the replacements.
checking
✓ node ·························· v24.14.0 (the harness requires >= 22.19)
✓ ssh ···································· OpenSSH_10.3p1, LibreSSL 3.3.6
✓ tailscale ················ 1.102.3 — the Tailscale SSH transport is available
✓ harness ················································· /Users/you/.dsh
✓ writable ··································· profiles/plugins accepts writes
✓ modules ················· profiles/node_modules/@deepseek-ai/cordis present
installing
✓ copied ········ dsh-remote-ssh → profiles/plugins/dsh-remote-ssh (8 files)
✓ patched ···································· cordis.patch.yml (8 rows)
✓ enabled ····························· settings.yaml remote-ssh.enabled = true
verifying
✓ files ······················································ 12 files in place
✓ composition ·············································· 8 expected rows
✓ setting ····································· remote-ssh.enabled = true
✓ module ············· registry.js imports and exports RemoteRegistry
──────────────────────────────────────────────────────────────────────────────
Done in 0.1s. Reload the harness page, then workspace "+" → "Serveur distant (SSH)".
| Flag | Effect |
|---|---|
--keep-off | install dormant — for a rollout where activation is audited separately |
--enable | activate even if a previous run left it switched off |
--link | symlink the packages instead of copying them (development) |
--dry-run | run the checks, report the changes, write nothing |
--no-color | plain output (also honours NO_COLOR) |
--uninstall | remove the packages, the composition rows, and the setting |
--dsh-home DIR | target a different harness home |
Use
- Reload the harness page.
- Click + in the sidebar's workspace header — or the 🖥 button in the sidebar foot, which opens the same dialog.
- Choose Remote server (SSH).
- Click + Server and fill in the connection: name, host, port, user, private key path, or a password.
- Browse to the folder, then Use this folder.
The folder appears as a normal workspace. Sessions opened there reach the server, and the profile stays available for every other session.
The interface follows your browser's language: English, French, or Chinese.
Tailscale
Two different things are called "connecting over Tailscale", and the plugin keeps them apart.
- OpenSSH over the tailnet — nothing to configure. Point a profile at a
MagicDNS name or a
100.xaddress and the realsshconnects over WireGuard, with your keys andknown_hostsas the authority. tailscale ssh— opt-in per profile. The Tailscale client wraps the systemssh: MagicDNS resolution, reachability throughtailscaled, and the host key verified against the key the coordination server advertises. Access is then governed by tailnet ACLs rather than by keys on disk.
The connect form lists your tailnet's peers; one click fills in the MagicDNS name, the user, and the transport when the peer runs the Tailscale SSH server. A peer reported as offline is named before the attempt, instead of surfacing as a connect timeout.
Work that outlives the harness
The agent loop runs inside the harness, so closing it ends the turn. What can keep working is the command, and detaching it is the whole trick: the plugin writes a small launcher on the server, starts it under its own session, and sends its output to a log file there.
- Close the harness whenever you like. The remote process keeps running.
- Come back and see it. Settings → Plugins shows every run with its live state — running, finished with its exit code, or gone — and its latest output.
- Stop it when you want. A running entry has a Stop button; the whole process group is signalled, so children go with it.
Nothing to configure: work started in a remote workspace is detached by default, and one
durable record per run lives in $DSH_HOME/remotes-sessions.json, shared by every session.
Updating
Re-run the installer. It rewrites its own composition block, and its closing line says which of two things the running harness needs:
- reload the page when only the browser half changed: the
bundle is served from bytes that
dsh-client-hmrpolls, so a new bundle is a new revision; - restart the harness (
dsh web) when the host half (lib/*.js) changed — Node's ESM loader keeps the module it booted with — or when a package was renamed: the harness keys its client-module table on the name it read at boot and keeps serving the new bundle under the old id, so every page load fails withloaded without registering. The installer detects both and says so.
node install.mjs # or the curl one-liner again
Troubleshooting
| Symptom | Cause and fix |
|---|---|
does not answer as a POSIX host | A Windows SSH server. Use a Linux/macOS host, or expose that machine's WSL sshd on its own port. |
Permission denied (publickey) | Run ssh <host> by hand first; the plugin uses the same client, agent, and config. |
| Host key changed | The plugin refuses, correctly. Remove the stale line from known_hosts once you are sure. |
glob/grep say a program is missing | Install ripgrep on the server (apt-get install ripgrep, dnf install ripgrep, apk add ripgrep). |
| A tailnet peer times out | Tailscale reported it offline; the connect form says so in advance. |
| The workspace shows no files in a local-only tool | That tool bypasses the filesystem seam, so it sees the empty local mirror. Use the workspace file tree. |
Failed to load plugins … loaded without registering "…dsh-remote-ssh-ui" after an update | The running harness still holds the package identity it read at boot. Restart dsh web; reloading the page is not enough. |
Limits worth knowing
- POSIX servers only. The far side is driven with POSIX shell source; a Windows OpenSSH server is refused rather than half-working.
- Remote commands are not confined. Sandboxing is a same-kernel facility; over SSH the account's own permissions are the boundary, and no sandbox facts are reported for a remote run. The filesystem fence is enforced.
globandgrepneed ripgrep on the server.- No remote file watching. The tree refreshes on navigation.
Links
- Repository — source, issues
- Releases — tarball and checksum
- Contributing
- Security policy — what the SSH channel guarantees, and how to report