← 返回列表
未验证
为子代理指定模型与推理强度并实时纠偏
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/10 · 已提供中文文档
一个极简、有主见的 DeepSeek Harness 子代理:由你选择子代理可以运行哪些模型以及以何种推理强度运行,并且修正会在下一步到达正在工作的子代理
综合分
30.2
GitHub 分
30.2
用户评分
—
★ Stars
1
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add dat-lequoc/dsh-opinionated-subagent该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-llm@deepseek-ai/dsh-settings@deepseek-ai/dsh-subagent@deepseek-ai/dsh-tools@deepseek-ai/schemastery用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-subagent-model
一个极简、有主见的 DeepSeek Harness subagent:由你决定子代理可以运行在哪些模型上、以何种推理强度运行,并且修正会在工作子代理的下一步到达,而不是等它这一轮结束后才到达。
它做什么
- 强制指定子代理的模型。 subagent 接受一个必填的 model 参数,其可选值就是你的允许列表。子代理绝不会悄悄继承对话所用的模型。
- 强制指定子代理的推理强度。 强度由你按路由设置,而不是由发起委派的代理设置——它没有强度参数,也无法继承你的强度。
- 在花费任何资源之前就拒绝错误的强度。 配置的强度会在任何 provider 调用之前,针对该确切模型所公布的集合进行校验。
- 在运行中的子代理的下一步进行引导。 随附的 send_message 会排在整个当前轮次之后;针对进行中工作的修正可能晚几分钟才到达。而这个工具会加入正在进行的轮次。
- 在记录中显示路由。 每次 subagent 调用都会渲染出它运行所用的模型,因此你无需翻阅会话日志来确认。
- 可在 UI 中配置。 设置 → 插件,强度下拉菜单会根据每个模型实际支持的内容填充。
- 在你选择启用之前保持中立。 安装后,它的行为与随附工具完全一致;当你编辑允许列表时,强制才开始生效。
每一部分都是可选的,并按行组合:可以只挂载委派前端,只挂载引导用的 send_message,或两者都挂载。
为什么
一个在没有 agentOptions 的情况下启动的子代理会继承其父代理的路由:resolveChildAgentOptions 会先展开父代理的 provider/model,而随附的 @deepseek-ai/dsh-tool-subagent 只接受 { description, prompt }——它没有为调用方提供任何选择方式。因此,工作代理会悄悄运行在主代理所用的任何模型上。
本插件替换了那个前端。model 参数是必填的,其 enum 就是你配置的允许列表,并且所选路由及其配置的强度会被固定到子代理上。
安装后在你明确表态之前不会改变任何东西
允许列表初始预置了一个条目 inherit/current,它明确跟随调用轮次自身的模型和强度。因此全新安装的行为与随附工具完全一致,如果你改变主意,也没有什么需要撤销的。
强制是选择性启用的:添加你想要的路由,并移除 inherit/current,即可让继承变得不可能。当它存在时,模型仍然可以指定它,所以它是一种许可,而不是回退——无论哪种情况,工具调用都会可见地记录子代理运行在哪条路由上。
inherit/current 解析的是当前轮次所记录的路由,而不是会话创建时的模型,因此会话中途切换模型也会被遵循。
它如何强制指定路由
harness 将这两个事实分开处理,因此插件使用两种机制:
| 事实 | 机制 | 是否持久? |
|---|---|---|
| provider + model | SubagentStartRequest.agentOptions,其优先级高于继承的父路由 | 是——无需桥接 |
| 推理强度 | 在子级自身上下文的 agent/created 边界上调用 installModelSelection | 预留是内存中的;子级的第一个 request/header 成为权威 |
agentOptions 没有 effort 字段——effort 只能通过 agent/request 瀑布流到达请求。lib/effort.js 使用 harness 自有的 installModelSelection 原语(与 Web 模型选择器使用的是同一个),这也能让提示词中的 {{model}} 插值和记录的 request/header 与实际发送的内容保持一致。
agent/created 是一个同步发布边界,因此选择会在子级能够组装提示词或发出请求之前就被安装好。
安装
dsh plugin --profile web add /path/to/dsh-subagent-model
这只会挂载宿主部分:即拥有你的允许列表的设置命名空间。它不注册任何工具。
工具行属于某个 agent 预设,因为委派工具是按 agent 组合的,并且必须替换随附的那一行,而不是与之并列——两行注册相同的 subagent 名称会冲突,而保留随附的那一行会让模型有办法绕过此策略。
在你的预设的 agent.cordis.yml 中,替换 tool-subagent 行:
- id: tool-subagent
name: dsh-subagent-model/spawn
config:
provider: spawn
toolName: subagent
backgroundMode: continuable # or one-shot
maxDepth: 3 # or provider-managed
配置
设置 → 插件 → 子代理模型。 该卡片会列出你允许的路由,每条路由都有一个 effort 下拉菜单,其选项来自该确切模型所宣称支持的内容,还有一个由实时模型目录提供的“添加路由……”选择器。编辑会在本地暂存,并在保存时通过带修订围栏的设置作用域提交,因此半途而废的编辑永远不会到达正在运行的 agent。横幅会明确说明你当前是继承还是强制。
两部分都随这一个包一起发布:安装它就会把卡片放到设置页面上。插件选项卡只渲染已注册卡片所声明的命名空间,因此没有可回退的通用 schema 驱动表单。
等价地,在 ~/.dsh/settings.yaml 中:
dsh-subagent-model:
routes:
- kiro/claude-opus-5
- deepseek/deepseek-chat
- inherit/current # remove to forbid inheriting the conversation's model
efforts:
kiro/claude-opus-5: high
deepseek/deepseek-chat: provider/default
- routes —— 完整的允许列表,写作 provider/model-id,外加可选的 inherit/current 条目。只有第一个 / 会拆分 provider 路由,因此模型 id 本身可以包含斜杠。默认为 [inherit/current]。
- efforts — 每条路由的 effort。映射中缺失的路由,或设置为 provider/default 的路由,不会发送显式 effort——而对于 inherit/current 来说,这意味着沿用调用轮次自身的 effort。任何其他值都必须是该确切模型所声明的 effort;未声明的 id 会在任何 provider 调用之前被拒绝,而不是被钳制。在 inherit/current 上显式指定 effort 会覆盖继承的 effort,因此你可以沿用模型但固定 effort。
两者在每次工具调用时都会被读取,因此编辑会在下一次委派时生效,无需重启。
值得了解的行为
- 空允许列表会注销该工具。 它绝不会回退到父级的模型——静默继承正是此插件存在所要防止的行为。提示词部分仍会陈述该规则,以便模型能够解释拒绝原因。
- 不再能解析的路由会被跳过并发出警告,其余路由继续工作。如果没有任何路由能解析,该工具会被注销。
- 模型无法选择 effort。 没有 effort 参数,子级也绝不会继承父级的 effort。
- 设置会在 execute 内部重新读取,因此一步之前捕获的 schema 无法授权你此后移除的路由。
- 前台调用被标记为并发不安全,因为它们共享一个与父级匹配的 effort 预留槽位。
- 每个具名 provider 都会在其路由被解析之前先被列出。 resolveModelInfo 不需要自行发现能力:适配器可以从一个仅由 listModels 填充的缓存中回答它,并在该缓存为空时报告一个静态回退列表。如果不先列出,provider 真正接受的 effort 会被当作未声明而拒绝。只有你的允许列表指定的 provider 才会被列出,因此委派绝不会唤醒无关的适配器。
配置参考
| 字段 | 默认值 | 含义 |
|---|---|---|
| provider | spawn | 用于启动子级的 ctx.subagents provider |
| toolName | subagent | 面向模型的工具名称;必须在活跃工具中唯一 |
| backgroundMode | continuable | continuable 返回一个可通过 send_message 访问的持久子代理 id;one-shot 默认等待 |
| persona | — | 可选的每个子级 persona,遮蔽部署 persona |
| maxDepth | 3 | 子级递归上限,或 provider-managed 表示不发送任何限制 |
查看子代理运行在哪个模型上
转录中的每次 subagent 调用都会渲染一行:一个状态点(运行中 / 已完成 / 失败)、单词 Subagent、子级运行所在的路由,以及该调用的简短描述。
它是只读的,不添加任何逻辑。路由就是该工具已经要求的 model 参数,因此不会记录或计算任何新内容。参数无法读取的调用——仍在流式传输、被中断,或由随附的前端发起(该前端没有 model 参数)——会渲染普通摘要,并且不会声称任何模型,而不是猜测一个。
该行声明了 subagent 工具名称。键控工具视图会替换通用行,而不是对其进行装饰,因此这是对该名称的一次有意接管;其他所有工具都保留其随附的渲染方式。在 spawn 行中设置不同的 toolName 意味着调用会回退到通用行。
引导正在运行的子代理
lib/control.js 是可选的 send_message 替代品。将其挂载在随附的控制行所在的位置:
- id: tool-subagent-control
name: dsh-subagent-model/control
随附的工具将每次投递都通过 ctx.subagents.followup() 路由,后者会调用 Agent.followup(),因此目标是 next-turn。对于正处于回合中途的子代理——每一步运行一个工具——消息会被接受,然后等待整个回合结束,因此针对进行中工作的纠正会在该工作完成后才到达。在一次真实会话中测量:一个子代理每回合执行一个 bash 步骤数到十,一次引导在 +7.4 秒被接受,在 +54.9 秒才被认领,停滞了 47.5 秒。
此版本会对正在运行的直接子代理调用 Agent.steer(),其目标是 next-step,并在下一个步骤边界被消费。其他所有情况都原样委托给原生服务:空闲、等待或不存在的子代理(它负责唤醒和冷恢复),以及任何权限不匹配(它负责权威拒绝)。所有权核算和结算不受影响。
结果会报告实际运行的是哪条路径,即 delivery: 'next-step' 或 'next-turn',因此模型可以判断纠正是否加入了当前回合。
与结算竞争的引导不会被静默丢失:disposal 会清空收件箱,因此如果子代理在同一 tick 内离开注册表,投递会回退到原生路径。
路由
| 路由 | 用途 |
|---|---|
| GET /subagent-model/catalog | 设置卡片读取的模型目录:每条路由及其原生输入模态和所宣称的推理努力,外加 inherit/current 条目。通过一个注入 webServer 的嵌套插件注册,因此该包仍可在没有 web 服务器的配置文件中挂载。 |
测试
npm test
51 个测试,全部通过。policy.test.mjs 和 reservations.test.mjs 无依赖——路由、effort 和 reservation 匹配规则是纯函数,不需要测试框架。integration.test.mjs 将工具挂载到真实的 Cordis 上下文上,使用真实的 ToolRuntime/SubagentRuntime,并针对一个捕获 provider 断言构建出的 SubagentStartRequest 携带了所选路由,包括验证预置的 inherit 路由能复现出厂行为。defaults.test.mjs 锁定行为中性的预置值。client-card.test.mjs 在没有浏览器的情况下测试浏览器卡片——它提供一个模块加载器和一个记录元素树的 React stub,然后断言 slot 注册、Save 之前不写入任何内容、Save 先写 efforts 再写 routes,以及移除的路由会连同其 effort 一起被移除。它还逐一核对卡片中命名的每个主题 token 是否存在于 Theme provider 发布的集合中,因为一个凭空捏造的 token 无法大声报错——CSS 会回退到字面量 fallback,所以一个拼写错误会让卡片渲染得无法阅读,而不是报错。依赖测试框架的测试套件在这些包无法解析时会自动跳过。
实时端到端检查
tests/live-headless.patch.yml 运行真实环境:它禁用 profile 中出厂的 tool-subagent 行,将此外挂挂载到其位置,并将 PARENT 固定到一个不在 allowlist 上的路由,因此子代理到达所选模型就证明该路由是被强制指定的,而非继承的。
dsh plugin --profile headless add /path/to/dsh-subagent-model
dsh --profile headless --patch tests/live-headless.patch.yml \
"Use the subagent tool once with model antigravity/gemini-3.7-flash and prompt 'name your model'."
在真实运行中已验证:当 parent 位于 unlimited/claude-sonnet-5,且 allowlist 在 high 下强制指定 antigravity/gemini-3.7-flash 时,子代理的持久化 request/header 记录了 provider: antigravity、model: gemini-3.7-flash、reasoningEffort: high——因此 effort 桥接到达了实际请求,而不仅仅是工具的标签——并且子代理报告自己为 Gemini 3.7 Flash。命名一个用户已移除的路由会在执行前被 schema enum 拒绝。
parent 路由在 patch 层中被固定,但 ~/.dsh/settings.yaml 中的 agent-default-model 块优先级更高:将其设置为相同的路由,或将其移除,否则运行会以 provider 设置所指定的路由启动。
已设计,未构建
docs/refork-spec.md 规定了 refork_subagent:将停滞或模型错误的子代理的整个对话在不同的路由上继续,方法是将它已完成轮次的历史复制到一个新的子代理中,而不是在一个并非产生该历史的模型上恢复旧的子代理。
这份规格已经足够完整,可以直接据此实现——每个机制都在当前源码中有引用,关键假设已在真实卡住的子进程上实测,两个未验证的风险也已标注。在要求实现之前,有两点约束值得了解:在回合中途被杀死的子进程会丢失该未完成的回合,因为种子必须在 turn/end 处结束;而将带种子的子进程与可继续模式结合,是已发布的 fork provider 刻意避免的组合,因此需要先进行实测。
已知限制
- effort 预留是内存中的。 在子进程创建与其首次请求之间发生进程重启会丢失预留的 effort,该子进程会回退到其路由的 provider 默认值。路由本身是持久化的,因此这不会静默更改模型。
- lib/client.js 是手写的惰性 CJS 工厂。 该仓库的 tsdown 客户端预设未发布,因此树外包必须自行复现该产物格式。因此该卡片直接使用 React.createElement 并自行绘制其界面外壳——客户端 bundle 纯净性门禁会拒绝跨插件的值导入,因此它无法复用已发布的卡片组件。
- 该卡片需要 catalog 路由才能提供选项。 没有 Web 服务器时,设置命名空间仍可通过 YAML 工作;选择器和 effort 下拉菜单只是空的。
- 每个 provider 一行工具。 两行共享同一个 toolName 会在注册时冲突,这是有意设计的。同作者(dat-lequoc)的其他插件
扫码进群