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
minimumReleaseAgegate can make adsh plugin add <spec>range resolution pick an older published version (without the seeds surface) for up to ~24h after a fresh publish — re-runnpx @mstar-harness/cli init --target dshafter 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 awaitrun_in_backgroundchildren. 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 withtool-subagent-controlBEFORE ending the turn. - No interaction channel —
ask_user_questionand approval prompts fail closed when no answerer is available. For unattended runs, keep a restricted permission preset (workspace-writewith approvalask, orread-onlyfor 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-modelsettings pin ofFallbacksChainfails withNO_ADAPTER(the web-profile artifact). Point the default model at a real provider, or installdsh-llm-fallbacksinto the headless profile too (note: on the published dsh 0.1.0-rc.6 the fallbacks settings integration predates theSettingsProvider.installSectionAPI, 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.ymloverrides work as documented (e.g.enforcement: hard+dispatchBindingon the mstar row); the mstar row's ownconfig: {}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-intentlisteners validate the v3 coordination-document target set — the v2 root{HARNESS_DIR}/status.json,workflows/<id>/snapshot.jsonandprojects/<id>/residuals.json— each with its matching engine validator (validateStatus= v2 root /validateWorkflowSnapshot/validateProjectRegister, the P2-fixedharnessDocKindOfTargetshape), plus the snapshot-targetfindingsCleanupGateextension per plan row that configures a mode (residuals read from the project registers). - Dispatch gate — a
tools/pre-executelistener on the delegation tool(s) validates subagent Assignment text through the engine's singlecomposeDispatchGatecomposition (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.mdwrites 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_validateregister onctx.tools. Themstar_iteration_gatemirror takes the v3 inputsnapshot_path({HARNESS_DIR}/workflows/<id>/snapshot.json— mirror ofmstar iteration gate --workflow <id>; the oldstatus_pathroot input is gone with the v1 read path). - Bundled commands —
ctx.commandsregistrations for/iteration-start,/iteration-drive,/iteration-loop,/codebase-audit(from the packagedharness-commands/mirror; each declares a frontmatterinputhint so the web client claims/nameand 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-executionregisters its ownctx.commandsentry (hint{operation JSON}) beside that mirror: a closed JSON union ofadopt(a canonicalexec-session-v1:reference, adopted only after a read-only C1 session resume succeeds),clear, orrun(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/clearwrite only this session's own durable binding slot). - Pre-step catalog row — every composed agent step appends ONE unified
mstar-enginecatalog 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 astatus.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;undefinedduring 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 nameddsh-llm-fallbacksis present, enabled (respectingentry.disabledand 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-seedgateway / 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, danglingroles.rules[].rolereferences, via the applied service's owndetectLegacyKeys(one warn citing its semantics); - declare skips/conflicts — local skips (
interpolation/no-persona) + upstream skips/conflicts (codepersona-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
…
Links
More in this category
Q00/ouroboros#integrations/dsh-plugin★ 6166
Config-only bundle that mounts Ouroboros through the DSH MCP client, exposing 36 interview, Seed, execution, evaluation, and evolution workflow tools in DSH.
loopx-project/loopx#dsh-loopx-plugin★ 6125
LoopX, a provider-neutral, local-first state kernel and control plane for long-horizon agents: keeps Goal, Todo, gate, evidence, quota, recovery, and handoff state above DeepSeek Harness, while the plugin bootstraps the CLI and skills, admits bounded same-session continuation, and adds a loopback GoalBar for the exact bound loop.
chuspeeism/dashi-taskboard#deepseek-harness★ 3273
Embeds the active installed Codex Taskboard runtime in the DeepSeek Harness sidebar, using its launcher runtime descriptor instead of a fixed port.
NanmiCoder/dsh-agent-teams★ 1883
AgentTeams multi-agent teams.
EthanYoQ/AI-Novel-Writer#dsh-ai-novel-writer★ 1241
Installs a dedicated AI novel-writing preset and workbench: revisioned local project assets, a compact side drawer, and native approval-gated single-file changes.
tong-io/tongflow#dsh-tongflow★ 1032
TongFlow film-crew studio for image, voice, music and video production: the agent writes per-asset TongFlow workflow files (.tongflow.json) that run through TongFlow plugins, with an embedded workflow canvas, a shot/character/take project layout and a manga-drama template; sessions starting with @tongflow open the Studio view.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.