btspoony/mstar-harness

dsh

技能驱动的 harness/loop 工程化工作流插件。

工作流与自动化cursor-pluginharness-engineeringknowledge-management

@mstar-harness/dsh

English | 中文

Morning Star as a first-class dsh (DeepSeek Harness) host — a cordis function plugin that mounts the mstar engine in-process, implements the engine HostAdapter (host: 'dsh'), guards {HARNESS_DIR}/status.json writes (validate + advisory; repair-escape under hard), blocks disallowed subagent dispatches when Enforcement: hard is on, lints SKILL.md writes under the mounted skill roots, mounts the mstar skills/ mirror through the dsh skill-filesystem provider (single canonical mount), and appends a durable mstar-engine catalog row (persisted-log readers also accept the legacy mstar-engine-status identity) to every composed agent step. Boot with a dsh Loader app; everything acts through the seam's refusal/advisory channels, never by patching the tools.

This plugin's runtime requirement comes from the dsh host: the runtime that host provides must expose the native node:sqlite module the mstar store requires (the capability the store gate enforces). A Bun-hosted host is not version-gated - engines.bun in the workspace manifests declares the development toolchain - and the store's Node version floor applies to Node entrypoints only. Node is not additionally required for this package.

Usage

How a dsh app consumes the plugin — install paths, configuration, what mounts at boot, and the enforcement semantics.

Install paths

The package ships as a workspace package (workspaces: ["packages/*"]) with the engine bundled into dist/ at build time (bun run build; dist is gitignored). The install path is the profile bundle, added to the shipped web profile (dsh --profile web — the ready-made web app profile, dsh web), through the dsh.bundle.patch manifest — a patch layer mounted over the dsh-base defaults. The patch also mounts ONE host-neutral MCP server row — the shared mstar-mcp connection for the whole host; a deployment that needs a pinned harness root launches it with mstar mcp --harness (mount contract: bundle/README.md).

One-command CLI entry (recommended) — npx @mstar-harness/cli init --target dsh installs the full capability in one go: it runs the two dsh plugin --profile web add installs below in order (the mstar bundle first, then dsh-llm-fallbacks), and npx @mstar-harness/cli doctor --target dsh reports each plugin row as uninstalled / disabled / mounted / drifted (drifted = the fallbacks row is installed at a version other than the pinned one; init --target dsh re-adds the pin, doctor exits non-zero until then). It is the same two-command install, orchestrated; --no-fallbacks skips the second row (and with it the seeded roles — see What you get below).

(a) Registry install (published form) — the npm package carries the built dist/ (no build step on install):

dsh plugin --profile web add @mstar-harness/dsh

(b) Local checkout install (dev) — the package checkout itself, for iterating on the plugin:

cd <repo>/packages/dsh
dsh plugin --profile web add .

dsh plugin --profile add initializes the profile on first use (web starts from the shipped template: @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app), forwards ` to pnpm in the profile directory, and reconciles the profile's dsh.profile.bundles layer list from the installed state: any dependency whose package.json declares dsh.bundle joins the layer stack. Relative specs (., file:/link:) anchor to the invoking directory, so add . runs from the package checkout; pnpm must be on PATH. A local checkout needs a prior bun run build (the prepare` script is intentionally NOT used — the monorepo builds packages explicitly, matching cli/opencode).

(c) Optional capability: `dsh-llm-fallbacks` (second command) — the role-based subagent configuration capability (see LLM fallbacks integration) is a SEPARATE plugin row and must be installed with its own command:

dsh plugin --profile web add dsh-llm-fallbacks

The two-command install is the contract — folding a dsh-llm-fallbacks row into this bundle's patch is explicitly rejected (roadmap §8.3 F4): the loader has no insert-if-absent semantics, so a same-id insert is a duplicate loader entry id boot failure (the whole dsh session fails to start), and a different-id insert mounts the plugin twice — two apply() runs with split fallback state (per-context state stores, double listeners, config-override lottery) for anyone who also installs the package directly. Layer order is the reconcile append order: dsh-llm-fallbacks lands after `dsh-base`/`llm-retry` (its hard ordering requirement) and after the mstar row. Single-command multi-activation is an upstream feature gap (reconcile dedup or insert-if-absent patch semantics), not actionable from this repo.

