dsh-openclaude-ecosystem
維護狀態: 活躍liangz2210-lgtm/dsh-openclaude-ecosystem
把 OpenClaude 註冊為 DeepSeek Harness 的命名子 Agent 提供商,支援任務圖、退出碼分類、停滯監控和自動恢復。當前為實驗性外掛,尚無真正完成的委派任務。
$ dsh plugin add dsh-openclaude-ecosystem0
星數
0
Fork
TypeScript
語言
MIT
授權條款
2026-09-15
建立於
2026-09-15
最近推送
README
dsh-openclaude-ecosystem
Everything needed to run the DSH web profile with the merged
dsh-openclaude plugin loaded, which in
turn drives OpenClaude (NVIDIA NIM, nemotron-3-ultra-550b-a55b).
Status: experimental, unproven
Read this before cloning. The plugin mounts, builds, and passes its own test suite (57 assertions), but it has never completed a real delegation.
- 1 real delegation, 0 successes. The only live call (2026-09-11, a single-file HTML game) ran 62 minutes producing nothing and was killed externally, classifying as
OPENCLAUDE_NON_ZERO_EXIT.- The root cause was not in this plugin. The child process had no read timeout on its HTTP request to NVIDIA and hung on a socket. The stall watchdog shipped to bound that failure (
IDLE_TIMEOUT→ auto--resume, plus aRUNTIME_EXCEEDEDwall-clock cap) has never been exercised against a live stall — the failure predates the fix.- It is unguarded by design.
--yolois hardcoded inbuildInvocation(executor/openclaude.ts), so a delegated task writes files and runs commands with no confirmation step. Scopeworkdirdeliberately.- It needs a third-party CLI you must install yourself. The plugin only spawns
openclaude; it holds no credentials. Without that CLI onPATH(or a resolvable fallback, seeresolveExecutable) nothing runs.
PLUGIN-STATUS-2026-09-11.mdis the full forensic snapshot of that one delegation, taken from process records rather than recollection. Treat it as the honest record of where this stands.
Use it
dsh # start, foreground; Ctrl-C stops it
dsh bg # start detached, wait until it answers, log under logs/
dsh status # pid / port / HTTP health
dsh stop # stop it
dsh restart
dsh check # pre-flight: compose the profile, start nothing
dsh logs 60 # tail the newest background log
dsh where # every path this depends on
Then open http://127.0.0.1:3080.
dsh is bin/dsh symlinked from ~/.local/bin/dsh (which is on PATH via
.zshrc). It is a thin front-end — start-dsh.sh owns the actual launch and
the arm64-node guard.
ds is a synonym for dsh (.zshrc: alias ds="dsh"). It previously read
cd ~/deepseek-harness && pnpm web, which could never work: the harness root
has no web script, so pnpm web fails with Command "web" not found. The
real entries are pnpm dsh (tsx source CLI) and pnpm dev:web (frontend dev
server).
Driving OpenClaude from inside DSH
The plugin self-mounts at boot — there is no enable step, no per-conversation setup, and nothing to re-arm after opening a new conversation. The commands are registered in the global layer of the command registry, so every session in the running instance shares them. Only a harness restart remounts the plugin.
Three commands exist, and /openclaude is the only one you use routinely:
/openclaude <request> run one ad-hoc request as a single task
/openclaude tasks/foo.json run a task-graph file (anything ending in .json)
/openclaude-status status of every task in the current graph
/openclaude-abort abort the run and kill in-flight CLI processes
Typing / in the input box opens the command menu (the UI registers itself as
the '/' trigger source over commands.list(sessionId)), so /openclaude can
be picked rather than typed. The parse rule is
/^\/([a-z][a-z0-9_-]*)(?=$|[\t\n\r ])/u, which means:
- The line must start with
/— no leading text. - The name is lowercase only, then a space.
/openclaude你的需求and/OpenClaude …both fail to parse and are sent to the model as ordinary chat. - Bare
/openclaudewith no argument parses fine and the handler answersUsage: /openclaude <graph.json | request>.
A command is a trigger, not a mode:
- It does not go to the model.
CommandDefinition.handleris documented as running "against the receiving agent without sending the command to the model", so typing it is a local action, not a prompt. - It does not reroute later messages. Nothing in the plugin hooks the session;
normal chat is never forwarded to OpenClaude. Every task needs its own
/openclaude. - Runs do not remember each other.
runRequest()builds a one-task graph, runs it, then closes the engine; context comes from files underworkdirviacollectContext, never from chat history. To continue earlier work, restate it in the request or write it into a graph JSON.
workdir defaults to . in cordis.patch.yml, resolved against the directory
dsh was launched from — so start dsh from the project you want OpenClaude
to edit, not from $HOME.
Every run is unattended and unguarded. --yolo is hardcoded in
buildInvocation (executor/openclaude.ts:215) and no permission-mode is set
by default, so nothing asks before a file is written or a command is run. A
request like "delete X" is executed without a confirmation step — scope the
workdir deliberately, and set permissionMode if you want a narrower mode.
If you want ordinary messages auto-routed to OpenClaude, that is a different integration: it needs a session-level hook the plugin does not currently have.
There is no clarification step, and that is structural
The workflow people expect — DSH clarifies the requirement, proposes a plan, then hands a settled brief to OpenClaude — is not what either half does:
/openclaude <string>never decomposes anything.runRequestbuilds exactly one task:title= the first 72 chars,description= the whole string,acceptanceCriteria: [], no dependencies, then runs it. The string is the brief.- The LLM supervisor is a reviewer, not a planner. It runs after an
attempt, sees the executor's report plus the acceptance results, and picks one
of five actions.
escalatemarks the task halted — it does not come back and ask you anything. - There is no channel to ask you mid-run. The executor is headless
(
--print --yolo), and the command handler blocks until the run is over. - The DSH side cannot act. The
webprofile disablestool-bash,tool-fs,tool-fs-search,tool-str-replace-editor,plan-mode,tool-subagent*,tool-workflowand more — 24 rows inpackages/bundle/web-app/cordis.patch.yml. So the conversational agent can reason with you but cannot read, write or run anything itself.
The two halves therefore talk past each other: the agent can plan but not act, the plugin can act but not plan or ask.
Getting the flow you actually wanted
Do the clarifying in the conversation, then hand over a task graph instead
of a sentence. /openclaude <file>.json is the path that gives you
decomposition, dependencies, parallelism (maxConcurrency, default 2) and
per-task retries (maxRetriesPerTask, default 3):
{
"tasks": [
{
"id": "t1",
"title": "抽出重试策略",
"description": "把 payments 的 timeout 重试逻辑抽成独立模块,保持现有行为",
"acceptanceCriteria": ["npm test -- payments 通过"],
"priority": 1
},
{
"id": "t2",
"title": "补指数退避与测试",
"description": "在 t1 的模块上加指数退避,最多 3 次,并补单元测试",
"dependencies": ["t1"],
"priority": 1
}
]
}
An array at the top level works too. Fields are optional except title;
description defaults to title, dependencies to [], priority to 1,
assignee to openclaude. Because the agent has no file tools, it has to hand
you this JSON as text — save it yourself, then run /openclaude tasks/x.json
with dsh started from that project.
That is usually not a failure — the command returns one summary line, only after the whole run finishes. There is no streaming into the conversation, and OpenClaude deliberately reports nothing while it is inside a model turn. The journal is the only honest progress view:
tail -n 1 <workdir>/.dsh/openclaude/tasks.jsonl # default storePath
Each line is a full task snapshot; status, attempts[].errorCode and
haltedReason tell you what happened. Useful codes:
errorCode |
Meaning |
|---|---|
OPENCLAUDE_ABORTED |
the caller's signal fired — a new command or a new conversation cancelled this one |
OPENCLAUDE_IDLE_TIMEOUT |
no progress for idleTimeoutMs (default 240s). A --heartbeat line is liveness, not progress, so it does not reset this clock — the child is alive and getting nowhere |
OPENCLAUDE_RUNTIME_EXCEEDED |
the per-attempt wall-clock cap maxRuntimeMs (default 3600s) was reached. The one failure the progress clock cannot see: a livelock that keeps reporting activity |
OPENCLAUDE_CLI_NOT_FOUND |
openclaude not resolvable; see the broken-symlink note below |
OPENCLAUDE_NON_ZERO_EXIT |
the CLI exited non-zero; the real code is in the result, not an exception |
Cross-check liveness without ps (which may be restricted):
lsof -p <dsh-pid> | grep cwd # the workdir OpenClaude inherits
pgrep -P <dsh-pid> # the openclaude child, if running
Expect runs to be slow: --max-turns 50 by default. --heartbeat 30s is what
makes a quiet run distinguishable from a dead one — but note that it only
reports liveness; it does not buy patience. A child that heartbeats forever
without producing anything is a stall, and idleTimeoutMs ends it. Do not read
"it is still heartbeating" as "it is still working". Under an x64 node on Apple
silicon (Rosetta) it is slower still.
Those defaults belong to the orchestrator (
/openclaude,idleTimeoutMs240s). The subagent provider below has its own, longer one (900s) — the two are configured separately.
The 编排模式 preset: let the agent delegate by itself
The commands above require you to package work as a task graph. The other path puts the decision inside the agent: a preset whose persona tells it to triage each request, plan, decompose, and hand engineering-heavy work to a child OpenClaude — while keeping clarification, planning, decomposition and acceptance for itself.
It ships as a user preset and is the default:
~/.dsh/.agent-presets/delegating/
preset.yml name: 编排模式, order: 5
agent.cordis.yml a full copy of `standard`, with two local edits
The two edits, and nothing else:
persona.config.textreplaced with the delegation policy (fromDELEGATION-POLICY.md);- in the
delegationgroup, the shipped-but-disabledtool-subagent-claude-coderow replaced by:
- id: tool-subagent-openclaude
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: openclaude
toolName: subagent_openclaude
enableRunInBackground: true
maxDepth: provider-managed
Because a preset is a full composition, not a patch, standard is left
untouched and the two coexist — pick either from the preset selector, or change
agent-presets.default in ~/.dsh/settings.yaml:
agent-presets:
default: delegating # or: standard
What the agent gets: subagent_openclaude (foreground by default, or background
with run_in_background: true → job_output / job_kill), plus its own
subagent and subagent_fork from standard. What it does not get: any
ability to see the child's intermediate steps, any --add-dir outside the
session cwd, or a child that shares the conversation — inheritsParentContext
is false, so the task brief must be self-contained.
The one failure the provider handles without telling anyone is a stall: a child
that makes no progress for idleTimeoutMs is treated as hung, killed, and
resumed with --resume <its own session id>, up to maxStalledRestarts times. A
stall that still reaches the agent therefore means recovery was already
exhausted.
"Progress" is the operative word, and it is not the same as "output": OpenClaude
heartbeats on stderr every 30s, and those lines prove the process is alive
while saying nothing about whether it is working. Treating them as progress is
what made the watchdog useless in the first place — the timer was reset every
30s, so a frozen child was never killed and the stall it exists to catch was the
one case it could not see. That was observed live on 2026-09-11: a delegation sat
frozen for over an hour with its TCP connection static, reported as running.
maxRuntimeMs now additionally bounds each attempt for the case a no-progress
rule structurally cannot cover — a child that keeps reporting activity while
getting nowhere.
Telling the agent the capability exists
Wiring the tool row is only half of it, and for a while it was the half we had.
Measured on 2026-09-11, with the preset correct and subagent_openclaude in the
catalog: zero delegation calls, and the reasoning trace never once contained
subagent, OpenClaude, 委派 or 派.
The cause is a gap the harness leaves on purpose. Registering a tool and
describing a tool are two different acts, and the harness only performs the
second one for the single shape it hard-codes: @deepseek-ai/dsh-tool-subagent
publishes a tool:<name> prompt section when backgroundMode is continuable.
A one-shot provider — ours, and the shipped claude-code provider alike — gets a
schema and nothing else. A schema states parameters, not abilities; nothing in it
says you can hand engineering work to another process.
So the provider announces itself, in packages/dsh-openclaude/src/subagent/index.ts:
// order 116.5 is where the harness writes its own "delegate in the background"
// text; 116.6 hitch-hikes onto the same tail-of-prompt attention band without
// demoting anyone.
ctx.systemPrompt.section({
name: `tool:${toolName}`,
order: 116.6,
text: context => tools.get(toolName, context.scope) === undefined ? '' : guidance,
})
116 characters, and it names the capability rather than restating the rules —
the routing criteria stay in the persona, referenced not duplicated. It reaches
systemPrompt and tools through a child fiber (ctx.inject([...])) rather than
by widening the row's inject, because a row that pends takes the delegation tool
down with it; and the registration tolerates a same-name collision instead of
letting a duplicate throw fail the mount. DELEGATION-POLICY.md §11.1 has the
four properties and the tests behind each.
DELEGATION-POLICY.md §12 covers the other half of the same update: six additions
to the persona (a three-part clarification gate, a "never ask about these" list,
recommended answers, rejected-alternatives in decisions, four-way verification
classification, and an empty-artifact ban), 1,846 → 2,726 characters, borrowed
sentence-by-sentence from github/spec-kit's command prompts while the four
places we are stronger — the rework loop, supervision, the red lines, the child
report contract — stay untouched.
What lives where
| Path | Role |
|---|---|
bin/dsh |
the dsh command (start / stop / status / logs / lock recovery) |
start-dsh.sh |
real launcher: verifies arm64 node + harness tree, then execs the CLI |
packages/dsh-openclaude/ |
the plugin: one package, three internal module groups |
DELEGATION-POLICY.md |
the delegation policy — and the source of the preset's persona text (§11 the measured non-delegation record, §11.1 the provider's own prompt section, §12 the six persona additions) |
PLAN-agent-plane-delegation.md |
the A/B route analysis, kept as the record of why B was chosen |
PLUGIN-UPDATE-PLAN.md |
the four-batch update plan this update executed, with the Spec Kit comparison it came from |
tools/ |
acceptance scripts: boot the real profile, mount the preset, assert on the session log (dsh-acceptance-guidance.sh is the one for the current state) |
LICENSE |
MIT |
Two directories named below are runtime artefacts and are not in this repo
(.gitignore excludes them): logs/ (dsh bg output, one file per start) and
backups/ (dated local snapshots, each with a MANIFEST.md and restore
commands). They were deliberately left out when publishing — the snapshots
carried a live API key in dead test scripts, and neither has value to a reader.
Wiring on the DSH side is four files, all outside this repo:
~/.dsh/profiles/web/package.json—dsh.profile.bundles, which must end withdsh-openclaude~/.dsh/profiles/web/cordis.patch.yml— the profile patch layer~/.dsh/profiles/web/node_modules/dsh-openclaude— symlink back intopackages/~/.dsh/.agent-presets/delegating/— the编排模式preset (persona + tool row)~/.dsh/settings.yaml—agent-presets.default: delegating~/.openclaude.json—envblock holding the NVIDIA key and default model
The bundle chain carries all eight bundles. On 2026-09-11 a lean pass briefly
dropped @liustack/modlens, dsh-vision-router, dsh-find-plugin and
@anysearch/anysearch-dsh, cutting the tool catalog from 46 to 16; it was rolled
back the same day. DELEGATION-POLICY.md §11 keeps the measurements as the
record of the experiment, and says why it was reverted. The round trip cost one
line each way, because an entry in dsh.profile.bundles only controls mounting
— the packages were never uninstalled from node_modules/. The problem the lean
pass was meant to solve was then solved the same day by the opposite means (add a
capability statement, remove nothing) — see §11.1 below.
Rolling back also restored attachment-local's image limits (20MiB / 100MP /
10000px), which dsh-vision-router contributes from its own bundle patch. The
profile's cordis.patch.yml therefore goes back to carrying no attachment-local
row, and it should stay that way: the profile patch layer composes after the
bundles and replaces the whole config block rather than merging into it, so a
hand-written row would wipe maxImageDimension — the exact trap the bundle's own
comment warns about.
Do not delete or move this directory: the profile loads the plugin through
that symlink, so a broken link takes the whole profile down at boot. The preset
is a separate copy under ~/.dsh/, so editing it does not touch this repo — and
vice versa: rebuilding the plugin does not update an already-copied preset.
Notes
- First boot is slow (measured 5s–167s here): mounting the other bundles is
the cost, not this plugin.
dsh bgwaits up toDSH_START_TIMEOUT(300s). - A dsh killed while holding the settings writer lock leaves
~/.dsh/settings.yaml.lockbehind and blocks the next boot;dshclears it automatically when the recorded pid is gone. - Environment overrides:
DSH_PORT,DSH_START_TIMEOUT,DSH_NODE,DSH_HARNESS_DIR,DSH_ECOSYSTEM_DIR. ~/.homebrew/bin/openclaudewas a broken symlink to~/openclaude/bin/openclaude(a deleted clone), which made bareopenclaudefail in the shell while the plugin still worked —resolveExecutableskips symlinks that failaccessSync(X_OK)and falls back to~/.homebrew/Cellar/node/*/lib/node_modules/@gitlawb/openclaude/bin/. Repointed to../Cellar/node/26.4.0/bin/openclaude(version 0.30.0). Ifopenclaudeever goes missing again, check that link before anything else.
更多「外掛工具」外掛
dsh-context
作者 bowenliang123
The best DeepSeek Harness plugin for context insight and management, with context dashboard / browser / sidebar and context command, for context statistics, composition, breakdown, evolution details, understanding how the context is made of, and how it evolves. 一站式 DeepSeek Harness 上下文視覺化外掛,Context 面板及瀏覽器和側邊欄與 Context 命令,透視上下文組成、演進、壓縮、剪枝等事件與動作。
api-relay-audit
作者 toby-bridges
本地 AI API 中轉與 LLM 代理安全審計工具,可檢測提示注入、模型替換、工具呼叫篡改、SSE 異常與 Web3 錢包風險。
awesome-deepseek-harness-plugins
作者 zhiyuan-fan
DeepSeek Harness 外掛精選清單,收錄外掛、擴充套件、工具、技能、客戶端與整合資源,中英雙語。
jingyun-dsh
作者 jingyunstudio
基於 Jingyun Studio + DeepSeek Harness (DSH) 打造的一站式 AI 商業化桌面客戶端,一個將 AI 智慧體 / 技能 / 工作流轉化為可交易商品的完整商業化平臺客戶端。 井云為 DSH 注入了完整的商業閉環:登入註冊 → 會員體系 → 訂閱支付 → 雲端資產 → 多端同步,讓 AI 開發者 30 分鐘內將自己的智慧體封裝為獨立的商業產品。
