SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

DeepSeek Harness plugins worth installing

Five DeepSeek Harness plugins that change how a rented server behaves: budget caps, tool permission rules, injection scanning, durable memory, LAN access.

Which DeepSeek Harness plugins are worth installing

DeepSeek Harness plugins are third-party code that runs inside your agent, with your agent's privileges, on a machine you pay for. The community list carries more than twenty categories and over a thousand entries. On a rented virtual private server (VPS) you need five: dsh-budget for spend caps, dsh-permission-rules for tool gating, dsh-defend for injection and secret scanning, dsh-memory for facts that outlive a session, and dsh-web-lan-access only after you have decided how you will authenticate.

The harness is dsh, DeepSeek's open-source agent harness, built so that everything is a plugin. Its own README calls it a developer preview and warns THERE WILL BE COMPATIBILITY-BREAKING CHANGES. That one fact shapes every choice below. Pin what you install, expect an upgrade to break it, and keep the set small enough that you can actually read all of it. If the harness is not running yet, start with a DeepSeek Harness install on a VPS and come back here.

How dsh installs a plugin, and where the config lands

dsh assembles itself from Cordis plugins, so a running instance is a tree of plugins rather than one program. A profile is a named composition of those plugins. The two templates are web and headless. $DSH_HOME defaults to ~/.dsh, and a profile lives at $DSH_HOME/profiles/<name>/, holding its own package.json, a dsh.profile manifest, and a cordis.patch.yml.

dsh plugin --profile web list
dsh plugin --profile web add dsh-budget
dsh plugin --profile web remove dsh-budget

Four source forms work: a bare npm package name, a scoped name such as @towzai/dsh-memory, a GitHub reference such as github:PerryLink/dsh-budget#main, and a local path with link: or ./. Reach for the GitHub form, because you can replace #main with a commit and get the same code back next month.

dsh plugin --profile web add "github:PerryLink/dsh-budget#461d478"

Layers apply in a fixed order: each bundle in the profile's listed order, then the profile's cordis.patch.yml, then the home-level cordis.patch.yml, then any --patch overlay. Order matters because a later layer can reconfigure or drop what an earlier one set up. So when a plugin looks installed but does nothing, open the profile's cordis.patch.yml and check two things: that its insert block is there, and that no later layer disables it.

As of 17 August 2026 the npm package @deepseek-ai/dsh is at 0.1.0-rc.7, while every plugin below declares compatibility with 0.1.0-rc.5 to 0.1.0-rc.6. That gap is normal in this ecosystem, and it is the usual reason a plugin stops loading: the harness moves faster than the plugins around it. Upgrade the harness on purpose, then test one plugin at a time.

Read a plugin before you trust it

A dsh plugin is not sandboxed from the harness. It loads into the same Cordis tree, in the same process, as the same operating system user, with access to the same model credentials and the same working tree. Installing one is closer to running someone else's script with sudo than to adding a browser extension. That is the same trust question Claude Code plugins raise, and the answer is the same: read the code, or do not install it.

Four things to check, in this order:

  • Which extension points it taps. tools/pre-execute means it sees every tool call and can block it. agent/pre-step means it sees your messages. webServer.tapIndex means it rewrites the page served to your browser. A plugin tapping none of those can do very little; a plugin tapping all of them is your security boundary.
  • Whether it talks to the network. Search the source for fetch, http, and any hard-coded hostname. A cost meter that phones home is shipping your usage pattern somewhere.
  • Whether it reads credentials. Anything touching credentials.* or a provider key needs a stated reason in the README.
  • The license and the last commit date. An unlicensed plugin, untouched for months, in an ecosystem that changes weekly, is a liability.

Then install by commit rather than by branch, and read the insert block the installer wrote into cordis.patch.yml. That block names the plugin id and the config it registered, which is the shortest honest description of what you just added. Keep provider keys out of a plugin's reach where the harness lets you, along the lines of keeping secrets out of AI agents.

dsh-budget: how do I stop an agent spending all night?

An agent on a VPS runs unattended. That is the point of putting it there, and it is also the risk. dsh-budget meters tokens and estimated cost per model, per session and per day, and enforces caps against those totals.

dsh plugin --profile web add "github:PerryLink/dsh-budget#461d478"

Its defaults are generous: 10 USD per session, 50 per day, and 500 per month. Those suit a funded team. On a personal server they are high enough that a runaway loop finishes before the cap ever fires, so lower them on the first day.

Chartdsh-budget default caps and a lower starting point for one small VPS (USD)
The data behind this chart
[
  {
    "label": "Per session",
    "plugin_default_usd": 10,
    "suggested_start_usd": 2
  },
  {
    "label": "Per day",
    "plugin_default_usd": 50,
    "suggested_start_usd": 5
  },
  {
    "label": "Per month",
    "plugin_default_usd": 500,
    "suggested_start_usd": 40
  }
]

The suggested column is a starting point for one operator on one box, not a published figure. Raise it when a real month of usage tells you to. A monthly cap of 40 USD with a session cap of 2 fails loudly and early, which is what you want while you are still learning the tool's appetite.

- id: budget
  config:
    budgets:
      session: 2
      daily: 5
      monthly: 40
    warnRatio: 0.8
    overLimit: block

The setting that changes how the machine behaves is overLimit. Its default is alert, which prints a warning and keeps spending, so out of the box this plugin is a dashboard. Set it to block and the harness refuses further model calls once a cap is reached, which means an overnight job stops instead of billing until morning. degrade is the third mode: it swaps the model for a cheaper one from the degradation map, which is right when a job must finish but does not need your best model. warnRatio defaults to 0.8, so you hear about it at 80 percent of the cap.

Two honest limits. Cost is computed from prices you supply: prices is empty by default and defaultPrice falls back to 1.0 USD per million input tokens and 3.0 per million output. Fill in the real numbers for your models or the dashboard is a guess dressed as a measurement. Second, the plugin aggregates in the running process from the session event stream, so the totals reset when the harness restarts. A crash loop, or a supervisor that restarts dsh, resets a daily cap. Treat dsh-budget as a guardrail against your own jobs and keep a spend limit on the provider account as the actual ceiling, which is the wider argument in controlling AI agent cost on a VPS.

Day to day you use /budget for the overview, /budget models for the per-model breakdown, and /budget unblock <scope> to lift a block once you have decided to keep going.

dsh-permission-rules: which tool calls should never run?

dsh-permission-rules puts declarative rules on the tools/pre-execute waterfall, so a rule is evaluated before a tool runs. Three actions exist. allow passes the call through, deny blocks it and returns a reason the model can read, and ask routes it to the official approval seam.

dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#b30b4fb"

Rules live in .dsh/rules.yaml relative to the session working directory, with a global fallbackPath and optional searchUp to walk up toward the filesystem root. Matching covers tool-name globs, parameter key and value globs, workspace-relative path globs, agent selectors such as main or subagent, and network targets.

rules:
  - match: { tools: [bash], params: { command: "rm -rf*" } }
    action: deny
    reason: "No recursive deletes"
  - match: { tools: [edit, write], paths: ["**/.env*", "**/secrets/**"] }
    action: ask
    reason: "Secret files need confirmation"
  - match: { tools: ["mcp__*"] }
    action: ask
    reason: "MCP tools need confirmation"

Evaluation is first match wins, so a broad allow near the top quietly cancels every narrower rule under it. Write the denials first and the permissive rules last. The tool-name glob covers mcp__*, which is how you gate tools that came from a model context protocol (MCP) server rather than from the harness itself, and that matters as soon as you are running MCP servers on a VPS.

One behaviour to plan for: ask needs an answerer. On a headless profile nobody may be watching, so an ask rule can hold a run up until someone appears. Use deny for anything you would never approve, keep ask for the profile you sit in front of, and if you want approvals to work unattended you need a real answering path, which is the subject of gating AI agent actions with approvals.

dsh-defend: what about prompt injection and leaked secrets?

dsh-defend scans at three points: inbound messages on agent/pre-step, tool arguments on tools/pre-execute including a destructive-delete guard, and tool results on tools/post-execute. That last one is the interesting one, because it is where content fetched from a web page gets a look before the model acts on it.

dsh plugin --profile web add "github:PerryLink/dsh-defend#7ba3427"