What you get with zero configuration — with BOTH rows installed (via the CLI entry or the two commands above), the mstar plugin declares the 13 mode: subagent mstar role seeds (derived from the bundled harness-agents/ mirror, project-manager excluded) into the fallbacks taxonomy at boot: each seeded role's persona defaults to its mirror description plus the mandatory role-loading guidance line, the seeded state stays revertible (the fallbacks/revert-seed gateway / the settings rollback button), and the runtime advisory reports missing ids and persona overrides. The seeds mechanism is B4 — the installed-deployment e2e (tests/install-e2e.spec.ts) closes the verification loop: a real init --target dsh install into a temp DSH_HOME, booted from the installed artifacts, asserts all 13 ids present in the effective taxonomy with non-empty personas. Not included: model routing, automatch dispatch, or the dsh TUI.

Role identity for delegated children needs ONE deployment-side declaration. dsh 0.2.1 delivers a child's identity by SELECTING an agent preset, so a role-matched delegation selects what rolePresets maps that role to. Declare one complete preset per role (@deepseek-ai/dsh-agent-preset row whose plugins mount @deepseek-ai/dsh-persona with the role's persona text — persona, tools and skills all live in that composition) and list its id in the plugin's rolePresets. A role with no mapping keeps the delegating agent's composition — nothing breaks, the child simply carries no role identity. See *Role identity delivery* below.

Fresh-publish age window: pnpm's minimumReleaseAge gate can make a dsh plugin add range resolution pick an older published version (without the seeds surface) for up to ~24h after a fresh publish — re-run npx @mstar-harness/cli init --target dsh after the window (or pin the version) to converge on the latest surface.

Headless profile (one-shot runs)

The plugin is not web-bound: it also mounts in the dsh headless profile (dsh --profile headless "" — the one-shot, no-GUI/no-port mode). The install path is identical, one command:

dsh plugin --profile headless add @mstar-harness/dsh

The shipped headless template auto-initializes on first use (@deepseek-ai/dsh-base + @deepseek-ai/dsh-headless), and the reconcile step appends the mstar row to the bundle stack (dsh-base → dsh-headless → @mstar-harness/dsh). Every plugin capability rides a dsh-base seam that headless inherits — status/dispatch/lease/worktree gates, the skill mount, the engine-status catalog, the harness-rules system-prompt injection, the three native tools — so the harness is fully live in a one-shot run. Launch from the repo working directory (the runner writes meta.cwd = process.cwd() and the per-workspace harness-dir probe starts there). The browser client half is web-profile-only and simply does not load.

Headless usage caveats (dsh 0.1.0-rc.6 verified):

  • One-shot turn model — the runner submits the task as one user message and exits when the agent goes idle (whenIdle()); it does NOT await run_in_background children. Foreground subagent dispatch works (a child session is created and its result reaches the parent); background QC-tri-style parallelism does NOT complete in-process — either dispatch QC seats foreground (serial wall time) or have the agent collect background results with tool-subagent-control BEFORE ending the turn.
  • No interaction channel — ask_user_question and approval prompts fail closed when no answerer is available. For unattended runs, keep a restricted permission preset (workspace-write with approval ask, or read-only for inspection) and verify the effective session permissions before starting work. Run only operations allowed within that boundary; if an operation needs approval, stop that operation and use the web profile to obtain it. Do not remove sandbox restrictions to bypass a missing approval channel. Interactive Prepare flows (grill-me) belong on the web profile.
  • Default model resolution — headless composes no fallbacks row, so an agent-default-model settings pin of FallbacksChain fails with NO_ADAPTER (the web-profile artifact). Point the default model at a real provider, or install dsh-llm-fallbacks into the headless profile too (note: on the published dsh 0.1.0-rc.6 the fallbacks settings integration predates the SettingsProvider.installSection API, so the virtual adapter does not register — this resolves with dsh ≥ 0.1.2-alpha).
  • Config via the profile user layer — profile-level cordis.patch.yml overrides work as documented (e.g. enforcement: hard + dispatchBinding on the mstar row); the mstar row's own config: {} stays neutral.

Configuration

Key · Type · Default · Meaning harnessDir · string · repo .mstarc [config] harness_dir, else per-session workspace probe (.mstar/ → .agents/ → .plans/ → plans/, from the session workspace root — never the launch cwd) · Explicit harness root; wins over engine probing. Required for repos whose harness root is not a probed name; the probe starts from the session workspace root (never the launch cwd) and STOPS there — it never walks above the session workspace, so a harness dir above it (e.g. a global ~/.mstar) is never adopted. enforcement · 'hard' \ · 'soft' · compass, else warn-only · Per-deployment override. Precedence: Config wins; else the Assignment's own **Enforcement**: hard header flag (dispatch gate only); else the repo .mstarc [config] enforcement; else the iteration compass frontmatter; else warn-only. Config / .mstarc soft are the ONLY local rollbacks — an Assignment-level soft does NOT override a hard compass. dispatchTools · string[] · ['subagent', 'subagent_fork'] · Delegation tool names the dispatch gate matches — the dsh preset's TWO delegation tools, subagent and its fork sibling subagent_fork (both carry Assignment-shaped { description, prompt } args; a toolName config may rename instances). dispatchBinding · string · unset — the caller is derived from the CALLING session · The dispatching agent's own harness role (the anti-recursion CALLER) — a ROOT-session identity. An Assignment whose Execute as equals the caller is self-recursion (dispatch.anti-recursion.self-type). The field is read ONLY when the calling session is the ROOT session (session.header.parentSession absent — in this harness the coordinating agent): there an explicit value (profile patch or settings form) wins verbatim, an explicitly blank value ('') fails closed, and an unset value reads as project-manager (an identity read, never a guess). A CHILD session — or a session the gate cannot read (the exec-less host beforeDispatch hook) — NEVER inherits it: the caller is EMPTY whatever the field says, so every Assignment-shaped dispatch fails closed with dispatch.anti-recursion.empty-binding, because a leaf must not dispatch and no configuration may lift that. Live-editable in the dsh plugin configuration form (same volatile path as roleGate). A deployment whose coordinator is NOT the root session cannot be served by this field. roleMap · Record · unset · mstar role id (Execute as) → dsh-llm-fallbacks role id. A taxonomy bridge for logging + future rule-driven interop ONLY — never consulted by the role-preset channel (see LLM fallbacks integration). rolePresets · Record · unset · mstar role id (Execute as) → deployed agent preset id; the role-preset channel's only payload source. A role-matched start selects that preset on the activation request (SubagentStartRequest.agentPreset), and dsh mounts it as the child's composition — persona (via @deepseek-ai/dsh-persona), tools and skills — replaying the selection on resume. The deployment declares one COMPLETE preset per role: a preset is a whole composition, never a persona-only patch. Merge order: a request that already carries agentPreset (the delegation tool's configured default, or the model's own agent_preset choice) wins AS-IS; otherwise the role is read from the Assignment header and a role with no entry stays silent. An empty/whitespace id fails Config validation at plugin mount. The selection is skipped (one contained debug log, start unaffected) when the provider does not support independent selection (capabilities.agentPreset false, or a provider that inherits its parent's conversation) or when the composition mounts no agentPresets registry (the web and headless bundles mount @deepseek-ai/dsh-agent-preset-registry; the base and SDK bundles do not). Configured ids are validated once per apply — an undeclared id gets one warning instead of surfacing later as a failed start. roleGate · 'off' \ · 'warn' \ · 'hard' · 'hard' · Dispatch-role normalization at the subagent start seam (see *Role identity delivery*). An explicit declaration — an Execute as: / Act as: / Role: field line, or a backticked/bold already-declared role id, both IN THE ASSIGNMENT HEADER — is rewritten into the canonical Execute as: header before the preset merge, so both role consumers bind it. hard refuses a start with NO reliable declaration, a start naming two or more DISTINCT roles (the message names the cause and the ids, the exact fix, and the escape hatch), a role field that IS present but unusable (empty, or not a lowercase role id — the message names the raw value and the accepted shape), and a canonical declaration that exists only inside a fenced code block (the header read is fence-unaware and the seam still binds that quote); warn logs ONE advisory per apply and starts unchanged; off skips the refusal only — a single explicit declaration is STILL normalized. Exemptions are judged at the child's EFFECTIVE start directory (an absolute cwd as-is, a relative one resolved against the parent's CURRENT directory — the working-directory projection the runtime itself resolves against, not the immutable header.cwd — and an omitted one inherited from it): a directory that resolves no {HARNESS_DIR} passes silently, as do prompts that already declare a usable role, requests carrying agentPreset, and unknown providers. This is one of the plugin's TWO LIVE-editable settings fields (roleGate and dispatchBinding): saving a different mode in the dsh plugin configuration form writes the profile patch and takes effect on the NEXT subagent start (no reload); every other field in this table is read once at apply and needs a reload. skillRoots · string[] · unset (no custom-root registration) · Additional skill roots registered with the dsh skill-filesystem provider (customSkillDirs semantics — scanned before user roots). Dev-time: the mirror /skills absolute path. bundledSkillDir · string · packaged harness-skills/ mirror (package-relative) · Bundled skill root registered with the dsh skill-filesystem provider (bundledSkillDir semantics — scanned last, trusted). Defaults to the package's OWN harness-skills/ mirror (synced by bundle-assets; gitignored) — package-relative, NOT cwd-anchored. An explicit value wins. catalogTtlMs · number · 60000 · Pre-step catalog cache refresh interval (ms): how often the per-workspace unified mstar-engine catalog row (iteration gate + workspace-state digest) re-reads the store.db execution authority / the compass / the knowledge index. The hot path is a timestamp compare + cache hit between refreshes; a mid-session plan/compass/residual change lands within one interval. workflowGate · 'off' \ · 'warn' \ · 'ask' \ · 'hard' · 'warn' · Workflow/ralph gate mode (see Gates → Workflow / ralph gate). off = pass-through with no verdict row; warn = advisory-only; ask = first-seen names route through the approval waterfall (P-c); hard = policy violations veto before any child starts. Default warn changes NO hard behavior — the gate is advisory-only unless the deployment opts into ask/hard. workflowNames · string[] · unset · Workflow name allowlist (P-a): meta.name values treated as KNOWN by the gate. Empty or absent ⇒ every name is unknown (documented — the gate is NOT "allow all" by omission). Ralph calls carry no meta.name — P-a never applies to them. Recommended for the mstar read-only fan-out path: ['mstar-qc-tri', 'mstar-audit-fanout', 'mstar-pr-seats'].

Recommended operator overlay. The three recommended workflowNames above cover the mstar read-only fan-out path (plan QC tri, large-repo /codebase-audit, /amazing-pr-review deep); listing them is an operator overlay in the profile layer, never an mstar default. With the shipped empty list every name is *unknown*, which under the default workflowGate: 'warn' is one workflow.name.unknown advisory the run survives. A production deployment that also wants unknown names vetoed sets workflowGate: 'hard'; the shipped default stays 'warn'.

bundledSkillDir defaults to the package's OWN harness-skills/ mirror (see Skills mount) — an explicit Config value still wins. A relative override remains cwd-anchored (skill-filesystem join() semantics against the dsh process cwd), so deployments overriding the default should pass an absolute path in the profile layer (see bundle/README.md).

Composed row set

The profile bundle composes the following rows — the registry rows come from the @deepseek-ai/dsh-base layer, and this bundle's patch inserts the mstar row over them with neutral defaults (the row set the full-app e2e fixture boots):

- name: '@deepseek-ai/dsh-skill'   # skill registry (ctx.skills) — dsh-base row
- name: '@deepseek-ai/dsh-tools'   # tool registry (ctx.tools) — dsh-base row
- name: '@deepseek-ai/dsh-commands' # command registry (ctx.commands) — dsh-base row
- name: '@mstar-harness/dsh'       # this bundle's patch insert (config: {} — plugin defaults apply)

The registry rows mount before the plugin so ctx.skills / ctx.tools / ctx.commands exist when the mstar gates, seam tools, and bundled commands register.

What the plugin does when mounted

  • Status gate — fs/write-intent + fs/edit-intent listeners validate the v3 coordination-document target set — the v2 root {HARNESS_DIR}/status.json, workflows//snapshot.json and projects//residuals.json — each with its matching engine validator (validateStatus = v2 root / validateWorkflowSnapshot / validateProjectRegister, the P2-fixed harnessDocKindOfTarget shape), plus the snapshot-target findingsCleanupGate extension per plan row that configures a mode (residuals read from the project registers).
  • Dispatch gate — a tools/pre-execute listener on the delegation tool(s) validates subagent Assignment text through the engine's single composeDispatchGate composition (field gate, anti-recursion precheck, default-branch gate — opencode/omp/CLI parity, so violation codes are identical by construction) plus the dsh lease gate and worktree L1/L2 checks.
  • Skill-authoring lint — SKILL.md writes under the configured skill roots run the engine skill-authoring lints (lintFrontmatter + lintFiveQuestion).
  • Seam lints — DESIGN.md / review-report ({REVIEW_DIR} = {HARNESS_DIR}/reviews/) / knowledge-doc / roles-dir writes under the harness get their artifact-specific engine lints.
  • Model-facing tools — exactly three tools register on ctx.tools, and only for what the MCP surface cannot do: `mstar_plan_bind` (the durable-slot producer — the only writer of the dsh-local executionBinding slot and its adoption receipt), `mstar_audit_validate` (the review-report credential scan, which no mstar_audit action publishes) and `mstar_execution_clear` (no shared execution.clear command exists). Every other runtime capability rides the MCP families, one tool per family with an action argument. Store-level commands (store.*) are the exception on purpose: they move the whole store, so they stay CLI-only and their family members are published with transport: "cli". The membership rule and each tool's unique contribution: § Native command surface.
  • Bundled commands — ctx.commands registrations for /iteration-start, /iteration-drive, /iteration-loop, /codebase-audit (from the packaged harness-commands/ mirror; each declares a frontmatter input hint so the web client claims /name and waits for the user's follow-up args instead of executing immediately; handlers steer the command body + user args into the receiving agent).
  • Native execution binding path — the durable execution binding is adopted automatically by a successful native mstar_plan_bind (both bind arms, each after the engine's read-only C1 session resume succeeds) and cleared on demand with the mstar_execution_clear tool: identity comes from exec.agent.id (no parameters, nothing model-choosable), the clear preserves the session's own user selection and exclusion floor, and an unbound session or an unreadable binding record gets a named refusal with its recovery instead of a silent write. There is no slash command on this path — to bind or clear an execution session an operator uses exactly these two native tools.
  • Pre-step catalog row — an advisory agent/pre-step listener appends ONE unified mstar-engine catalog message carrying ONE delta line (workflow / non-Done plans / branch anchors / phase gate verdict when a steering compass resolved) plus the workflow warning disclosure when the selection carries one. The residuals rollup, branch policy, spec-integration anchor, row scopes, knowledge digest, compass direction and agent-flow ledger are payload-only (panel + engine authorities) — § Engine-status catalog. Emission is change-driven per session+workspace — first emission, then only when the rendered row or the panel-relevant payload changes; the start of a turn is never a trigger, and a workspace with no resolved {HARNESS_DIR} composes no row. The build behind it shares one in-memory TTL cache (catalogTtlMs, default 60 s); the newest per-session emission persists in the control-root store.db (dsh_status_emissions), not a file cache.

Enforcement semantics

Warn-only by default: gate violations log and emit advisory events (mstar/status-gate, mstar/dispatch-gate, mstar/skill-lint, seam advisories) and the action proceeds. Enforcement: hard — from the iteration compass frontmatter, the Assignment header, or the plugin Config (enforcement: hard) — escalates violations to a real veto/deny through the cordis refusal channels: subagent dispatch returns PreToolDecision { kind: 'deny', reason } without delegating; status/skill-lint writes are never hard-vetoed because the intent waterfall is content-blind — an already-invalid document is