rulemux-dsh
DeepSeek Harness (dsh) plugin for rulemux.
rulemux keeps your rule files in one place and delivers them to each agent. dsh is plugin-first and
does not scan a rules directory on its own, so this plugin is the delivery: it asks the rulemux
CLI for the rules rendered for the current workspace (rulemux inject --agent dsh) and injects that
text into the session once, at the first user turn — the same rules, the same token cost as a host
that reads a rules directory natively, because it is the same renderer.
For rules, nothing is written into your workspace — no .dsh/rules, no copies, no residue to
clean up: the rules travel from your source files straight into the session.
Skills are the one exception (since plugin 0.5.0 / CLI 0.5.0). dsh has a native skills
subsystem, and skills load progressively (name+description at session start, full text only when
called by name) — so, unlike rules, they cannot be injected: they must exist as real files. The
plugin therefore runs rulemux sync --hook --agent dsh when a session starts, and copies land as
__rulemux__<skill-name>/ in <git-root>/.dsh/skills (workspace scope) and/or ~/.dsh/skills
(user scope). Your own skills are never touched, and .agents/skills (a cross-framework convention
dir) is never written. A failed sync is logged and the session continues — rules are unaffected.
Install
dsh plugin --profile <name> add rulemux-dsh
That is the whole thing — the plugin is published on npm. Installing straight from git also works
(no npm involved), which is handy for testing an unreleased change:
dsh plugin --profile <name> add "github:cq-guojia/rulemux#path:/dsh-plugin"
The host half needs no build: index.mjs is plain ESM. The details-page panel is bundled into
lib/client.js, and that artifact is committed — so a git install still needs no allowBuilds
approval (see “Build” below if you change src/client/).
Restart dsh after installing — the plugin mounts on the next start.
What you see in dsh
- Plugin list: an icon and a title, in the language of your UI (English / Chinese).
- Plugin detail page: an informational panel — the config file path
~/.rulemux/config.toml (with a copy button), what you need to edit by hand, a minimal example,
and a button that opens the project on GitHub.
It does not save anything. ~/.rulemux/config.toml is the single source of truth and you edit
it in your own editor. dsh's own settings document is not a second home for it: the official config
form can only store values in dsh's document (the active profile's Cordis patch), and a plugin on
the detail page has no session context — it cannot read a file off your disk. So the panel tells you
where the file is and what it looks like, and the editing stays where it always was.
Build (only for the details-page panel)
npm install
npm run build # -> lib/client.js (re-run and commit the artifact after changing src/client/)
npm run typecheck
The flow you get
dsh plugin --profile <p> add "github:cq-guojia/rulemux#path:/dsh-plugin"
# restart dsh → ~/.rulemux/config.toml now exists (see below)
# edit it → add your [[source]] / [[skill_source]] entries
# open ONE session → the rules are injected, the skills synced
No throwaway "chat once so it can create the config" session: readiness starts at load, before any
session exists.
At dsh startup: three steps, all or nothing
This package declares no dependencies on purpose: a hard rulemux dependency would make the
whole dsh plugin add fail whenever the registry mirror lags — an install-time failure you cannot
act on. dsh runs no install scripts either, so instead the plugin makes itself ready when it loads,
i.e. when dsh starts, and it does exactly three things. Every one of them must hold; otherwise the
session fails with an error.
- The CLI is there and can do the job.
rulemux is resolved (a dependency copy if one exists,
else PATH) and its reported version must satisfy >=0.4.2 — the first release whose config
parser accepts the line-trailing # comments that rulemux init writes and the docs use. An
older CLI (e.g. 0.3.1–0.4.1) knows dsh but then fails to parse such a config, so it must not be
trusted. Only when the version is entirely unreadable does the check fall back to whether
rulemux --help lists the dsh agent (the guard for a binary whose --version lies). A CLI that
fails is upgraded with pnpm add -g, falling back to npm install -g and finally
npm install -g --force (which overrides a rulemux binary that was copied onto PATH by hand,
the usual cause of an EEXIST), and re-checked afterwards. If it still fails, that is a
failure, not a success.
- This plugin is loaded. Nothing to check: the fact that this code runs at all is the proof.
- The config exists.
~/.rulemux/config.toml, created with rulemux init --agent dsh when it is
missing. An existing config counts as success and is never overwritten.
Because steps 1 and 3 need no workspace, they run at load; step 2 is the load itself.
Failures are loud and actionable: the error names the step that failed, carries the raw output, and
says where to fix it (then restart the session to retry). There is deliberately no "succeeded
halfway" notice — a half-ready plugin is useless, and it must not look healthy.
Asking for the rules is separate, and never fails the session
rulemux inject --agent dsh runs once per session — it needs the session's workspace, which does
not exist at load time. A non-zero exit is logged (console.error, which lands in dsh's log) and
the session carries on without an injected block.
A config with no [[source]] yet — or one whose sources do not cover this workspace — lands here
too: the CLI prints nothing and exits 0, so nothing is injected. That is a normal outcome, not a
readiness failure: it has no bearing on the three steps above.
What it does
- At load (dsh startup) → starts the three-step readiness chain, so
~/.rulemux/config.toml is there
before you need it. At agent/session-start → makes sure that chain has run before the first turn,
and starts the one-per-session skills sync.
agent/pre-step → on the first turn that carries user input, awaits the skills sync (so the first
model step sees the skills on disk), then runs rulemux inject --agent dsh and injects its stdout
as recalled material. It asks once per session and only re-asks if a later compaction provably
dropped the block — never every turn.
- Skills → one
rulemux sync --hook --agent dsh per session; real copies into
<git-root>/.dsh/skills / ~/.dsh/skills as __rulemux__<skill-name>/; a failed sync is logged,
never fatal. For rules it still writes nothing anywhere, so there is no per-workspace state to
keep in sync — and nothing left behind when you stop using it (skill copies are converged by
rulemux uninstall, exactly like every other agent).
Requirements
- Node 18+ (plain ESM) — dsh's own runtime satisfies this.
- Network and permission for a global install, only if
rulemux is missing or too old.
- A rulemux config (
~/.rulemux/config.toml) whose [[source]] entries cover the workspace; it is
created for you at dsh startup, then it is yours to fill in.
License
MIT — same as rulemux.