dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
One set of rules, one source of truth: edit once, and it reaches every agent in every workspace. Same effect and same token cost as writing
AGENTS.mdby hand — never fades out, never accumulates, no symlinks, free to add and remove files.
.codebuddy/rules/, .trae/rules/, a single
AGENTS.md, an injected recall block… one coding convention has to be rewritten once per agent.You maintain one source of truth: a set of rule documents you wrote, declared in
~/.rulemux/config.toml as the tree below. rulemux sync copies them — really copies — into
each agent's native rules directory. Upper layers are inherited by the ones below them, so
one edit reaches every agent in every workspace on the next sync.
One source of truth (your own .md files, declared in ~/.rulemux/config.toml)
│
├─ Layer 1 · org-wide rules ─────────────► every agent × every workspace
│ │
│ ├─ Layer 2 · dev rules
│ │ ├─ Layer 3 · mini-app rules
│ │ │ └─ …
│ │ ├─ Layer 3 · backend rules
│ │ │ └─ …
│ │ └─ Layer 3 · client-side rules
│ │ └─ …
│ │
│ └─ Layer 2 · non-dev rules
│ └─ Layer 3 · workspace daily rules …
│ └─ …
│
└─ Layer 4 · one workspace's own rules ──► only in that workspace
rulemux sync ↓ real copies (never fades out / never accumulates / no symlinks)
workspace A ──► .codebuddy/rules/ .trae/rules/ (dsh: injected, nothing on disk)
workspace B ──► .codebuddy/rules/ .trae/rules/ (dsh: injected, nothing on disk)
… under layer 3 is a padding layer — use is expanded recursively,
so you can nest as deep as you like. The only thing not allowed is a cycle
(A uses B and B uses A), which fails at config load.The tree is built with use on [[file_group]] (inherit the layer above) plus workspace on
[[source]] (where it lands):
[[file_group]]
name = "base" # layer 1: org-wide
path = ["/rules/00-base.md"]
[[file_group]]
name = "dev" # layer 2: dev rules
use = ["base"] # inherits layer 1
path = ["/rules/10-dev.md"]
[[file_group]]
name = "miniapp" # layer 3: mini-app rules
use = ["dev"] # inherits layer 2 (and therefore layer 1)
path = ["/rules/20-miniapp.md"]
[[source]]
groups = ["miniapp"] # lands as base + dev + miniapp
workspace = ["/work/miniapp-a", "/work/miniapp-b"]
[[source]]
path = ["/rules/proj-x-only.md"] # layer 4: this workspace only
workspace = ["/work/proj-x"]
See §5.2 for the full grouping and matching syntax.
Why not "just inject with a hook"? Hook injection lands in the dynamic part of the context: it gets summarised away on compaction (fades out) or re-appended every turn (token blow-up). See design/architecture.md §一.
rulemux ships one adapter per agent, and an adapter is only installable once its rules directory and hook location have been confirmed by a real canary test. Today:
| Agent | Tier | Rules directory | Skills directory | Status |
|---|---|---|---|---|
| codebuddy | Tier-1 (real copy) | .codebuddy/rules/; user-level ~/.codebuddy/rules/ | .codebuddy/skills/; user-level ~/.codebuddy/skills/ + ~/.workbuddy/skills/ (both written) | ✅ Verified — installable (codebuddy-cn is an alias) |
| workbuddy | Tier-1 (real copy) | .codebuddy/rules/ (shared with codebuddy, canary-proven 2026-10-09) | .codebuddy/skills/ (shared with codebuddy); user-level: both dirs written as above | ✅ Verified — installable (separate app: own ~/.workbuddy/settings.json hook, no longer an alias of codebuddy) |
| claude (Claude Code) | Tier-1 | .claude/rules/ | not yet | ⚠️ registered, not verified yet — cannot be installed |
| trae (Trae CN) | Tier-1 (real copy) | .trae/rules/ | .trae/skills/ (workspace, 🔴 pending canary); user-level ~/.trae-cn/skills/ (verified) | ✅ Verified — installable (2026-10-09 canary: user-level hook ~/.trae-cn/hooks.json; the international ~/.trae build is not wired yet) |
| codex | Tier-2 (injection) | none — injects into context | not yet | ⚠️ registered, not verified yet |
| opencode | Tier-2 (injection) | none — injects into context | not yet | ⚠️ registered, not verified yet |
| dsh (DeepSeek Harness) | Tier-2 (injection) | none — injects into context | .dsh/skills/; user-level ~/.dsh/skills/ | ✅ Verified — installable; rules are delivered by a separate dsh plugin — install it with dsh plugin --profile <p> add rulemux-dsh (rulemux only renders the text with inject, and writes nothing into your workspace for rules). Skills are real copies into the native skills dirs, synced by the same plugin at session start (source-verified 2026-10-14 against deepseek-ai/deepseek-harness, verified on a real dsh 2026-10-15) |
rulemux init refuses any agent that is not verified — it will not half-install an adapter
whose behaviour has not been proven. rulemux doctor marks unverified agents with ⚠.[[skill_source]]; they are really copied into the agent's native skills
directory (as __rulemux__<skill-name>/ — your own skills are never touched). With scope = "user"
they land in the user-level (global) directories. Rules and skills both support the dual scope;
see §5.4. claude / codex / opencode do not take skills yet; trae takes skills (CN: workspace .trae/skills + user-level ~/.trae-cn/skills, workspace pending canary).AGENTS.md). It injects via the session hook and never touches your own AGENTS.md, but
it cannot satisfy the "never fades out" bar. See
design/features/hook-injection.md..dsh/skills/: it has no hook file and no rules directory, so rulemux never writes into your
workspace for rules. A separate plugin package in this repo
(dsh-plugin/) — on npm as rulemux-dsh — asks the CLI for the rules rendered for
the session's workspace (rulemux inject --agent dsh) and injects that text once, on the first
user turn. Install it the normal dsh way — dsh plugin --profile web add rulemux-dsh (or straight
from git: add "github:cq-guojia/rulemux#path:/dsh-plugin"); rulemux init --agent dsh installs
nothing — it just prints that command.
Skills (2026-10-14): dsh does have a native skills subsystem (.dsh/skills in the workspace,
~/.dsh/skills user-level — both source-verified against deepseek-ai/deepseek-harness), so the
same plugin also runs rulemux sync --hook --agent dsh when a session starts and real copies land
there as __rulemux__<skill-name>/. Nothing else is written, and .agents/skills (a cross-framework
convention dir) is never touched.
The plugin then sets itself up as dsh starts (restart dsh after installing): readiness is three
steps and all of them must hold — the rulemux CLI is resolved and its version satisfies the
plugin's minimum (an older one is upgraded with pnpm/npm and re-checked — never accepted as-is),
the plugin itself is loaded, and ~/.rulemux/config.toml exists (created for you when missing,
never overwritten). Any step that does not hold raises, so the session fails visibly instead of
quietly running without rules. Because it happens at startup, ~/.rulemux/config.toml is already
there when you go to edit it: install → restart → edit the config → open one session. Asking for
the rules is a separate concern: a failing rulemux inject is logged, and the session carries on
without an injected block. Uninstalling rulemux cannot stop the plugin (it belongs to the host), so
rulemux uninstall --agent dsh says so — and points out any leftover .dsh/rules files from the
old design, which rulemux no longer writes but also no longer deletes.rulemux is a single Go binary with zero third-party dependencies.
# a) from a GitHub Release (recommended): put the binary on your PATH
# e.g. /usr/local/bin/rulemux (Windows: rulemux.exe)
# b) from source
go install github.com/cq-guojia/rulemux@latest
# or
git clone https://github.com/cq-guojia/rulemux.git && cd rulemux && go build -o rulemux .
# c) via npm (convenience channel that puts the binary on your PATH)
npm i -g rulemux
There is no auto-update. Upgrades are manual and handled by your package manager
(go install …@latest, Homebrew/Scoop/apt later on). This is a deliberate decision — see
design/requirements.md §五.
An upgrade keeps your hooks current — by itself, with no npm install scripts. A hook command can
change between releases (0.2.0 added --hook), so rulemux fixes its own stale hook the next time an
agent runs it: the first rulemux sync after an upgrade rewrites rulemux's own SessionStart entry
into the current format. Nothing else is touched — other tools' hooks and every other key stay as they
were, and nothing is ever created. Because this lives in the binary, it works no matter how rulemux was
installed and needs no postinstall allow-list from npm.
You can also do it on demand:
rulemux init --refresh # rewrite rulemux's OWN hooks only: never installs, never creates files
rulemux doctor now reports a hook that is installed but out of date (and how to fix it). If you
moved this agent's config directory with CODEBUDDY_CONFIG_DIR, rulemux follows it — see
design/external/agent-rules-dirs.md §五.
Every other agent is “rulemux init --agent <id> installs a session hook”. dsh is the exception:
it has no hook file and no rules directory, so rulemux init --agent dsh installs nothing and only
prints the command below — a dsh plugin does the delivery, asking rulemux for the text:
dsh plugin --profile <your profile> add rulemux-dsh # npm
# or straight from git, to try an unreleased change:
dsh plugin --profile <your profile> add "github:cq-guojia/rulemux#path:/dsh-plugin"
Restart dsh after installing: the plugin becomes ready when dsh starts — it checks the
rulemux CLI (upgrading it and re-checking when it is too old) and creates ~/.rulemux/config.toml
when it is missing (never overwriting one that exists). Any step that fails raises, so a session
never quietly runs without rules. Because this happens at startup, the config file is already there
when you go to edit it: install → restart → edit the config → open one session.
On that session the plugin runs rulemux inject --agent dsh and injects the rendered rules —
nothing is written into your workspace: no .dsh/rules, no copies, nothing left behind to clean
up. (Older versions of rulemux wrote copies into .dsh/rules and the plugin read them back; that
directory is now unused — if you have one left over, delete it yourself, or run
rulemux uninstall --agent dsh which will point at it.)
# 1. Install the session hook for an agent (--agent is REQUIRED)
rulemux init --agent workbuddy
# First run also writes a sample config to ~/.rulemux/config.toml
# 2. Edit ~/.rulemux/config.toml and point `path` at your real rule files
rulemux doctor # 3. Self-check: binary/PATH, agents, config validity
rulemux verify # 4. Canary check: drop a probe and ask the agent to recite its token
# From now on every new session triggers `rulemux sync --hook --agent codebuddy` automatically.
rulemux sync --all # optional: re-align EVERY workspace recorded in the ledger, in one shot
rulemux uninstall --agent codebuddy # remove rulemux again (--yes skips the confirmation)
Where the hook goes: into the user-level host config ~/.codebuddy/settings.json (the same
place a tool like Hindsight registers itself). Install once, and it applies to every workspace —
you do not re-run init per project. When it fires, rulemux uses the current workspace (its cwd)
to match the workspace entries in your config and decides what to deliver.
Config file: ~/.rulemux/config.toml (override with --config <path> on any command).
[[source]]| Field | Meaning |
|---|---|
path | Source file(s), anywhere on disk. Single value or a list. A list shares the agents/workspace below. |
agents | Which agents receive this batch. Omitted = all supported agents. |
workspace | Which workspaces these rules apply to. Omitted / "*" / "**" / "all" = every workspace. Single value, a list, or a glob. |
scope | Where the batch lands: "workspace" (default) = each workspace's agent dir; "user" = the agent's user-level (global) dir, shared by every workspace. With scope = "user", workspace is ignored. |
Globbing follows the usual rules: * is one path segment, ** spans segments and may appear in
the middle — e.g. workspace = "/abs/**/B" matches a directory named B at any depth.
[[source]]
path = ["/your/rules/a.md", "/your/rules/b.md"]
agents = ["codebuddy"] # omit to target every supported agent
# workspace is omitted => every workspace
# workspace = ["/path/to/proj-a", "/path/to/proj-b"]
# workspace = "/abs/**/B" # glob: any depth, named B
# scope = "user" # land in the agent's user-level (global) dir instead
When you have many files, bundle them and reference the bundle by name.
# ---- File groups ---------------------------------------------------------
[[file_group]]
name = "base"
path = ["/your/rules/team-conventions.md", "/your/rules/style.md"]
[[file_group]]
name = "proj"
use = ["base"] # nests the base group (a list: ["base", "dev"])
path = ["/your/rules/project-a.md"]
# ---- Workspace groups ----------------------------------------------------
[[workspace_group]]
name = "dev"
workspace = ["/path/to/proj-1", "/path/to/proj-2"]
# ---- Use them ------------------------------------------------------------
[[source]]
groups = ["base", "proj"] # expands to every file in those groups
path = ["/your/rules/extra.md"] # mixing is allowed: groups + standalone files
agents = ["codebuddy"]
workspace_groups = ["dev"] # expands to every workspace in the group
workspace = ["/path/to/standalone"] # also allowed alongside workspace_groups
Things worth knowing:
use is a list — use = ["dev", "qa"] reuses several groups at once, and groups nest
(A pulls in B plus its own files).use is expanded recursively (for file groups and workspace
groups alike), so you can nest as deep as you like. The only thing not allowed is a cycle
(A uses B and B uses A), which fails at config load.groups with path, and workspace_groups with workspace, in the same
source. The result is the union.On every sync rulemux computes the set of prefixed files that should exist, then removes any
__rulemux__* file that is not in that set, copies/overwrites the ones that are missing or changed,
and skips the rest.
__rulemux__ + source basename (a short hash is appended on name collisions).__rulemux__ prefix in the rules directory are never touched, so your own
rule files stay safe.⚠️ Run
rulemux syncafter editing your config or source docs After you edit~/.rulemux/config.toml(adding/removing a[[source]], changingpath) or any source rule document, runrulemux sync --agent codebuddy(a barerulemux synconly touches the agents already recorded for this workspace — name--agent <id>to add one).
- If you run sync: the next new session reads the updated rules.
- If you don't: CodeBuddy / WorkBuddy still auto-sync on every session start via the SessionStart hook, but the harness fixes the rule snapshot at the start of the session (before the hook runs), so the current session still sees the old version — the update only takes effect on the second new session.
[[skill_source]], shipped 2026-10-12)A skill is a directory (SKILL.md plus optional references/ / scripts/), loaded
progressively (name+description at session start, full text on demand) — so, exactly like rules,
it can only be delivered by a real copy into the native skills directory, never by hook
injection. Add [[skill_source]] to ~/.rulemux/config.toml:
[[skill_source]]
path = ["~/my-skills"] # skill root: every child dir containing SKILL.md is one skill
agents = ["codebuddy"] # omitted = every supported agent
workspace = "*" # omitted = every workspace (workspace scope only)
# groups = ["frontend"] # reuse a [[skill_group]] bundle instead of repeating path
# workspace_groups = ["dev"] # reference a [[workspace_group]] bundle (shared with rules)
[[skill_source]]
groups = ["frontend"] # bundle several skill roots under one name
agents = ["codebuddy"]
scope = "user" # user-level (global): workspace-independent, every workspace
[[skill_group]] # bundle skill roots so several sources can reuse them
name = "frontend"
path = ["~/react-skills", "~/css-skills"]
# use = ["base"] # nest another skill group (list, may be multiple)
path, agents, workspace, scope, plus the
bundling keys groups (→ [[skill_group]]) and workspace_groups (→ [[workspace_group]]).
[[skill_group]] bundles several skill root directories (name / path / use); nesting and
cycle detection work exactly like file groups.<workspace>/.codebuddy/skills/
(shared by codebuddy and workbuddy) or <git根>/.dsh/skills/ (dsh) or <workspace>/.trae/skills/ (trae, CN,
🔴 pending canary); user scope → ~/.codebuddy/skills/
and ~/.workbuddy/skills/ (both written, so both apps see it) or ~/.dsh/skills/ (dsh) or ~/.trae-cn/skills/ (trae, CN).
Rules work the same way: [[source]] also accepts scope = "user", landing in
~/.codebuddy/rules/ + ~/.workbuddy/rules/.__rulemux__<skill-name>/; your own skills
(no prefix) are never touched; removing a source removes its copies on the next sync; sources
are only ever read.path takes more than a local dir): gh:owner/repo or a full git URL
(git@host:owner/repo.git = SSH, https://… = HTTPS) is git cloned into
~/.rulemux/skills/<owner>-<repo>/ on the first sync; npm:<package> is npm packed and unpacked
there. After the first fetch it is treated as a local dir (zero network on later syncs). Transport
(SSH vs HTTPS) is whatever the URL you wrote implies — rulemux never picks for you. Skills under the
source root are auto-discovered (bounded scan for SKILL.md, like npx skills --all), so a repo
that groups many skills (e.g. gh:anthropics/skills) works with no extra config; append #subpath
(e.g. gh:anthropics/skills#skills/docx) to point at one container or a single skill. Inspect with
rulemux skill list; upgrade with rulemux skill update <key> / --all (git pull / npm re-pack, in
place — manual only, never in the hook). Top-level fetch_timeout = "30s" caps how long a first-time
fetch may run before it warns and skips on the hook path.name to match ^[a-z0-9]+(?:-[a-z0-9]+)*$ and rejects the legacy camelCase invocation keys
(disableModelInvocation / modelInvocable / userInvocable) by dropping the whole skill —
CodeBuddy accepts both. rulemux copies skills byte-for-byte and never rewrites content, so a
skill that loads in CodeBuddy may silently not load in dsh. CodeBuddy marketplace extras
(display_name / description_zh / version / allowed-tools) are simply ignored by dsh.
dsh also finds project skills relative to the git root (nearest .git), not your cwd.rulemux init writes this file to ~/.rulemux/config.toml on first run. Everything is commented
out — nothing is synced until you uncomment an example or add your own with real paths. It is
reproduced here one-to-one so you can read the whole thing before editing your copy.
# rulemux configuration file
#
# Default location: ~/.rulemux/config.toml (or pass --config <path> to any command)
#
# At the start of every agent session, rulemux copies the "source rule files" described
# by each [[source]] below into that agent's native rules directory, using a __rulemux__
# prefix: it cleans up its own residue and never touches your own files.
#
# Each [[source]] accepts these fields (all optional, each has a default):
#
# path Source file(s); may live anywhere on disk. Both forms work:
# path = "/abs/path/to/your-rules.md" # a singl