@hydraharness/harness-tool-subagent 0.1.1-rc.6

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @hydraharness/harness-tool-subagent
2
+
3
+ The model-facing delegation tool over one configured `ctx.subagents` provider. Changing the provider changes transport without changing the execution contract.
4
+
5
+ ## Provider selection and lifecycle
6
+
7
+ 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.
8
+
9
+ 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 whose message contains the stop-reason headline, an optional provider-authored `SubagentResult.diagnostic`, and then any preserved partial assistant text. The diagnostic remains separate from `SubagentResult.output`, so a truncated answer is never reported as success or confused with infrastructure detail. If result collection and disposal both reject, the errored result preserves both failures.
10
+
11
+ `backgroundMode` selects both the background route and the omitted `run_in_background` default. `one-shot` waits in the foreground by default; an explicit `true` registers a plain parent-owned Task and returns canonical `{ kind: 'background', jobId }`, rendered as `started background subagent job <id>`, even when the provider supports continuable children. Generic task tools own its later status, collection, cancellation, and notices; a failed Task keeps the stop reason and the same optional provider diagnostic in its detail. `continuable` runs in the background when the argument is omitted or `true`; an explicit `false` waits for the result in the foreground. Its background route requires a provider with the `prepareContinuable` capability, calls `ctx.subagents.startContinuable()`, and returns `{ kind: 'continuable', subagentId }`, rendered as `started continuable subagent <childId>; not a background job — wait for the runtime notice, do not call job_output or job_kill`. The child id is never valid for `job_output` or `job_kill`; the route resolves at inbox acceptance, so it neither waits for nor collects a result. The continuation service delivers one settlement notice whenever the child's Activation ends, containing its outcome and any final assistant message independently of `report`; use that notice instead of starting a duplicate delegation. The optional global `send_message` tool starts later work in the same child conversation. 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 [background-first delegation Agent Note](../../../.agents/notes/implemented/feature/2026-08-11-background-first-continuable-delegation.md).
12
+
13
+ `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).
14
+
15
+ ## Config
16
+
17
+ | Key | Meaning |
18
+ |---|---|
19
+ | `provider` (required) | Provider name (`spawn`, `fork`, `acp`, ...). |
20
+ | `toolName` | Model-facing name, default `subagent`; distinct for every loaded instance. |
21
+ | `enableRunInBackground` | Exposes background mode, default `true`; disabling also rejects forced background calls. |
22
+ | `backgroundMode` | Background lifecycle policy, default `one-shot`. `one-shot` defaults calls to foreground; `continuable` defaults them to background, requires the provider's `prepareContinuable` capability, and returns a durable child id without requiring the follow-up tool. |
23
+ | `agentOptions` | Provider-specific child `provider`, `model`, and positive `maxTokens`; the in-process provider treats explicit values as overrides of inherited parent options. |
24
+ | `persona` | Per-child persona; requires provider `persona` capability. |
25
+ | `toolFilter` | Per-child global-tool restriction; requires `toolFilter` capability. |
26
+ | `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. |
27
+
28
+ ## Concurrency
29
+
30
+ Foreground and background calls are concurrency-safe: sibling delegations in one assistant message overlap under the loop's rolling pool (`maxParallelToolCalls`), and results still commit in model order. Children work in their own sessions and a run never mutates the parent session; the one-shot background form's one parent-owned write — registering a Task — is a synchronous, commutative insertion that tolerates concurrent dispatch, so overlapping background calls acquire their job ids in dispatch-race order. Coordinating sibling workspace effects belongs to the model, exactly as it already does for background and continuable children. See the [parallel subagent Agent Note](../../../.agents/notes/implemented/feature/2026-08-09-parallel-subagent-delegations.md) and the [parallel tool-call Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md).
31
+
32
+ ## Model Experience
33
+
34
+ ### Tool schema
35
+
36
+ #### What the model sees
37
+
38
+ The generated default [`subagent` schema](../../../docs/tool-catalog.md#hydraharness-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`: continuable mode documents its `true` default, runtime settlement notice, explicit foreground override, and that its durable id is not accepted by `job_output` or `job_kill`; one-shot mode documents its `false` default and the job id collected with `job_output` or stopped with `job_kill`. While the tool is visible in an assembly's scope, a `tool:<toolName>` system-prompt section tells the model to start independent continuable delegations together, keep working while they run, use the settlement notice instead of duplicate work, and choose foreground only when its next action depends on the result; a tool restriction removes both its schema and this guidance.
39
+
40
+ #### Token effect
41
+
42
+ Fixed schema cost per parent request; each provider instance adds one schema, and each continuable instance adds one short system-prompt section.
43
+
44
+ #### KV Cache effect
45
+
46
+ Prefix-stable while provider instances, names, descriptions, and schemas are unchanged. Provider registration lifecycle may invalidate parent reuse from the first changed tool definition.
47
+
48
+ ### Foreground result
49
+
50
+ #### What the model sees
51
+
52
+ The call retains the description and prompt. Success contains only the child's final text; other outcomes become `Error: <stop reason>`, followed by a safe provider diagnostic when present and then any partial assistant text. Intermediate child steps stay out of the parent.
53
+
54
+ #### Token effect
55
+
56
+ The prompt and result remain in parent history until compaction; child working context remains in the child.
57
+
58
+ #### KV Cache effect
59
+
60
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
61
+
62
+ ### Background result
63
+
64
+ #### What the model sees
65
+
66
+ Start returns exactly `started continuable subagent <childId>; not a background job — wait for the runtime notice, do not call job_output or job_kill` in configured continuable mode, or `started background subagent job <id>` in configured one-shot mode. In one-shot mode the generic task surface provides later status, final output, cancellation responses, and notices; failed status detail includes the provider diagnostic when the result supplied one. In continuable mode this tool returns no result of its own; the child id is not accepted by generic job tools, its settlement reaches the parent as a [service-owned notice](../subagent/README.md#settlement-notice), and an independently loaded `send_message` tool delivers follow-ups.
67
+
68
+ #### Token effect
69
+
70
+ 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 — its settlement notice arrives independently of any tool result.
71
+
72
+ #### KV Cache effect
73
+
74
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
75
+
76
+ ## Known Limitations and Deferred Work
77
+
78
+ - **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. The settlement notice states how that child ended and carries any final assistant message, but it is not this call's return value and cannot be awaited here.
79
+ - **Duplicate names across waiting one-shot instances are detected late** (`TODO(subagent-dup-toolname)`) — continuable instances reserve their prompt-section name during plugin application, but preventing provider-registration rollback for waiting one-shot instances requires a registry of intended names.
80
+ - **Child policy is fixed per instance** — another model, persona, tool filter, or depth cap requires another distinctly named tool.
package/lib/index.js ADDED
@@ -0,0 +1,302 @@
1
+ import z from "@hydraharness/schemastery";
2
+ import { defineTool } from "@hydraharness/harness-tools";
3
+ import { assertSubagentMaxDepth, settleRun } from "@hydraharness/harness-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 @hydraharness/harness-tool-subagent
13
+ */
14
+ const name = "tool-subagent";
15
+ const inject = [
16
+ "tools",
17
+ "subagents",
18
+ "systemPrompt"
19
+ ];
20
+ /** Prompt order after bounded delegation policy and before child reporting. */
21
+ const SUBAGENT_SECTION_ORDER = 116.5;
22
+ const Config = z.object({
23
+ provider: z.string().required(),
24
+ toolName: z.string().default("subagent"),
25
+ enableRunInBackground: z.boolean().default(true),
26
+ backgroundMode: z.union(["one-shot", "continuable"]).default("one-shot"),
27
+ agentOptions: z.object({
28
+ provider: z.string(),
29
+ model: z.string(),
30
+ maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)
31
+ }).default(void 0),
32
+ persona: z.string(),
33
+ toolFilter: z.object({
34
+ allow: z.array(z.string()).default(void 0),
35
+ deny: z.array(z.string()).default(void 0)
36
+ }).default(void 0),
37
+ maxDepth: z.union([z.natural().max(Number.MAX_SAFE_INTEGER), z.const("provider-managed")]).default(3)
38
+ });
39
+ /** Render text blocks from the canonical JSON block array without trusting arbitrary values. */
40
+ function outputValueText(values) {
41
+ return values.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value) && value.type === "text" && typeof value.text === "string").map((value) => value.text).join("");
42
+ }
43
+ /** Settle pending startup without rejecting the task producer contract. */
44
+ async function settleStart(start, signal) {
45
+ try {
46
+ return await settleRun(await start);
47
+ } catch (error) {
48
+ return signal.aborted && !(error instanceof AggregateError) ? { status: "killed" } : {
49
+ status: "failed",
50
+ detail: String(error)
51
+ };
52
+ }
53
+ }
54
+ /** A non-`completed` stop reason means the child did not finish cleanly. */
55
+ function stopReasonError(result) {
56
+ switch (result.stopReason) {
57
+ case "completed": return;
58
+ case "aborted": return "subagent run was cancelled";
59
+ case "error": return "subagent run failed";
60
+ case "max-tokens": return "subagent run hit its token limit before finishing";
61
+ case "refusal": return "subagent declined the task";
62
+ default: return `subagent run ended abnormally (${String(result.stopReason)})`;
63
+ }
64
+ }
65
+ /**
66
+ * Append provider-authored failure detail and the child's preserved partial
67
+ * answer to a stop-reason error, keeping diagnostic text separate from the
68
+ * child's assistant output.
69
+ * @param error - the stop-reason headline.
70
+ * @param result - the child's terminal result.
71
+ * @returns the headline, diagnostic, and partial text that are present.
72
+ */
73
+ function withDiagnosticAndPartialText(error, result) {
74
+ const diagnostic = result.diagnostic === void 0 ? "" : `\nDiagnostic: ${result.diagnostic}`;
75
+ const text = result.output.filter((block) => block.type === "text").map((block) => block.text).join("");
76
+ return `${error}${diagnostic}${text.length === 0 ? "" : `\nPartial output before the run ended:\n${text}`}`;
77
+ }
78
+ /**
79
+ * Collect and release one foreground run without letting disposal replace an
80
+ * independent result failure.
81
+ */
82
+ async function settleForegroundRun(run) {
83
+ const [execution] = await Promise.allSettled([run.result.then((result) => {
84
+ const error = stopReasonError(result);
85
+ if (error !== void 0) throw new Error(withDiagnosticAndPartialText(error, result));
86
+ return {
87
+ kind: "foreground",
88
+ runId: run.id,
89
+ output: result.output
90
+ };
91
+ })]);
92
+ const [disposal] = await Promise.allSettled([Promise.resolve().then(() => run.dispose())]);
93
+ if (execution.status === "rejected") {
94
+ if (disposal.status === "rejected") throw new AggregateError([execution.reason, disposal.reason], `subagent run failed: ${String(execution.reason)}; dispose failed: ${String(disposal.reason)}`);
95
+ throw execution.reason;
96
+ }
97
+ if (disposal.status === "rejected") throw disposal.reason;
98
+ return execution.value;
99
+ }
100
+ /**
101
+ * Model-facing wording from the provider's conversation-history descriptor
102
+ * ({@link SubagentProvider.inheritsParentContext}).
103
+ * A fresh child needs a standalone prompt; a forked child already sees the
104
+ * conversation's completed turns — telling the model to restate everything
105
+ * (or, worse, that the child "does not see this conversation") would be false
106
+ * for a fork.
107
+ * @param inheritsConversation - whether the child's conversation is seeded
108
+ * with the parent's completed turns; this says nothing about tool, service,
109
+ * scope, or authority inheritance.
110
+ * @returns the tool `description` and the `prompt` parameter description.
111
+ */
112
+ function providerWording(inheritsConversation) {
113
+ if (inheritsConversation) return {
114
+ 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). 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 its result, not its intermediate steps.",
115
+ 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."
116
+ };
117
+ return {
118
+ description: "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation.",
119
+ promptDescription: "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
120
+ };
121
+ }
122
+ /** Resolve the model's optional scheduling request into one execution route. */
123
+ function resolveDelegationRun(request, options) {
124
+ if (!options.backgroundEnabled) {
125
+ if (request.run_in_background === true) throw new Error("run_in_background is disabled for this tool instance (enableRunInBackground: false)");
126
+ return { runInBackground: false };
127
+ }
128
+ return { runInBackground: request.run_in_background ?? options.continuable };
129
+ }
130
+ function apply(ctx, config) {
131
+ if (config.maxDepth !== "provider-managed") assertSubagentMaxDepth(config.maxDepth);
132
+ 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");
133
+ const backgroundEnabled = config.enableRunInBackground !== false;
134
+ const continuable = (config.backgroundMode ?? "one-shot") === "continuable";
135
+ const toolName = config.toolName ?? "subagent";
136
+ let disposeTool;
137
+ const mount = (provider) => {
138
+ 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`);
139
+ const wording = providerWording(provider.inheritsParentContext);
140
+ if (continuable && provider.prepareContinuable === void 0) throw new Error(`tool-subagent: provider "${provider.name}" does not support \`backgroundMode: continuable\``);
141
+ disposeTool = ctx.tools.register(defineTool({
142
+ name: toolName,
143
+ description: wording.description + (backgroundEnabled ? continuable ? " This tool runs in the background by default and immediately returns a durable continuable subagent id, not a background job id. Do not pass that id to `job_output` or `job_kill`. When the run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; wait for that notice rather than starting a duplicate delegation. `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result." : " This call waits for the result by default. Set `run_in_background: true` to return a job id; collect with `job_output` and stop with `job_kill`." : " This call waits for the subagent and returns its result."),
144
+ parameters: {
145
+ description: {
146
+ type: "string",
147
+ required: true,
148
+ description: "A short (3-5 word) description of the delegated task, for display."
149
+ },
150
+ prompt: {
151
+ type: "string",
152
+ required: true,
153
+ description: wording.promptDescription
154
+ },
155
+ ...backgroundEnabled ? { run_in_background: {
156
+ type: "boolean",
157
+ description: continuable ? "Whether to run in the background and return a durable continuable subagent id immediately. Defaults to true. This id is not a background job id; do not pass it to `job_output` or `job_kill`. Set false to wait for the result when your next action depends on it." : "Whether to run as a background job and return its id. Defaults to false; collect with job_output or stop with job_kill."
158
+ } } : {}
159
+ },
160
+ output: {
161
+ schema: { oneOf: [
162
+ {
163
+ type: "object",
164
+ additionalProperties: false,
165
+ properties: {
166
+ kind: {
167
+ type: "string",
168
+ required: true,
169
+ const: "background"
170
+ },
171
+ jobId: {
172
+ type: "string",
173
+ required: true
174
+ }
175
+ }
176
+ },
177
+ {
178
+ type: "object",
179
+ additionalProperties: false,
180
+ properties: {
181
+ kind: {
182
+ type: "string",
183
+ required: true,
184
+ const: "continuable"
185
+ },
186
+ subagentId: {
187
+ type: "string",
188
+ required: true
189
+ }
190
+ }
191
+ },
192
+ {
193
+ type: "object",
194
+ additionalProperties: false,
195
+ properties: {
196
+ kind: {
197
+ type: "string",
198
+ required: true,
199
+ const: "foreground"
200
+ },
201
+ runId: {
202
+ type: "string",
203
+ required: true
204
+ },
205
+ output: {
206
+ type: "array",
207
+ required: true,
208
+ items: { type: "json" }
209
+ }
210
+ }
211
+ }
212
+ ] },
213
+ render: (_args, value) => [{
214
+ type: "text",
215
+ text: value.kind === "background" ? `started background subagent job ${value.jobId}` : value.kind === "continuable" ? `started continuable subagent ${value.subagentId}; not a background job — wait for the runtime notice, do not call job_output or job_kill` : outputValueText(value.output)
216
+ }]
217
+ },
218
+ isConcurrencySafe: () => true,
219
+ presentCall: (args) => ({
220
+ card: "generic",
221
+ title: args.description || "Subagent",
222
+ kind: "other",
223
+ rawInput: args.prompt
224
+ }),
225
+ async execute(args, exec) {
226
+ const parent = exec.agent;
227
+ if (!parent) throw new Error("subagent tool requires a calling agent (exec.agent was undefined)");
228
+ const maxDepth = typeof config.maxDepth === "number" ? config.maxDepth : void 0;
229
+ const request = {
230
+ label: args.description,
231
+ prompt: [{
232
+ type: "text",
233
+ text: args.prompt
234
+ }],
235
+ parent,
236
+ ...config.agentOptions !== void 0 ? { agentOptions: config.agentOptions } : {},
237
+ ...config.persona !== void 0 ? { persona: config.persona } : {},
238
+ ...config.toolFilter !== void 0 ? { toolFilter: config.toolFilter } : {},
239
+ ...maxDepth !== void 0 ? { maxDepth } : {}
240
+ };
241
+ if (resolveDelegationRun(args, {
242
+ backgroundEnabled,
243
+ continuable
244
+ }).runInBackground) {
245
+ if (continuable) return {
246
+ kind: "continuable",
247
+ subagentId: (await ctx.subagents.startContinuable({
248
+ provider: config.provider,
249
+ label: args.description,
250
+ request,
251
+ signal: exec.signal
252
+ })).childId
253
+ };
254
+ const jobs = ctx.get("jobs");
255
+ if (jobs === void 0) throw new Error("background jobs unavailable: load @hydraharness/harness-jobs and @hydraharness/harness-tool-jobs");
256
+ return {
257
+ kind: "background",
258
+ jobId: jobs.start({
259
+ kind: "subagent",
260
+ label: args.description,
261
+ owner: parent,
262
+ run: () => {
263
+ const controller = new AbortController();
264
+ return {
265
+ cancel: (reason) => {
266
+ controller.abort(reason ?? "background subagent task killed");
267
+ },
268
+ done: settleStart(ctx.subagents.start(config.provider, {
269
+ ...request,
270
+ signal: controller.signal
271
+ }), controller.signal)
272
+ };
273
+ }
274
+ })
275
+ };
276
+ }
277
+ return settleForegroundRun(await ctx.subagents.start(config.provider, {
278
+ ...request,
279
+ signal: exec.signal
280
+ }));
281
+ }
282
+ }));
283
+ };
284
+ ctx.on("subagent/provider-added", (provider) => {
285
+ if (provider.name === config.provider && disposeTool === void 0) mount(provider);
286
+ });
287
+ ctx.on("subagent/provider-removed", (name) => {
288
+ if (name !== config.provider || disposeTool === void 0) return;
289
+ disposeTool();
290
+ disposeTool = void 0;
291
+ });
292
+ const present = ctx.subagents.getProvider(config.provider);
293
+ if (present !== void 0) mount(present);
294
+ else ctx.logger.info(`subagent provider "${config.provider}" not registered yet; the "${config.toolName ?? "subagent"}" tool will register when it appears`);
295
+ if (backgroundEnabled && continuable) ctx.systemPrompt.section({
296
+ name: `tool:${toolName}`,
297
+ order: SUBAGENT_SECTION_ORDER,
298
+ text: (context) => disposeTool === void 0 || ctx.tools.get(toolName, context.scope) === void 0 ? "" : `Use ${toolName} in the background by default. Its returned continuable id is not a background job id: do not pass it to job_output or job_kill. Start independent delegations together in one assistant message, continue useful work while they run, and wait for the runtime notice rather than starting a duplicate delegation. Set \`run_in_background: false\` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.`
299
+ });
300
+ }
301
+ //#endregion
302
+ export { Config, apply, inject, name };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hydraharness/harness-tool-subagent`.
4
+ * @module @hydraharness/harness-tool-subagent/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hydraharness/harness-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,69 @@
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 @hydraharness/harness-tool-subagent
9
+ */
10
+ import type { Context } from '@hydraharness/cordis';
11
+ import z from '@hydraharness/schemastery';
12
+ import type { AgentOptions } from '@hydraharness/harness-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`). `one-shot` defaults calls
31
+ * to foreground; `continuable` defaults them to background, requires a provider
32
+ * with the `prepareContinuable` capability, and returns the durable child id.
33
+ * Follow-up adapters remain independently optional.
34
+ */
35
+ backgroundMode?: 'one-shot' | 'continuable';
36
+ /**
37
+ * Agent options applied to every child; omitted fields use child-loop defaults.
38
+ */
39
+ agentOptions?: AgentOptions;
40
+ /**
41
+ * Per-child persona that shadows `deployment:persona`. Requires the
42
+ * provider's `persona` capability; omission preserves the deployment persona.
43
+ */
44
+ persona?: string;
45
+ /**
46
+ * Tool filter applied to every child. Filtered tools disappear from its
47
+ * prompt and reject execution. Requires the provider's `toolFilter`
48
+ * capability; unknown names fail startup.
49
+ */
50
+ toolFilter?: {
51
+ /** Global tool names the child keeps; everything else is removed. */
52
+ allow?: string[];
53
+ /** Global tool names removed from the child. */
54
+ deny?: string[];
55
+ };
56
+ /**
57
+ * Maximum child depth: a non-negative safe integer (default `3`; `0` forbids
58
+ * delegation entirely), or `'provider-managed'` to send no cap. A numeric cap
59
+ * requires the provider's `depthLimit` capability (mount fails loud
60
+ * otherwise). The provider checks the calling agent's current depth at every
61
+ * start; the tool remains model-visible so runtime policy owns rejection.
62
+ * `'provider-managed'` is for an out-of-process provider whose recursion
63
+ * budget belongs to the child runtime or its own deployment.
64
+ */
65
+ maxDepth?: number | 'provider-managed';
66
+ }
67
+ export declare const Config: z<Config>;
68
+ export declare function apply(ctx: Context, config: Config): void;
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hydraharness/harness-tool-subagent`.
3
+ * @module @hydraharness/harness-tool-subagent/invariant
4
+ */
5
+ import type { Context } from '@hydraharness/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,69 @@
1
+ {
2
+ "name": "@hydraharness/harness-tool-subagent",
3
+ "description": "Model-facing subagent delegation tool over the ctx.subagents seam",
4
+ "hydra": {
5
+ "plugin": {
6
+ "application": "Let the agent delegate a task to a child agent."
7
+ }
8
+ },
9
+ "version": "0.1.1-rc.6",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
16
+ "directory": "packages/subagent/tool-subagent"
17
+ },
18
+ "type": "module",
19
+ "main": "lib/index.js",
20
+ "types": "lib/types/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./lib/types/index.d.ts",
24
+ "default": "./lib/index.js"
25
+ },
26
+ "./invariant": {
27
+ "types": "./lib/types/invariant.d.ts",
28
+ "default": "./lib/invariant.js"
29
+ },
30
+ "./src/*": "./src/*",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "lib/index.js",
35
+ "lib/invariant.js",
36
+ "lib/types/**/*.d.ts"
37
+ ],
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@hydraharness/harness-agent": "^0.1.1-rc.6",
41
+ "@hydraharness/harness-llm": "^0.1.1-rc.6",
42
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
43
+ "@hydraharness/harness-system-prompt": "^0.1.1-rc.6",
44
+ "@hydraharness/harness-jobs": "^0.1.1-rc.6",
45
+ "@hydraharness/cordis": "^4.0.2",
46
+ "@hydraharness/harness-subagent": "^0.1.1-rc.6",
47
+ "@hydraharness/harness-tools": "^0.1.1-rc.6"
48
+ },
49
+ "dependencies": {
50
+ "@hydraharness/schemastery": "^3.18.2"
51
+ },
52
+ "devDependencies": {
53
+ "@hydraharness/cordis-plugin-loader": "^1.0.3",
54
+ "@hydraharness/harness-agent": "^0.1.1-rc.6",
55
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
56
+ "@hydraharness/harness-llm": "^0.1.1-rc.6",
57
+ "@hydraharness/harness-session": "^0.1.1-rc.6",
58
+ "@hydraharness/harness-session-persistence": "^0.1.1-rc.6",
59
+ "@hydraharness/harness-subagent": "^0.1.1-rc.6",
60
+ "@hydraharness/harness-subagent-spawn-in-process": "^0.1.1-rc.6",
61
+ "@hydraharness/harness-system-prompt": "^0.1.1-rc.6",
62
+ "@hydraharness/harness-jobs": "^0.1.1-rc.6",
63
+ "@hydraharness/harness-tool-jobs": "^0.1.1-rc.6",
64
+ "@hydraharness/harness-session-persistence-jsonl": "^0.1.1-rc.6",
65
+ "@hydraharness/cordis": "^4.0.2",
66
+ "@hydraharness/harness-jobs-local": "^0.1.1-rc.6",
67
+ "@hydraharness/harness-tools": "^0.1.1-rc.6"
68
+ }
69
+ }