dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
The Multitask "shock absorber" mode for DeepSeek Harness — the main conversation never touches your codebase.
English · 中文 · Design notes and measured evidence · MIT License
A work mode for DeepSeek Harness(DSH) in which the main conversation does exactly three things: understand what you asked for, turn it into task briefs, and report back what the Subagents concluded. Every piece of process data — file contents, command output, build logs, search results, dead ends — is digested inside a Subagent's context and never enters the main one. The main conversation therefore holds only high-value information.
This is a mechanism, not a prompt asking the model to behave. Once the mode is selected, the main conversation (the coordinator) genuinely has a narrower tool surface: the tools that would drag in process data — writing files, running commands, searching the web — are not in its visible set. It cannot call them, so it cannot pollute its own context. A Subagent (worker) is the opposite: it gets the full tool surface, does the work, and returns only its conclusion.
Zero runtime dependencies. No changes to DSH source.
At a glance:
| Capability | What it gives you |
|---|---|
| Narrowed coordinator tool surface | the main conversation cannot write, run commands or search — those tools are genuinely absent, not merely discouraged |
| Process data never reaches the main conversation | file contents, command output, build logs and dead ends stay inside a worker's context |
| Four kinds of Subagent | a full-surface worker, a fork that inherits completed turns, a programmatic PTC worker and a read-only worker, chosen by task shape |
| Dispatch to another mode | hand a brief to any other installed agent preset and receive only its conclusion |
| Two-level approval gate | safe commands run untouched; dangerous ones raise a card in the main conversation |
| Corrections take effect immediately | a follow-up message stops the worker's current turn first instead of queueing behind the wrong work |
| Mode isolation | several modes can share one workspace; each narrows only its own sessions |
| Cross-session settings | pick a model per worker kind and set the parallelism limit, both in Settings |
| Automatic workspace permission repair | on Windows the one missing permission is added at session start, and only that one |
Read-only metadata tool file_info | byte counts and file kinds without reading contents, so the coordinator can verify a claim itself |
| Zero runtime dependencies | no changes to DSH source |
| Main conversation (coordinator) | Subagent (worker) | |
|---|---|---|
| Tool surface | read-only checks + delegation + result retrieval + interaction | full (write files, run commands, build, search…) |
| Context | your request + task briefs + conclusions | all the process data |
| Job | understand, translate, dispatch, verify, report | do the work |
What the coordinator keeps (a whitelist):
read grep glob file_infosubagent subagent_fork subagent_ptc subagent_minimalsend_message interrupt_agent list_agentsjob_list job_output job_killworkflowask_user_question todo_write create_goal get_goal update_goal exit_plan_mode present skillAnything not on that list is withheld by default — write, edit, pwsh, web_search, web_fetch, read_image, ssh_*, task_board_*, plugin_manager, and so on. The rule is a complement: withheld = currently visible − kept. Any tool the host adds later is therefore also withheld from the coordinator by default, with no change needed in this plugin.
The read-only tools are kept on purpose: the coordinator is meant to verify a Subagent's claims independently instead of trusting them blindly.
The narrowing is applied only to sessions whose live preset binding is this preset. A session belonging to another mode in the same workspace keeps its own tool roster and is not touched — so you can run several modes side by side without them eating each other's tools.
Switching a session out of the mode lifts the restriction again, and switching into it before the first turn narrows it before the first request is even made.
presetId is that ownership key. If you change it to install variants side by side, each variant narrows only its own sessions; and if it no longer matches the registered preset id, no session is narrowed at all — the mode can look installed while quietly doing nothing.
When ownership cannot be read, the guard prefers not to narrow rather than risk narrowing a neighbour's session. The cost of a registry hiccup is therefore one possibly un-narrowed turn, never a broken neighbouring mode.
dsh plugin --profile <profile> add dsh-multitask
--profile is an option and cannot be dropped — dsh plugin add dsh-multitask fails outright with error: required option '--profile <name>' not specified. <profile> is the profile to install into: the desktop app owns one called desktop, and this checkout commonly uses web; if you are unsure, look at the directories under ~/.dsh/profiles/.
The package then lands in that profile's node_modules, and DSH automatically appends dsh-multitask to the profile's dsh.profile.bundles layer list (because this package declares dsh.bundle.patch). The bundled cordis.patch.yml inserts a bare package name with no absolute paths — so it works on any machine, any user name, any install location. Restart the app afterwards — bundle layers are composed at startup.
You can also install it from the in-app Plugins page, which needs no command line.
Desktop (Electron) note. Fully quit the app first — pnpm rewrites the profile's
node_modules, and the running app holding those files open will make it fail. The desktop app's owndshlauncher can managedesktop; adshinstalled separately from npm is refused outright (error: profile "desktop" is managed exclusively by the Electron application). In that case use the in-app Plugins page, or the source route below.
To run the sources in your working tree (edits without going through npm), append this block to the profile's cordis.patch.yml, pointing at your local entry with an absolute file:// URL:
- insert:
- id: multitask
name: 'file:///absolute/path/to/dsh-multitask/lib/index.js?v=6'
Two things to watch:
% in the path must be percent-encoded (common on Windows). Let a program encode it rather than typing it by hand — in PowerShell: (New-Object System.Uri('H:\my repo\dsh-multitask\lib\index.js')).AbsoluteUri.?v=N is a cache key, and it only means anything in this file:// mode (see Development → "Making source edits take effect").Adding this row needs no restart: DSH watches cordis.patch.yml and hot-reloads it.
When creating a new session, pick "Multitask 减震器模式" in the mode selector, then just ask for what you want.
The mode can be chosen before a session's first turn starts, and a session whose first turn has not yet started can still be switched into or out of it. What is frozen is a session that has already started running its first turn — that is an existing DSH constraint (the tool surface and prompt freeze on the first turn), not a limitation of this plugin.
The coordinator translates your request into self-contained task briefs — a Subagent cannot see this conversation, so a brief must carry everything it needs (goal, verified current state, project conventions, safety rails, numbered tasks, conditional branches, stop conditions, report format). Independent briefs are dispatched in parallel, and the coordinator keeps working while they run.
The coordinator has four delegation paths, and the difference is mainly the tool surface:
| Delegation tool | Tool surface | Best for |
|---|---|---|
subagent | full | the default — writing code, running tests, editing files, execution work |
subagent_fork | full + the completed turns of this conversation — it does not see the turn currently in flight, so a fact stated in the dispatching turn must be repeated in the brief | review and continuation — when the work must build on what is already established here |
subagent_ptc | PTC-collapsed: only run_code is directly callable; every other tool is reached from inside a program via a generated SDK | programmatic bulk work — filtering, aggregating, or moving large data sets whose intermediate values should never enter the context |
subagent_minimal | read-only: write and execute tools are removed, but it keeps read grep glob read_image file_info skill todo_write present and can still search and fetch the web | investigation, quick answers, fact-checking |
Pick by the shape of the task, not by habit. The four paths have different tool surfaces, and the choice is made per delegation in the coordinator persona (section ## Choosing which worker to delegate to).
Delegation is one level deep: coordinator → worker, and a worker cannot delegate further. Jobs that need splitting are split by the coordinator into flat briefs.
The four kinds also differ in persona, not only in tool surface. A fork worker is told that the seeded turns are the coordinator's work and not its own, so it reviews instead of pretending to have done it. A PTC worker is told that its closing message is the fallback channel if run_code breaks. The read-only worker is told it may search and fetch the web, so it does not report back that it "cannot look anything up".
On top of that, five delegation tools — subagent, subagent_fork, subagent_ptc, subagent_minimal, subagent_preset — are removed from a worker's tool catalog outright, for every kind except the read-only worker. A worker therefore cannot delegate: the tool is not there rather than present-and-refused. This costs no capability, because the host's fixed depth of 1 would refuse every such call anyway; it only stops that schema being re-sent on every single request. The coordinator is untouched by this, and the read-only worker is narrowed by its own allowlist instead.
This is not a security boundary — the real block is the host's depth limit. If it fails to install it degrades silently, leaving only a log line containing WORKER-GUARD-SKIPPED, and nothing is lost functionally. Its switch is the worker-guard row's config.enabled in presets/multitask/composition.mjs (which also accepts an additive config.extraDeny), and changing it requires a restart.
If a worker's persona cannot be installed at all, that worker keeps the coordinator persona and the log shows WORKER-PERSONA-SKIPPED.
When the coordinator sends a follow-up to a running worker, the target's current turn is stopped first, and only then is the message delivered — it does not queue until the next step boundary. This matters because a single step can be one long tool call: a build, a dependency pull, a recursive scan. A correction that merely queued would let the wrong instruction run to completion before your new one was even read.
Stopping is not "the task was cancelled". The inbox and any descendants are kept, every fact already accepted is still in the session log, and only the one in-flight action is dropped. A correction to work that was on the right track therefore loses almost nothing — and the message may carry information the worker could not have had.
It only ever acts in the coordinator → worker direction. A message that a worker sends upward is not preceded by an interrupt, because that would cut the main conversation's own turn. If the interrupt fails, or the deployment exposes no interrupt capability, the message is still sent normally and corrections simply revert to queueing: you see an info line, not an error. Its only switch is the interrupt-before-send row's config.enabled in the composition, and changing it requires a restart.
file_info toolIt is this package's own tool and the only self-authored tool on the coordinator's keep list. Its input is exactly one field: paths, an array of one or more path strings (up to 256), resolved by the filesystem backend with relative paths taken against the session working directory.
The output is one entry per requested path — requestedPath, resolvedPath, status, sizeKnown, sizeBytes when known, and a note explaining an odd state — plus a totals block (requested, files, directories, others, missing, errors, bytes, unknownSizes, complete).
status is one of file, directory, other, missing or error. A directory is reported as a directory with no byte count. A missing or uninspectable path is reported as such and is never reported as 0 bytes: when the backend cannot supply a size, sizeKnown is false and sizeBytes is absent rather than fabricated as zero.
totals.bytes sums only the known sizes, unknownSizes counts the rest, and complete is true only when every requested path is a known-size regular file — so a partial sum never masquerades as a total.
It never reads file contents and never modifies anything: it only resolves and stats each path. That is exactly why it is safe to keep in a mode whose entire point is keeping data out of the main conversation, and why the coordinator can use it to check a worker's claim ("five files, this many bytes") instead of trusting the report. It also serves the read-only worker, and it is deliberately not affected by the DSH_MULTITASK_MULTI_MODE switch.
If the filesystem capability is unavailable at load time the tool is simply not registered — the model never sees a route that would fail — leaving a log line containing FILE-INFO-SKIPPED; success logs FILE-INFO-ENGAGED.
The four paths above are this mode's own workers. One more path reaches outside this mode:
| Delegation tool | Target | Best for |
|---|---|---|
subagent_preset | any installed agent preset (including third-party modes installed from the market) | when the job is genuinely something that other mode exists to do |
This is what makes the coordinator a layer between you and every installed mode: it picks one by reading what that mode says about itself, hands the brief over, and only receives the conclusion. The work happens in that mode's worker, not in your main conversation.
To use it, call with list: true first to read the roster (id, name, description, and whether each mode can currently compose a session), then choose by description. The description is the only reliable statement of a mode's capabilities — do not guess from the id. A mode marked CANNOT RUN is broken and must not be chosen.
Three boundaries:
subagent_preset belongs to the coordinator; the tool refuses a worker's call itself, with its own message ("A delegated worker cannot dispatch further (delegation depth is fixed at 1)"), before any depth limit is consulted — deliberately, so the error names the real cause instead of surfacing a bare depth 2 exceeds maxDepth 1 that looks unrelated. Splitting therefore always stays with the coordinator.A fourth boundary (measured, and worth knowing): a cross-mode worker is not governed by this mode's guards.
In setup() a subagent is attached to the target mode's scope with bindScopeParent(childKey, targetGenerationKey), and events like agent/created and tools/pre-execute reach only listeners on that scope chain. A cross-mode worker's chain is [itself, target mode] — it does not contain this mode. That cuts both ways:
never, so a cross-mode worker's dangerous operations are refused outright (fail-closed — never silently allowed), but no card is raised to ask you. It is governed by the target mode; if that mode has guards of its own, they apply.So cross-mode dispatching is a capability router, not a security proxy. Work that needs this mode's two-level gate should still go to one of the four built-in paths.
The provider is fixed to spawn and is not a configuration option: upstream requires a provider that can select another preset to declare the agentPreset capability and not inherit the parent's context. The built-in spawn satisfies both and fork does not. Hard-coding it avoids a class of capability error that would otherwise only surface at delegation time.
Once installed, DSH's settings gain the following.
The "Multitask" page — two cards
| Card | Field | What it does |
|---|---|---|
| Model used by Subagents | multitask.subagentModels[] | the default model for each of the four worker kinds. Either "use one model for every Subagent" or per-kind; the default is "inherit coordinator". Applies only to sessions created afterwards |
| Subagent parallelism limit | subagent.maxActiveSubagents | the same field as the row in Settings → General, and it takes effect immediately |
The "General" page — two rows (alongside the built-in language / appearance / developer-tools rows)
| Row | Field | Scope |
|---|---|---|
| Maximum recursion depth | subagent.maxDepth | host-level: applies to every mode, not just Multitask |
| Subagent parallelism limit | subagent.maxActiveSubagents | same; and it is the same value as the card on the Multitask page |
The session header also shows how many Subagents are currently running.
The model card's shape is derived, never stored: all four kinds pointing at one route means "use one model for every Subagent", anything else means per-kind. Switching the shape creates no dirty state.
enabled, presetId, order, guardEnabled, keep and extraDeny have no UI at all and are profile-YAML only — see "Tunables (profile)" below.There is only one value: the host's subagent.maxActiveSubagents. It appears in two places in Settings (the card on the Multitask page, and a row in General), but both edit the same field — change it in either and the other follows immediately. This plugin adds no cap of its own, and there is no second layer cutting you off.
That field's default is 8: with no configuration, 8 is all you get. But 8 is a default, not a ceiling — whatever you write is the limit. To run N, set it to N — in eith