@deepseek-ai/dsh-tool-subagent 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/subagent/tool-subagent/README.md
5
+ README.md: 6ec313b3b97f0ffa7488025d4314b1c6231a6f6a
6
+ README.zh.md: 1fd88363b3ade9d57c194580f81295eed139ac50
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @deepseek-ai/dsh-tool-subagent
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ The model-facing delegation tool over one configured `ctx.subagents` provider. Changing the provider changes transport without changing the execution contract.
6
+
7
+ ## Provider selection and lifecycle
8
+
9
+ Each plugin instance binds one `provider` to one `toolName`; the model receives no provider selector. Load another distinctly named instance to expose another transport. The tool registers only while its provider exists, avoiding sibling load-order and provider-reload dependencies. Its description follows `provider.inheritsParentContext`: fresh children require standalone prompts, while forked children already see completed parent turns.
10
+
11
+ A foreground call passes the execution signal through startup and execution, awaits `run.result`, and always awaits `run.dispose()` before returning. Only `completed` returns the canonical `{ kind: 'foreground', runId, output: JsonValue[] }`, rendered as the same final text; abort, refusal, token limit, and other failures become errored tool results without partial output. If result collection and disposal both reject, the errored result preserves both diagnostics.
12
+
13
+ With `run_in_background: true`, `backgroundMode` selects the route. `one-shot` registers a plain parent-owned Task and returns canonical `{ kind: 'background', taskId }`, rendered as `started background subagent task <id>`, even when the provider supports continuable children; generic task tools own its later status, collection, cancellation, and notices. `continuable` requires a provider with the `prepareContinuable` capability, calls `ctx.subagents.startContinuable()`, and returns `{ kind: 'continuable', subagentId }`, rendered as `started subagent <childId>`. The continuable route resolves at inbox acceptance: the child owns its own turns from there, so this call neither waits for nor collects a result, and the child does not report back — its transcript by that id is the source of its output, and the optional global `send_message` tool sends it more work. Starting continuable work does not require `send_message` to be loaded. See the [background subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md), the [continuable subagents Agent Note](../../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md), and the [merged-service Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md).
14
+
15
+ `toolFilter` changes the child's global tool layer but is not a parent-derived authority ceiling. See the [agent-scope security non-goal](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals).
16
+
17
+ ## Config
18
+
19
+ | Key | Meaning |
20
+ |---|---|
21
+ | `provider` (required) | Provider name (`spawn`, `fork`, `acp`, ...). |
22
+ | `toolName` | Model-facing name, default `subagent`; distinct for every loaded instance. |
23
+ | `enableRunInBackground` | Exposes background mode, default `true`; disabling also rejects forced background calls. |
24
+ | `backgroundMode` | Background lifecycle policy, default `one-shot`. `continuable` requires the provider's `prepareContinuable` capability and returns a durable child id; it does not require the follow-up tool. |
25
+ | `agentOptions` | Provider-specific child `provider`, `model`, and positive `maxTokens`; the in-process provider treats explicit values as overrides of inherited parent options. |
26
+ | `persona` | Per-child persona; requires provider `persona` capability. |
27
+ | `toolFilter` | Per-child global-tool restriction; requires `toolFilter` capability. |
28
+ | `maxDepth` | Absolute delegation-depth cap, default `3` (`0` forbids delegation); a numeric cap requires the `depthLimit` capability and fails the mount without it. `'provider-managed'` sends no cap for an out-of-process provider whose budget belongs to the child harness. The tool stays visible at the cap; each attempted start checks the calling agent's current depth and returns an errored tool result when rejected. |
29
+
30
+ ## Concurrency
31
+
32
+ Foreground and background calls are exclusive. Children may share the parent's workspace or external resources, and a unary classifier cannot prove that sibling delegations have disjoint effects. See the [parallel tool-call Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md).
33
+
34
+ ## Model Experience
35
+
36
+ ### Tool schema
37
+
38
+ #### What the model sees
39
+
40
+ The generated default [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent) under this instance's configured name while its provider exists. Provider context inheritance changes the tool and prompt descriptions; enabled background mode adds `run_in_background`, and continuable mode describes starting a background subagent that keeps its conversation and returns its subagent id, while one-shot mode describes a background task id collected with `task_output` and stopped with `task_kill`.
41
+
42
+ #### Token effect
43
+
44
+ Fixed schema cost per parent request; each provider instance adds one schema.
45
+
46
+ #### KV Cache effect
47
+
48
+ Prefix-stable while provider instances, names, descriptions, and schemas are unchanged. Provider registration lifecycle may invalidate parent reuse from the first changed tool definition.
49
+
50
+ ### Foreground result
51
+
52
+ #### What the model sees
53
+
54
+ The call retains the description and prompt. Success contains only the child's final text; other outcomes become `Error: <message>`. Intermediate child steps stay out of the parent.
55
+
56
+ #### Token effect
57
+
58
+ The prompt and result remain in parent history until compaction; child working context remains in the child.
59
+
60
+ #### KV Cache effect
61
+
62
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
63
+
64
+ ### Background result
65
+
66
+ #### What the model sees
67
+
68
+ Start returns exactly `started subagent <childId>` in configured continuable mode, or `started background subagent task <id>` in configured one-shot mode. In one-shot mode the generic task surface provides later status, final output, cancellation responses, and notices. In continuable mode the child does not report back; an independently loaded `send_message` tool delivers follow-ups, and the child's transcript by its id is the source of its output.
69
+
70
+ #### Token effect
71
+
72
+ The acknowledgement is retained; a one-shot final output enters parent history only when collected or injected, while a continuable child's output never returns through this tool.
73
+
74
+ #### KV Cache effect
75
+
76
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
77
+
78
+ ## Known Limitations and Deferred Work
79
+
80
+ - **Background runs expose no result through this tool** — a one-shot task's final output is collected through the generic task surface, and a continuable child's output stays in its own session, read by its subagent id.
81
+ - **Duplicate names across waiting instances are detected late** (`TODO(subagent-dup-toolname)`) — preventing provider-registration rollback requires a registry of intended names.
82
+ - **Child policy is fixed per instance** — another model, persona, tool filter, or depth cap requires another distinctly named tool.
package/README.zh.md ADDED
@@ -0,0 +1,82 @@
1
+ # @deepseek-ai/dsh-tool-subagent
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 基于一个已配置 `ctx.subagents` 提供方、面向模型的委派工具。更换提供方只会改变传输,不会改变执行约定。
6
+
7
+ ## 提供方选择与生命周期
8
+
9
+ 每个插件实例把一个 `provider` 绑定到一个 `toolName`;模型不会收到提供方选择器。如需公开另一种传输,请加载另一个名称不同的实例。工具只在其提供方存在时注册,从而避免对同级加载顺序和提供方重新加载的依赖。工具描述遵循 `provider.inheritsParentContext`:新建子 agent(智能体)需要独立提示词,而 fork 子 agent 已能看到父级已完成轮次。
10
+
11
+ 前台调用会让执行信号贯穿启动和执行,等待 `run.result`,并且在返回前总会等待 `run.dispose()`。只有 `completed` 会返回规范值 `{ kind: 'foreground', runId, output: JsonValue[] }`,并渲染为相同的最终文本;中止、拒绝、token 上限和其他失败都会变成出错的工具结果,不包含局部输出。如果结果收集与 dispose(资源释放)都 reject,出错的结果会保留两项诊断信息。
12
+
13
+ 设置 `run_in_background: true` 后,`backgroundMode` 会选择路由。`one-shot` 会注册一个归父级所有的普通 Task,并返回规范值 `{ kind: 'background', taskId }`,渲染为 `started background subagent task <id>`,即使提供方支持可继续子 agent 也不例外;通用 Task 工具负责其后续状态、收集、取消和通知。`continuable` 要求提供方具备 `prepareContinuable` 能力,调用 `ctx.subagents.startContinuable()`,并返回 `{ kind: 'continuable', subagentId }`,渲染为 `started subagent <childId>`。可继续路由在 inbox 接受时结算:子 agent 自此拥有自己的轮次,因此该调用既不等待也不收集结果,而且子 agent 不会回报——通过该 id 查看其 transcript(文本记录)即是其输出来源,可选的全局 `send_message` 工具则向其发送更多工作。启动可继续工作不要求加载 `send_message`。见 [后台 subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md)、[可继续的 subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md)和[服务合并 Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)。
14
+
15
+ `toolFilter` 会改变子 agent 的全局工具层,但不是从父级派生的权限上限。见 [agent 作用域的安全非目标](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals)。
16
+
17
+ ## 配置
18
+
19
+ | 键 | 含义 |
20
+ |---|---|
21
+ | `provider`(必填) | 提供方名称(`spawn`、`fork`、`acp` 等)。 |
22
+ | `toolName` | 面向模型的名称,默认 `subagent`;每个已加载实例必须不同。 |
23
+ | `enableRunInBackground` | 公开后台模式,默认 `true`;禁用时也会拒绝强制后台调用。 |
24
+ | `backgroundMode` | 后台生命周期策略,默认 `one-shot`。`continuable` 要求提供方具备 `prepareContinuable` 能力并返回持久化子 agent ID;它不要求加载后续消息工具。 |
25
+ | `agentOptions` | 传给具体提供方的子 agent `provider`、`model` 和正整数 `maxTokens`;进程内提供方会用显式值覆盖继承的父级选项。 |
26
+ | `persona` | 每个子 agent 独立的 persona;要求提供方具备 `persona` 能力。 |
27
+ | `toolFilter` | 每个子 agent 独立的全局工具限制;要求提供方具备 `toolFilter` 能力。 |
28
+ | `maxDepth` | 绝对委派深度上限,默认 `3`(`0` 禁止委派);数值上限要求 `depthLimit` 能力,缺失时挂载失败。对于预算由子 harness 拥有的进程外提供方,`'provider-managed'` 不发送上限。工具在达到上限时仍然可见;每次尝试启动都会检查调用 agent 的当前深度,被拒绝时返回出错的工具结果。 |
29
+
30
+ ## 并发
31
+
32
+ 前台调用和后台调用均互斥。子 agent 可能共享父级工作区或外部资源,一元分类器无法证明同级委派的效果彼此不相交。见 [并行工具调用 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md)。
33
+
34
+ ## 模型体验
35
+
36
+ ### 工具 schema
37
+
38
+ #### 模型看到的内容
39
+
40
+ 当提供方存在时,以当前实例配置的名称公开已生成的默认 [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent)。提供方是否继承上下文会改变工具描述和提示词描述;启用后台模式会添加 `run_in_background`,可继续模式描述为启动一个保留其对话并返回子 agent id 的后台子 agent,而一次性模式描述为返回一个用 `task_output` 收集、用 `task_kill` 停止的后台任务 id。
41
+
42
+ #### Token 影响
43
+
44
+ 每个父级请求都会产生固定的 schema token 开销;每个提供方实例增加一个 schema。
45
+
46
+ #### KV Cache 影响
47
+
48
+ 只要提供方实例、名称、描述和 schema 不变,前缀就保持稳定。提供方注册生命周期可能从首个变化的工具定义开始,使父级复用失效。
49
+
50
+ ### 前台结果
51
+
52
+ #### 模型看到的内容
53
+
54
+ 调用会保留描述和提示词。成功时只包含子 agent 的最终文本;其他结果变为 `Error: <message>`。子 agent 中间步骤不会进入父级。
55
+
56
+ #### Token 影响
57
+
58
+ 提示词和结果会留在父级历史中,直到上下文压缩(context compaction);子 agent 工作上下文留在子 agent 中。
59
+
60
+ #### KV Cache 影响
61
+
62
+ 仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
63
+
64
+ ### 后台结果
65
+
66
+ #### 模型看到的内容
67
+
68
+ 在配置的可继续模式下,启动时返回内容恰为 `started subagent <childId>`;在配置的一次性模式下,则返回 `started background subagent task <id>`。一次性模式下,通用 Task 接口提供后续状态、最终输出、取消响应和通知。可继续模式下,子 agent 不会回报;独立加载的 `send_message` 工具会投递后续消息,而通过其 id 查看子 agent 的 transcript 即是其输出来源。
69
+
70
+ #### Token 影响
71
+
72
+ 确认消息会被保留;一次性最终输出只在收集或注入时进入父级历史,而可继续子 agent 的输出绝不会通过本工具返回。
73
+
74
+ #### KV Cache 影响
75
+
76
+ 仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
77
+
78
+ ## 已知限制与暂缓事项
79
+
80
+ - **后台运行不通过本工具公开结果**:一次性任务的最终输出通过通用 Task 接口收集,可继续子 agent 的输出留在其自身会话中,按其 subagent id 读取。
81
+ - **等待中实例的重复名称发现较晚**(`TODO(subagent-dup-toolname)`):若要阻止提供方注册回滚,需要一份预期名称注册表。
82
+ - **每个实例的子 agent 策略固定**:其他模型、persona、工具过滤器或深度上限都需要另一个名称不同的工具。
package/lib/index.js ADDED
@@ -0,0 +1,260 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { defineTool } from "@deepseek-ai/dsh-tools";
3
+ import { assertSubagentMaxDepth, settleRun } from "@deepseek-ai/dsh-subagent";
4
+ //#region lib/types/index.js
5
+ /**
6
+ * Model-facing delegation through one configured `ctx.subagents` provider.
7
+ * Provider lifecycle controls tool registration and context-sensitive schema
8
+ * wording. Foreground calls always dispose the run after collection.
9
+ * Background policy is selected by this plugin's configuration: one-shot
10
+ * calls own a plain Task, while continuable calls use
11
+ * `ctx.subagents.startContinuable()`.
12
+ * @module @deepseek-ai/dsh-tool-subagent
13
+ */
14
+ const name = "tool-subagent";
15
+ const inject = ["tools", "subagents"];
16
+ const Config = z.object({
17
+ provider: z.string().required(),
18
+ toolName: z.string().default("subagent"),
19
+ enableRunInBackground: z.boolean().default(true),
20
+ backgroundMode: z.union(["one-shot", "continuable"]).default("one-shot"),
21
+ agentOptions: z.object({
22
+ provider: z.string(),
23
+ model: z.string(),
24
+ maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)
25
+ }).default(void 0),
26
+ persona: z.string(),
27
+ toolFilter: z.object({
28
+ allow: z.array(z.string()).default(void 0),
29
+ deny: z.array(z.string()).default(void 0)
30
+ }).default(void 0),
31
+ maxDepth: z.union([z.natural().max(Number.MAX_SAFE_INTEGER), z.const("provider-managed")]).default(3)
32
+ });
33
+ /** Render text blocks from the canonical JSON block array without trusting arbitrary values. */
34
+ function outputValueText(values) {
35
+ return values.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value) && value.type === "text" && typeof value.text === "string").map((value) => value.text).join("");
36
+ }
37
+ /** Settle pending startup without rejecting the task producer contract. */
38
+ async function settleStart(start, signal) {
39
+ try {
40
+ return await settleRun(await start);
41
+ } catch (error) {
42
+ return signal.aborted ? { status: "killed" } : {
43
+ status: "failed",
44
+ detail: String(error)
45
+ };
46
+ }
47
+ }
48
+ /** A non-`completed` stop reason means the child did not finish cleanly. */
49
+ function stopReasonError(result) {
50
+ switch (result.stopReason) {
51
+ case "completed": return;
52
+ case "aborted": return "subagent run was cancelled";
53
+ case "error": return "subagent run failed";
54
+ case "max-tokens": return "subagent run hit its token limit before finishing";
55
+ case "refusal": return "subagent declined the task";
56
+ default: return `subagent run ended abnormally (${String(result.stopReason)})`;
57
+ }
58
+ }
59
+ /**
60
+ * Collect and release one foreground run without letting disposal replace an
61
+ * independent result failure.
62
+ */
63
+ async function settleForegroundRun(run) {
64
+ const [execution] = await Promise.allSettled([run.result.then((result) => {
65
+ const error = stopReasonError(result);
66
+ if (error !== void 0) throw new Error(error);
67
+ return {
68
+ kind: "foreground",
69
+ runId: run.id,
70
+ output: result.output
71
+ };
72
+ })]);
73
+ const [disposal] = await Promise.allSettled([Promise.resolve().then(() => run.dispose())]);
74
+ if (execution.status === "rejected") {
75
+ if (disposal.status === "rejected") throw new AggregateError([execution.reason, disposal.reason], `subagent run failed: ${String(execution.reason)}; dispose failed: ${String(disposal.reason)}`);
76
+ throw execution.reason;
77
+ }
78
+ if (disposal.status === "rejected") throw disposal.reason;
79
+ return execution.value;
80
+ }
81
+ /**
82
+ * Model-facing wording from the provider's conversation-history descriptor
83
+ * ({@link SubagentProvider.inheritsParentContext}).
84
+ * A fresh child needs a standalone prompt; a forked child already sees the
85
+ * conversation's completed turns — telling the model to restate everything
86
+ * (or, worse, that the child "does not see this conversation") would be false
87
+ * for a fork.
88
+ * @param inheritsConversation - whether the child's conversation is seeded
89
+ * with the parent's completed turns; this says nothing about tool, service,
90
+ * scope, or authority inheritance.
91
+ * @returns the tool `description` and the `prompt` parameter description.
92
+ */
93
+ function providerWording(inheritsConversation) {
94
+ if (inheritsConversation) return {
95
+ description: "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps.",
96
+ promptDescription: "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."
97
+ };
98
+ return {
99
+ description: "Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation.",
100
+ promptDescription: "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
101
+ };
102
+ }
103
+ function apply(ctx, config) {
104
+ if (config.maxDepth !== "provider-managed") assertSubagentMaxDepth(config.maxDepth);
105
+ if (config.toolFilter !== void 0 && config.toolFilter.allow === void 0 && config.toolFilter.deny === void 0) throw new Error("tool-subagent: `toolFilter` is configured but names neither `allow` nor `deny` — remove the key or fill the filter");
106
+ let disposeTool;
107
+ const mount = (provider) => {
108
+ if (typeof config.maxDepth === "number" && !provider.capabilities.depthLimit) throw new Error(`tool-subagent: provider "${provider.name}" cannot enforce maxDepth (no depthLimit capability) — set maxDepth: 'provider-managed' to leave the recursion budget to the provider`);
109
+ const wording = providerWording(provider.inheritsParentContext);
110
+ const backgroundEnabled = config.enableRunInBackground !== false;
111
+ const continuable = (config.backgroundMode ?? "one-shot") === "continuable";
112
+ if (continuable && provider.prepareContinuable === void 0) throw new Error(`tool-subagent: provider "${provider.name}" does not support \`backgroundMode: continuable\``);
113
+ disposeTool = ctx.tools.register(defineTool({
114
+ name: config.toolName ?? "subagent",
115
+ description: wording.description + (backgroundEnabled ? continuable ? " Set `run_in_background: true` to start a background subagent that keeps its conversation: you receive only its subagent id, never its result, and it works on its own. Use this for work whose result you do not need returned by this call; `send_message` sends it more work." : " Set `run_in_background: true` to return a task id; collect with `task_output` and stop with `task_kill`." : ""),
116
+ parameters: {
117
+ description: {
118
+ type: "string",
119
+ required: true,
120
+ description: "A short (3-5 word) description of the delegated task, for display."
121
+ },
122
+ prompt: {
123
+ type: "string",
124
+ required: true,
125
+ description: wording.promptDescription
126
+ },
127
+ ...backgroundEnabled ? { run_in_background: {
128
+ type: "boolean",
129
+ description: continuable ? "Run as a background subagent that keeps its conversation and return only its subagent id. This call never returns its result; send it more work with send_message." : "Run as a background task and return its id; collect with task_output or stop with task_kill."
130
+ } } : {}
131
+ },
132
+ output: {
133
+ schema: { oneOf: [
134
+ {
135
+ type: "object",
136
+ additionalProperties: false,
137
+ properties: {
138
+ kind: {
139
+ type: "string",
140
+ required: true,
141
+ const: "background"
142
+ },
143
+ taskId: {
144
+ type: "string",
145
+ required: true
146
+ }
147
+ }
148
+ },
149
+ {
150
+ type: "object",
151
+ additionalProperties: false,
152
+ properties: {
153
+ kind: {
154
+ type: "string",
155
+ required: true,
156
+ const: "continuable"
157
+ },
158
+ subagentId: {
159
+ type: "string",
160
+ required: true
161
+ }
162
+ }
163
+ },
164
+ {
165
+ type: "object",
166
+ additionalProperties: false,
167
+ properties: {
168
+ kind: {
169
+ type: "string",
170
+ required: true,
171
+ const: "foreground"
172
+ },
173
+ runId: {
174
+ type: "string",
175
+ required: true
176
+ },
177
+ output: {
178
+ type: "array",
179
+ required: true,
180
+ items: { type: "json" }
181
+ }
182
+ }
183
+ }
184
+ ] },
185
+ render: (_args, value) => [{
186
+ type: "text",
187
+ text: value.kind === "background" ? `started background subagent task ${value.taskId}` : value.kind === "continuable" ? `started subagent ${value.subagentId}` : outputValueText(value.output)
188
+ }]
189
+ },
190
+ async execute(args, exec) {
191
+ const parent = exec.agent;
192
+ if (!parent) throw new Error("subagent tool requires a calling agent (exec.agent was undefined)");
193
+ const maxDepth = typeof config.maxDepth === "number" ? config.maxDepth : void 0;
194
+ const request = {
195
+ label: args.description,
196
+ prompt: [{
197
+ type: "text",
198
+ text: args.prompt
199
+ }],
200
+ parent,
201
+ ...config.agentOptions !== void 0 ? { agentOptions: config.agentOptions } : {},
202
+ ...config.persona !== void 0 ? { persona: config.persona } : {},
203
+ ...config.toolFilter !== void 0 ? { toolFilter: config.toolFilter } : {},
204
+ ...maxDepth !== void 0 ? { maxDepth } : {}
205
+ };
206
+ if (args.run_in_background === true) {
207
+ if (!backgroundEnabled) throw new Error("run_in_background is disabled for this tool instance (enableRunInBackground: false)");
208
+ if (continuable) return {
209
+ kind: "continuable",
210
+ subagentId: (await ctx.subagents.startContinuable({
211
+ provider: config.provider,
212
+ label: args.description,
213
+ request,
214
+ signal: exec.signal
215
+ })).childId
216
+ };
217
+ const tasks = ctx.get("tasks");
218
+ if (tasks === void 0) throw new Error("background tasks unavailable: load @deepseek-ai/dsh-tasks and @deepseek-ai/dsh-tool-tasks");
219
+ return {
220
+ kind: "background",
221
+ taskId: tasks.start({
222
+ kind: "subagent",
223
+ label: args.description,
224
+ owner: parent,
225
+ run: () => {
226
+ const controller = new AbortController();
227
+ return {
228
+ cancel: (reason) => {
229
+ controller.abort(reason ?? "background subagent task killed");
230
+ },
231
+ done: settleStart(ctx.subagents.start(config.provider, {
232
+ ...request,
233
+ signal: controller.signal
234
+ }), controller.signal)
235
+ };
236
+ }
237
+ })
238
+ };
239
+ }
240
+ return settleForegroundRun(await ctx.subagents.start(config.provider, {
241
+ ...request,
242
+ signal: exec.signal
243
+ }));
244
+ }
245
+ }));
246
+ };
247
+ ctx.on("subagent/provider-added", (provider) => {
248
+ if (provider.name === config.provider && disposeTool === void 0) mount(provider);
249
+ });
250
+ ctx.on("subagent/provider-removed", (name) => {
251
+ if (name !== config.provider || disposeTool === void 0) return;
252
+ disposeTool();
253
+ disposeTool = void 0;
254
+ });
255
+ const present = ctx.subagents.getProvider(config.provider);
256
+ if (present !== void 0) mount(present);
257
+ else ctx.logger.info(`subagent provider "${config.provider}" not registered yet; the "${config.toolName ?? "subagent"}" tool will register when it appears`);
258
+ }
259
+ //#endregion
260
+ export { Config, apply, inject, name };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-tool-subagent`.
4
+ * @module @deepseek-ai/dsh-tool-subagent/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-tool-subagent";
7
+ /** Cordis companion plugin name. */
8
+ const name = "tool-subagent-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this model-facing adapter has no independent lifecycle stream; execution
13
+ * relations are owned by the capability seam it calls.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Model-facing delegation through one configured `ctx.subagents` provider.
3
+ * Provider lifecycle controls tool registration and context-sensitive schema
4
+ * wording. Foreground calls always dispose the run after collection.
5
+ * Background policy is selected by this plugin's configuration: one-shot
6
+ * calls own a plain Task, while continuable calls use
7
+ * `ctx.subagents.startContinuable()`.
8
+ * @module @deepseek-ai/dsh-tool-subagent
9
+ */
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ import z from '@deepseek-ai/schemastery';
12
+ import type { AgentOptions } from '@deepseek-ai/dsh-agent';
13
+ export declare const name = "tool-subagent";
14
+ export declare const inject: string[];
15
+ /** Config: which registered provider this tool delegates to, plus child defaults. */
16
+ export interface Config {
17
+ /** The `ctx.subagents` provider name to start runs on (e.g. `spawn`, `acp`). */
18
+ provider: string;
19
+ /**
20
+ * Model-facing tool name (default `subagent`). Each loaded instance must use
21
+ * a distinct name.
22
+ */
23
+ toolName?: string;
24
+ /**
25
+ * Expose `run_in_background` (default true). Disabled instances omit the
26
+ * parameter and reject forced background calls.
27
+ */
28
+ enableRunInBackground?: boolean;
29
+ /**
30
+ * Background execution policy (default `one-shot`). `continuable` requires a
31
+ * provider with the `prepareContinuable` capability and returns the durable
32
+ * child id; follow-up adapters remain independently optional.
33
+ */
34
+ backgroundMode?: 'one-shot' | 'continuable';
35
+ /**
36
+ * Agent options applied to every child; omitted fields use child-loop defaults.
37
+ */
38
+ agentOptions?: AgentOptions;
39
+ /**
40
+ * Per-child persona that shadows `deployment:persona`. Requires the
41
+ * provider's `persona` capability; omission preserves the deployment persona.
42
+ */
43
+ persona?: string;
44
+ /**
45
+ * Tool filter applied to every child. Filtered tools disappear from its
46
+ * prompt and reject execution. Requires the provider's `toolFilter`
47
+ * capability; unknown names fail startup.
48
+ */
49
+ toolFilter?: {
50
+ /** Global tool names the child keeps; everything else is removed. */
51
+ allow?: string[];
52
+ /** Global tool names removed from the child. */
53
+ deny?: string[];
54
+ };
55
+ /**
56
+ * Maximum child depth: a non-negative safe integer (default `3`; `0` forbids
57
+ * delegation entirely), or `'provider-managed'` to send no cap. A numeric cap
58
+ * requires the provider's `depthLimit` capability (mount fails loud
59
+ * otherwise). The provider checks the calling agent's current depth at every
60
+ * start; the tool remains model-visible so runtime policy owns rejection.
61
+ * `'provider-managed'` is for an out-of-process provider whose recursion
62
+ * budget belongs to the child runtime or its own deployment.
63
+ */
64
+ maxDepth?: number | 'provider-managed';
65
+ }
66
+ export declare const Config: z<Config>;
67
+ export declare function apply(ctx: Context, config: Config): void;
68
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-tool-subagent`.
3
+ * @module @deepseek-ai/dsh-tool-subagent/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "tool-subagent-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-tool-subagent",
3
+ "description": "Model-facing subagent delegation tool over the ctx.subagents seam",
4
+ "version": "0.0.1-rc.1",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/subagent/tool-subagent"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "BSD-3-Clause",
34
+ "peerDependencies": {
35
+ "@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
36
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
37
+ "@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
38
+ "@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
39
+ "@deepseek-ai/dsh-tasks": "^0.0.1-rc.1",
40
+ "@deepseek-ai/dsh-tools": "^0.0.1-rc.1",
41
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
42
+ },
43
+ "dependencies": {
44
+ "@deepseek-ai/schemastery": "^3.18.1-rc.1"
45
+ },
46
+ "devDependencies": {
47
+ "@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
48
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
49
+ "@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
50
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
51
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.1",
52
+ "@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.1",
53
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1-rc.1",
54
+ "@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
55
+ "@deepseek-ai/dsh-tasks": "^0.0.1-rc.1",
56
+ "@deepseek-ai/dsh-tasks-local": "^0.0.1-rc.1",
57
+ "@deepseek-ai/dsh-subagent-spawn": "^0.0.1-rc.1",
58
+ "@deepseek-ai/dsh-tool-tasks": "^0.0.1-rc.1",
59
+ "@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.1",
60
+ "@deepseek-ai/dsh-tools": "^0.0.1-rc.1",
61
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
62
+ }
63
+ }