Defaults are cautious rather than strict: detection.injectionAction, detection.jailbreakAction and detection.secretAction are all ask, while detection.secretBlockCritical is true, so a critical secret is blocked whatever the other settings say. On an unattended box change the actions you care about to block, because ask with nobody to ask is not a decision.

The audit design deserves credit. defend/detection events record rule id, family, category, severity, decision and scan facts, and never the matched text; secrets are recorded by type only. So enabling the audit log does not create a second copy of the credential you were trying to protect.

Be clear about what this buys you. Detection is rule-based, and the README says plainly that novel phrasing and multi-step attacks can get past it. It lowers the rate of obvious attacks. It does not make an agent safe to point at untrusted content, so keep the permission rules underneath it.

dsh-memory: what does the agent remember tomorrow?

Two different plugins are called dsh-memory, which is worth knowing before you type an install command. Install by explicit source so you get the one you read.

The one I would put on a small server is the SQLite build. It registers as memory, keeps one file at $DSH_HOME/memory/memory.db shared across profiles, and exposes memory_write, memory_search and memory_forget. Search is keyword-based over the stored text and tags. There is no embedding service, no API key, and no extra process.

dsh plugin --profile web add "github:ben7am1n/dsh-memory#def7c6a"

The configuration you will touch is small: path for the database file, promptRecentCount (default 10) for how many unpinned memories get injected, and promptMaxChars (default 2000) for the rendering budget. It leans on node:sqlite, which Node 22 and 24 still mark experimental, so a Node upgrade is a thing to test rather than assume.

Here is what memory actually changes about the machine: injected memories ride in the system prompt on every turn. A 2000-character budget is a few hundred extra input tokens on every single request, forever. That is a real line on your bill, and it is why dsh-budget belongs on the box before dsh-memory does. Keep promptMaxChars tight and prune with memory_forget instead of letting the file grow.

The alternative build stores memories in a YAML file and does embedding search with automatic prompt injection, installed with dsh plugin --profile web add github:towzai/dsh-memory. It needs a local ollama instance and an embedding model, qwen3-embedding:0.6b by default, overridable with DSH_MEMORY_EMBED_MODEL. Semantic recall is better than keyword recall. It also means a second service and a set of model weights resident on the same server, which on a small plan is memory taken from the work you rented the box to do. Pick it when you have RAM to spare. The general tradeoff between recall quality and resident cost is covered in local memory for agents.

dsh-web-lan-access: should the web UI listen beyond loopback?

npx @deepseek-ai/dsh web serves the interface on 127.0.0.1:3080. Browsers only expose crypto.randomUUID() in a secure context, so loading that same page over plain HTTP from another machine fails. dsh-web-lan-access fixes it by tapping webServer.tapIndex to inject a small polyfill, and it switches the server binding to 0.0.0.0.

Read its own warning before you install it. Binding 0.0.0.0 makes the agent reachable without authentication by anyone on the same local area network (LAN), and on a server with a public IP address that means the whole internet. A short list of sensitive methods (settings.*, credentials.*, llm.discoverModels) stays pinned to loopback and returns 403 from remote origins, which limits the damage. It does not prevent it, because the tool-calling surface is still open to whoever finds the port.

Most of the time you do not need this plugin at all. Forward the port over SSH instead.

ssh -N -L 3080:127.0.0.1:3080 you@your-server

Then open http://127.0.0.1:3080 in your local browser. The harness keeps listening only on loopback, so nothing is exposed, and because browsers treat 127.0.0.1 as a secure origin, crypto.randomUUID() is available and no polyfill is needed. One command, no plugin, no new attack surface.

Install the plugin only when a forward will not do, for example when a phone on the same network has to reach the UI. If you do, bind it behind a private network interface, keep a firewall rule that allows only that interface, and list the names you will use under trustedHosts on the web-runtime entry. For genuine multi-user access there is dsh-passwords, which adds subuser permissions, per-subuser hourly token and daily time quotas, automatic TLS (transport layer security) certificates through Let's Encrypt, and an encrypted audit log. Judge it as a platform rather than a plugin: it wants ports 80 and 443, it ships its own installer, and the documented quick path pipes a shell script from the network straight into bash. Prefer npm install -g dsh-passwords followed by dsh-passwords install, so the code sits on disk where you can read it before it runs.

