DeepSeek Harness 插件

dsh-subagent-registry

dsh plugin: register locally-defined custom agents (~/.dsh/agents/*.md) as callable subagents via the use_agent tool(英文原文)

跳到安装方式

来源信息

GitHub 仓库
fan56/dsh-subagent-registry
最近更新
2026年8月21日
分类
自动化与任务
GitHub stars
0
载体类型
plugin
目录证据
上游声明已找到 dsh.bundle
证据路径
package.json#dsh.bundle
核对版本
0.1.0-rc.8
上游核对日期
2026-08-20

该证据由上游目录提供。本站没有安装、运行或安全审核这个插件。

安装

默认先复制一段 Prompt,让 Agent 读 GitHub 仓库和源码;需要自己装时再切到命令。

复制这段 Prompt,发给 DSH、Codex 或其他 Agent,让它先读 GitHub 仓库和源码。

请先不要安装或执行任何命令。阅读这个插件的 GitHub 仓库、README 和关键源码,然后用清楚、直接的方式回答以下问题,帮助我判断它是否适合我的需求:

1. 这个插件是什么,解决什么问题;
2. 适合哪些用户和典型使用场景;
3. 安装后如何使用,并给出一个最小使用示例;
4. 有哪些已知限制,以及隐私、安全、兼容性或维护风险;
5. 给出“推荐 / 有条件推荐 / 不推荐”的明确建议和理由。

请区分仓库明确说明、根据源码推断和未知信息。证据不足时请明确说明,不要猜测或照抄 README。

GitHub:https://github.com/fan56/dsh-subagent-registry
插件名:dsh-subagent-registry
作者:fan56

检查来源文件

安装前先看这个插件目录里的 README 和其他文件。

文件资源管理器3 个文件
README.md来源说明 · 只读预览

dsh-subagent-registry

Register locally-defined custom agents (~/.dsh/agents/*.md) as callable subagents in dsh: the main conversation can invoke any of them by name through the use_agent tool, and each runs as a real dsh subagent with its own persona (system prompt). When a run is interrupted (error, cancellation, crash, token limit), the next use_agent call for the same agent resumes it from its saved partial work instead of restarting from scratch.

中文简介:把 ~/.dsh/agents/*.md 定义的自定义 agent(frontmatter 元数据 + markdown 正文作为 persona)注册成 dsh 可按名调用的 subagent。主对话通过 use_agent 工具点名调用;每个自定义 agent 以独立 subagent 运行,拥有自己 的 system prompt,跑在 dsh 自带的 spawn provider 上,不需要 patch dsh 本体。

How it works

  • One file per agent: <agents-dir>/<name>.md — a loose key: value

frontmatter block (name, description, model, deep, thinking, display_name, …) followed by a markdown body that is used verbatim as the child's persona (system prompt).

  • One tool: at session startup the plugin registers use_agent

(configurable toolName). The tool's static description carries the roster — every agent name plus its sanitized description — so the main model can pick an agent by name.

  • At call time the target file is re-read and parsed; the body becomes the

child's persona, the frontmatter model (provider/model route) is split into agentOptions, and the child is started through the already-assembled spawn subagent provider (the same single-instance realm dsh uses for its native subagent tool). The result is returned to the parent conversation.

Installation

Option A — add this checkout as a dsh plugin (tui profile):

dsh plugin --profile tui add ~/github/dsh-subagent-registry

Option B — npm dependency: npm i @aiwayds/dsh-subagent-registry, then load the plugin under the stable id dsh-subagent-registry in your profile's config, or mount it through a bundle patch (see cordis.patch.yml in this repo for the pattern).

Configuration

Config fieldDefaultDescription
agentsDir~/.dsh/agentsDirectory holding <name>.md agent definitions.
providerspawnSubagent provider the child runs through (reuses dsh-base's spawn).
toolNameuse_agentName of the registered tool.
leafDenyTools[] (computed default)Explicit tool-deny list installed on deep: 0 (leaf) children. Empty = computed default (every agent-spawning tool in the dsh base distribution plus toolName).
resumeautoWhen use_agent continues a prior interrupted run: auto resumes whenever one exists, opt-in only when the caller passes resume: true, off never (an explicit resume: true still overrides).

Resuming interrupted runs

Long subagent runs (>10 min) die for many reasons — API errors, cancellation, a crashed dsh process, a token limit — and re-dispatching the same agent used to mean redoing everything from zero. It doesn't anymore.

Nothing extra is logged: dsh already persists every in-process subagent child as a full session under the deployment's session store (~/.dsh/sessions/...), interrupted runs included. What was missing is the recall layer, which this plugin now provides on top of the stock mechanisms:

1. On each use_agent call, the plugin enumerates the calling conversation's prior one-shot children (ctx.subagents.listChildren, which merges the live store with session persistence) and picks the newest inactive child whose creation label matches the requested agent. 2. The child's persisted event log is classified by its last turn/end: anything other than completed (error, aborted, max-tokens, crash with no recorded turn result) marks the run as interrupted and resumable. 3. The child session is resumed (ctx.agents.resume) with the same composition a fresh dispatch applies — the current agent-file body as the persona, the frontmatter model route, and the leaf tool scoping for deep: 0 agents — and driven for exactly one continuation turn with a "continue where you left off, don't redo finished work" instruction. The original task and all partial work are already in the child's replayed context. 4. The result flows back to the parent like any use_agent result, prefixed with a provenance line naming the resumed session. If the continuation fails again, the next call simply resumes it again — each retry keeps accumulating progress.

Tool-call parameters:

ParameterEffect
fresh: trueForce a clean start, ignoring any interrupted prior run.
resume: trueRequire resuming; the call fails loudly if no interrupted run exists.

Lookups (and the resume itself) fail open: with no session persistence mounted, an unreadable log, any enumeration error, or a resume that cannot even start (e.g. a concurrent duplicate resume raced to the session id), the tool silently falls back to a fresh dispatch — resume is an optimization, never a blocker. A continuation turn that runs and fails again is different: its partial output is kept and surfaced as an error, never silently thrown away. Note that only runs dispatched with a label are discoverable; this plugin has always stamped display_name ?? agent name as the label, so pre-existing failed runs are resumable too.

deep semantics

deep is the agent's spawn-depth budget, declared in the frontmatter:

deepMeaning
0Leaf: the agent runs normally but can never start a subagent.
>= 1May start subagents. Default when the key is absent: 1.

Implementation, at use_agent execute time:

  • deep: 0 — the start request carries toolFilter: { deny: [...] } and

no maxDepth. The in-process spawn driver applies the filter as a scoped tools.restrict() in the child's creation window, so the named tools vanish from the child's tool prompt and refuse to execute — the child keeps its full non-spawn tool set but has zero spawn capability. Passing maxDepth: 0 (the old behavior) would have rejected the child's own start, since the child's absolute depth is always ≥ 1. Default deny list: subagent, subagent_fork, workflow, ralph, plus this plugin's own tool name (use_agent by default; a customized toolName is denied automatically). send_message / interrupt_agent / list_agents only address already-running children and cannot spawn, so they stay visible. Override with leafDenyTools when your deployment's tool set differs.

  • deep >= 1 — no toolFilter; maxDepth is set to the child's absolute

depth plus deep — a relative budget. Start can never be blocked by the depth check (childDepth ≤ childDepth + deep always holds), while the "deep = how many generations of subagents I may open" reading is preserved. Each subsequent delegation level enforces its own per-request caps (the native subagent tool defaults to maxDepth: 3), which acts as the outer recursion backstop.

thinking semantics

The optional frontmatter thinking key sets the reasoning effort used for the dispatched child's model calls:

ValueMeaning
off / low / medium / high / maxReasoning effort stamped onto every model call of the child.
(key absent)Nothing is injected — the child runs at the model's default effort.

Values outside this whitelist are not clamped: the agent file is marked broken at parse time (invalid \thinking\: expected one of off/low/medium/high/max, got "…") and excluded from the roster until fixed. Upgrading note: this whitelist check is fail-loud as of this version — an invalid value (including a case mismatch such as High) drops the whole agent file from the roster, so check your existing .md files before upgrading.

Mechanically the effort travels out of band: while a run is live the child session id is mapped to the declared level, and an agent/request waterfall listener stamps that reasoningEffort onto every model-call config proposed for the child — identically across the fresh-dispatch, resume, and fallback branches. Only registry-dispatched children are injected; all other agents pass through untouched.

Scope: the injection rides this plugin's in-process agent/request waterfall, so it only applies to children that actually run through an in-process spawn/fork provider. Children of remote providers (codex, claude-code, acp, …) never enter this process's agent loop; for them the declared thinking level has no effect and no error is raised.

Known residual risk: on a fresh dispatch there is a theoretical race window — the child session id is registered only after start() returns, while the child's first turn may begin running synchronously — so with very low probability the first model request goes out at the model's default effort and later requests self-heal onto the declared level. A root fix requires upstream first-class reasoning-effort support in the start request.

Adapter support for medium depends on the route: llm-deepseek routing advertises only off / low / high / max, so a declared medium fails loudly with an unsupported reasoning-effort error before any network I/O; pi-ai-style routes accept whatever efforts the model catalog advertises for the selected model.

Example:

---
name: workhorse
display_name: 牛马狗
description: "牛马狗:干活的主力……"
model: opencode-go/deepseek-v4-flash
thinking: medium
deep: 0
---
You are 牛马狗,干活的主力。……(这里写完整的 system prompt)

Default agent roster

The author's personal ~/.dsh/agents/ ships with three agents: workhorse (牛马狗, the general workhorse), oldfox (老法师, the review/audit oracle), and rubber-duck (小黄鸭). rubber-duck is the multimodal visual agent: it reads screenshots / charts / OCR text and draws plotext / mermaid / matplotlib figures, running on an image-capable model. It now occupies the role formerly filled by the removed ArtyDuck (艺术鸭) in this setup.

Usage example

A multimodal example — ~/.dsh/agents/rubber-duck.md:

---
name: rubber-duck
display_name: 小黄鸭
description: "小黄鸭:多模态视觉 agent,看图识别、OCR、画 plotext/mermaid 图……"
model: digitalvolvo/kimi-k2.7-code
thinking: max
extensions: ["*"]
---
你是「小黄鸭」——多模态视觉 agent。……(这里写完整的 system prompt)

Then ask in a conversation:

> 用 rubber-duck 看一下这个浏览器截图,描述页面状态并提取文字

The main model calls use_agent(agent: "rubber-duck", prompt: "…"), the child runs with the file body as its persona and an image-capable model, reads the screenshot, and its result comes back into the conversation.

A text-only example — ~/.dsh/agents/workhorse.md:

---
name: workhorse
display_name: 牛马狗
description: "牛马狗:干活的主力……"
model: opencode-go/deepseek-v4-flash
deep: 0
---
You are 牛马狗,干活的主力。……(这里写完整的 system prompt)

Then ask in a conversation:

> 用 workhorse 把今天的发布清单整理成表格

The main model calls use_agent(agent: "workhorse", prompt: "…"), the child runs with the file body as its persona, and its result comes back into the conversation.

Known limitations

  • deep is a per-agent relative budget enforced at each use_agent call;

it does not re-arm deeper descendants. Real recursion is additionally bounded by every spawning tool's own maxDepth (native subagent tools default to maxDepth: 3) as the outer backstop.

  • Continuable / background follow-up conversations with a custom agent

(send_message-style resumption) are v2; today every use_agent run is one-shot (a resumed run is still one continuation turn on the old session).

  • Resume matches by agent label within the same parent conversation: if you

re-dispatch the same agent for a different task after an interruption, pass fresh: true (or the resumed agent will continue the old task).

  • Resume requires the deployment's session persistence (the JSONL/SQLite

session store every standard dsh profile mounts); without it the tool silently dispatches fresh.

  • Resume candidates are matched by creation label within the parent

conversation. This plugin labels its children display_name ?? agent name; a one-shot child started by another tool (e.g. the native subagent tool) with the same label in the same conversation is indistinguishable and would be resumed under this plugin's persona.

  • tools.restrict() validates the deny list against globally registered tool

names and throws on unknown names — the default list only names tools the stock dsh base distribution always registers; non-stock deployments should tune leafDenyTools.

Publishing note (read before npm publish)

This plugin is a dsh plugin: its @deepseek-ai/* imports must resolve to the single dsh closure instance the host provides. Never declare @deepseek-ai/* in dependencies — pnpm would install a second copy of the cordis/dsh-session closure, breaking module identity and surfacing as bizarre runtime errors like Cannot read properties of undefined (reading 'prepare') in session-persistence. Keep them in peerDependencies (and devDependencies for local typecheck/build), matching @aiwayds/dsh-tui-pi's convention.

Development

npm run check    # tsc --noEmit -p tsconfig.json
npm run build    # tsc -p tsconfig.json -> lib/
npm test         # deep-semantics + display-name + resume (no LLM)

License

MIT