Preface¶
DSH’s subagents allow a session to offload tasks to another agent for parallel processing, but execution still happens on the local machine. When your agent needs to run on another machine, using that machine’s model configuration, tools, and sandbox, a local subagent won’t help. The usual approach is to open a public port, configure tokens, and write a WebSocket protocol—this means extra exposure surface and a set of infrastructure independent of DSH.
dsh-remote-subagent takes a simpler path: it reuses DSH’s built-in headless entry point, reading the remote dsh headless --json JSON event stream back to the local machine directly via an SSH exec channel. Below is an introduction to what this plugin is, how to install it, how to use it, and its clear boundaries.
What is it¶
This is a plugin for DSH (DeepSeek Harness), author deepseekv41flash, categorized under Workflow. Currently shows 1 star on the SkillHub directory page and GitHub, version 0.1.0, license MIT.
It solves a problem: a local session needs another machine’s DSH to do the work for it, and wants to see that agent’s thinking, tool calls, and tool results, as well as continue asking questions after it finishes.
The core mechanism is the remote_agent tool. The local session uses it to dispatch tasks to the remote DSH. The remote agent executes using its own model configuration, tools, sandbox, and session storage. The JSON events during the process are streamed back to the local machine in real-time via SSH.
Local Session ── remote_agent tool ──SSH──> Remote dsh headless --json <task>
▲ │
└──── Event Stream (thinking / tool_call / tool_result / final) ◄──┘
Choosing dsh headless --json instead of building a custom service offers three benefits:
- The remote side doesn’t need to open a public port, nor does it require tokens or WebSocket protocols;
- The remote agent uses its own profile, and the session is stored in the remote
$DSH_HOME/sessions, making it visible in the remote DSH Web UI as well; - The event stream is the “work process,” so no additional telemetry is needed.
Installation & Enabling¶
The installation command given in the README is:
# From npm, install to current profile (bundle form)
dsh plugin --profile <profile> add dsh-remote-subagent
# Or install dsh-remote-subagent in DSH Plugin Market/plugin_manager
Dependencies are installed automatically (ssh2, version requirement ^1.17.0). After enabling, the “Remote Agent” icon appears in the left sidebar, and the remote_agent tool appears on the model side.
The plugin’s runtime requirements are written in package.json: Node.js >=20, DSH engine >=0.1.0-rc.6 <0.3.0. The remote side also requires dsh to be in the SSH login PATH, and the headless profile must be available.
Where do machines come from¶
The plugin does not require you to configure an SSH list twice. It prioritizes reading the dsh-remote registry:
$DSH_HOME/remote-workspaces/machines.json
The default machine resolution order is: plugin config config.machineId → registry currentId → first saved machine → config.machine (used for standalone deployments without dsh-remote). Authentication supports password, private key (including passphrase), and SSH agent.
Host keys use the TOFU (Trust On First Use) strategy: the fingerprint is recorded to $DSH_HOME/dsh-remote-subagent/known_hosts.json on the first connection; every subsequent connection must match, or it will be rejected.
remote_agent Tool¶
The plugin exposes a remote_agent tool supporting the following actions:
| action | Description | Key Parameters |
|---|---|---|
spawn |
Start a new remote run (independent remote session) | task, machine?, cwd? |
wait |
Block until run ends or timeout (default 30s, max 120s; timeout does not affect remote continuation) | runId, waitSeconds? |
get |
Status + recent N process events + result | runId, eventLimit? |
send |
Append message to the same remote session (new run, --session-id resume, turn+1) |
runId, task |
list |
List of recent runs | limit? |
cancel |
Terminate remote process (kill -TERM based on pid file) | runId |
machines |
List available remote machines | — |
The reusability semantics of the two actions need to be distinguished clearly:
spawnalways creates a new remote session, equivalent to the built-in subagent’s spawn;sendreuses the same remote session that the run belongs to, equivalent to the subagent’s send_message;- Multiple concurrent
spawns = multiple remote sessions that do not share context; only one turn can run in the same session at the same time.
Run history and sessionId are persisted locally; after a DSH restart, you can still continue send for the same session.
Panel¶
Opening the main panel in the left sidebar “Remote Agent”:
- Left column: New task (select machine + task text + dispatch), run list (status dot / task summary / machine / time elapsed);
- Right column: Run details (status, remote session id, cwd, turns, event count, token), real-time event timeline (1.5s polling increment, auto-scroll to bottom), result box, terminate / refresh, append message input box.
The data interface behind the panel (GitHub repo is the client side, Desktop uses /api prefix, Web uses raw paths):
GET /dsh-remote-subagent/runs
GET /dsh-remote-subagent/run?id=<run>&from=<seq>&limit=<n>
POST /dsh-remote-subagent/action {action: spawn|send|cancel, ...}
Optional Configuration¶
Plugin configuration is written under config: in the cordis.patch.yml of the current profile. All fields are optional:
| Field | Default | Description |
|---|---|---|
machineId |
'' |
Default machine (id/name/host) |
machinesFile |
'' |
Custom machine registry path |
headlessCommand |
dsh headless |
Remote command prefix |
cwd |
'' |
Remote working directory (empty = machine home) |
maxConcurrent |
4 |
Parallel run limit |
timeoutSeconds |
900 |
Single run limit, terminates remote process on timeout |
maxEvents |
4000 |
Max events retained per run |
connectTimeoutMs |
20000 |
SSH connection timeout |
Typical Usage¶
The following is a complete call sequence provided in the README: check remote health once, then append a follow-up question:
remote_agent({action:"spawn", task:"在 /opt/app 跑一次只读健康检查:磁盘、内存、监听端口,最后总结"})
→ runId
remote_agent({action:"wait", runId, waitSeconds:120})
remote_agent({action:"get", runId, eventLimit:30})
remote_agent({action:"send", runId, task:"把监听端口按大小排序再列一次"})
First spawn to get the runId, then wait for it to end or timeout, then use get to fetch the recent process events and results; send appends a message in the same remote session as the previous step, turn +1, allowing the model to see the previous context.
Data & Rollback¶
The plugin writes two types of data locally:
$DSH_HOME/dsh-remote-subagent/runs/<runId>.json: Metadata + event transcript for each run. History persists after restart; runs still running at the moment of restart are marked as failed/interrupted, but the session id is retained, allowingsendto resume.$DSH_HOME/dsh-remote-subagent/known_hosts.json: TOFU host fingerprints.
When uninstalling, remove the dsh-remote-subagent bundle from the profile; to clear data, delete $DSH_HOME/dsh-remote-subagent/. The plugin makes no persistent changes to the remote side; it only writes a temporary pid file to /tmp/dsh-remote-subagent-*.pid (cleaned up automatically on normal exit) and occupies the remote session directory.
Test Status¶
The README records the test results of v0.1.0 (2026-11-04) on a Linux remote (DSH 0.2.0-rc.2, SSH password login):
spawntook 11s, generated 23 events, turn 1, includingtool_callandtool_result;sendcontinued the samesession-*, turn 2 correctly referenced the previous context’s values, and the values in the answer were consistent with turn 1;- After DSH restart + plugin hot reload,
sendcould still resume the same remote session (turn 3); - After
cancel, there were no residualdsh headlessprocesses on the remote side, and the pid file was cleaned up.
The README also mentions offline unit test coverage: machine resolution order and errors, event stream parsing/pagination/resumption/cancellation/persistence, panel routing, SSH references and TOFU fingerprints, and Client slot registration.
Known Boundaries¶
These limitations come from the README and need to be confirmed against your environment before actual use:
- The remote side requires
dshto be in the SSH login PATH, and the headless profile must be available; the remote version is too old, or lacks--json/--session-id, it is unavailable; - Only POSIX remotes are supported (commands wrapped with
sh -c); Windows remotes are not implemented; - dsh-remote’s
useSshConfigalias machines and ProxyJump jump hosts are not supported; direct connection entries must be used; - The remote run’s model / tools / sandbox are completely determined by the remote profile; the local side cannot modify them;
waittimeout does not terminate the remote run; interruptions during execution are not supported; you need towaitorcancelbeforesend;- The event stream only covers headless single turns; if the remote agent throws work into a background job, subsequent output will not appear in this run’s stream.
Use Cases & Notes¶
Suitable scenarios are: you have a dedicated machine running DSH (e.g., a server with specific model configurations, specific tools, or an isolated sandbox), and you want the local session to dispatch tasks to it for execution, while viewing the complete execution process in the local panel. It separates the execution environment and interaction environment; the remote side does not need to expose any ports.
Points to note:
- After the plugin is installed in the current DSH profile, it runs with the permissions of the current dsh process. Before installing third-party plugins, it is recommended to read the source code (GitHub repository) and confirm the license—this plugin is declared as MIT;
- It depends on dsh-remote’s
machines.jsonas the machine source. If you already use dsh-remote to manage machines, you don’t need to configure them again. Without dsh-remote, you need to useconfig.machine; - “Remote session can append messages” means session content remains on the remote side, so pay attention to the remote storage and cleanup strategy.
Summary¶
What dsh-remote-subagent does is not complicated: it encapsulates the SSH call to dsh headless --json into a remote_agent tool, paired with event stream readback, run persistence, and a panel. Its value lies in enabling “changing execution environments” without needing to start new services or change protocols, at the cost of only supporting POSIX remotes and the remote behavior being completely determined by the remote profile.
Plugin Directory Page: https://www.skillhub.cn/plugins/deepseekv41flash/dsh-remote-subagent
Repository: https://github.com/deepseekv41flash/dsh-remote-subagent