SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Run DeepSeek's dsh-superagent on a VPS

Install DeepSeek's dsh-superagent prototype on a VPS as its own Linux user, pin the commit, keep the panel on loopback, and restrict its GitHub account.

What dsh-superagent is, in its own words

To run dsh-superagent on a VPS, you install a DeepSeek Harness bundle that watches one GitHub repository and opens pull requests when an allowed person asks for work. Before anything else, read the status line at the top of its README: "Research prototype for reference only, not an official DeepSeek product." The README also says the code may contain bugs, and that you should review it and adapt it before you use it.

So this guide treats it as a prototype. You pin the exact commit, run it as its own Linux user, and give its GitHub account access to one repository only. The parent project is just as clear. The DeepSeek Harness README says it "is in _developer preview_ and iterating rapidly" and warns: "THERE WILL BE COMPATIBILITY-BREAKING CHANGES." If you have not run DeepSeek Harness before, start with running DeepSeek Harness on a VPS, because dsh-superagent is a plugin for it and not a separate program.

What dsh-superagent does once it is running

The README describes it as "a proactive coding agent for one GitHub repository, packaged as a bundle for DeepSeek Harness." In practice it works like this:

  • It signs in to GitHub as a dedicated user account and reads that account's notifications.
  • It polls. The deployment guide says a new Issue or a comment that mentions the agent should show up as a Ticket "within a polling interval (one minute by default)".
  • Each Ticket gets its own agent session and its own directory.
  • It replies in the original conversation, for example with a pull request.
  • A web panel lists the Tickets that need a person. The panel is served on 127.0.0.1.

Feishu (Lark) chat is an optional second source. This guide covers GitHub only.

Why an always-on poller belongs on a VPS

The agent only works while it is polling. On a laptop it stops every time the lid closes, so a request posted at night waits until morning. A small VPS polls around the clock. It also gives you a clean place to apply the isolation the docs recommend. The execution doc says workers run commands without approval as the operator's OS user. On a laptop that user is you, with your SSH keys and browser sessions. On a VPS it can be an account that holds nothing except the agent's own credentials.

Requirements, and where Ubuntu 24.04 falls short

The deployment guide lists these:

  • Node.js 22.19+ or 24+. The repo's package.json says ^22.19.0 || >=24.0.0.
  • pnpm.
  • git 2.32+.
  • Python 3.8+.
  • tmux.
  • GitHub CLI (gh) 2.48 or later.
  • A repository on github.com. GitHub Enterprise Server is not supported.
  • A DeepSeek Harness model provider.
  • git-lfs, if your repository uses LFS.

Ubuntu 24.04's own archive covers only part of this list. git, Python and tmux from noble meet the guide's floors, but noble's Node.js is too old, it has no pnpm package, and its gh is too old, so those three come from elsewhere. git in noble is 2.43.0, which is above the floor. The gh package is the trap. Noble ships gh 2.45.0, which is below the 2.48 the guide requires, so sudo apt install gh gives you a CLI that is too old. Install gh from GitHub's own release instead. On 2 November 2026 the latest release was v2.103.3, published 26 October 2026. The block below downloads its amd64 .deb, checks it against the release's checksum file, and installs it:

sudo apt update && sudo apt install -y git tmux python3 curl
curl -fLO https://github.com/cli/cli/releases/download/v2.103.3/gh_2.103.3_linux_amd64.deb
curl -fLO https://github.com/cli/cli/releases/download/v2.103.3/gh_2.103.3_checksums.txt
grep gh_2.103.3_linux_amd64.deb gh_2.103.3_checksums.txt | sha256sum -c -
sudo apt install -y ./gh_2.103.3_linux_amd64.deb
gh --version
git --version
python3 --version

The sha256sum line must print OK. If it does not, delete the file and download it again. On an ARM VPS, replace amd64 with arm64 in both places. gh --version must print 2.48 or later. If it prints 2.45.0, the Ubuntu package is still in use, so check that the apt install ./ line succeeded. A .deb installed this way does not update itself. GitHub's Linux install page also documents an apt repository that does, if you prefer that route. python3 --version must be 3.8 or later.

Give the agent its own Linux user

The execution doc is direct about the built-in guards: "These wrappers and directories establish defaults, not a security boundary." The deployment guide gives the fix: "For isolation, use a dedicated OS account containing only the agent's credentials." Create that account now. Do not add it to the sudo group.

sudo useradd -m -s /bin/bash agent
sudo -iu agent

Every command from here to the end of the install runs as agent. The account has no password, so nobody can log in as it over SSH. You reach it only through sudo -iu agent from your own admin user.

Install Node.js, pnpm and the pinned dsh

Install Node.js with nvm (Node Version Manager) inside the agent's home directory. This puts global npm packages under /home/agent, so npm install -g works without root and the agent cannot change system files.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash
. "$HOME/.nvm/nvm.sh"
nvm install --lts
node --version
npm install -g pnpm@latest
pnpm --version
npm install -g @deepseek-ai/dsh@0.2.1-rc.3
command -v dsh

nvm install --lts installs the current LTS line. On 2 November 2026 nodejs.org showed v26.13.0 as the latest LTS, which satisfies the >=24.0.0 half of the range. command -v dsh should print a path under /home/agent/.nvm/. An empty result means the nvm environment is not loaded in this shell, so run the . "$HOME/.nvm/nvm.sh" line again.

The guide pins @deepseek-ai/dsh@0.2.1-rc.3. On 2 November 2026 that version was not the newest on npm: the latest tag pointed to 0.2.1-rc.5. The guide says: "The pinned version above is public; other versions must satisfy engines.dsh in package.json." That range is ^0.2.1-alpha.2. Stay on the pin unless you have a reason to move, and test any move on a copy first.

Sign in the agent's GitHub CLI

The agent needs its own GitHub user account, never yours. The setup guide says why: "everything the agent writes appears under this name." A GitHub App will not work, because, in the guide's words, "dsh-superagent reads the account's notifications." Create the account on github.com first. Then set up a gh wrapper with its own config directory, so it can only ever hold the agent's credentials. This block is from the deployment guide:

mkdir -p ~/.local/bin
cat > ~/.local/bin/gh-agent <<'EOF'
#!/bin/sh
GH_CONFIG_DIR="$HOME/.config/gh-agent" exec gh "$@"
EOF
chmod +x ~/.local/bin/gh-agent
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
export PATH="$HOME/.local/bin:$PATH"
gh-agent auth login
gh-agent api user --jq .login

The two PATH lines are our addition. They make sure gh-agent is found in this shell and in later ones. During gh-agent auth login, sign in as the agent account. The last command prints the login it is signed in as. If it prints your own username, you signed in with the wrong account. Run gh-agent auth logout and sign in again.

Install dsh-superagent at a pinned commit

The repository has no tags and no releases. On 2 November 2026 its whole history was one commit: 6337fc823ce20251c8de8085948165c81aaa701e, dated 31 October 2026, with the message "feat: add the dsh-superagent research prototype". Without a tag, the only way to install a version you can name is to check out that SHA. If upstream has pushed since, check the new commits before you move to them.

cd ~
git clone https://github.com/deepseek-ai/dsh-superagent-prototype.git dsh-superagent
cd dsh-superagent
git checkout 6337fc823ce20251c8de8085948165c81aaa701e
git log -1 --format='%H %cs'
pnpm install && pnpm build

git log should print the SHA above and 2026-10-31. Git will also tell you that you are in a detached HEAD state. That is expected, because you checked out a commit and not a branch.

The next step creates the superagent profile. It is destructive. The guide says: "If the profile already exists, that command replaces it and deletes its cordis.patch.yml, including machine configuration." Run ls ~/.dsh/profiles/ first. If superagent is listed, stop here, because you already have an install and this command would wipe its configuration. Only on a machine with no superagent profile, run:

dsh superagent --from-default-profile web --dump-config > /dev/null
dsh plugin --profile superagent add "link:$PWD"

The first line creates the profile from the Web template. The second links this clone into it as a plugin. The guide keeps this profile separate from your normal one because the plugin changes process-wide PATH and git settings.

Fill in the machine configuration

Copy the example patch file into the profile, then replace every <placeholder> in it.

cp examples/profile.cordis.patch.yml ~/.dsh/profiles/superagent/cordis.patch.yml
nano ~/.dsh/profiles/superagent/cordis.patch.yml

