Установленная копия роли теперь воспроизводима: после копирования roles/ install.sh вставляет в ~/.dsh/roles/dev.md пункт «в DSH исполнение плана — skill sdd» (после «Режим по умолчанию», с откатом в конец файла, если якоря нет). Вставка идемпотентна: каждая установка ролей перекопирует файл и добавляет ровно одну заметку. Общий roles/dev.md не меняется — в Claude Code строка не действует. --check сообщает ok/WARN про заметку; тест проверяет наличие, отсутствие дубля после второго запуска и строку в --check. PORT.md §6 объясняет. Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
16 KiB
DSH port — design and spike notes
Status: spike 0.2.1, live guard/require-plan/bootstrap verified; /role
command works, the model-invocable role skill and the asynchronous SDD
protocol are verified live (three-task trial run). This directory is the
DeepSeek Harness adapter for claude-harness; the Claude Code installation and
the repository's source of truth are unchanged.
1. Verdict
This is a port, not a copy. Three layers map differently:
| Layer | Claude Code | DeepSeek Harness | Status |
|---|---|---|---|
| Instructions | ~/.claude/CLAUDE.md, project CLAUDE.md |
$DSH_HOME/AGENTS.md, project CLAUDE.md/AGENTS.md, nested |
almost 1:1 |
| Skills | ~/.claude/skills, .claude/skills |
~/.dsh/skills, .agents/skills, customSkillDirs |
1:1 format |
| Roles | roles/, /role skill |
same files + /role command + model-invocable role skill |
close |
| Hard rules | PreToolUse shell hooks |
ctx.tools.guard Cordis guard |
logic reused, wrapper new |
| Superpowers | plugin + SessionStart hook |
skills + agent/pre-step bootstrap |
content 1:1, bootstrap new |
| SDD | Task (synchronous) |
subagent (asynchronous) / workflow |
semantics differ |
| Gitea, OpenSpec | CLI + skills | bash + skills |
1:1 |
| codegraph | MCP | MCP (mcp__codegraph__*) |
1:1 with names |
| auto-memory | Claude Code feature | none | dropped/replaced with files |
| install | install.sh + settings.json |
bundle + profile manager | new |
The important discovery: DSH loads CLAUDE.md as a project instruction file,
so the project templates and nested instructions need no rewrite. The global
file has to be installed as $DSH_HOME/AGENTS.md because DSH does not read
~/.claude/CLAUDE.md.
2. What this spike implements
guard-mainandrequire-planthroughctx.tools.guard. The adapter synthesizes the Claude hook payload ({cwd, tool_name, tool_input}), runs the existinghooks/guard-main.sh/hooks/require-plan.sh, turnsexit 2into a denial reason, and fails open on every other error. The Python core (hooks/harness_target.py) and its tests remain the source of truth. A guard is synchronous and monotonic: it runs aftertools/pre-execute, and no later listener can turn the denial back into permission.- Superpowers bootstrap through
agent/pre-step. It reads the installedusing-superpowers/SKILL.md, strips frontmatter, appendsbundle/dsh-tools.md, wraps the result in<EXTREMELY_IMPORTANT>, and injects it once per session. A content-hash marker handles dedupe; scanningsession.surface.nodesre-injects after compaction. - Roles —
/roleis a real command (ctx.commands.register); it reads~/.dsh/roles/<role>.md, falls back to~/.claude/roles/<role>.md, then to the repository'sroles/, and reads the project.claude/roles/<role>.md. It injects the role text as a user message and starts a turn for the five-line summary. Because a model cannot call a slash command,skills/role/SKILL.mdis the model-invocable loader: it infers the role, reads the file, and tells the model to follow it. Auto-memory is explicitly dropped; the role's "Читать на старте" section is left to the model. - Display metadata and an optional CodeGraph MCP overlay so the bundle can be installed through the Plugins page and codegraph stays available.
3. What is deliberately deferred
harness-checkandrole-hint: non-blocking SessionStart hooks. They can becomeagent/pre-stepinjections later; the first message role rule already covers most ofrole-hint.briefing: needs an agent preset with read-only tools and a model route; not part of the guard/bootstrap spike.- SDD parity: the Claude
Taskis synchronous, while DSHsubagentreturns a child id and reports later. Thesddskill implements the asynchronous analogue (report files, notification as the wait,send_messagefor fix rounds, ledger as memory); there is no parity and the skill says so. - Codegraph by default:
dsh/mcp.codegraph.ymlis an opt-in overlay; the bundle does not assume the binary is installed. harness-doctor: its scripts inspect~/.claude; a DSH variant is a separate task.- Auto-memory, caveman: no direct DSH equivalents; caveman is off by default anyway.
- Full installer:
install.shprepares global instructions and skills; the bundle itself is installed byplugin_managerbecause the Desktop profile is Electron-managed.
4. Architecture
dsh/
bundle/
package.json # dsh.bundle.patch, exports, display resources
cordis.patch.yml # inserts the dsh-harness row
index.js # ctx.tools.guard + agent/pre-step + /role wiring
command-role.js # /role command glue (DSH imports live only here)
lib/guards.js # Claude-hook JSON adapter, fail-open
lib/bootstrap.js # skill resolution, marker, dedupe, compaction
lib/role.js # role parsing and text building (pure)
dsh-tools.md # superpowers tool mapping
hooks -> ../../hooks # local-install symlink to the tested bash/Python hooks
locale/{en,ru}.json
icon.svg
tests/ # Node tests, no DSH runtime needed
skills/role/SKILL.md # model-invocable role loader (installed to ~/.dsh)
install.sh # --check / --global / --skills / --roles / --all
mcp.codegraph.yml # optional CodeGraph MCP row
The bundle imports only Node builtins and files inside this package. It
deliberately does not import @deepseek-ai/dsh-llm or
@deepseek-ai/schemastery: a local-directory profile install resolves imports
from the profile, not from the DSH installation, so a DSH-internal import fails
at activation with ERR_MODULE_NOT_FOUND. lib/message.js is a local copy of
the tiny createUserMessage constructor, and Config was replaced by defaults
normalized in apply(). The pure modules are testable with plain Node; this is
why the port's 26 tests run without a DSH process.
Guard flow
model calls write/edit/bash
-> tools/pre-execute (approval, extensible policy)
-> ctx.tools.guard (dsh-harness, synchronous, monotonic)
-> lib/guards.js maps tool + args
-> bash hooks/guard-main.sh (cwd, tool_name, tool_input)
-> python3 hooks/harness_target.py guard-main
-> exit 2 => deny reason (final)
-> exit 0 or any failure => allow
-> require-plan.sh for write/edit
-> execution
The guard returns a denial; the tool pipeline materializes it as the model-facing error. Other listeners cannot turn it back into permission. A script failure, missing Python, or malformed JSON fails open, matching the harness invariant.
Bootstrap flow
agent/pre-step
-> next() (other injections happen first)
-> skip if reject/aborted, or if subagent and bootstrapSubagents=false
-> prepareBootstrap(): resolve skill, read mapping, hash content, build text
-> isBootstrapVisible(agent, decision.messages, marker)?
yes -> no-op
no -> append createUserMessage({ source: {kind:"user"} })
The marker is an HTML comment at the top of the message. The visible-surface
scan uses agent.session.surface.nodes + eventAt(seq), the same mechanism the
skill catalog uses, so a compacted message is re-injected on the next step.
5. Configuration
The bundle row can be configured from the profile patch. Defaults are suitable for the repository layout:
- id: dsh-harness
config:
enabled: true
guards: true
bootstrap: true
roleCommand: true
bootstrapSubagents: false
hooksDir: '' # empty -> bundle/hooks -> ../../hooks
rolesDir: '' # empty -> ~/.dsh/roles, ~/.claude/roles, repo roles/
projectRolesDir: '' # empty -> <cwd>/.claude/roles
roleNames: [dev, admin, analyst, qa]
bootstrapSkillPath: '' # empty -> ~/.dsh/skills, then Claude cache
toolMappingPath: '' # empty -> bundle/dsh-tools.md
bootstrapMaxBytes: 65536
Model routes and delegation depth
SDD needs two things the default profile does not give: a model choice for
subagent and a second level of delegation for a background controller.
dsh/subagent-models.yml is the opt-in overlay for both:
# 1. copy the two entries into the profile patch (desktop is Electron-managed)
open -e ~/.dsh/profiles/desktop/cordis.patch.yml # append dsh/subagent-models.yml
# 2. restart DSH, then check
bash dsh/install.sh --check # "ok: subagent maxDepth=2 (...)"
After the restart the subagent and subagent_fork schemas accept
provider, model, reasoning_effort, and a background controller subagent
can dispatch an implementer. Routes measured in 0.2.1-rc.3:
deepseek-official/deepseek-flash (transcription and mechanics) and
deepseek-official/deepseek-v4-pro (integration and every review). Without the
overlay the agent runs everything on the session model and at depth 1; the
sdd skill must say so in its report instead of pretending the routes exist.
The overlay stays out of dsh/bundle/cordis.patch.yml on purpose: the
subagent-model-selection-settings row exists in the web/desktop profile and is
absent in headless, and enabling it from the bundle would break headless boot
with "requires ... in the Host scope".
bootstrapSkillPath resolution order: explicit path, DSH_SUPERPOWERS_SKILL,
~/.dsh/skills, ~/.agents/skills, the newest
~/.claude/plugins/cache/claude-plugins-official/superpowers/*/, then
~/.claude/skills. The DSH copy wins because dsh/install.sh --skills copies
real files there; the Claude cache is only a fallback.
6. Install and verify
Prerequisites: the claude-harness checkout, a superpowers source, DSH
0.2.1-rc.3, python3, git, Node. Claude Code itself is optional after
dsh/install.sh --skills --roles has copied the skills and roles into
~/.dsh; the hooks come from this checkout.
# 1. Static checks
bash dsh/install.sh --check
# 2. Global instructions, skills, and roles (run from a terminal; writes
# outside the session workspace)
bash dsh/install.sh --global
bash dsh/install.sh --skills
bash dsh/install.sh --roles
# or all three at once:
bash dsh/install.sh --global --skills --roles
# 3. Install the bundle in DSH:
# Plugins -> install bundle from dsh/bundle
# or ask the agent:
# plugin_manager install_bundle target=/absolute/path/to/claude-harness/dsh/bundle
# 4. Optional: CodeGraph MCP
# apply dsh/mcp.codegraph.yml as a --patch overlay; the tool is
# mcp__codegraph__codegraph_explore.
--roles also appends the DSH SDD note to ~/.dsh/roles/dev.md (the bullet that
points dev at the sdd skill). The note lives only in the installed copy: the
shared roles/dev.md stays Claude Code-compatible, so a Claude session never
sees DSH-only instructions. The insertion is idempotent — every --roles run
recopies the role from the repository and re-inserts exactly one note.
Live checks:
- New session: ask "what skills do you have?" — superpowers skills and the DSH catalog should be visible.
- On
main, ask the agent to create a file — expectBLOCKED [guard-main]. - In a worktree on a feature branch, edit
src/...without a plan — expectBLOCKED [require-plan]; add.boundedand retry. /role dev— expect the five-line summary and the injected role rules.- The acceptance prompt "Let's make a react todo list" must trigger
brainstormingbefore code. If it does not, the bootstrap is not visible; check the plugin row and the skill path. - Guard marker: the injected message starts with
<!-- DSH-HARNESS-BOOTSTRAP:<hash> -->; after compaction a new marker is appended.
Verified live on 2026-10-24 in the desktop profile:
- the bootstrap marker
<!-- DSH-HARNESS-BOOTSTRAP:6b676dd9bb8a0234 -->and the fullusing-superpowersbody arrived in a fresh session, and the skill catalog showed the superpowers skills; writeto a file onmainreturnedBLOCKED [guard-main];- a Bash redirect write on
mainreturnedBLOCKED [guard-main]; - a
writeon a feature branch was allowed; writetosrc/probe.tson the branch returnedBLOCKED [require-plan], and the same write succeeded after creating.bounded.
Still pending: /role dev, the fresh-session "react todo list" acceptance, and
re-injection after /compact.
Automated tests:
bash dsh/tests/run.sh
They cover main/detached guards, branch allowance, Bash writes, require-plan
with .bounded and a branch plan, bootstrap frontmatter/marker/dedupe,
role-layer composition, and package metadata.
Composition smoke test (no model call): the bundle was installed with
dsh plugin --profile test add dsh/bundle into a temporary DSH_HOME based on
the shipped headless profile. --dump-config shows the dsh-harness row, the
profile lists @local/dsh-harness in dsh.profile.bundles, and the package's
internal hooks symlink resolves to the repository hooks/. The temporary
profile was removed; the real desktop profile was not touched.
7. Risks and open questions
- DSH is 0.2.1-rc.3. The plugin/event API is not frozen; pin the version and re-run the tests after upgrades.
- Local profile imports are not resolved from the DSH installation. The
first install attempt failed at activation with
ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/schemastery'. The bundle now imports only Node builtins and local files:lib/message.jsreplacescreateUserMessagefrom@deepseek-ai/dsh-llm, andConfigwas dropped in favor of defaults normalized inapply(). Any future DSH service must be reached throughctx(injected service), never through an@deepseek-ai/*import; a package test enforces this. ctx.tools.guardis synchronous. The guard resolves cwd throughworkingDirectory.get(session)(sync) and runs the hooks withexecFileSync. If a future DSH version changes the guard contract or makes cwd resolution async, the adapter needs atools/pre-executefallback. The guard is global and monotonic; it runs aftertools/pre-execute, so an earlier user approval cannot turn aguard-maindenial back into permission.agent/pre-stepinjection. Verified live: the bootstrap marker and body arrived in a fresh session, and the skill catalog showed the superpowers skills. The localcreateUserMessagecopy works withsource.kind: "user", which avoids an unknown session source variant. Re-injection after compaction is still pending.- Subagent bootstrap.
isSubagentprobes common fields; if none are present on this version, the bootstrap reaches children too. The skill's<SUBAGENT-STOP>makes that harmless but token-expensive. - Model tiers.
sonnet/haiku/opusare not DSH models. The port uses two live routes (deepseek-flash,deepseek-v4-pro) from the opt-in overlay indsh/subagent-models.yml; without it every dispatch runs on the session model. ~/.claudedependence.dsh/install.sh --skills --rolescopies skills and roles into~/.dsh; the bootstrap and/roleprefer those copies, and the Claude cache/roles are fallbacks. An existing installation must rerun--skills --rolesonce to become independent of a Claude Code rollback.- Superpowers porting rules. The upstream guide requires a session-start
bootstrap with no per-session opt-in;
agent/pre-stepsatisfies that shape, but a real acceptance transcript is still required before claiming a port. - Hooks symlink. Local-directory installation links the checkout, so
bundle/hooks -> ../../hooksworks. A packed/published bundle must include a real copy ofhooks/; thefilesfield already lists it.
8. Next phases
- Finish the live smoke test: the fresh-session
roleskill acceptance, plus the bootstrap and/compactchecks already verified. - Add
harness-checkand optionalrole-hintas non-blocking pre-step injections. - Build
briefingas an agent preset (read-only tools + model route). - SDD — done (change
dsh-sdd): thesddskill (async controller, reports, review, fix-loop, ledger) plus the overlay in §5; proven by a three-task trial run (openspec/changes/dsh-sdd/trial/, results indesign.md). - Enable CodeGraph MCP by default when the binary is present; update role and
project instructions to
mcp__codegraph__codegraph_explore. - Port
harness-doctorchecks to DSH paths. - Decide the long-term home: keep
dsh/in this repository while it shares hooks/roles/skills, then extract adsh-harnessrepository when the bundle gets its own release cadence.