dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Multi-agent orchestration plugin for DeepSeek Harness (dsh).
Splits long tasks across multiple purpose-built agents so no single agent's context accumulates the whole task: a planner decomposes work into a task graph, each worker runs in a fresh one-shot context holding only what its task needs, and workers return structured reports instead of transcripts.
Core loop: plan → dispatch (parallel) → structured reports → review → pass | redo.
Status: full loop + cross-restart recovery + Web panel + adoption prompt —
plan → dispatch → structured reports → review (pass/fail) → reroute → re-dispatch
→ finalize all work; on top of that the task graph survives a restart (the host's
own log is replayed into it) and the conversation composer shows an orchestration
panel — both verified live. A system-prompt section exists to get the pipeline
chosen for large requests that never name it: it was verified that the section
reaches the model, positioned after the workflow and subagent sections, but the
behavioural half — whether the agent actually calls orchestrate_plan when nobody
names it — is not verified yet (the run that would settle it was blocked on an
invalid API key). State is keyed per conversation; worker tool access is
whitelist-constrained; worker profiles are configurable. 16 offline test groups
pass (CI runs them on every push/PR). See ../DESIGN.zh.md.
Naming: the repository and the local folder keep the historical name
dsh-orchestra; the GitHub repo and the package aredsh-taskforce. The tool namesorchestrate_*, the service nameorchestraand the projection keyorchestraGraphare frozen on purpose — they appear in the host's own tool-call log, so renaming them would break recovery for sessions recorded before the change. Do not "tidy" them up.
From a local checkout (run pnpm install inside this directory after cloning —
the bundle has one npm dependency; see Compatibility for the
dsh versions this was verified against):
git clone https://github.com/LiSheng5/dsh-taskforce.git
pnpm install
dsh plugin --profile web add <path-to>/dsh-taskforce
<path-to> must be an absolute path. dsh plugin add also accepts git specs
directly (github:LiSheng5/dsh-taskforce) — pnpm then resolves the dependency at
install time.
Using the desktop app rather than the CLI? It ships its own launcher
(resources\runtime\cli\bin\dsh.cmd on Windows) and manages the desktop
profile exclusively: booting or dumping that profile from the CLI is refused,
installing into it is not.
"<DeepSeek Harness>\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop add <path-to>\dsh-taskforce
The app's own sidebar Plugins page installs the same way — a registry name, a git address, a tarball or an absolute local path — and the bundle is enabled by default. Restart the app to load it.
| dsh version | status |
|---|---|
0.1.7-rc.2 | developed against; full loop, restart recovery and Web panel verified |
0.2.0-rc.2 | the desktop app's runtime; install, mount and prompt assembly verified headless (the section renders where it should, tool text reaches the model) — the full orchestration loop and the Web panel have not been re-run on it |
dsh is a developer preview and officially announces breaking changes. After upgrading dsh, run the offline regression suite once — it needs no dsh, no API key, no network:
node test/local-loop.mjs
The same suite runs in CI on every push and pull request
(.github/workflows/offline-regression.yml, Node 20 / 22 / 24), so "run it once
after upgrading dsh" and "CI blocks regressions" are the same command.
Upgrading from a version that predates the restart-recovery fix? Conversations it touched need a one-time repair — see Upgrading from a version that predates the fix.
A composition without @deepseek-ai/dsh-system-prompt loses exactly one thing:
the prompt section that makes the pipeline get chosen on its own. Every tool
still works when it is named explicitly.
This plugin spawns real sub-agents with real powers: file reads/writes and shell access inside the session workspace, bounded by each worker profile's tool restrictions. Read dsh's own SAFETY notes before running orchestrations on workspaces you care about, keep the per-task timeout and attempt caps enabled, and treat worker reports as claims — that is what the reviewer is for.
Workers also lose the host's agent-plane control tools by default
(send_message, interrupt_agent, list_agents, subagent, subagent_fork):
send_message would post a worker's text straight into the orchestrator's
session, which is exactly what this plugin exists to prevent, and the others
let a worker disturb its parent or start more agents. This list was calibrated
by measurement (a real researcher worker reported exactly these tools); set
denyControlPlane: false to keep them.
index.js plugin entry: name / inject / apply
client.js browser half: the panel above the composer (lazy __ModuleLoader__ factory)
cordis.patch.yml bundle patch inserted into the profile composition
src/protocol.js Task / Report / Verdict JSON schemas (host JSON-Schema subset)
src/graph.js DAG validation, readiness, status transitions (CAS)
src/service.js orchestration state (ctx.provide('orchestra'))
src/events.js restart recovery: folds the host's tool/call + tool/result back into a graph
src/router.js context router: the task book a worker receives
src/profiles.js agent_type → persona / reasoning effort
src/prompts.js every model-facing string: the adoption section + planner guidance
src/json-schema.js argument validator (subset semantics)
src/retry.js auto-retry for malformed tool-JSON stream failures
src/tools/ plan / status / dispatch / review / reroute / finalize (+ ping)
scripts/ maintenance: repair session logs written by older versions
test/local-loop.mjs offline regression suite (fake host, no network)
The graph lives in memory, keyed per conversation, so a restart loses it. What brings it back is the session log the host already keeps:
tool/call (full arguments) and tool/result
(rendered output), so orchestration state is durable in the host's own
vocabulary — the plugin writes no session events of its own.orchestraGraph session projection (under
ctx.inject([...]), so a composition without the projection registry is
unaffected) that purely folds those events into the task graph, and loads it
the first time a fresh process touches the conversation.tool/call before the tool body runs, so applying eagerly would apply the
call being executed right now a second time. A dispatch that was cut short by
a restart is therefore never half-applied either: its task stays where the
orchestrator last saw it — ready to run again.✓ <id> — <detail> render contract
(early builds) recover their plan, attempts, verdicts and finalized flag but
not their dispatch outcomes: the old digest carries no success marker, and
inventing one would be worse than leaving the task where it stopped. Such a
result still consumes its pending call, so it is never misreported as
interrupted by a restart.orchestrate_status answers with the recovered graph after a restart.
Early versions appended their own event types, which made those conversations
unloadable after a dsh restart (contains event type "orchestra/graph" … refusing to interpret the log). The repair script adds the harness's own
compatibility marker (ignorable) so they load again, backing up first:
node scripts/repair-session-logs.mjs --dry-run # report first
node scripts/repair-session-logs.mjs # repair (self-checked, backup kept)
node scripts/repair-session-logs.mjs --restore # undo
Conversations that have an orchestration show a panel above the composer (a conversation that never planned anything renders nothing at all):
▾ ▣ orchestra #1 · 1/2 · review 1 PASS
─────────────────────────────────────
✓ t1 researcher completed att 1
read package.json — name dsh-taskforce, version 0.1.0 …
· t2 coder pending → ready
orchestraGraph projection
(client.js calls useProjection('orchestraGraph')), so it shows exactly
what orchestrate_status reports, updates itself as events arrive, and after
a restart shows the recovered graph.client.js, declared by
dsh.client.platform=web with immediately). It inherits the host theme via
currentColor and pulls in no stylesheet or third-party dependency.The plugin also registers a system-prompt section (tool:orchestrate_plan,
order 2810) so the host agent reaches for the pipeline on a large request it was
never told to name — measured behaviour before this was todo_write plus doing
the work itself. It follows the host's own convention (workflow owns order 2600,
subagent 2800), is registered under ctx.inject(['systemPrompt'], …) so a
composition without that service merely loses the advocacy, and renders as an
empty string for any agent that cannot see orchestrate_plan — which is every
worker, because a spawned child's persona only shadows the deployment persona
and global sections do reach it. All model-facing text lives in src/prompts.js.
orchestrate_plan — submit the task graph (mission_brief + tasks); validates
fail-loud and returns the ready set. Its description is where the
decomposition rules live: workstreams are how you find the cut but must be
restated as file ownership, task counts scale with the request, the
parallel-writer hazard, two worked skeletons, and the anti-patterns.orchestrate_status — compact one-row-per-task view plus dispatchable ids,
and the diagnostic for tasks that failed without producing a report.orchestrate_dispatch — spawn ready tasks as isolated one-shot workers,
block until they settle, return one report digest per task (≤4 concurrent).orchestrate_review — run the reviewer over the reports; returns a verdict.orchestrate_reroute — send review issues back to workers with guidance.orchestrate_finalize — record the delivery summary and lock the graph.orchestra_ping — liveness probe.The orchestrator writes per-task personas itself: each task in a plan may
carry a persona field — a role definition A tailors to that task ("你是数据库
迁移专家,只改 SQL 文件"). It becomes the worker's primary system prompt, layered
above the profile's discipline (non-negotiable constraints that A cannot
override: researchers don't write, reviewers don't fix, …). model and
reasoning_effort are likewise per-task fields A sets at plan time.
Two more ways to tune what each worker role is like — both take effect after restarting the dsh process:
Edit src/profiles.js directly. Each role has role (identity line),
discipline (permanent constraints), effort (off|low|high|max), and
toolDenyPatterns — regexes matched (case-insensitively) against the host's
global tool names to hard-restrict that role's tools.
Config overrides (no code edit). Add a config: block to the plugin
entry in the profile's cordis.patch.yml (e.g.
~/.dsh/profiles/web/cordis.patch.yml):
- id: dsh-taskforce
name: dsh-taskforce
config:
taskTimeoutMs: 1200000
maxConcurrentWorkers: 6
profiles:
researcher:
role: |
你是资深代码考古学家……
discipline: |
只读,不修改任何文件。
effort: off
toolDenyPatterns: ["write", "bash"]
coder:
model: deepseek-chat
Config role/discipline/model/effort replace the builtin values;
toolDenyPatterns are concatenated with the builtin ones. Engine knobs:
provider (default spawn), maxConcurrentWorkers (4), maxAttempts (3),
maxReviewRounds (3), taskTimeoutMs (600000).
The orchestrator is not limited to the four builtin worker types. In a plan it
may declare new worker types — orchestrate_plan accepts a workers field
where A programs one agent spec per type:
"workers": [
{
"type": "ui-builder",
"role": "你是界面构建师,专注像素级还原。", // identity (required for new types)
"discipline": "不得改动 src/api/ 下的文件。", // optional; generic floor otherwise
"effort": "high",
"model": "deepseek-flash", // optional defaults
"toolDenyPatterns": ["bash"]
}
]
"agent_type": "ui-builder". Shadowing a builtin type
("type": "coder", no role) replaces only the parts it names.persona still layers above the declared
role, and the declared discipline is always appended — A cannot write it away.defineTool compiles to; the only dependency is the npm-published
@deepseek-ai/schemastery for the Config schema, so pnpm-local-path
installs stay trivial.orchestrate_plan description says HOW to decompose,
and the parameter descriptions carry per-field rules — all of it in
src/prompts.js as module constants, because sections and tool schemas are
re-sent on every request and byte-stable text is what keeps the provider's
prefix cache reusable. Both texts reach the orchestrator only: a worker gets
an empty section and never sees the planner's schema, so TASK_ITEM_SCHEMA
wording costs nothing per worker turn. The offline suite enforces a character
budget on each, so the per-request cost cannot silently grow.resolveToolFilter
always enumerates first and matches second.tool/call + tool/result); the plugin adds
host-log audit lines (orchestra/graph, …) and no session event type the
harness would refuse to read back.submitPlan / markDispatched / settleReport methods with the recorded
arguments instead of reimplementing the state machine, so a recovered graph
cannot drift from live semantics.MIT — see LICENSE.