stateDir is required and must be an absolute path, for example /home/agent/superagent-state. It holds the database, the command shims, the agent's git config and the Ticket directories, so it is the directory you back up. The webserver entry sets the panel's port. The docs use 3082 in their examples. Invalid values or unknown keys prevent startup, so a typo shows up at the first start and not later.

Start it, and check it is healthy

The project ships a launcher at scripts/dsh-superagent with four subcommands: start, stop, restart and status.

scripts/dsh-superagent start
scripts/dsh-superagent status; echo "exit=$?"
tmux capture-pane -pt dsh-superagent | grep http

start runs dsh --profile superagent in a tmux session named dsh-superagent. It waits up to 60 seconds for the runtime to report ready, then returns. status reports through its exit code:

  • 0: running normally.
  • 1: stopped, duplicated, unreachable, or a runtime error. A restart may help.
  • 2: a component outside the runtime failed. A restart will not fix it.
  • 3: waiting for setup.

On a fresh install, expect 3 until you finish setup in the panel. The tmux capture-pane line prints the panel URL from the startup log. Logs and control files are kept under .artifacts/ in the clone.

In the panel, choose Superagent in the sidebar and finish the settings. Under Sources, enable the GitHub repository and the agent CLI. Under Policy, add the GitHub users who may ask for work. This step matters: until the allowed list has entries, no request becomes a Ticket. Then test it. Post a new comment on an issue that mentions the agent account. Within a minute, Overview should show a Ticket. Editing an existing issue body to add a mention does not trigger one.

Can the tmux launcher run under systemd?

Not in any supported way. The project ships no systemd unit. The deployment guide never mentions systemd, boot or reboot, and the launcher script contains no systemd code. Its recovery design is tmux plus a scheduled health check.

The launcher also does not fit how systemd watches a service. start creates a detached tmux session, waits for the health check, and exits. A Type=simple unit would see that exit as the service ending. systemd's default KillMode=control-group kills every remaining process in the unit's control group when the unit stops, and the tmux server started from the unit is in that group. Running dsh --profile superagent --no-open directly as the service is possible in principle. It skips the launcher's lock file, its logs, its stop marker and its graceful stop, which waits up to five minutes for running Executors and never force-kills them. We did not test any unit file. If you write one, it is your own design.

Use what the guide documents. It has you create an hourly check "in the DeepSeek Harness profile you normally use, not in superagent". The check runs status and restarts the service when it returns 1, with --respect-stop so that a stop you made on purpose stays stopped. The check lives in another profile so it keeps running when the agent is down. On a VPS, that normal profile is the one to keep alive across reboots, which is what running dsh headless under systemd covers. Nothing in dsh-superagent restarts itself after a reboot, so without that check the agent stays down until you run start. Keep in mind that restarts end running Assignments.

Reach the panel over an SSH tunnel or Tailscale

The panel listens on 127.0.0.1 only, and the superagent docs describe no login for it. Plan as if anyone who reaches the port can use it. So never open it to the internet with a port rule or a reverse proxy. Forward it over SSH from your laptop instead, using your normal admin account:

ssh -N -L 3082:127.0.0.1:3082 you@your-vps

-L forwards port 3082 on your laptop to 127.0.0.1:3082 on the VPS. -N runs no remote command, which is right for a tunnel. Leave it running and open the URL from the startup log in your laptop's browser. It works unchanged, because the local port matches. If your webserver entry uses another port, change both numbers. The same pattern for the stock DSH Web UI is covered in reaching the DSH web UI on a VPS.

With Tailscale, the simplest route is the same tunnel to the VPS's tailnet name, which lets you close public SSH entirely. Tailscale also has tailscale serve 3082, which proxies the local port to devices on your tailnet only. We did not test the panel behind it. Never use Funnel here, because Funnel publishes the port to the public internet.

One more point about loopback. Any process on the VPS can reach 127.0.0.1. The management API under /orchestrator/api/ needs only an x-orchestrator-request: 1 header, and the guide says API saves carry panel-level permissions. Keep unrelated services and users off this box.

Give the GitHub account write access to one repository

The guide asks for "a GitHub user account for the agent" with "write access to the repository". It names no token scopes and no wider role, so do not give it more. Invite the agent account as a collaborator on this one repository and nothing else.

The one-repository rule matters because of how the tokens work. GitHub's notifications API only accepts a personal access token (classic) with the notifications or repo scope. gh auth login asks for repo, read:org and gist at minimum. A classic repo token reaches every repository the account can reach, so the account's membership list is the real limit. Read more about why in giving your AI agent its own identity.

Write access is broader than it sounds. In an organization, the Write role cannot push to protected branches or change repository settings. It can create, edit, run and cancel GitHub Actions workflows, and it can create, update and delete Actions secrets and variables. On a repository owned by a personal account, collaborators get push access and can merge pull requests. Personal repositories have no read-only level for collaborators. So protect the default branch with required reviews (setting branch rules needs Admin), and keep secrets that matter out of this repository.

The threat model: write access plus issue text

Here is the risk in one sentence. An agent with write credentials reads issue and comment text and acts on it, which makes it a prompt-injection target. The project does not use the term prompt injection. Its authorization doc states the rule: "Messages, comments and quoted documents are inputs to a decision; they cannot change those permissions." The host enforces the allowed lists. Only people on the list can request work. The text the agent reads while doing that work can still come from anyone: an issue body it was pointed at, a quoted log, a file in a dependency.

The defaults help, so keep them. Accept mode starts at observe, which records proactive work the agent would do without doing it. Auto-merge is off: the execution doc says "By default, the Executor never enables auto-merge as the agent account". The setup guide says people should merge until the agent's pull requests prove reliable. Auto-merge is controlled by a prompt and not by host logic, so branch protection with required approvals is the control that actually holds. Do not set the unlisted-addressed cell to explicit, because the docs say that "lets anyone in that scope request work by mentioning the agent." For the general pattern and more defences, read how prompt injection works against coding agents. If you want the blast radius even smaller, a disposable VM for coding agents is the next step past a dedicated Linux user.

Upgrading without losing state

The guide's upgrade path is to wait for Executors to go idle, back up the database, pull, rebuild and restart. Because you pinned a commit, replace the pull with a checkout of the new SHA you have reviewed:

cd ~/dsh-superagent
cp /home/agent/superagent-state/orchestrator.sqlite ~/orchestrator.sqlite.bak
git fetch origin
git log --oneline HEAD..origin/main
NEW_SHA=paste-the-reviewed-sha-here
git checkout "$NEW_SHA"
pnpm install && pnpm build
scripts/dsh-superagent restart

Before you run the checkout, set NEW_SHA to the full SHA of the commit you reviewed in the git log output. Use your own stateDir path in the cp line. Keep that backup. The guide warns that a database migrated by a newer version cannot be opened by an older one, so the backup is your only way back. Never run the profile-creation command during an upgrade.

FAQ

Is dsh-superagent an official DeepSeek product?

No. Its README says: "Research prototype for reference only, not an official DeepSeek product." It also says the code may contain bugs and should be reviewed and adapted before use. The repository had one commit and no releases on 2 November 2026, so pin that commit SHA when you install it.

Why does dsh-superagent not react when I mention it in an issue?

The usual causes are that the comment's author is not on the allowed list, the GitHub Source is disabled, or processing is paused. With an empty allowed list, no request becomes a Ticket. Editing an existing issue body to add a mention also does nothing, so post a new comment. With polling at its one-minute default, a Ticket should appear in Overview within a minute.

Can I run the dsh-superagent tmux launcher as a systemd service?

Not in a documented way. The project ships no unit, and its docs never mention systemd. The launcher starts a detached tmux session and exits, so systemd does not see a long-running main process. The documented recovery is an hourly check in your normal DeepSeek Harness profile that runs status and restarts on exit code 1.

Can dsh-superagent use a GitHub App instead of a user account?

No. The deployment guide says a GitHub App cannot be the agent, because dsh-superagent reads the account's notifications. Create a separate GitHub user for the agent, never your own account, and give it write access to only the one repository it works on.

Do I need Node.js 24, or does Node.js 22 work?

Both work. The deployment guide and the repo's package.json accept Node.js 22.19 or later on the 22 line, or any version from 24.0.0 up. On 2 November 2026 nodejs.org listed v22, v24 and v26 as LTS lines, and nvm install --lts installed v26.

Was this article useful?