DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

zeta987 /

zeta987/dsh-roles-zeta

Verified

Role-based subagent delegation for DeepSeek Harness: one delegate tool over a folder of role files

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@25ba079d

English | 繁體中文 | 简体中文

dsh-roles-zeta

dsh-roles-zeta gives a dsh agent one delegate tool and a folder of role files. A call names a role; the role file supplies the child's persona, model route, reasoning effort, and tool surface. It sits on the same ctx.subagents service the built-in subagent tool uses, so providers, delegation depth, continuation, and background settlement keep working unchanged. The built-in subagent and subagent_fork tools stay mounted: they let the model pick a route per call, while a role is fixed when the file is written.

Each role reaches its child as an Agent Preset: a copy of the calling agent's own preset with the role's persona and only the plugins its tools need (see Roles as agent presets). This needs dsh 0.2.1-rc.2 or later, where a delegation carries no persona or tool filter of its own.

Around the tool sit three surfaces, each mounted only where its host service exists: one slash command per role in the Web input box, a Role agents page in the Web settings, and a watcher on the role directory so an edit reaches the next call without recomposing the profile.

Install

dsh plugin --profile web add dsh-roles-zeta
# restart the host

Profiles are separate install roots, so install into each profile that should have it. github:zeta987/dsh-roles-zeta and file:<path> specs work too. On the first load the eight role files are written to $DSH_HOME/agents ($DSH_HOME defaults to ~/.dsh), and a new session has a delegate tool whose description lists them.

~/.dsh/agents
├── code-mapper-lite.md   explorer.md            log-distiller.md   triage.md
├── deep-coordinator.md   hypothesis-debate.md   reviewer.md        worker.md
└── .seeded.json

Updating

Nothing updates on its own: dsh plugin forwards to pnpm inside the profile, starting the host installs nothing, and no page checks a registry. To move to a release:

dsh plugin --profile web add dsh-roles-zeta@0.4.0
# restart the host

Name the version. update stays inside the ^0.x range pnpm wrote at install time, and pnpm 11+ hides a version published less than 24 hours ago from outdated and @latest (minimumReleaseAge); an explicit version installs right away. Role files you edited are left alone; shipped ones you never touched are refreshed on the next load.

Slash commands

Every loaded role is a command in the Web input box. Type / to see them next to /plan and /goal; /reviewer look at lib/index.js queues a turn asking the agent to call delegate with role reviewer and that text as the task, run it in the foreground, and relay the result. The delegation therefore stays in the transcript as an ordinary tool call, where /plan <message> puts its message too.

Commands follow the roster: adding, renaming, or removing a role re-registers them and the menu refreshes. A role whose id collides with another plugin's command is skipped as a command (logged) and still works through the tool. commands: false mounts none; commandPrefix: "r-" gives /r-reviewer.

The Web input box with / typed: the roles listed as commands next to the built-in ones

Settings page

Settings → Role agents lists the roster with a badge per role (shipped, yours, edited) and edits one role at a time: id, the when line, the route (with the session's allowed routes as suggestions), the effort, the tools as a checklist of what is registered right now (or "inherit everything" plus the delegation toggle), and the persona. Saving writes <id>.md into $DSH_HOME/agents, the same file you would write by hand.

Button On disk
Delete, on a role you added Removes the file
Delete, on a shipped role Leaves a disabled: true stub so the packaged copy stops loading; listed under Disabled shipped roles until restored
Restore this role Copies the shipped file back over yours
Restore the shipped roles The same for all eight; roles you added are never touched

The page is a browser module over JSON routes under /__dsh/roles-zeta/, gated by the same browser-session cookie and Host/Origin fence as the rest of the Web API. settingsPage: false mounts neither.

Settings → Role agents: the roster on top, the reviewer role open in the editor below

Role files

One Markdown file per role: frontmatter for the machine fields, body as the child's persona prefix.

---
id: reviewer
when: Deep read-only reviewer for correctness, regressions, edge cases, and missing tests.
route: deepseek-official/deepseek-flash
effort: high
allow: [read, glob, grep, web_search, web_fetch, skill]
---

Review like an owner.
Prioritize correctness, regressions, edge cases, and concurrency hazards.
Field Required Meaning
id yes Role id; the _ spelling resolves to the same role
when yes One line; the model's menu entry and the command description
route no A model id (deepseek-flash) or an exact provider/model
effort no off/minimal/low/medium/high/xhigh/max, clamped per route
allow no Tool names the child needs; the plugins that register them are kept (per plugin, see below) and the child is then masked to exactly these names; omitted inherits everything, [] keeps no tool plugin
delegation no true keeps the delegation plugins in a child without an allow list
disabled no true removes a shipped role

A role with an allow list can delegate only if the list names the delegation tools; one without an allow list only with delegation: true.

Where roles load from

Two roots, later winning: the package's own roles/*.md, then $DSH_HOME/agents. The package seeds its roster into your directory on first load and keeps it managed while you leave it alone:

What you do Next load
Nothing Shipped files follow the package; a release that changes a role updates it
Edit one It is yours and is never overwritten again
Delete one It is not seeded again, but the packaged copy still loads; remove the role itself with a disabled stub or the settings page
Add one It loads alongside the shipped roles

.seeded.json records what the package wrote. A file you drop in replaces the shipped role by id; a stub with only id and disabled: true removes it. The directory is watched, so any change is re-read after a short quiet period. seedRolesDir: false stops the package from writing into your directory; watchRolesDir: false reads it once per composition.

Configuration

- insert:
    - id: roles-zeta
      name: dsh-roles-zeta
      config:
        provider: spawn
        toolName: delegate
        backgroundMode: continuable
        maxDepth: 2

No model and no path appears here: role files name their route and the plugin resolves it. The row is host plane, so every agent in every preset sees the tool.

Field Default Meaning
dshHome $DSH_HOME, else ~/.dsh Harness home the role directory is derived from
rolesDir $DSH_HOME/agents Your role directory: seed target and override root
seedRolesDir true Write the shipped roster into rolesDir and keep it current while unedited
watchRolesDir true Reload the roster when a file in rolesDir changes
commands true One /<id> slash command per role
commandPrefix (empty) Text placed before every command name
settingsPage true Mount the settings API and page
provider spawn ctx.subagents provider name
toolName delegate Model-facing tool name
backgroundMode continuable continuable returns a durable child id; one-shot a job id
enableRunInBackground true Expose run_in_background
maxDepth 2 Delegation-depth cap for a started child
defaultRoute (empty) Used when a role names no route:
routes {} Alias table: deep: { provider: deepseek-official, model: deepseek-flash } lets roles say route: deep

How a route resolves

A route: is looked up in this order: the row's routes aliases; the session's subagent-model-selection allowed list (the models ticked in the DSH settings); the registered providers' catalogs; an inline model: { provider, model } on the role. The catalog step is what lets a headless profile resolve without any settings surface. When the allowed list is enabled it also gates the role's route: a registered model the list excludes is rejected with a message, not run quietly.

With no route named anywhere the child inherits the calling agent's provider and model, and the result says so; the plugin never picks from the allowed list by itself. A bare model id registered under two providers must be written as provider/model.

Effort is clamped onto the levels the route advertises, the higher one on a tie: deepseek-flash offers off/low/high/max, so medium becomes high and xhigh becomes max. The shipped roles all say high. A substitution is reported in the delegation result.

Roles as agent presets

A delegation request names an Agent Preset for the child to mount, and that preset alone decides the child's persona and tools. On its first call for a role, the plugin registers one preset for that role and the caller's preset:

  • Id roles-zeta/<role>@<parent preset>, for example roles-zeta/reviewer@standard, named Role: reviewer (standard).
  • Rows are the parent preset's own plugin rows, copied. The @deepseek-ai/dsh-persona row's prefix becomes the role body; its suffix and other settings stay. A parent without a persona row gets one.
  • Tool plugins are filtered by plugin row, not by tool. dsh does not record which plugin registered a tool, so the plugin carries a table of the shipped tool plugins and the names each one registers (dsh-tool-fs: read, write, edit, read_image; dsh-tool-fs-search: glob, grep; and so on). With an allow list, a tool plugin stays when it registers at least one allowed name. Without one, every plugin stays except those registering a delegation tool (subagent, subagent_fork, workflow, ralph), unless delegation: true. allow: [] keeps no tool plugin.
  • Rows the table does not know (third-party plugins, infrastructure such as dsh-skill-filesystem or compaction) are kept as they are, because another row may depend on them. A group that loses every row is dropped.
  • A per-tool mask is then applied to the child itself as it is created (tools.restrict() on its own scope). With an allow list it sees exactly those names; without one, and without delegation: true, it loses delegate, subagent, subagent_fork, workflow, and ralph. This also covers sibling tools of a kept plugin and host-plane tools no preset owns.

The preset is reused while the role file and the parent preset stay the same; editing either registers a fresh one on the next call. A role child that delegates again clones the original parent preset, so personas never stack. The log says which rows each role preset left out.

What the model sees

One tool schema per session: a roster line per role and the allowed routes in the description; parameters role, description, prompt, an optional route alias, provider/model when the session enables model selection, and run_in_background. A call resolves the role, preflights the route, registers or reuses the role's preset, and starts one child on it. A foreground call returns the child's final text; a continuable background call returns started subagent <id> and settles through the runtime's notice.

Known limitations

  • The preset still loads whole plugins; the mask hides the extras. A kept dsh-tool-fs row still mounts write, edit, and read_image for a reviewer allowed read, and host-plane tools (delegate, working_directory, the MCP resource tools, every connected MCP server's mcp__* tools) still exist outside the preset. Neither reaches the child: the per-tool mask hides every name its allow list does not give, and a role without one loses delegate and the delegation tools unless delegation: true. The mask is computed when the child is created, so an allowed tool that registers later (an MCP server that connects afterwards) stays hidden from that child, and a tool the child registers in its own scope (its structured-output tool) is never masked.
  • Role presets show up next to the shipped ones. They are ordinary Agent Presets, so the Web preset picker lists them after standard, cordis, and the rest.
  • Resuming a background child after a restart needs its preset again. Role presets are registered on first use. Messaging a role child that was started before the host restarted fails with an unknown-preset error until the same role has been delegated once from the same parent preset.
  • The tool table follows dsh 0.2.1-rc.2. A tool plugin the table does not list is kept whole, and a tool renamed in a later dsh release stops being recognized until the table is updated.
  • A role cannot change the workspace instruction chain. Every child loads the same AGENTS.md/CLAUDE.md chain as its parent.
  • A slash command is a request, not a guarantee. The model performs the delegation; the transcript shows whether it did.
  • The settings page edits files, not sessions. A running child is unaffected by a later edit; the next start reads the new file.
  • No per-role memory.
—/ 5

No ratings yet

Verified DSH bundle

Commit 25ba079dda17

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout