DeepSeek Harness Plugin

btspoony/mstar-harness#dsh

Stars ★ 62 Downloads (30d) 4,570 Category Workflow & Automation Added 2026-08-14 npm @mstar-harness/dsh

Skill-driven harness/loop engineering workflow agent plugin.

Install

# from npm (prebuilt)

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

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:btspoony/mstar-harness#path:/packages/dsh

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

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 advertised floor is Bun >=1.4.0 (dsh is Bun-hosted). That is not a Node requirement and does not ask you to install Node alongside Bun 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:

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 <name> add <spec> initializes the profile on first use (web starts from the shipped template: @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app), forwards <spec> 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.

Fresh-publish age window: pnpm's minimumReleaseAge gate can make a dsh plugin add <spec> 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 "<task>" — 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 7 model-facing 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 → fail-closed empty-binding under hard The dispatching agent's own harness role (the anti-recursion CALLER); an Assignment whose Execute as equals it is self-recursion.
roleMap Record<string, string> 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 persona channel (see LLM fallbacks integration).
rolePersonas Record<string, string> unset (bundled mirror default) mstar role id (Execute as) → persona text; the native subagent persona channel's override source — a role-matched start (one-shot start or the opt-in continuable startContinuable) merges the persona into the native request persona slot (the child embodies the role persona INSTEAD OF the deployment persona; persisted + reapplied on resume). Merge order: the request's own persona wins AS-IS (a caller-set persona is never overridden — no role merge); otherwise a non-empty entry beats the bundled harness-agents/ mirror default, an empty-string entry is treated as unset and falls through to the mirror default, and an absent entry uses the mirror default (see LLM fallbacks integration).
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 <repo-root>/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 (watermark + iteration gate + workspace-state digest) re-reads status.json / 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/<id>/snapshot.json and projects/<id>/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 / audit-plan / knowledge-doc / roles-dir writes under the harness get their artifact-specific engine lints.
  • Model-facing tools — mstar_sdd_workspace, mstar_sdd_task_brief, mstar_iteration_gate, mstar_design_md_validate, mstar_audit_validate, mstar_compound_validate, mstar_roles_validate register on ctx.tools. The mstar_iteration_gate mirror takes the v3 input snapshot_path ({HARNESS_DIR}/workflows/<id>/snapshot.json — mirror of mstar iteration gate --workflow <id>; the old status_path root input is gone with the v1 read path).
  • 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).
  • Plugin-owned command — /mstar-execution registers its own ctx.commands entry (hint {operation JSON}) beside that mirror: a closed JSON union of adopt (a canonical exec-session-v1: reference, adopted only after a read-only C1 session resume succeeds), clear, or run (a native argv launched shell-free under an overwritten execution identity, with every spoof/legacy env key removed). Identity comes from the carrying native session's own header, known leaf/subagent seats refuse on every operation before any parse or harness probe, and the payload is never steered into the model — a cooperative operator entry, not a lifecycle veto and not an OS process fence (adopt/clear write only this session's own durable binding slot).
  • Pre-step catalog row — every composed agent step appends ONE unified mstar-engine catalog message: the watermark (unified mstar version, harness dir, enforcement), the iteration phase-gate section (when a steering compass resolves) and the workspace-state digest (plan registry, open residuals, branch/policy anchors, active leases, knowledge digest, compass direction — when the workspace has a status.json). The row is digest-gated (injected once per turn, re-injected only when it changed) and shares one TTL-cached per-workspace build (catalogTtlMs, default 60 s).

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 allowed as a repair escape (hard: true, repair: true advisory) so the repairing write can land. Config soft is the only local rollback; hard gates are never a global default.

Gates

Status gate

fs/write-intent + fs/edit-intent listeners (registered prepend so they run before dsh-fs-policy) gate writes to the v3 harness coordination-document target set — the v2 root status.json, workflows/<id>/snapshot.json and projects/<id>/residuals.json — each validated by its matching engine validator (harnessDocKindOfTarget classification; the snapshot kind additionally runs the findingsCleanupGate extension per plan row that configures a mode, over the project registers), against the current on-disk document (parsed exactly once — no TOCTOU double read). The gate never throws: every decision surfaces as the mstar/status-gate advisory and the intent waterfall is delegated via next(). Warn mode (default) logs + emits on violations. Hard mode allows an already-invalid document as a repair escape (error-level log + advisory with hard: true, repair: true) — the intent waterfall carries no incoming content, so a hard veto on an invalid document would deadlock the very write that repairs it. Unexpected internal errors degrade to allow in BOTH modes with a degraded: true advisory (error-containment envelope); the corrupting write itself cannot be vetoed on this seam (see Known Limitations).

Dispatch gate

tools/pre-execute listener on the delegation tool(s): parses the payload's Assignment text and runs the engine's SINGLE dispatch-gate composition (composeDispatchGate — shape guard, validateAssignmentFields, antiRecursionPrecheck, default-branch gate, header-region enforcement; the same composition the opencode/omp/CLI bindings use, so violation codes are identical by construction) over the header region, plus the dsh-side worktree L1/L2 checks and the lease gate. The refusal channel is PreToolDecision { kind: 'deny', reason } returned without calling next(); warn mode logs, emits mstar/dispatch-gate, and delegates. Non-Assignment prompts and non-delegation tools are inert. Engine failures degrade to allow in both modes observably: the catch path emits the plugin-owned advisory with degraded: true + an error log, so a hard deployment can detect a dead control instead of a silent pass. Registered prepend so an earlier-mounted decision can never…

Lease gate

Additive beyond the opencode field set: for writable dispatches whose Assignment declares Execution mode: sdd or whose plan row is InProgress, verifyPlanExecutionLease + dispatch-context comparisons (holder, worktree_path, working_branch) run against the ACTIVE workflow snapshot's plan rows (workflows/<id>/snapshot.json — the v3 lease home; the root v2 status.json supplies the active workflows[]). Violations use the dsh-side lease.dispatch.* namespace; read-only roles skip the check entirely. A missing status.json on an sdd dispatch is NOT a silent fail-open: it surfaces lease.dispatch.unverifiable (advisory in warn, deny under hard) — the execution_lease cannot be confirmed without the status file. Non-SDD dispatches keep the degrade-allow (no lease obligation). All Assignment field reads are scoped to the engine assignmentHeaderRegion (body-quoted examples never leak into header fields).

Skill lint gate

fs/write-intent listener scoped to SKILL.md files under the configured skill roots runs the engine skill-authoring lints (lintFrontmatter + lintFiveQuestion — the CLI mstar skill lint combination) on the pre-write on-disk document. The slot is content-blind (the intent waterfall carries only (target, actor)), so: missing file = first create = pass; clean on-disk doc = silent pass; violations in warn mode = advisory + delegate; violations in hard mode = repair escape — the document is ALREADY invalid, so this write may BE the repair (error-level log + hard: true, repair: true advisory with the enforced hardBlocked verdict). Enforcement resolves like the other gates (Config override, else the iteration compass, else warn-only). The gate never throws; read failures and unexpected errors degrade to allow with a degraded: true advisory. The typed hard veto (SkillLintVetoError, code skill-lint.veto) lives on the incoming-document branch (lintSkillWrite) — see Known Limitations for its current wiring.

Workflow / ralph gate

A tools/pre-execute branch (BEFORE the subagent prompt branch) gates the workflow and ralph tool calls — the remaining model-reachable fan-out that carries no Assignment text. It matches the FIXED tool names (workflow / ralph); a renamed workflow instance is out of scope (the name guard is the fixed default). Non-workflow tools are untouched — the subagent branch owns them, semantics unchanged.

Four-tier mode (Config workflowGate, default warn): off (pass-through, no verdict row), warn (advisory-only), ask (first-seen names route through dsh's approval waterfall — {kind:'ask'}, fail-closed upstream; this gate invents no answerer), hard (policy violations veto before any child starts). The policy is the SINGLE decision point — P-b lease attribution runs FIRST and preempts P-a/P-c, then the P-a name allowlist, then P-c first-seen ask.

Policy off warn (default) ask hard
P-b: workspace has an InProgress plan without execution_lease coverage allow (gate short-circuits off) warn — allowed + advisory (workflow.lease.uncovered) + one warn warn — allowed + advisory + one warn (the ask channel is for first-seen NAMES, never the workspace red line) deny — veto before any child starts (workflow.lease.uncovered), reason cites the plan id
P-a: workflow name ∈ workflowNames (non-empty list) allow (short-circuit) allow — no advisory (P-a passes under every mode) allow — no ask allow
P-a: workflow name unknown (empty/absent list ⇒ every name unknown) allow (short-circuit) warn — allowed + advisory (workflow.name.unknown) + one warn ask (first-seen) → {kind:'ask'}; the cached decision (allow/deny) is reused afterwards — never a re-ask for a resolved name deny — veto before any child starts (workflow.name.unknown), reason names the name
ralph (no meta.name — no allowlist identity) allow (short-circuit) allow — P-a/P-c NEVER apply allow — P-a/P-c NEVER apply allow — P-a/P-c NEVER apply; P-b still applies (deny when uncovered)

Default-warn rationale. warn is the default so the gate never surprises a deployment into a hard block: it is advisory-only unless the operator opts into ask (human ask channel) or hard (veto). An empty/absent workflowNames makes every name unknown — the gate is not "allow all" by omission, but the default mode turns that into an advisory, not a block.

Interaction with Enforcement: hard. The workflow gate's mode is its OWN Config knob — the cross-cutting Enforcement: hard resolution (compass / Assignment header / Config enforcement) does NOT escalate workflowGate. A hard-enforcement deployment still runs the workflow gate in its configured mode (default warn = advisory-only) unless it also sets workflowGate: 'ask' or 'hard'; conversely workflowGate: 'hard' vetoes regardless of the cross-cutting resolution. The two must not be confused: the workflow gate closes the "ungated fan-out under Enforcement: hard" gap only when the deployment opts its mode in.

Fail-open edges (documented, never crash a compliant call). (1) Malformed args — a workflow call without a non-empty string meta.name (after control-char normalization), or a ralph call without a string objective → pass-through + ONE warn under every mode (hard included), and NO verdict row (no policy verdict was produced). A name that is only control characters normalizes to empty → malformed. (2) Unreadable status.json — the P-b status read through the contained resolver path throws → P-b is degraded for that call only with ONE warn; P-a/P-c (name-based, no status dependency) still run. The gate NEVER throws: every read is structural.

Verdict ledger rows. Every gated call records ONE durable workflow-verdict row in the agent-flow ledger (the P2 ledger plan's record path, fully contained — a failing ledger write never reaches the gate): tool (workflow | ralph), workflow (normalized meta.name) or objective, mode (never off — off short-circuits before the policy), and the verdict vocabulary ok / advisory / denied / ask (the ask verdict is the extension: a first-seen ask is itself a gated call, so its row carries ask until the approval waterfall resolves it — "one ledger row per gated call"). Violation codes come from the verdict, never guessed: workflow.name.unknown (P-a) vs workflow.lease.uncovered (P-b). Fail-open paths (malformed args / unreadable status) record nothing; calls with no resolved harness dir skip the row (same silent no-op as the dispatch record path).

P-c answer-observation seam. The gate cannot observe the ask outcome — the tool registry's serviceAsk consumes the approval result internally. The run-start observation IS the answer seam: an ALLOWED ask executes the call → the durable tool-workflow/run-start session event lands in the parent session log → the workflow-ledger consumer records the W-B2 workflow-run row AND caches allow for the run's name into the apply-scoped WorkflowAskCache. A DENIED answer produces no run → no observation → the next same-name call under ask re-asks (fail-closed — no grant evidence, never an invented allow). Cache keys are the normalized (ASCII control chars stripped) uncapped name at BOTH seams — the gate composes meta.name and the observation records runName through the SAME normalizeWorkflowName, so a control-char name (au\u0000dit) can never wedge the cache (asks once, observes under the same key), and a >1024-char name still keys on the full name (the ledger ROW display name is capped separately; the identity axis is never truncated). The cache is apply-scoped — a fresh apply (HMR reload) starts empty, so an unresolved first-seen re-asks per call until an observation (or an explicit record()) lands. A throwing cache record degrades the observation with one warn — the ledger row is already appended, the run is never affected.

P-b preemption. The lease red line runs FIRST: an uncovered InProgress plan in the calling workspace means NO writable fan-out should start children until the plan is recovered — independent of the workflow name (the same red line as the Assignment-keyed lease gate), and it applies to ralph too. Under warn/ask it is advisory-only (allowed + one warn); the ask channel never substitutes for the workspace red line.

LLM fallbacks integration

The optional dsh-llm-fallbacks plugin (installed with the second command — see Install paths) powers role-based subagent configuration — the role seeds and the adoption advisory; role persona delivery itself rides dsh's NATIVE subagent persona channel (independent of fallbacks, see below). The mstar plugin carries zero runtime AND zero type references to the package — src/ has no import of it (runtime or type); the consumed service surface is mirrored by the local structural types in src/gates/fallbacks-structural.ts, and the package itself is a dev-time-only dependency of the mstar plugin (type mirroring + the real-package test harness). dist/ names the package only in ONE string literal — the probe's loader-entry match — never an import or a type reference; the interop is a decision-point capability probe, never a module-internals read.

Capability probe

Two views over the mounted state (point-in-time reads at decision points, no cache — loader mounts entries concurrently):

  • fallbacksService(ctx) — the named cordis service (ctx.get('llm-fallbacks')) while the plugin is applied; undefined during HMR/fiber-swap windows even when the loader entry lives (the entry is declarative and outlives a fiber swap).
  • fallbacksMounted(ctx) — capability view, service-first with a loader-entries fallback: the loader entry named dsh-llm-fallbacks is present, enabled (respecting entry.disabled and group rows), and has a live fiber.

Distinct states: mounted (service applied — full capability), unmounted (no entry: the fallbacks plugin was not installed — the mstar capability degrades, never breaks), disabled (entry present but disabled/grouped — capability off), and the HMR window (entry lives, service absent — the loader fallback covers it).

Role persona delivery (native subagent persona channel)

Persona delivery rides dsh's NATIVE SubagentStartRequest.persona slot (@deepseek-ai/dsh-subagent): the plugin intercepts the ctx.subagents service READ through the cordis internal/get waterfall — the framework's documented service-read interception hook — and wraps the runtime value so a role-matched start merges the persona into the request BEFORE the child is composed — on BOTH start surfaces: the one-shot start AND the opt-in continuable startContinuable (tool-subagent backgroundMode: 'continuable'; its ContinuableStartSpec.request carries the same persona slot). The underlying SubagentRuntime object is never mutated (no monkey-patching), and the wrapping listener is fiber-scoped (an HMR re-apply unwinds and restores it, re-binding the fresh Config). Native semantics: the request persona registers the scoped deployment:persona section (order 0) on the child, SHADOWING the deployment persona for that child alone — the child EMBODIES the role persona instead of coexisting with it — is persisted in the child descriptor, and is reapplied on resume. Role identity uses the SAME engine Assignment header grammar as the dispatch gate. Persona lookup is the single personaFor chain — rolePersonas[executeAs] → bundled mirror default → skip — never gated on roleMap or on the fallbacks mounted state (persona delivery is fallbacks-independent). An explicit request persona (tool-subagent's own Config.persona) wins over the role persona — caller intent is never overridden.

Capability gate (per surface): one-shot SubagentRuntime.start REJECTS a persona request for a provider without the native persona capability (fail loud, no silent degradation — out-of-process providers ship without it), so the channel checks getProvider(name).capabilities.persona FIRST on that surface. The continuable surface is gated by the NATIVE continuable contract instead — SubagentCapabilities is documented as ONE-SHOT-scoped and continuable children are composed by the continuation manager itself, gated by provider.prepareContinuable; the channel checks that, skipping the merge for providers without continuable support (the native start fails loud on its own — the persona is never logged as delivered for a start that would reject). Either way the merge is skipped with one contained debug log and the start proceeds unchanged — never a failed dispatch.

Zero-config defaults: when rolePersonas has no entry for a role, the persona comes from the bundled harness-agents/ mirror — the repo-root agents/ shells synced by bundle-assets at build (shipped in the published tarball; package-relative resolution, so the bundle works from any launch cwd). The shell file stem is the role id; the default is its frontmatter description block scalar. A shell is eligible when its frontmatter mode is absent or subagent — the primary shell (project-manager) is never offered as a subagent persona default. A default whose description carries the interpolation hazard ({{ paired with a later }}) is warned + skipped at extraction (never a boot throw); a shell edit (mtime change) re-extracts on the next decision-point read. With the mirror absent (bundle-assets not run) lookups are config-only, and a config miss logs one debug per apply.

Seam probe (fail-loud): the channel probes the cordis internal/get seam once per apply (temporary canary listener + one controlled proxied ctx.subagents read — never ctx.get); a missing or unrecognized seam logs ONE warn — role persona channel not installed — cordis 'internal/get' seam missing or unrecognized (rolePersonas will not be merged into subagent starts) + (reason: seam-absent | wrap-skipped) — instead of failing silently. A healthy boot produces NO warn (the locked outcome table): on today's composition the apply ctx does not resolve subagents, so each apply emits one no-warn service-absent debug — cordis resolves the service in dispatch scopes, where reads are intercepted per read; probe-internal errors degrade fail-open (one debug). wrap-skipped is pinned but not reachable from the shipped apply wiring — it fires where subagents is resolvable at apply, a path covered by the harness pins. The seam name is pinned by the dedicated probe family tests/persona-seam-probe.spec.ts (via the exported PERSONA_SEAM_EVENT constant); the installed-artifact assertion arms once the release carrying the probe publishes (surface-vintage guard in tests/install-e2e.spec.ts).

Role seeds + adoption advisory

When the optional dsh-llm-fallbacks capability is mounted (the second install command — see Install paths), the mstar plugin zero-config declares the 13 mode: subagent mstar role seeds into the fallbacks seed registry: persona = the harness-agents/ mirror description (verbatim) + one mandatory-load guide line (Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.); a persona carrying the {{...}} interpolation hazard is skipped + warned, never declared. The declaration merge-preserves the currently-seeded non-mstar ids from the readback — e.g. the 5 upstream preset roles the upstream package self-declares at its own apply: upstream declare REPLACES the whole registry, so without preservation a mstar-only batch would strip preset ids of their seeded annotations (rows remain, unseeded). The declaration re-fires idempotently on every fallbacks (re-)apply (HMR/fiber swap) — never from a one-shot latch — so either boot order (presets first or mstar first) converges to the same 18-id fully-seeded registry. Boot-time convergence is a bounded retry: the provider's seed write channel binds one macrotask after its apply, so a first-attempt reject with seeds: settings service is unavailable inside that apply window is retried (3 attempts across the provider's apply window) and a transient reject converges on its own; only if every attempt ultimately fails does the declaration log exactly one terminal error, while the advisory's decision-point re-declare stays available as the retry path. No manual roles.list edit is required.

A warn-only advisory pass (logger mstar/fallbacks-advisory) runs once per apply — attempted at apply and, when the fallbacks row mounts after dsh (the loader mounts entries concurrently), once at the first subagent/start decision point. With the service present, the pass FIRST awaits the idempotent re-declare (closing the boot race) then reads the EFFECTIVE state (getEffectiveRoles) and reports, bounded to at most one warn per category:

  • missing mstar roles — an mstar id with no effective row (one warn listing them; the id set is derived from the harness-agents/ mirror — never hardcoded; no mirror → the check is skipped with one debug);
  • persona overrides — an mstar role whose row persona differs from the seed default (one warn naming them + the revert entry: the fallbacks/revert-seed gateway / the fallbacks settings-card rollback button — the operator override is retained until reverted);
  • empty personas — rows with a missing/blank persona, only when non-seeded or overridden-empty (one warn naming them);
  • legacy keys — chains, roles.default, roles.list[].label/.description, dangling roles.rules[].role references, via the applied service's own detectLegacyKeys (one warn citing its semantics);
  • declare skips/conflicts — local skips (interpolation / no-persona) + upstream skips/conflicts (code persona-source — operator override retained) merge into ONE warn; seeded-at-default is silent (one debug naming the ids).

On the loader-fallback path (no service) the structural roles.list read is preserved (missing ids / empty personas; no revert entry — no seeds surface; the legacy-keys check is skipped — never reimplemented). A row config that is absent or not an object, or an unreadable roles.list, skips the pass with one debug log. The advisory never writes the fallbacks config — the only write path is the idempotent seeds re-declare through the released seeds surface (no-delta → no settings write upstream) — never throws, and is not invoked when fallbacks is unmounted: it is a signal, not a gate.

Config surface

Key Type Default Meaning
roleMap Record<string, string> unset mstar role id → fallbacks role id. Taxonomy bridge for logging + future rule-driven interop only — unused by the persona channel by design (persona delivery is rolePersonas/mirror-sourced).
rolePersonas Record<string, string> unset (bundled mirror default) mstar role id → persona text; the persona channel's override source — a mirror default is used when a role has no entry.

Service

apply constructs ctx.dshMstar (engine-backed: validateStatus, validateResidual, findingsCleanupGate, resolveCompassEnforcement, resolveHarnessDir, readHarnessVersion, applyEnforcement). Layering: the P1 gates are co-located engine wrappers in this package importing the engine directly (same plugin, engine bundled at build time); ctx.dshMstar is the composition/test façade for inject consumers; the host adapter (below) is the host-facing facade. The engine is the single grammar for both paths. The companion entry @mstar-harness/dsh/invariant reserves package ownership with a documented no-op installer.

Host adapter

The plugin implements the engine HostAdapter contract (host: 'dsh') as DshHostAdapter, exposed as the ctx.dshHostAdapter service. Detection: the engine detectHost maps the dsh delegation tool name — ToolSignal subagent (the model-facing dsh subagent tool) — to 'dsh', evaluated after omp and before kimi/zcode/codex; hybrid sessions lose to earlier rows by fixed order. The adapter routes through the SAME validation cores as the in-plugin gates (one code path): beforeStatusWrite(path, doc) validates the incoming document when the host provides it, else the on-disk fallback (missing file = first create = pass); beforeDispatch(assignment) runs the field + branch + anti-recursion gate with the enforced hardBlocked verdict (the lease gate stays listener-side — it binds the ToolExecution session context the hook does not carry); beforeMerge(lease) is a thin wrapper over the engine validateIntegrationMergeLease (the reservation write into status.json is a P3 seam). log defaults to the dsh ctx logger mstar/host-adapter.

The frozen skill-root form for dsh (engine resolveSkillRoot('dsh', …)) is $DSH_BUNDLED_SKILL_DIR/<name>[/<rel>] — the resolver defines the canonical form used by skill-relative path resolvers (resolveAssetPath); it does NOT mount the directory. Mounting is the plugin's job (next section).

Skills mount

The mstar skills mount through the dsh skill-filesystem provider as a single canonical mount: the plugin registers its configured roots as one provider (providerName: 'mstar', includeDefaultRoots: false — isolated, it must never see the host app's own project/user skills), and the engine form above is the shared skill-root contract. Two Config paths populate it:

Path Mechanism When
Bundled default bundledSkillDir defaults to the package's OWN harness-skills/ mirror — the repo-root skills/ (19 mstar-* + pm) synced by bundle-assets at build time (gitignored), resolved package-relative (not cwd-anchored — works from any launch cwd) Published package / any deployment without an override
Custom roots skillRoots / explicit bundledSkillDir → skill-filesystem customSkillDirs / bundledSkillDir entries (explicit values win) Local development / tests / deployments with a different mirror

The packaged mirror is a single canonical mount: skill content lives once in the repo-root skills/ mirror and is synced into the package (like opencode's harness-skills/), so mstar skills stay standalone-usable everywhere. No double-loading: the opencode plugin ships the same skills in its own package, so dsh must mount them ONLY through this single skill-filesystem path.

Dev-time reality: the @deepseek-ai/dsh-skill-filesystem runtime is a peer-stub (contract-mirroring registration, no file watcher), so the mount is verified through real composition against the stub + the actual mirror skills/ frontmatter (engine lintSkillFrontmatter); real-runtime composition (real seam packages, watcher, $DSH_BUNDLED_SKILL_DIR env flow) is the deployment target, not covered by this package's suite.

Commands

The plugin registers the bundled mstar commands (omp/opencode parity surface) on ctx.commands: harness-commands/*.md — the repo-root commands/ mirror (iteration-start, iteration-drive, iteration-loop, codebase-audit) synced by bundle-assets at build time (gitignored). Each registration reads the command's name/description/input frontmatter; a declared input hint is advertised as input.hint, which flips the dsh web client's decision table from detached bare execution to a leadingInput claim — the menu pick inserts /name into the composer (command-colored token, the hint as ghost text) and the line submits only on Enter, so the user can type follow-up args (the /plan / /goal / /advisor interaction). The handler steers the command body into the receiving agent as a USER-source message (the dsh-plan-mode command precedent — source: { kind: 'user' }, so the model treats the body as a task to execute, not injected context; the dsh-commands "explicitly schedule model-visible work through the receiving Agent" path), appending the user's typed args as a ## User input section when present, and returns a success result. Registration is deferred with ctx.inject(['commands'], …) — the same optional-unit pattern as the tools — so the plugin boots without the commands service; an absent mirror (no bundle-assets run) registers nothing.

Engine seam mapping

Every engine module attaches to a dsh surface — delivered except the lint module's plan/tdd fs gates (deferred; see Known Limitations):

Engine module dsh seam Status
core (applyEnforcement, GateResult/Severity) cross-cutting veto/reject delivered (P1)
path (resolveHarnessDir) harness-dir probing + {HARNESS_DIR}/status.json target matching delivered (P1)
status (validateStatus, validateResidual, findingsCleanupGate) fs/write-intent + fs/edit-intent on status.json delivered (P1)
lease (verifyPlanExecutionLease, validateIntegrationMergeLease) exec lease: tools/pre-execute (inside the dispatch gate); merge lease: HostAdapter.beforeMerge delivered (P1 exec / P3 merge)
dispatch (composeDispatchGate, isReadOnlyAssignmentRole, parseAssignmentFields) tools/pre-execute on the subagent tool (PreToolDecision.deny to block); agent/pre-step advisory delivered (P1)
host (resolveSkillRoot, HostAdapter) engine host.ts detection row + plugin adapter (host: 'dsh') delivered (P2)
skill-authoring (lintFrontmatter, lintFiveQuestion) skill-filesystem roots + fs/write-intent on SKILL.md delivered (P2)
lint (lintSkillFrontmatter, planQualityBar, assertSddTddTriple) not wired — plan/tdd fs gates are a deferral; lintSkillFrontmatter runs only in the skills-mount test suite deferred
agent catalog first-party plugin catalog source (the anchor) + /api/mstar/engineStatus snapshot read (model-visible ⟺ logged) delivered (P2)
sdd (sddWorkspace, taskBrief) defineTool wrappers registered on ctx.tools delivered (P3)
iteration (evaluatePhaseGate, parseCompassFrontmatter) agent/pre-step + iteration gate delivered (P3)
worktree (l1PreDispatchCheck, l2PreDispatchCheck) tools/pre-execute L1/L2 (inside the dispatch gate) delivered (P3)
design-md / audit / compound / roles fs/write-intent + defineTool wrappers on ctx.tools delivered (P3)

Engine-status catalog

An advisory agent/pre-step waterfall listener appends ONE mstar-engine catalog MessageSource to every composed step — the first-party plugin arm, exactly { kind: 'plugin', plugin: 'mstar-engine', form: 'catalog' } and no other member (mirroring the dsh tool-skill precedent): the model-visible <mstar_engine_status> block renders the watermark fields — mstar version (plugin own manifest; the single-version invariant pins the bundled engine to the same version), harness dir (resolved {HARNESS_DIR}, none when absent), and enforcement (compass mode, soft / hard (compass)) — plus the iteration phase-gate section (when a steering compass + status.json resolve: iteration id, transition, all-plans-done, gate verdict + violation codes — the mstar iteration gate tool result shape) and the workspace-state digest section (when the workspace has a status.json): plans (id(status) registry), residuals (open counts by severity), branch (base → target, spec integration), policy (push policy, worktree mode, control root), leases (active plan execution leases: holder + worktree), knowledge (knowledge-index doc count + categories) and direction (the steeri

…

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.