How to remove a plugin cleanly

Uninstalling is two steps, and people skip the second one.

dsh plugin --profile web remove dsh-budget
dsh plugin --profile web list

list should no longer show it. Then open $DSH_HOME/profiles/web/cordis.patch.yml and delete any leftover insert block naming that plugin, because that entry is what loads it into the tree. Restart the harness so the tree is rebuilt; a plugin already loaded stays loaded until you do. Finally, remember that data outlives code. $DSH_HOME/memory/memory.db and .dsh/rules.yaml survive an uninstall. Delete them yourself if removing the data was the point.

What I read, and when

Every reference here is a commit, not a branch, because main will be different code by the time you read this. I read all of it on 17 August 2026, and the harness itself was at 0.1.0-rc.7 on npm that day.

Exact commits behind this shortlist
  • The community plugin list at f2918fb, 17 August 2026. Linked once on purpose. It is a directory, and a directory is not a recommendation.
  • dsh-budget at 461d478, 17 August 2026. Apache 2.0. Declares dsh 0.1.0-rc.6, Node 22.19 or 24 and up.
  • dsh-permission-rules at b30b4fb, 17 August 2026. Apache 2.0. Declares dsh 0.1.0-rc.5 to 0.1.0-rc.6.
  • dsh-defend at 7ba3427, 17 August 2026. Apache 2.0. Declares dsh 0.1.0-rc.6.
  • dsh-memory at def7c6a, 13 August 2026. MIT. SQLite build.
  • dsh-web-lan-access at e27e909, 16 August 2026. MIT.

Recheck those pins before you copy a command. In a developer-preview ecosystem, a version number with a date on it is the only kind that means anything.

FAQ

Which DeepSeek Harness plugins should I install first on a VPS?

Install dsh-budget and dsh-permission-rules before anything else. A budget with overLimit: block stops an unattended run from spending all night, and a .dsh/rules.yaml file stops a tool call you would never have approved. Add dsh-defend once the agent reads anything from the public web, and dsh-memory when you notice yourself pasting the same context into every session. Skip the themes and status chips: they add code running with your agent's privileges without changing what the machine does.

Are dsh plugins sandboxed from the harness?

No. A plugin loads into the same Cordis tree as the harness, in the same process, as the same operating system user, with the same model credentials and the same working tree. A plugin tapping tools/pre-execute sees and can block every tool call; one tapping agent/pre-step sees your messages. So read the source, check the license and the last commit date, and install by commit rather than by branch so the code cannot change under you.

Will dsh-budget actually stop the agent, or only warn me?

That depends on overLimit. The default is alert, which warns at warnRatio and keeps spending. block refuses further model calls once a cap is reached, and /budget unblock <scope> lifts it when you choose to continue. degrade switches to a cheaper model from the degradation map. One limit matters: totals are aggregated in the running process from the session event stream, so restarting the harness resets them, and a restart loop will defeat a daily cap. Keep a spend limit on the provider account as the real ceiling.

How do I remove a dsh plugin completely?

Run dsh plugin --profile web remove <package-name>, confirm with dsh plugin --profile web list, then open $DSH_HOME/profiles/web/cordis.patch.yml and delete any leftover insert block for that plugin, because that entry is what loads it. Restart the harness so the plugin tree is rebuilt. Data written by the plugin stays on disk: $DSH_HOME/memory/memory.db and .dsh/rules.yaml both survive the uninstall until you delete them yourself.

Is it safe to expose the dsh web UI over the network?

Not on its own. dsh web listens on 127.0.0.1:3080, and dsh-web-lan-access changes that binding to 0.0.0.0. Its own README states that this makes the agent reachable without authentication by anyone on the same network, and on a public IP address that means the internet. A few methods (settings.*, credentials.*, llm.discoverModels) stay pinned to loopback and return 403 from remote origins, which reduces the damage without stopping it. Use an SSH port forward, ssh -N -L 3080:127.0.0.1:3080 you@your-server, or put the port behind a private network interface plus a firewall rule, and add real authentication before anything is reachable from outside.