dsh/subagent-models.yml — opt-in overlay: subagent-model-selection-settings с маршрутами deepseek-flash/deepseek-v4-pro и subagent.maxDepth: 2. В бандл не входит: строки нет в headless, включённый флаг ломает загрузку. install.sh --check сообщает overlay и находит maxDepth: 2 в патче профиля. PORT.md §5 — как включить и что это даёт; §7/§8 обновлены. Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
331 lines
16 KiB
Markdown
331 lines
16 KiB
Markdown
# DSH port — design and spike notes
|
|
|
|
Status: **spike 0.2.0, live guard/require-plan/bootstrap verified; `/role`
|
|
command works, the model-invocable `role` skill is added but its live
|
|
acceptance is pending**. 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
|
|
|
|
1. **`guard-main` and `require-plan`** through `ctx.tools.guard`. The adapter
|
|
synthesizes the Claude hook payload (`{cwd, tool_name, tool_input}`), runs
|
|
the existing `hooks/guard-main.sh` / `hooks/require-plan.sh`, turns `exit 2`
|
|
into 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 after `tools/pre-execute`, and no later
|
|
listener can turn the denial back into permission.
|
|
2. **Superpowers bootstrap** through `agent/pre-step`. It reads the installed
|
|
`using-superpowers/SKILL.md`, strips frontmatter, appends
|
|
`bundle/dsh-tools.md`, wraps the result in `<EXTREMELY_IMPORTANT>`, and
|
|
injects it once per session. A content-hash marker handles dedupe; scanning
|
|
`session.surface.nodes` re-injects after compaction.
|
|
3. **Roles** — `/role` is a real command (`ctx.commands.register`); it reads
|
|
`~/.dsh/roles/<role>.md`, falls back to `~/.claude/roles/<role>.md`, then to
|
|
the repository's `roles/`, 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.md` is 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.
|
|
4. **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-check` and `role-hint`**: non-blocking SessionStart hooks. They can
|
|
become `agent/pre-step` injections later; the first message role rule already
|
|
covers most of `role-hint`.
|
|
- **`briefing`**: needs an agent preset with read-only tools and a model route;
|
|
not part of the guard/bootstrap spike.
|
|
- **SDD**: the Claude `Task` is synchronous, while DSH `subagent` returns a
|
|
child id and reports later. This needs a semantic rewrite (async controller
|
|
with `send_message` + `maxDepth: 2`, or a `workflow` script). Do not claim SDD
|
|
parity from this spike.
|
|
- **Codegraph by default**: `dsh/mcp.codegraph.yml` is 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.sh` prepares global instructions and skills; the
|
|
bundle itself is installed by `plugin_manager` because 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:
|
|
|
|
```yaml
|
|
- 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:
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
# 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.
|
|
```
|
|
|
|
Live checks:
|
|
|
|
1. New session: ask "what skills do you have?" — superpowers skills and the
|
|
DSH catalog should be visible.
|
|
2. On `main`, ask the agent to create a file — expect
|
|
`BLOCKED [guard-main]`.
|
|
3. In a worktree on a feature branch, edit `src/...` without a plan — expect
|
|
`BLOCKED [require-plan]`; add `.bounded` and retry.
|
|
4. `/role dev` — expect the five-line summary and the injected role rules.
|
|
5. The acceptance prompt "Let's make a react todo list" must trigger
|
|
`brainstorming` before code. If it does not, the bootstrap is not visible;
|
|
check the plugin row and the skill path.
|
|
6. 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
|
|
full `using-superpowers` body arrived in a fresh session, and the skill
|
|
catalog showed the superpowers skills;
|
|
- `write` to a file on `main` returned `BLOCKED [guard-main]`;
|
|
- a Bash redirect write on `main` returned `BLOCKED [guard-main]`;
|
|
- a `write` on a feature branch was allowed;
|
|
- `write` to `src/probe.ts` on the branch returned `BLOCKED [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
|
|
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.js`
|
|
replaces `createUserMessage` from `@deepseek-ai/dsh-llm`, and `Config` was
|
|
dropped in favor of defaults normalized in `apply()`. Any future DSH service
|
|
must be reached through `ctx` (injected service), never through an
|
|
`@deepseek-ai/*` import; a package test enforces this.
|
|
- **`ctx.tools.guard` is synchronous.** The guard resolves cwd through
|
|
`workingDirectory.get(session)` (sync) and runs the hooks with
|
|
`execFileSync`. If a future DSH version changes the guard contract or makes
|
|
cwd resolution async, the adapter needs a `tools/pre-execute` fallback. The
|
|
guard is global and monotonic; it runs after `tools/pre-execute`, so an earlier
|
|
user approval cannot turn a `guard-main` denial back into permission.
|
|
- **`agent/pre-step` injection.** Verified live: the bootstrap marker and body
|
|
arrived in a fresh session, and the skill catalog showed the superpowers
|
|
skills. The local `createUserMessage` copy works with `source.kind: "user"`,
|
|
which avoids an unknown session source variant. Re-injection after compaction
|
|
is still pending.
|
|
- **Subagent bootstrap.** `isSubagent` probes 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`/`opus` are not DSH models. The port uses two
|
|
live routes (`deepseek-flash`, `deepseek-v4-pro`) from the opt-in overlay in
|
|
`dsh/subagent-models.yml`; without it every dispatch runs on the session model.
|
|
- **`~/.claude` dependence.** `dsh/install.sh --skills --roles` copies skills
|
|
and roles into `~/.dsh`; the bootstrap and `/role` prefer those copies, and
|
|
the Claude cache/roles are fallbacks. An existing installation must rerun
|
|
`--skills --roles` once 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-step` satisfies 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 -> ../../hooks` works. A packed/published bundle must include a
|
|
real copy of `hooks/`; the `files` field already lists it.
|
|
|
|
## 8. Next phases
|
|
|
|
1. Finish the live smoke test: the fresh-session `role` skill acceptance, plus
|
|
the bootstrap and `/compact` checks already verified.
|
|
2. Add `harness-check` and optional `role-hint` as non-blocking pre-step
|
|
injections.
|
|
3. Build `briefing` as an agent preset (read-only tools + model route).
|
|
4. SDD: change `dsh-sdd` — the `sdd` skill (async controller, reports, review,
|
|
fix-loop, ledger) plus the overlay in §5; proven by a three-task trial run in
|
|
a temporary repository. See
|
|
`openspec/changes/dsh-sdd/design.md`.
|
|
5. Enable CodeGraph MCP by default when the binary is present; update role and
|
|
project instructions to `mcp__codegraph__codegraph_explore`.
|
|
6. Port `harness-doctor` checks to DSH paths.
|
|
7. Decide the long-term home: keep `dsh/` in this repository while it shares
|
|
hooks/roles/skills, then extract a `dsh-harness` repository when the bundle
|
|
gets its own release cadence.
|