Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

my-workbench

English | 简体中文

Portable OpenCode + Claude Code + ZCode + DeepSeek Harness agent setup, installed into your project (or at user level with --user; ZCode and DSH are always user-level) with one command.

npx my-workbench

my-workbench copies a curated multi-agent configuration into the current project:

  • .opencode/ — OpenCode core config and native .opencode/agents/ subagents: eight specialized agents — orchestrator, explorer, librarian, oracle, designer, fixer, observer, improver.
  • .claude/ — the same specialists as native Claude Code subagents (.claude/agents/*.md). Installed identically in every target.
  • ~/.zcode/ — opt-in ZCode support (--zcode): user-level global instructions plus the specialist subagents as ZCode user agents (~/.zcode/agents/*.md). Nothing is written inside the project.
  • ~/.dsh/ — opt-in DeepSeek Harness support (--dsh): one agent preset under <DSH_HOME>/.agent-presets/my-workbench/ — the orchestrator prompt as the preset persona, plus a packaged lane plugin that owns the seven named specialist tools and a settings page for pinning each lane's model and reasoning effort. The tools are preset-scoped; the settings page needs one inert row in every DSH profile (web for dsh web, desktop for DSH Desktop), the entire profile-level footprint. Nothing is written inside the project.

OpenCode target

  • --opencode (native) — core config (opencode.jsonc) plus native .opencode/agents/*.md subagents. Requires the OpenCode app (opencode on PATH). Nothing is downloaded. The universal behavior rules (agents/disciplines.md) are appended to every agent's own prompt at assembly, so each agent carries them and the orchestrator never pastes them into a delegation brief.

Usage

npx my-workbench                    # install both .opencode/ and .claude/
npx my-workbench --opencode         # OpenCode setup only
npx my-workbench --claude           # Claude Code setup only
npx my-workbench --user             # user-level install: ~/.config/opencode/ + ~/.claude/
npx my-workbench --force            # overwrite files that already exist
npx my-workbench --dry-run          # preview without writing
npx my-workbench --zcode            # ZCode user-level setup only (~/.zcode)
npx my-workbench --dsh              # DSH agent preset only (~/.dsh)
npx my-workbench --dsh --dsh-profile desktop  # DSH preset + the desktop profile's page row only
npx my-workbench assemble [--check] # in this repo: regenerate .claude/agents/ and .opencode/

Existing files are skipped unless --force is given, so re-running is safe. A --force overwrite snapshots and restores your model settings — the model/effort/variant/thoughtLevel lines of every agents/*.md (per backend) — so your retuned pins survive upgrades. The two JSON files it deploys are seed stubs, not owned files: --force reconciles only the stub's own keys (agent in .claude/settings.json, $schema in opencode.jsonc) into an existing file and never touches any other byte of it.

Version management

Every install — and assemble in this repository — writes a small stamp file recording the version of the CLI that deployed there, one per deployment realm: my-workbench.version in the project root for project-level runs, and ~/.my-workbench.version for user-level runs (--user, or the always-user-level ZCode/DSH targets). A mixed run (e.g. npx my-workbench --claude --zcode) stamps both. The stamp is refreshed unconditionally on every run, so it always names the running version.

npx my-workbench --upgrade   # compare deployed versions with the npm latest

--upgrade compares each selected realm's stamp with the latest version on the npm registry (npm_config_registry is respected; it defaults to https://registry.npmjs.org). When a stamp is outdated it auto-upgrades: it re-runs the install as npx --yes my-workbench@latest [<targets>] [--user] --force — the redeploy overwrites the deployed files (intentional for an upgrade) and needs npm/npx on PATH. A deployment that is numerically newer than the registry latest is left untouched. --upgrade exits 0 only when every checked stamp matches the latest after the run; it exits 1 on a registry error, when no stamp exists at all (run the install first), or when the redeploy fails.

User-level install

npx my-workbench --user installs the same assets at user level instead of the current project: OpenCode assets go to ~/.config/opencode/ (its XDG config directory; global agents are read from ~/.config/opencode/agents/) and Claude Code assets to ~/.claude/ (settings.json + agents/*.md). User-level files apply to every project and are not meant for version control; on name conflicts project-level agents win over user-level ones. ZCode is always user-level and ignores --user.

ZCode target

npx my-workbench --zcode installs ZCode support at user level only: ~/.zcode/AGENTS.md (global instructions for the main agent) and ~/.zcode/agents/*.md (subagents). ZCode has no configurable main agent and no project-level subagents, so there is nothing to install inside a project.

  • The global file is agents/prompts/orchestrator.md — the orchestrator prompt drives the main agent, with the ZCode dispatch convention substituted from agents/backends/zcode/slots/dispatch.md.
  • Subagents run with injectAgentsMd: false: the global file is not injected into them, so every delegation brief must carry full context.
  • Opt-in only (--zcode or zcode) — the default targets remain .opencode/ + .claude/. Nothing is downloaded. Existing files are skipped unless --force. Restart ZCode sessions to pick up changes.

DSH target

npx my-workbench --dsh installs one DeepSeek Harness agent preset at user level: <DSH_HOME>/.agent-presets/my-workbench/ (DSH_HOME, else ~/.dsh). DSH reads presets from its home, so there is nothing to install inside a project.

  • preset.yml — display name and description for the preset picker.
  • agent.cordis.yml — the composition: the orchestrator prompt as the preset's persona, the full standard tool set, and one row naming the lane plugin.
  • lane-plugin/ — a packaged plugin the preset mounts. It owns the seven specialist tools and their prompts, so a lane's route can be pinned and persists.
  • lane-plugin-ui/ — the settings page, packaged separately and mounted by one inert profile row (see below). This is the whole profile-level footprint.

Lane plugin layout

lane-plugin/                        # preset row: ./lane-plugin/src/index.js
├── package.json                    # name my-workbench-lanes (host only, no client half)
├── src/index.js                    # host half: the 7 delegation tools + the settings namespace
├── src/roster.generated.js         # host lane fields, rendered from dsh/lanes.json
└── src/prompts.generated.js        # the 7 prompts, rendered from agents/prompts/*.md

lane-plugin-ui/                     # profile row: file:///…/lane-plugin-ui/src/index.js
├── package.json                    # name my-workbench-lanes-ui, exports "./client", dsh.client.platform "web"
├── src/index.js                    # inert host half: registers nothing at all
└── lib/client.js                   # settings page with display labels rendered from dsh/lanes.json
  • The host row's name is relative (./lane-plugin/src/index.js): a preset row's bare package name resolves from the host composition's base, not from the preset directory, so a package shipped alongside the composition would not be found.
  • The host half's two deployment imports (@deepseek-ai/dsh-tools, @deepseek-ai/schemastery) are baked into its installed src/index.js as absolute file: URLs at install time, resolved from your DSH home. That is why no dependency command is ever needed.
  • The plugin must be named by this preset only: it registers a settings namespace, and DSH refuses a duplicate registration, so a second preset mounting the same row would fail loudly.
  • Both installed manifests, and the page's own header, name the release that produced them. DSH reports each active plugin package to the model provider as {name, version} read from its nearest manifest, so package.json's version is rendered from the CLI's own version at install time rather than authored: my-workbench@0.14.0 installs my-workbench-lanes@0.14.0. The browser half has no manifest field the shell reads — the boot-graph entry accepts only id/url/rev/inject/external/immediately — so its version is written into the file header instead. assemble --check fails if a source manifest's version drifts from the CLI's, which keeps the published pair in step at release time.
  • Those three release-stamped files refresh on every run, like the version stamp, because skip-if-exists would otherwise freeze the first release's version in place forever: the two package.json files and lane-plugin-ui/lib/client.js. Re-running needs no --force for them, and the refresh is byte-identical when nothing changed, so it is a no-op on disk. Every other file keeps the ordinary skip-unless---force semantics.

Why one profile row per profile (and what it costs)

DSH discovers browser ("client") plugin halves by scanning the profile loader's own entries — an agent preset is a separate loader tree mounted under a scope, so a row inside it is never scanned, its page is never served, and clientModules.clientPath(...) stays undefined. The host half mounts and its settings namespace registers; only the page needs the profile. A DSH process composes exactly one profile, so the row is per profile, not per DSH home: --dsh maintains one marked, managed block in the cordis.patch.yml of every profile it finds, each holding a single inert row:

# >>> my-workbench lane settings page (managed block - regenerated by `my-workbench --dsh`) >>>
- insert:
    - id: my-workbench-lanes-ui
      name: 'file:///…/.agent-presets/my-workbench/lane-plugin-ui/src/index.js'
# <<< my-workbench lane settings page <<<
Profile Who composes it Maintained
web dsh web (the web GUI) yes
desktop DSH Desktop (the desktop app) yes
anything else any other DSH client or setup yes — no name is assumed
node_modules nobody: it is DSH's shared dependency root no, never

No profile name is assumed, because none of them is fixed: DSH Desktop composes a profile it names itself (desktop on the machine this was written for), and a machine can hold profiles this CLI has never heard of. The rule is structural instead — a profile is any directory under <DSH_HOME>/profiles/ that carries cordis.yml (the root entry list every profile has) or cordis.patch.yml (the user patch layer this CLI writes) — so the desktop app is covered by the same rule that covers the web GUI. Use --dsh-profile <name> to maintain exactly one profile instead.

  • The block is replaced in place on every run; your own rows and comments are never touched or reordered. --dry-run writes nothing.
  • The row is inert: lane-plugin-ui declares no dependencies at all, registers no tool, no prompt section and no service. No bundle layer changes and nothing is installed into any node_modules.
  • The page is registered synchronously from apply() and decides inside the component what to show: live controls while the lane host half's my-workbench-lanes namespace is registered, an inert placeholder otherwise. Registering after an await left the shell's ledger entry active: false and the settings panel blank, which is why the decision moved into the component.
  • A deployment that never mounts MyWorkbench therefore sees the nav entry with no controls and no write path. Once any MyWorkbench session has mounted the host half (the namespace is process-global), the same page is live from every session — it edits pins that only MyWorkbench sessions consume.
  • The component re-reads the namespace each time the section is opened, so the page needs one page reload to pick up a changed bundle, not to pick up a mounted host half.
  • Changing the lane plugin's HOST half needs a DSH restart, not just a reinstall. The preset row is imported once per process — Node's ESM cache, and the loader re-imports the SAME specifier when it re-mounts a stale composition — so --dsh --force alone leaves the running process on the old code. Worse, the stale mount's settings-namespace registration is never released (ensureStanding drops the mount without disposing its scope), so old code that registers unconditionally fails the next mount with settings namespace "my-workbench-lanes" is already registered. The shipped host half tolerates that duplicate and reads through the surviving registration; the tolerance takes effect after the next DSH restart. The settings page is a browser module and needs only the page reload.
  • When no directory under <DSH_HOME>/profiles/ is a profile (none carries cordis.yml or cordis.patch.yml) nothing is written and the exact row is printed for you to paste — the CLI never guesses at a directory name.
  • A live DSH is reported before anything is written. DSH refuses profile changes while its desktop app is running, and a running process keeps the composition it booted with, so an install against a live DSH lands on disk without taking effect. The CLI reads the process table first and says so (naming the process it matched); it does not block, because a preset-directory refresh is still worth doing while DSH runs. On a platform where the process table cannot be read, the same warning is printed — a missed warning costs a confusing no-op, a false one costs a sentence.

Delegation and per-lane routing

  • Delegation is native. The model calls subagent_explorer, subagent_librarian, subagent_oracle, subagent_designer, subagent_fixer, subagent_observer, or subagent_improver instead of naming a subagent type in a Task tool. Lanes run in the background by default and answer with a durable child id, which send_message continues — that is the session handle the orchestrator prompt talks about.
  • Each tool carries that specialist's prompt, rendered from agents/prompts/*.md, and restricts the child the way the prompt does: read-only lanes lose write/edit, observer and improver also lose the shell, and every lane loses the delegation tools, so lanes cannot spawn lanes.
  • Per-lane model and reasoning effort. Open the DSH web GUI's settings and pick MyWorkbench 赛道模型: one row per lane with a model select and a reasoning-effort select, plus 应用 / 全部改回继承 / 刷新模型目录. Writes go to the plugin's own settings namespace and persist to <DSH_HOME>/settings.yaml, so they survive a DSH restart. A fresh install is seeded from a recommended mapping shipped as the namespace's composition base layer; leave a lane on 继承会话模型 to inherit the session's route.
  • Opt-in only (--dsh or dsh) — the default targets remain .opencode/ + .claude/. It requires an existing DSH home, downloads nothing, and skips existing files unless --force. Open a new DSH session to pick the preset up.

Install, verify, roll back

npx my-workbench --dsh --force        # (re)write ~/.dsh/.agent-presets/my-workbench/
                                      # + the managed row in every profile's cordis.patch.yml:
                                      #   ~/.dsh/profiles/web/       (dsh web)
                                      #   ~/.dsh/profiles/desktop/   (DSH Desktop)
npx my-workbench --dsh --dsh-profile desktop --force
                                      # the desktop profile's row only

Order matters: install → start a new DSH session on the MyWorkbench preset (that mounts the lane host half) → reload the web page (that runs the page's gate) → check settings → MyWorkbench 赛道模型 shows seven lanes. In DSH Desktop the same page appears after a restart of the app; close DSH and DSH Desktop while installing — DSH refuses profile changes while the desktop app is running, and the CLI warns when it sees a live process. Also confirm the seven subagent_* tools are listed and that a pinned lane's child session header carries the pinned provider/model/effort.

To roll back: remove the managed block from each profile's cordis.patch.yml and delete ~/.dsh/.agent-presets/my-workbench (rm -rf). npx my-workbench --dsh --force restores both. What remains after a rollback is at most an inert my-workbench-lanes: section in ~/.dsh/settings.yaml.

Single source, assembled output

Everything is generated from one tree, agents/:

agents/
├── disciplines.md    # the universal disciplines, appended to every agent prompt at assembly
├── disciplines_cn.md # Chinese reference translation of disciplines.md (never packaged)
├── roster.json       # agent descriptions and backend frontmatter, authored once
├── prompts/          # each agent prompt body, once (attribution notice kept here, dropped when assembled)
├── prompts_cn/       # Chinese reference translations (never packaged)
└── backends/         # everything backend-specific; each backend may carry slots/*.md
                      # (per-backend text substituted for {{slot:...}} placeholders in agents/prompts/)
    ├── claude/
    │   ├── settings.json   # main-thread agent setting
    │   └── slots/          # Claude-specific prompt text
    ├── opencode/
    │   ├── slots/                # OpenCode-specific prompt text
    │   └── opencode.jsonc        # core config
    ├── zcode/
    │   └── slots/          # dispatch.md (every backend has it; not enumerated here)
    └── dsh/
        ├── lanes.json        # lane keys, tools, labels, permissions and model defaults
        ├── agent.cordis.yml  # preset composition; {{prompt:<agent>}} embeds prompts/<agent>.md
        ├── preset.yml        # preset display metadata (name/description)
        ├── lane-plugin/      # the preset-mounted HOST half (design record: docs/dsh-lane-plugin/)
        │   └── host-package/          # ESM plugin package: 7 lane tools + settings namespace
        │       ├── package.json
        │       └── src/index.js       # {{dep:<alias>}} is resolved to a file: URL at install
        ├── lane-plugin-ui/   # the profile-mounted settings PAGE
        │   ├── package.json           # exports "./client", dsh.client.platform "web"
        │   ├── src/index.js           # inert host half (registers nothing)
        │   └── lib/client.js          # browser half with a rendered lane roster
        └── slots/            # dispatch.md ({{slot:dispatch}} inside the orchestrator prompt)

In your project, .claude/agents/ and .opencode/ are assembled from it. Edit agents/, then run npx my-workbench assemble (--check detects drift) — the same workflow works in this repository and in any target project. agents/roster.json supplies descriptions and backend frontmatter, and the check rejects orphan prompt bodies. Prompt bodies may reference {{slot:<name>}} placeholders; each backend supplies their text in agents/backends/<backend>/slots/, and a slot referenced by a prompt but missing for a backend fails assembly. The DSH composition embeds {{prompt:<agent>}}; agents/backends/dsh/lanes.json drives the installed lane prompt module, host roster and settings page labels. assemble --check renders these, checks the page's dsh.client/./client declaration and browser imports, and imports its inert host half.

Attribution

Agent prompts are adapted from oh-my-opencode-slim (MIT License, © 2025) — except improver, which is written in-house. The notice sits at the top of every adapted file under agents/prompts/ — provenance is kept with the source — and assembly drops it from the prompts it generates, so no agent is given a licence statement. agents/prompts_cn/ and agents/disciplines_cn.md exist for reference only and are never packaged.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages