refactor(subagent): compose new children through presets
This commit is contained in:
1 parent
a9afe76200
commit
648ab5167e
122 files changed
+1234
-794
No files matched your search
+8
-8
@@ -9,14 +9,14 @@
|
||||
en: 1fe344c95a0de36d
|
||||
zh: 656c3e0f1117f64e
|
||||
/agent-note-forked-children-preserve-the-parent-request-prefix/decision:
|
||||
en: b70aafc9ce30dd00
|
||||
zh: 74e3ff915bd968a9
|
||||
en: 3f5b3a4646c9466b
|
||||
zh: 690172d6a4025b2a
|
||||
/agent-note-forked-children-preserve-the-parent-request-prefix/alternatives-considered:
|
||||
en: 80504ffd8331e6c5
|
||||
zh: 193261bdde24b4bb
|
||||
en: bc437309b64997ab
|
||||
zh: 947010eed8d6fac4
|
||||
/agent-note-forked-children-preserve-the-parent-request-prefix/consequences:
|
||||
en: 0738d36f17c889b5
|
||||
zh: b71ee47f75d6d4e0
|
||||
en: 0f4d735328b3211d
|
||||
zh: db4adb0f46b33bba
|
||||
/agent-note-forked-children-preserve-the-parent-request-prefix/consequences/accepted-risks:
|
||||
en: f634caa9973eaedb
|
||||
zh: 820291096c8e7352
|
||||
en: 72f4913b603e8596
|
||||
zh: 50924c62200070d1
|
||||
@@ -18,7 +18,7 @@ The model-facing `send_message` tool is registered globally for every Agent in a
|
||||
|
||||
The base bundle and the `cordis`, `standard`, and `ptc` CLI presets bind fork to the continuable lifecycle. Base-backed profiles, including headless, inherit that default. `ForkInProcessProvider.prepareContinuable()` and `ctx.subagents.startActivation()` preserve the same request-prefix behavior across these compositions.
|
||||
|
||||
Byte-identical prefix reuse is qualified by explicit deployment choices. A fork delegation that applies a child persona or `toolFilter` may still change the request head. In particular, filtering out `send_message` removes both the schema and the return guidance from the child; the runtime does not bypass an explicit allow-list.
|
||||
Byte-identical prefix reuse depends on the assembled inputs. Structured output or generated SDK differences can change the request head. Historical child descriptors can also restore persona or tool restrictions during cold resume; filtering out `send_message` removes both its schema and return guidance. New fork starts inherit the parent preset without these override fields.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -28,11 +28,11 @@ Byte-identical prefix reuse is qualified by explicit deployment choices. A fork
|
||||
|
||||
**Add the return instruction to the system prompt.** This would place child-only bytes ahead of inherited messages. Appending it to the initial user task preserves the inherited prefix and keeps the parent id next to the task that needs it.
|
||||
|
||||
**Ignore an explicit child `toolFilter`.** Structural return tools previously bypassed the child allow-list. Rejected because a declared tool restriction must determine both schema visibility and guidance; hidden authority would make the model-facing roster inaccurate.
|
||||
**Ignore a historical child `toolFilter` on resume.** A stored restriction must continue to determine both schema visibility and guidance; silently discarding it changes the restored agent’s behavior.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Parent and continuable-fork child expose byte-identical ordered tool schemas when the delegation does not request a persona or tool filter.
|
||||
- Parent and continuable-fork child expose byte-identical ordered tool schemas when they use the same preset and have no child-only structured output or restored historical overrides.
|
||||
- The inherited Session seed precedes the child's initial task and return guidance.
|
||||
- The base bundle and full CLI presets use local continuable fork for both parent and caller delivery, without a child-only request-head addition.
|
||||
- The child can send messages to its direct parent through `send_message`. Parent delivery adds a separate runtime settlement notice; caller delivery returns the result through the activation receipt.
|
||||
@@ -40,4 +40,4 @@ Byte-identical prefix reuse is qualified by explicit deployment choices. A fork
|
||||
|
||||
### Accepted risks
|
||||
|
||||
Provider-side prefix reuse still depends on the selected provider and model and on the absence of explicit persona or tool-filter differences. The harness proves equality of its assembled request-head inputs, not a provider's cache behavior.
|
||||
Provider-side prefix reuse still depends on the selected provider and model and matching assembled inputs. The harness tests equality of its assembled request-head inputs, not a provider’s cache behavior.
|
||||
@@ -18,7 +18,7 @@ fork 与 spawn 的差异在于:fork 会用 parent 已完成轮次的前缀作
|
||||
|
||||
base 组合包与 `cordis`、`standard` 和 `ptc` CLI preset 将 fork 绑定为可继续生命周期。基于 base 的 profile(包括 headless)继承这一默认值。`ForkInProcessProvider.prepareContinuable()` 与 `ctx.subagents.startActivation()` 在这些组合中保持相同的请求前缀行为。
|
||||
|
||||
逐字节相同的前缀复用受显式部署选择约束。配置 child persona 或 `toolFilter` 的 fork 委派仍可能改变请求头。尤其是过滤掉 `send_message` 时,child 会同时失去该 schema 与返回指引;运行时不会绕过显式 allow-list。
|
||||
逐字节相同的前缀复用取决于组装输入。结构化输出或生成 SDK 差异可以改变请求头。历史子级 descriptor 也可在冷恢复时恢复 persona 或工具限制;过滤掉 `send_message` 会同时移除其 schema 和返回指引。新 fork 启动继承父级 preset,不含这些覆盖字段。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -28,11 +28,11 @@ base 组合包与 `cordis`、`standard` 和 `ptc` CLI preset 将 fork 绑定为
|
||||
|
||||
**把返回指令放进系统提示词。** 这会在继承消息之前加入 child-only 字节。将其追加到初始用户任务,可以保留继承前缀,并让 parent id 紧邻需要它的任务。
|
||||
|
||||
**忽略显式 child `toolFilter`。** 结构性返回工具过去会绕过 child allow-list。否决该方案,因为声明的工具限制必须同时决定 schema 可见性与指引;隐藏权限会让面向模型的工具清单失真。
|
||||
**恢复时忽略历史子级 `toolFilter`。** 存储的限制必须继续决定 schema 可见性和指引;静默丢弃它会改变恢复 agent 的行为。
|
||||
|
||||
## 后果
|
||||
|
||||
- 未请求 persona 或工具过滤时,parent 与可继续 fork child 暴露逐字节相同且顺序一致的工具 schema。
|
||||
- 使用相同 preset 且没有子级专属结构化输出或恢复的历史覆盖时,父级与可继续的 fork 子级公开逐字节相同、顺序一致的工具 schema。
|
||||
- 继承的 Session 种子位于 child 初始任务及其返回指引之前。
|
||||
- base 组合包与完整 CLI preset 对 parent 和 caller 投递均使用本地可继续 fork,不增加 child-only 请求头字段。
|
||||
- child 可以通过 `send_message` 向直接 parent 发消息。parent 投递会追加独立的运行时结算通知;caller 投递通过 activation 回执返回结果。
|
||||
@@ -40,4 +40,4 @@ base 组合包与 `cordis`、`standard` 和 `ptc` CLI preset 将 fork 绑定为
|
||||
|
||||
### 已接受风险
|
||||
|
||||
提供方侧前缀复用仍取决于选定的提供方和模型,以及是否不存在显式 persona 或工具过滤差异。harness 证明的是自己组装出的请求头输入相等,而不是提供方的缓存行为。
|
||||
提供方侧前缀复用仍取决于所选提供方和模型,以及组装输入是否相同。harness 测试的是自身组装请求头输入相等,而非提供方的缓存行为。
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
en: 1188c699ce6e6ca2
|
||||
zh: 4d5b974cd1216f06
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision/one-model-tool-and-one-return-instruction:
|
||||
en: 8a4be155a4984ca8
|
||||
zh: 00c2a61da2443530
|
||||
en: 4b0a25389324fa91
|
||||
zh: c2cdd9f7e40ef43b
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision/complete-removal-and-reintroduction-condition:
|
||||
en: 5a9ec6478cf7430c
|
||||
zh: f4c2f067cc72040e
|
||||
|
||||
@@ -46,7 +46,7 @@ interface SendMessageInput {
|
||||
}
|
||||
```
|
||||
|
||||
Parents and children inherit the same definition in the same registry order. The standard definition carries a process-stable internal identity that a scoped same-name tool does not satisfy. A child `toolFilter` may explicitly remove the inherited tool, and a scoped replacement may provide different semantics; neither case receives the standard call instruction. When the standard tool remains visible, the continuation manager appends the JSON-encoded direct parent id and the instruction to send one self-contained result before finishing, plus earlier actionable findings, to the child's initial user task. For a fork child this task follows the inherited completed-turn prefix; no child-only system-prompt section or tool schema precedes that prefix.
|
||||
Parents and children inherit the same definition in the same registry order. The standard definition carries a process-stable internal identity that a scoped same-name tool does not satisfy. An independently selected preset may omit the tool, a historical descriptor filter may remove it on resume, and a scoped replacement may provide different semantics; neither case receives the standard call instruction. When the standard tool remains visible, the continuation manager appends the JSON-encoded direct parent id and the instruction to send one self-contained result before finishing, plus earlier actionable findings, to the child's initial user task. For a fork child this task follows the inherited completed-turn prefix; no child-only system-prompt section or tool schema precedes that prefix.
|
||||
|
||||
The instruction is guidance, not settlement enforcement. Sending does not end the child's turn, zero or several calls remain mechanically valid, and the runtime never rejects a child for staying silent. The manager-owned `subagent-settled` notice remains unconditional and separately attributed because it records how an Activation ended and preserves terminal output when the child cannot cooperate.
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ interface SendMessageInput {
|
||||
}
|
||||
```
|
||||
|
||||
parent 与 child 以相同注册表顺序继承相同定义。标准定义携带进程稳定的内部身份,同名的作用域工具不满足该身份。child `toolFilter` 可以显式移除继承的工具,作用域替代工具也可以提供不同语义;两种情况都不会收到标准调用指令。当标准工具仍可见时,继续执行管理器会把经过 JSON 编码的直接 parent id、结束前发送一份自包含结果的指令,以及更早发送可操作发现的指令追加到 child 初始用户任务。对 fork child 而言,该任务位于继承的已完成轮次前缀之后;没有 child 专属系统提示词 section 或工具 schema 位于此前缀之前。
|
||||
parent 与 child 以相同注册表顺序继承相同定义。标准定义携带进程稳定的内部身份,同名的作用域工具不满足该身份。独立选择的 preset 可以省略该工具,历史 descriptor 过滤器可在恢复时移除该工具,作用域替代工具也可以提供不同语义;两种情况都不会收到标准调用指令。当标准工具仍可见时,继续执行管理器会把经过 JSON 编码的直接 parent id、结束前发送一份自包含结果的指令,以及更早发送可操作发现的指令追加到 child 初始用户任务。对 fork child 而言,该任务位于继承的已完成轮次前缀之后;没有 child 专属系统提示词 section 或工具 schema 位于此前缀之前。
|
||||
|
||||
该指令是指导,不是结算强制。发送不会结束 child 轮次,机制仍允许零次或多次调用,runtime 绝不会因 child 保持沉默而拒绝它。由管理器负责的 `subagent-settled` 通知仍无条件发送并采用独立来源,因为它记录 Activation 如何结束,并在 child 无法配合时保留终态输出。
|
||||
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: 5cfe619038339caa
|
||||
zh: c2d2651d304d867c
|
||||
/agent-note-preset-scoped-schedule-tools/consequences:
|
||||
en: 546d83d28487d1c1
|
||||
zh: 438922cd4f3ff9ec
|
||||
en: d7c7e6b8e6268559
|
||||
zh: 801e5be70e00cc90
|
||||
/agent-note-preset-scoped-schedule-tools/testing:
|
||||
en: 1c5f34cf30a24fd7
|
||||
zh: 6f7a372de52d0ecd
|
||||
@@ -26,7 +26,7 @@ English | [中文](2026-09-24-preset-scoped-schedule-tools.zh.md)
|
||||
- Each preset that mounts the row pays the four schemas' fixed token cost, and tool availability is read from the composition instead of inferred from the Host service's presence.
|
||||
- A deployment can mount `dsh-schedule` for storage and delivery without granting its agents model-driven reminder management.
|
||||
- `dsh-schedule` injects no `ctx.tools` and registers no model-facing tool.
|
||||
- A delegated child cannot use the four tools through either layer: the `tool-subagent` and `tool-subagent-fork` rows in `standard`, `cordis`, and `ptc` deny all four names through `toolFilter`, and the provider applies that filter in the child scope through `ctx.tools.restrict()`, so the tools leave the child's prompt; `subagentCallerRefusal` in `dsh-tool-schedule` independently returns `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }` from every one of the four tools when the caller's delegation depth is above zero, so a call that reaches a tool anyway is refused.
|
||||
- A delegated child inherits the preset's four reminder schemas. `subagentCallerRefusal` in `dsh-tool-schedule` returns `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }` from each tool when the caller's delegation depth is above zero. The [preset composition decision](../simplification/2026-10-09-preset-owned-subagent-composition.md) removes the redundant child filter; visibility does not authorize execution.
|
||||
- A Session a delegated child owns can never receive a delivered reminder, so `ScheduleService.create` and `ScheduleService.update` refuse it with `subagent_session`. The refusal reads the delegation depth — the accounting the delegation cap itself enforces, carried by the persisted session header across a cold resume. The rule sits in the service, so other consumers reach it too — the Automation surface included.
|
||||
|
||||
## Testing
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Status: implemented
|
||||
- 挂载该行的每个 preset 承担四个 schema 的固定 token 成本,工具可用性取自组合,而不是由宿主服务是否存在推断。
|
||||
- 部署可以只为存储与投递挂载 `dsh-schedule`,而不授予其 Agent 由模型驱动的提醒管理能力。
|
||||
- `dsh-schedule` 不注入 `ctx.tools`,也不注册任何面向模型的工具。
|
||||
- 被委派的子 agent 无法通过两道中的任何一道使用这四个工具:`standard`、`cordis` 与 `ptc` 中的 `tool-subagent` 与 `tool-subagent-fork` 两行通过 `toolFilter` 拒绝全部四个名称,provider 会在子作用域内通过 `ctx.tools.restrict()` 应用该过滤,因此这些工具会从子 agent 的 prompt 中消失;`dsh-tool-schedule` 中的 `subagentCallerRefusal` 会在调用方委派深度大于零时,从四个工具中的每一个独立返回 `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }`,因此即使某个调用仍能到达工具,也会被拒绝。
|
||||
- 被委派的子 agent 继承 preset 的四个提醒 schema。调用方委派深度大于零时,`dsh-tool-schedule` 中的 `subagentCallerRefusal` 从每个工具返回 `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }`。[preset 组装决策](../simplification/2026-10-09-preset-owned-subagent-composition.zh.md)移除了重复的子级过滤;可见性不授予执行权限。
|
||||
- 被委派子 agent 拥有的 Session 永远收不到投递的提醒,因此 `ScheduleService.create` 与 `ScheduleService.update` 会以 `subagent_session` 拒绝它。该拒绝读取委派深度——委派封顶本身执行的记账,由持久化 session header 承载并跨冷恢复保留。这条规则在服务层,因此其它消费方(包括自动化任务页面)也会命中。
|
||||
|
||||
## Testing
|
||||
|
||||
+14
-14
@@ -9,29 +9,29 @@
|
||||
en: 311bc04b9c36ab53
|
||||
zh: 0c52e6493e9350a5
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision:
|
||||
en: 79b3fcfaaaaccf9e
|
||||
zh: 0287bfb41c7b9707
|
||||
en: 45111f900708aaf9
|
||||
zh: e75bed4951516963
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/persona-is-a-scoped-shadow:
|
||||
en: 8b299c99d26aa5c3
|
||||
zh: 6956efbd236cef8e
|
||||
en: f9ee31a1fdcdd38f
|
||||
zh: c9d05721cef80659
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/tool-filtering-is-one-live-global-view-rule:
|
||||
en: a9fe46283ea28722
|
||||
zh: 7b7d1be7c070b893
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/depth-is-an-absolute-tree-cap:
|
||||
en: 9145bec4e3f7eb5b
|
||||
zh: 986276925cb064d9
|
||||
en: 208c1aad9d2d94a5
|
||||
zh: 8712ca1232a20d00
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/capability-gating-keeps-providers-honest:
|
||||
en: 5334afafefdb0f59
|
||||
zh: 7e96c9a08bbc3d1a
|
||||
en: 1669c3372b728fc8
|
||||
zh: 349ee461c9307811
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/unpublished-setup-makes-the-first-request-correct:
|
||||
en: 3c988dfff07c8da3
|
||||
zh: 8ce2fbcc438e7dd8
|
||||
en: 32b0648b901c49ad
|
||||
zh: b72f29a8be987479
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/visibility-is-not-authority:
|
||||
en: d0db5310a87ff8e4
|
||||
zh: 4b9f502238cd4bae
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/alternatives-considered:
|
||||
en: 78ae41331d8fcc3e
|
||||
zh: a93ee10b3e30c267
|
||||
en: cf5006aa2b345a37
|
||||
zh: f26a9a1ab9a83a46
|
||||
/agent-note-configure-subagent-persona-tool-visibility-and-depth/consequences:
|
||||
en: d5f05d63b7a01dd8
|
||||
zh: f0be1e88b06d8cc9
|
||||
en: 58a53e46a6ce8307
|
||||
zh: 6e552d8b6ff4b186
|
||||
+12
-10
@@ -12,7 +12,7 @@ These controls affect the child's first model request and therefore cannot be in
|
||||
|
||||
## Decision
|
||||
|
||||
Subagent starts have three independent composition controls: `persona`, `toolFilter`, and `maxDepth`. A provider advertises support for each control, the service rejects unsupported requests before starting a run, and an in-process provider installs the requested composition while the child is still unpublished.
|
||||
This note retains the depth policy and the persona/tool-filter semantics needed to restore historical child descriptors. New children use [preset-owned composition](../simplification/2026-10-09-preset-owned-subagent-composition.md); new requests do not accept persona or tool filters. Historical overrides are applied while a resumed child is unpublished.
|
||||
|
||||
The controls answer different questions:
|
||||
|
||||
@@ -22,16 +22,18 @@ The controls answer different questions:
|
||||
| `toolFilter` | Which deployment-global tools enter this child's visible tool view? | A scoped restriction filters globals before child-local tools are added |
|
||||
| `maxDepth` | How deep may this delegation tree grow? | A start whose child depth exceeds the absolute cap is rejected |
|
||||
|
||||
`dsh-tool-subagent` exposes the controls as plugin configuration and copies them into each request it creates. Direct `SubagentRuntime` callers may choose them per request. The provider capability descriptor remains the source of truth for whether a backend can honor each field.
|
||||
`dsh-tool-subagent` and direct `SubagentRuntime` callers expose `maxDepth`. Its provider capability determines whether a backend can enforce the cap. Persona and tool-filter values come only from historical descriptors during cold resume.
|
||||
|
||||
### Persona is a scoped shadow
|
||||
|
||||
The persona control changes one child without changing deployment-wide prompt assembly. During unpublished setup, an in-process provider registers a child-scoped section named `deployment:persona-prefix`; ordinary most-specific-wins resolution replaces the global section only in that child's assemblies.
|
||||
A historical persona changes one resumed child without changing deployment-wide prompt assembly. During unpublished setup, the manager registers a child-scoped section named `deployment:persona-prefix`; ordinary most-specific-wins resolution replaces the inherited section only in that child’s assemblies.
|
||||
|
||||
The value has the same strict template semantics as the deployment persona. Omitting it inherits the deployment section through the global layer; an explicit empty string shadows the global persona with an empty section. Parent and sibling personas never enter the child's flat scope.
|
||||
The value has the same strict template semantics as the deployment persona. Omitting it preserves the composed persona; an explicit empty string shadows that persona with an empty section.
|
||||
|
||||
This uses the normal system-prompt registration mechanism rather than a second persona channel. The first prompt therefore sees the same named contribution that later prompts and prompt-inspection tools see.
|
||||
|
||||
<a id="tool-filtering-is-one-live-global-view-rule"></a>
|
||||
|
||||
### Tool filtering is one live global-view rule
|
||||
|
||||
The tool filter controls capability visibility and executable lookup together. An in-process provider installs `ToolRuntime.restrict()` in the child's scope before publication, and the registry's single resolver applies the same result to wire tool schemas, lookup, execution, and PTC mode SDK generation. Independently registered system-prompt sections remain owned by their plugins. The filesystem, search, and web tool plugins use the existing `PromptSection.text({ scope })` callback and `ctx.tools.get(name, scope)` to omit guidance for unavailable tools and select applicable cross-tool text. This keeps the original wording and ordering for a supported tool set and works for any agent scope, including underlying PTC capabilities whose wire presentation is `run_code`. It adds no section-ownership metadata or assembly pass; unrelated static prose is not automatically rewritten by `restrict()`.
|
||||
@@ -53,19 +55,19 @@ The depth limit bounds recursive delegation independently of tool visibility. A
|
||||
|
||||
The effective parent depth is the greater of durable `SessionHeader.delegationDepth` and runtime `AgentOptions.subagentDepth`. An in-process child records its derived depth in the session header, and resume restores that header, so a restart cannot lower the recursion count.
|
||||
|
||||
Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; loader-resolved `dsh-tool-subagent` configuration instead defaults to `3`, accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. Three is a small finite default that still permits a root plus three descendant generations, and the [base profile](../../../../packages/bundle/base/cordis.patch.yml) uses that general policy. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`.
|
||||
Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap. `dsh-tool-subagent` reads the Host `subagent.maxDepth` setting when its own cap is omitted; the setting initially equals `1`. Explicit `'provider-managed'` omits the cap for an out-of-process provider whose deployment owns its recursion budget. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`.
|
||||
|
||||
A deployment can combine depth and filtering, but the numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent's current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A deployment may separately deny delegation tools in children when its visibility policy is static. Neither choice changes the provider's conversation-history behavior.
|
||||
The numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent’s current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A preset can omit delegation tool plugins independently of the depth policy.
|
||||
|
||||
### Capability gating keeps providers honest
|
||||
|
||||
Capabilities separate a requested feature from a provider implementation. `SubagentCapabilities` advertises `persona`, `toolFilter`, and `depthLimit`; `SubagentRuntime.start()` checks every present request field against those flags before calling the provider.
|
||||
Capabilities separate a requested feature from a provider implementation. `SubagentCapabilities.depthLimit` advertises depth enforcement; `SubagentRuntime.startActivation()` checks a requested cap before calling the provider.
|
||||
|
||||
This lets spawn and fork providers share the in-process implementation while external providers advertise only what they can enforce. A request never degrades silently: selecting an unsupported control produces `UNSUPPORTED_CAPABILITY`, and no run or lifecycle event exists.
|
||||
|
||||
### Unpublished setup makes the first request correct
|
||||
|
||||
All child-local composition is complete before the child becomes observable. The in-process provider supplies one setup callback to agent creation; that callback installs persona, tool restriction, and structured-output contributions in the child's scope. Only after setup succeeds does creation publish the session and agent and allow the driver to start.
|
||||
All child-local composition is complete before the child becomes observable. The manager supplies a setup callback to agent creation that composes the preset, restores historical persona and tool restrictions on cold resume, and installs structured output. Only after setup succeeds does creation publish the session and agent and allow the driver to start.
|
||||
|
||||
A setup failure rolls back the private child. No observer can acquire a child whose first prompt used the deployment persona or unfiltered tool set and whose later prompts use the requested configuration.
|
||||
|
||||
@@ -79,7 +81,7 @@ A security design would need a separate authority representation, propagation ru
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Create one provider per persona or tool set.** This multiplies providers that share the same transport and lifecycle implementation, makes dynamic deployment configuration awkward, and still needs a recursion mechanism. Providers remain about execution transport; requests carry per-child composition.
|
||||
**Create one provider per persona or tool set.** This multiplies providers that share the same transport and lifecycle implementation and still needs a recursion mechanism. Providers remain about execution transport; presets own new child composition, while descriptors retain historical overrides.
|
||||
|
||||
**Copy the parent's complete tool view.** Registration scope is flat by design, and lifetime ownership does not imply visibility inheritance. Copying a resolved view would also freeze dynamic global registrations and conflate composition with authority without defining either contract fully.
|
||||
|
||||
@@ -91,6 +93,6 @@ A security design would need a separate authority representation, propagation ru
|
||||
|
||||
## Consequences
|
||||
|
||||
Contributors can configure child role, visible global tools, and recursion without defining new providers. Capability checks fail before ownership starts, unpublished setup makes the first request consistent, and one tool resolver prevents presentation/execution drift.
|
||||
Historical child composition survives cold resume without defining new providers. Capability checks fail before ownership starts, unpublished setup makes the first request consistent, and one tool resolver prevents presentation/execution drift.
|
||||
|
||||
The cost is that deployments must understand live allow/deny behavior and the distinction between visibility and authority. A model may call a visible delegation tool after the current depth policy forbids another child and receive an error. Provider authors must advertise each supported control accurately, and in-process providers must install every requested contribution before publication. The controls deliberately do not solve security confinement or parent-to-child non-escalation.
|
||||
+10
-10
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `maxDepth`。提供方声明对每个控制的支持情况,服务在启动运行之前拒绝不受支持的请求,进程内提供方在子 agent 尚未发布时安装所请求的组合。
|
||||
本记录保留深度策略,以及恢复历史子级 descriptor 所需的 persona/工具过滤语义。新子级使用 [preset 拥有的组装](../simplification/2026-10-09-preset-owned-subagent-composition.zh.md);新请求不接受 persona 或工具过滤。历史覆盖在恢复的子级尚未发布时应用。
|
||||
|
||||
这些控制回答不同的问题:
|
||||
|
||||
@@ -22,13 +22,13 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma
|
||||
| `toolFilter` | 部署全局工具中哪些进入该子 agent 的可见工具视图? | 一个有作用域的限制在添加子 agent 局部工具之前过滤全局工具 |
|
||||
| `maxDepth` | 这棵委派树最深可以长到多少层? | 子 agent 深度超过绝对上限时,启动请求被拒绝 |
|
||||
|
||||
`dsh-tool-subagent` 将这些控制作为插件配置暴露,并复制到它创建的每个请求中。直接调用 `SubagentRuntime` 的调用方可以按请求选择这些控制。提供方的能力描述符仍然是后端能否兑现各字段的真源。
|
||||
`dsh-tool-subagent` 和直接调用 `SubagentRuntime` 的调用方公开 `maxDepth`。对应的提供方能力决定后端是否能强制执行上限。persona 和工具过滤值仅在冷恢复时来自历史 descriptor。
|
||||
|
||||
### 人设是有作用域的遮蔽
|
||||
|
||||
人设控制改变一个子 agent 的行为,而不改变部署级的提示词组装。在未发布的设置阶段,进程内提供方在子 agent 作用域中注册一个名为 `deployment:persona-prefix` 的段落;普通的最具体者优先解析规则仅在该子 agent 的组装中替换全局段落。
|
||||
历史 persona 改变一个恢复子级的行为,而不改变部署级提示词组装。在未发布的设置阶段,管理器注册子级作用域中名为 `deployment:persona-prefix` 的段落;普通的最具体者优先解析规则只在该子级的组装中替换继承段落。
|
||||
|
||||
其值与部署人设具有相同的严格模板语义。省略时通过全局层继承部署段落;显式空字符串则以空段落遮蔽全局人设。父级和兄弟级的人设永远不会进入子 agent 的扁平作用域。
|
||||
该值与部署 persona 具有相同的严格模板语义。省略时保留组装的 persona;显式空字符串则以空段落遮蔽该 persona。
|
||||
|
||||
这使用的是常规的系统提示词注册机制,而非第二条人设通道。因此第一次提示词看到的命名贡献与后续提示词和提示词检查工具看到的一致。
|
||||
|
||||
@@ -55,19 +55,19 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma
|
||||
|
||||
有效父级深度取持久 `SessionHeader.delegationDepth` 与运行时 `AgentOptions.subagentDepth` 中的较大值。进程内子 agent 把推导出的深度记录在会话 header 中,恢复时会重新载入该 header,因此重启无法降低递归计数。
|
||||
|
||||
每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;经 loader 解析的 `dsh-tool-subagent` 配置则默认值为 `3`、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。三是一个较小的有限默认值,仍允许 root 加三代后代,[base profile](../../../../packages/bundle/base/cordis.patch.yml)采用这项通用策略。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。
|
||||
每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限。`dsh-tool-subagent` 省略自身上限时读取 Host 的 `subagent.maxDepth` 设置,其初始值为 `1`。显式 `'provider-managed'` 为部署自行管理递归预算的进程外提供方省略上限。提供方缺少 `depthLimit` 时,数值工具上限在提供方挂载阶段失败。
|
||||
|
||||
部署可以组合深度与过滤,但数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。可见性策略固定的部署可以另外在子 agent 中 deny 委派工具。两种选择都不改变提供方的对话历史行为。
|
||||
数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。preset 可独立于深度策略省略委派工具插件。
|
||||
|
||||
### 能力门控保持提供方诚实
|
||||
|
||||
能力将请求的功能与提供方实现分离。`SubagentCapabilities` 声明 `persona`、`toolFilter` 和 `depthLimit`;`SubagentRuntime.start()` 在调用提供方之前,对照这些标志检查请求中每个存在的字段。
|
||||
能力将请求的功能与提供方实现分离。`SubagentCapabilities.depthLimit` 声明深度强制执行能力;`SubagentRuntime.startActivation()` 在调用提供方前检查请求的上限。
|
||||
|
||||
这使 spawn 和 fork 提供方可以共享进程内实现,而外部提供方只声明自己能强制执行的部分。请求永远不会静默降级:选择不受支持的控制会产生 `UNSUPPORTED_CAPABILITY`,不会有运行或生命周期事件存在。
|
||||
|
||||
### 未发布设置使第一次请求正确
|
||||
|
||||
所有子 agent 局部的组合在子 agent 变得可观察之前完成。进程内提供方向 agent 创建提供一个设置回调;该回调在子 agent 作用域中安装人设、工具限制和结构化输出贡献。只有设置成功后,创建才发布会话和 agent 并允许驱动器启动。
|
||||
所有子级局部组装均在子级可观察前完成。管理器向 agent 创建提供设置回调,组装 preset,在冷恢复时恢复历史 persona 和工具限制,并安装结构化输出。只有设置成功后才发布会话和 agent,并允许驱动器启动。
|
||||
|
||||
设置失败会回滚私有子 agent。没有观察者能获取到一个「第一次提示词使用了部署人设或未过滤工具集、后续提示词才使用所请求配置」的子 agent。
|
||||
|
||||
@@ -81,7 +81,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**为每种人设或工具集创建一个提供方。** 这会使共享相同传输和生命周期实现的提供方成倍增加,使动态部署配置变得笨拙,且仍需要递归机制。提供方的职责是执行传输;请求承载每个子 agent 的组合。
|
||||
**为每种 persona 或工具集创建一个提供方。** 这会使共享同一传输和生命周期实现的提供方成倍增加,且仍需要递归机制。提供方负责执行传输;preset 拥有新子级组装,descriptor 保留历史覆盖。
|
||||
|
||||
**复制父级的完整工具视图。** 注册作用域设计上是扁平的,生命周期所有权不意味着可见性继承。复制已解析视图还会冻结动态全局注册,并在未完整定义任一约定的情况下混淆组合与授权。
|
||||
|
||||
@@ -93,6 +93,6 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma
|
||||
|
||||
## 后果
|
||||
|
||||
贡献者可以配置子 agent 的角色、可见全局工具和递归深度,而无需定义新的提供方。能力检查在所有权开始之前失败,未发布设置使第一次请求一致,单一工具解析器防止呈现/执行漂移。
|
||||
历史子级组装在冷恢复后保留,无需定义新的提供方。能力检查在所有权开始前失败,未发布设置使第一次请求一致,单一工具解析器防止呈现与执行不一致。
|
||||
|
||||
代价是部署方必须理解活跃的 allow/deny 行为以及可见性与授权的区别。当前深度策略禁止再创建子 agent 后,模型仍可能调用可见的委派工具并收到错误。提供方作者必须准确声明每个受支持的控制,进程内提供方必须在发布前安装所有请求的贡献。这些控制有意不解决安全隔离或父到子的非升权问题。
|
||||
@@ -12,8 +12,8 @@
|
||||
en: a321ee6871ef14c0
|
||||
zh: 599093af0657a675
|
||||
/agent-note-independent-spawn-preset-selection-and-restoration/alternatives-considered:
|
||||
en: f155c5366bb0a0a6
|
||||
zh: f9d265b3f45e895c
|
||||
en: 82fa00b86c8d375b
|
||||
zh: eb00a537a854dfe4
|
||||
/agent-note-independent-spawn-preset-selection-and-restoration/consequences:
|
||||
en: 57531f2f2eb7da5b
|
||||
zh: 206564a73e8d7193
|
||||
@@ -24,7 +24,7 @@ Historical children without this ordering retain the implicit join. A creation h
|
||||
|
||||
**Store a preset id in a new descriptor version.** The existing selection event and projection already persist the id. Another field would duplicate it and require a descriptor migration for a distinction the existing event order can express.
|
||||
|
||||
**Support a nested preset object.** A second configuration spelling would add precedence and validation rules without serving an existing master caller. Flat configuration and request fields preserve master's persona and toolFilter inputs; fork does not accept independent preset selection.
|
||||
**Support a nested preset object.** A second configuration spelling adds precedence and validation rules without serving an existing caller. A flat preset id selects composition; fork does not accept independent preset selection.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ spawn 子 Agent 需要可复用的 persona、工具和技能组合,同时保
|
||||
|
||||
**在新版本 descriptor 中存储 preset id。** 已有选择事件和投影已经持久化该 id。增加字段会重复存储,并为现有事件顺序已能表达的区别引入 descriptor 迁移。
|
||||
|
||||
**支持嵌套 preset 对象。** 第二种配置写法会增加优先级与校验规则,却不服务于任何 master 现有调用方。扁平配置和请求字段保留 master 的 persona 与 toolFilter 输入;fork 不接受独立 preset 选择。
|
||||
**支持嵌套 preset 对象。** 第二种配置写法会增加优先级与校验规则,却不服务于现有调用方。扁平的 preset id 选择组装;fork 不接受独立 preset 选择。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-09-preset-owned-subagent-composition.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-10-09-preset-owned-subagent-composition.md
|
||||
/agent-note-preset-owned-subagent-composition:
|
||||
en: 08d7745426af0373
|
||||
zh: 26f9afb3cc941a9a
|
||||
/agent-note-preset-owned-subagent-composition/problem:
|
||||
en: 97cd4f941fe8eaa5
|
||||
zh: b3dabbd7020a8991
|
||||
/agent-note-preset-owned-subagent-composition/decision:
|
||||
en: b5d4094a6717d4d9
|
||||
zh: 8e712256a13fdc4c
|
||||
/agent-note-preset-owned-subagent-composition/alternatives-considered:
|
||||
en: e8c558f14ee494d0
|
||||
zh: dcf35164f24fb215
|
||||
/agent-note-preset-owned-subagent-composition/consequences:
|
||||
en: bf5dc2338fb1a85c
|
||||
zh: d9eb25c6b41013af
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Agent Note: Preset-owned subagent composition
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-09-preset-owned-subagent-composition.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Agent Presets already compose persona and tool plugins. Independent child persona and tool-filter inputs create a second place to configure that composition. The shipped consumers of child filtering are the six schedule deny lists in the spawn and fork rows of the standard, PTC, and Cordis presets; reminder executors already refuse delegated callers.
|
||||
|
||||
## Decision
|
||||
|
||||
New subagent requests select an Agent Preset where the provider supports it. Persona and tools come from its plugin list. Fork inherits the parent's preset revision; model and effort selection keep their existing configuration. No preset-level filter or finer plugin split is added without a concrete consumer.
|
||||
|
||||
`dsh-tool-subagent` rejects removed `persona` and `toolFilter` keys at load, including empty values, because deleting Schemastery fields alone does not reject unknown configuration. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.md) states the lost independent override capability and the migration.
|
||||
|
||||
Released child descriptors keep their persona and filter fields, parsing, and cold-resume application. The [original composition note](../feature/2026-07-12-subagent-persona-tool-filter-and-depth.md) remains active for these historical semantics and the independent depth policy. The [spawn preset decision](../feature/2026-09-20-spawn-agent-preset-selection.md) continues to own selection and restoration identity.
|
||||
|
||||
The shipped child schedule deny lists are absent. Reminder schemas remain visible when inherited from the preset; each executor rejects delegated callers. This avoids that source of parent/fork schema differences. It establishes no measured provider cache-hit improvement and does not promise equality when other request inputs differ.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Move the filter into each preset.** This preserves two ways to select tool availability and has no shipped consumer after removing the redundant schedule deny lists. Plugin composition supplies the needed choice.
|
||||
|
||||
**Keep per-request overrides alongside presets.** This retains independent fork overrides, but also retains precedence and provider capability flags for an unused configuration path. The removal deliberately gives up that flexibility for new children.
|
||||
|
||||
**Delete historical descriptor fields with the request fields.** This would change or reject restored sessions. New-request simplification does not authorize rewriting released Session data.
|
||||
|
||||
## Consequences
|
||||
|
||||
Preset selection owns new child persona and tools. Fork cannot independently replace persona or trim inherited tools, and JavaScript configuration with the removed keys fails explicitly. Historical restoration retains its previous semantics. Focused tests cover removed-key diagnostics, historical persona/filter restoration, inherited reminder schemas and executor refusal; recorded sessions cover preset-owned tool availability and messaging guidance.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Agent Note: 由 preset 拥有 subagent 组装
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-09-preset-owned-subagent-composition.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Agent Preset 已经组合 persona 和工具插件。独立的子级 persona 和工具过滤输入又增加了一处组装配置。已交付的子级过滤消费者是 standard、PTC 和 Cordis preset 中 spawn 与 fork 配置行的六份 schedule deny 列表;提醒执行器已经拒绝委派调用方。
|
||||
|
||||
## 决策
|
||||
|
||||
新 subagent 请求在提供方支持时选择 Agent Preset。persona 和工具来自其插件列表。fork 继承父级的 preset 代际;model 和 effort 选择保留现有配置。没有具体消费者时,不添加 preset 级过滤器或更细的插件拆分。
|
||||
|
||||
`dsh-tool-subagent` 在加载时拒绝已删除的 `persona` 和 `toolFilter` 键,包括空值,因为仅删除 Schemastery 字段不会拒绝未知配置。[升级指南](../../../../docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.zh.md)说明失去的独立覆盖能力及迁移方法。
|
||||
|
||||
已发布的子级 descriptor 保留 persona 和 filter 字段、解析及冷恢复应用。[原组装记录](../feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md)继续保留,以说明这些历史语义及独立的深度策略。[spawn preset 决策](../feature/2026-09-20-spawn-agent-preset-selection.zh.md)继续拥有选择与恢复身份的依据。
|
||||
|
||||
已交付配置中不含子级 schedule deny 列表。从 preset 继承的提醒 schema 仍然可见;每个执行器拒绝委派调用方。这消除了一处父级与 fork 子级 schema 差异的来源,但不代表测得了提供方缓存命中率改善,也不保证其他请求输入不同时仍然相等。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**把过滤器移入各 preset。** 这保留了两种选择工具可用性的方式,且移除重复的 schedule deny 列表后没有已交付消费者。插件组装已能提供所需选择。
|
||||
|
||||
**在 preset 之外保留逐请求覆盖。** 这保留独立的 fork 覆盖,也保留了未使用配置路径的优先级和提供方能力标志。此次删除明确放弃新子级的这项灵活性。
|
||||
|
||||
**随请求字段一起删除历史 descriptor 字段。** 这会改变或拒绝恢复的会话。简化新请求不意味着可以重写已发布的 Session 数据。
|
||||
|
||||
## 后果
|
||||
|
||||
preset 选择拥有新子级的 persona 和工具。fork 不能独立替换 persona 或裁剪继承工具,含已删除键的 JavaScript 配置会明确报错。历史恢复保留原有语义。针对性测试覆盖删除键的诊断、历史 persona/filter 恢复、继承的提醒 schema 及执行器拒绝;会话录制覆盖由 preset 控制的工具可用性和消息指引。
|
||||
@@ -771,16 +771,17 @@ describe('a forked session', () => {
|
||||
})
|
||||
|
||||
describe('a delegated child', () => {
|
||||
it('runs on the composition its parent runs on', async () => {
|
||||
it.each([false, true])('inherits preset tools with seeded=%s and refuses child reminders', async (seeded) => {
|
||||
const parent = await ctx.agents.create({
|
||||
sessionId: SessionId('preset-child-parent'),
|
||||
sessionId: SessionId(`preset-child-parent-${seeded}`),
|
||||
meta: { agentPreset: 'standard' },
|
||||
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
|
||||
})
|
||||
// Exactly what an in-process subagent driver's creation window does.
|
||||
const child = await parent.agent.ctx.agents.create({
|
||||
sessionId: SessionId('preset-child'),
|
||||
meta: childSessionMeta(parent.agent, 1, false),
|
||||
sessionId: SessionId(`preset-child-${seeded}`),
|
||||
meta: childSessionMeta(parent.agent, 1, seeded),
|
||||
...(seeded ? { seed: [], inheritedEventCount: SessionLogOffset(0) } : {}),
|
||||
setup: (agentCtx) => {
|
||||
return applyChildComposition(agentCtx, parent.agent, {})
|
||||
},
|
||||
@@ -790,6 +791,21 @@ describe('a delegated child', () => {
|
||||
// The shipped `standard` preset is the whole coding agent; an empty
|
||||
// child here is the defect, and equality alone would not catch it.
|
||||
expect(toolNames(ctx, child.agent)).toContain('bash')
|
||||
expect(ctx.tools.schemas(child.agent)).toEqual(ctx.tools.schemas(parent.agent))
|
||||
const calls = [
|
||||
['schedule_create', { prompt: 'Check', after_seconds: 60, title: 'Check' }],
|
||||
['schedule_list', {}],
|
||||
['schedule_delete', { id: 'task-1' }],
|
||||
['schedule_update', { id: 'task-1', title: 'Check' }],
|
||||
] as const
|
||||
for (const [name, args] of calls) {
|
||||
expect(toolNames(ctx, child.agent)).toContain(name)
|
||||
const result = await ctx.tools.execute({
|
||||
callId: ToolCallId(`child-${name}`), signal: new AbortController().signal,
|
||||
name, arguments: args, agent: child.agent,
|
||||
})
|
||||
expect(result.value).toEqual({ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' })
|
||||
}
|
||||
expect(child.agent.session.header.agentPreset).toBe('standard')
|
||||
} finally {
|
||||
await child.dispose()
|
||||
|
||||
@@ -3964,22 +3964,6 @@ export interface Config extends PresetSelectionConfig {
|
||||
* Agent options applied to every child; omitted fields use child-loop defaults.
|
||||
*/
|
||||
agentOptions?: AgentOptions
|
||||
/**
|
||||
* Per-child persona that shadows `deployment:persona-prefix`. Requires the
|
||||
* provider's `persona` capability; omission preserves the deployment persona.
|
||||
*/
|
||||
persona?: string
|
||||
/**
|
||||
* Tool filter applied to every child. Filtered tools disappear from its
|
||||
* prompt and reject execution. Requires the provider's `toolFilter`
|
||||
* capability; unknown names fail startup.
|
||||
*/
|
||||
toolFilter?: {
|
||||
/** Global tool names the child keeps; everything else is removed. */
|
||||
allow?: string[]
|
||||
/** Global tool names removed from the child. */
|
||||
deny?: string[]
|
||||
}
|
||||
/**
|
||||
* Maximum child depth: a non-negative safe integer (`0` forbids delegation),
|
||||
* or `'provider-managed'` to send no cap. A numeric cap
|
||||
|
||||
@@ -3966,22 +3966,6 @@ export interface Config extends PresetSelectionConfig {
|
||||
* Agent options applied to every child; omitted fields use child-loop defaults.
|
||||
*/
|
||||
agentOptions?: AgentOptions
|
||||
/**
|
||||
* Per-child persona that shadows `deployment:persona-prefix`. Requires the
|
||||
* provider's `persona` capability; omission preserves the deployment persona.
|
||||
*/
|
||||
persona?: string
|
||||
/**
|
||||
* Tool filter applied to every child. Filtered tools disappear from its
|
||||
* prompt and reject execution. Requires the provider's `toolFilter`
|
||||
* capability; unknown names fail startup.
|
||||
*/
|
||||
toolFilter?: {
|
||||
/** Global tool names the child keeps; everything else is removed. */
|
||||
allow?: string[]
|
||||
/** Global tool names removed from the child. */
|
||||
deny?: string[]
|
||||
}
|
||||
/**
|
||||
* Maximum child depth: a non-negative safe integer (`0` forbids delegation),
|
||||
* or `'provider-managed'` to send no cap. A numeric cap
|
||||
|
||||
@@ -23,8 +23,6 @@ interface SubagentCapabilities {
|
||||
readonly agentPreset: boolean
|
||||
readonly outputSchema: boolean
|
||||
readonly depthLimit: boolean
|
||||
readonly toolFilter: boolean
|
||||
readonly persona: boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -116,22 +114,6 @@ interface SubagentStartRequest {
|
||||
* start otherwise.
|
||||
*/
|
||||
readonly maxDepth?: number
|
||||
/**
|
||||
* Optional child tool scoping. Requires {@link SubagentCapabilities.toolFilter};
|
||||
* rejected at start otherwise. In-process backends apply it as a scoped
|
||||
* `tools.restrict()` in the child's creation window: the named tools vanish
|
||||
* from the child's prompt AND refuse to execute (one visibility), with loud
|
||||
* unknown-name validation.
|
||||
*/
|
||||
readonly toolFilter?: ToolRestriction
|
||||
/**
|
||||
* Optional per-child persona. Requires {@link SubagentCapabilities.persona};
|
||||
* rejected at start otherwise. In-process backends register it as a scoped
|
||||
* `deployment:persona-prefix` section on the child, SHADOWING the deployment's
|
||||
* persona for this child alone — same template semantics as the deployment
|
||||
* persona (strict `{{…}}` interpolation against the registered variables).
|
||||
*/
|
||||
readonly persona?: string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ interface SubagentCapabilities {
|
||||
readonly agentPreset: boolean
|
||||
readonly outputSchema: boolean
|
||||
readonly depthLimit: boolean
|
||||
readonly toolFilter: boolean
|
||||
readonly persona: boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -116,22 +114,6 @@ interface SubagentStartRequest {
|
||||
* start otherwise.
|
||||
*/
|
||||
readonly maxDepth?: number
|
||||
/**
|
||||
* Optional child tool scoping. Requires {@link SubagentCapabilities.toolFilter};
|
||||
* rejected at start otherwise. In-process backends apply it as a scoped
|
||||
* `tools.restrict()` in the child's creation window: the named tools vanish
|
||||
* from the child's prompt AND refuse to execute (one visibility), with loud
|
||||
* unknown-name validation.
|
||||
*/
|
||||
readonly toolFilter?: ToolRestriction
|
||||
/**
|
||||
* Optional per-child persona. Requires {@link SubagentCapabilities.persona};
|
||||
* rejected at start otherwise. In-process backends register it as a scoped
|
||||
* `deployment:persona-prefix` section on the child, SHADOWING the deployment's
|
||||
* persona for this child alone — same template semantics as the deployment
|
||||
* persona (strict `{{…}}` interpolation against the registered variables).
|
||||
*/
|
||||
readonly persona?: string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.md
|
||||
/:
|
||||
en: 0351f79f0f7acc90
|
||||
zh: c6815e557ffa1a2f
|
||||
/configure-subagent-composition-through-presets:
|
||||
en: aab975172f278acf
|
||||
zh: 4e6dad4ef487cc52
|
||||
/configure-subagent-composition-through-presets/change:
|
||||
en: e758e1d0c34846db
|
||||
zh: 93675aa1b3f7be4e
|
||||
/configure-subagent-composition-through-presets/migration:
|
||||
en: 0ae5b3e77d8c4b83
|
||||
zh: 746b44875e23dea6
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Subagent persona and toolFilter configuration is replaced by Agent Preset composition."
|
||||
---
|
||||
|
||||
# Configure subagent composition through presets
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`dsh-tool-subagent` rejects `persona` and `toolFilter` configuration. New `SubagentStartRequest` values and provider capabilities no longer expose either field. In-process spawn selects a preset through `agentPreset`; its persona and tools come from that preset's plugins. Fork inherits the parent's preset and cannot independently change persona or restrict tools. Model and reasoning-effort options are unchanged.
|
||||
|
||||
The shipped `standard`, `ptc`, and `cordis` delegation tools no longer hide `schedule_*` tools. Delegated callers still receive the existing execution refusal. Historical child Session descriptors retain their persona and tool restrictions when resumed.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Remove `persona` and `toolFilter` from `dsh-tool-subagent` rows in your profile patches and custom presets. Update programmatic callers and provider capability declarations to remove the same fields.
|
||||
2. For spawn, define an Agent Preset containing `dsh-persona` and the required tool plugins, then set the delegation tool's `agentPreset` to its id. Use `allowedAgentPresets` to offer several declared presets to the parent model. Presets select plugin compositions; there is no replacement per-child tool-name filter.
|
||||
3. For fork, remove the overrides and accept the inherited composition. If the task needs a different composition, use spawn with a preset; it does not inherit the parent's conversation.
|
||||
4. Start a new Session and delegate a task. Confirm that the child uses the selected preset's persona and tools. Existing persisted child Sessions need no manual edits.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Subagent 的 persona 与 toolFilter 配置由 Agent Preset 组装替代。"
|
||||
---
|
||||
|
||||
# 通过 preset 配置子 Agent 组装
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`dsh-tool-subagent` 拒绝 `persona` 与 `toolFilter` 配置。新的 `SubagentStartRequest` 和提供方能力声明也不再暴露这两个字段。进程内 spawn 通过 `agentPreset` 选择 preset,其 persona 与工具来自该 preset 的插件。fork 继承父方 preset,不能独立改变 persona 或限制工具。模型与推理强度选项保持不变。
|
||||
|
||||
出货的 `standard`、`ptc` 和 `cordis` 委派工具不再隐藏 `schedule_*` 工具。委派调用方仍会收到已有的执行拒绝。历史子 Session 的 descriptor 在恢复时仍应用其 persona 与工具限制。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 从 profile patch 和自定义 preset 的 `dsh-tool-subagent` 行中移除 `persona` 与 `toolFilter`。同步从程序化调用方和提供方能力声明中移除这两个字段。
|
||||
2. 对于 spawn,定义包含 `dsh-persona` 和所需工具插件的 Agent Preset,再将委派工具的 `agentPreset` 设为其 id。使用 `allowedAgentPresets` 向父模型开放多个已声明 preset。preset 选择插件组合,不提供替代的逐子级工具名过滤器。
|
||||
3. 对于 fork,移除覆盖并接受继承的组合。如果任务需要不同组合,使用带 preset 的 spawn;它不继承父方对话。
|
||||
4. 启动新 Session 并委派任务,确认子级使用所选 preset 的 persona 与工具。已有持久化子 Session 无须手动修改。
|
||||
@@ -260,7 +260,7 @@ describe('headless runner', () => {
|
||||
await test.ctx.plugin(SubagentRuntime)
|
||||
test.ctx.subagents.registerProvider({
|
||||
name: 'external', inheritsParentContext: false,
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
start: async () => ({
|
||||
id: brandString<SessionId>('settlement-child'),
|
||||
result: Promise.resolve({ output: [{ type: 'text' as const, text: 'child answer' }], stopReason: 'completed' as const }),
|
||||
|
||||
@@ -95,23 +95,11 @@
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
modelSelectionSettings: true
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: tool-subagent-fork
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: fork
|
||||
toolName: subagent_fork
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: workflow-ptc
|
||||
name: '@deepseek-ai/dsh-workflow-ptc'
|
||||
config:
|
||||
|
||||
@@ -96,23 +96,11 @@
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
modelSelectionSettings: true
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: tool-subagent-fork
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: fork
|
||||
toolName: subagent_fork
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: workflow-ptc
|
||||
name: '@deepseek-ai/dsh-workflow-ptc'
|
||||
disabled: true
|
||||
|
||||
@@ -96,23 +96,11 @@
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
modelSelectionSettings: true
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: tool-subagent-fork
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: fork
|
||||
toolName: subagent_fork
|
||||
toolFilter:
|
||||
deny:
|
||||
- schedule_create
|
||||
- schedule_delete
|
||||
- schedule_list
|
||||
- schedule_update
|
||||
- id: workflow-ptc
|
||||
name: '@deepseek-ai/dsh-workflow-ptc'
|
||||
config:
|
||||
|
||||
@@ -1297,8 +1297,6 @@ describe('out-of-process delegation boundary', () => {
|
||||
expect(providerRequest?.cwd).toBe(process.cwd())
|
||||
expect(providerRequest?.agentOptions).toBeUndefined()
|
||||
expect(providerRequest?.maxDepth).toBeUndefined()
|
||||
expect(providerRequest?.persona).toBeUndefined()
|
||||
expect(providerRequest?.toolFilter).toBeUndefined()
|
||||
expect(providerRequest).not.toHaveProperty('permission')
|
||||
expect(providerRequest).not.toHaveProperty('sandboxMode')
|
||||
expect(providerRequest).not.toHaveProperty('approvalPolicy')
|
||||
|
||||
@@ -62,8 +62,6 @@ class StubProvider implements SubagentProvider {
|
||||
agentPreset: false,
|
||||
outputSchema: options?.outputSchema ?? true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
}
|
||||
this.inheritsParentContext = options?.inheritsParentContext ?? false
|
||||
}
|
||||
|
||||
@@ -7531,7 +7531,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'SubagentCapabilities',
|
||||
declaration: 'export interface SubagentCapabilities {\n readonly agentOptions: boolean;\n readonly agentPreset: boolean;\n readonly outputSchema: boolean;\n readonly depthLimit: boolean;\n readonly toolFilter: boolean;\n readonly persona: boolean;\n}',
|
||||
declaration: 'export interface SubagentCapabilities {\n readonly agentOptions: boolean;\n readonly agentPreset: boolean;\n readonly outputSchema: boolean;\n readonly depthLimit: boolean;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SubagentCatalogEntry',
|
||||
@@ -7607,7 +7607,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'SubagentStartRequest',
|
||||
declaration: 'export interface SubagentStartRequest {\n readonly cwd?: string;\n readonly agentPreset?: string;\n readonly label?: string;\n readonly prompt: ContentBlock[];\n readonly parent: Agent;\n readonly signal: AbortSignal;\n readonly agentOptions?: AgentOptions;\n readonly outputSchema?: ObjectJsonSchema;\n readonly maxDepth?: number;\n readonly toolFilter?: ToolRestriction;\n readonly persona?: string;\n}',
|
||||
declaration: 'export interface SubagentStartRequest {\n readonly cwd?: string;\n readonly agentPreset?: string;\n readonly label?: string;\n readonly prompt: ContentBlock[];\n readonly parent: Agent;\n readonly signal: AbortSignal;\n readonly agentOptions?: AgentOptions;\n readonly outputSchema?: ObjectJsonSchema;\n readonly maxDepth?: number;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SubagentStopReason',
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
en: cf78b9dd5596495f
|
||||
zh: bd44560f8c2435e8
|
||||
/deepseek-ai-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees:
|
||||
en: b09ee4be5e522c29
|
||||
zh: ffd30b57d7fc96e8
|
||||
en: f715f25f411d50f5
|
||||
zh: 4063a0499dc47466
|
||||
/deepseek-ai-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees/read-guidance:
|
||||
en: f8961df3f3a6a671
|
||||
zh: 85c7bd418b40b089
|
||||
|
||||
@@ -136,7 +136,7 @@ Read these pages when the package-level contract is not enough. They move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
At assembly time, each guidance section checks `ctx.tools.get(name, scope)` and renders only while its tool is visible to that agent. The write paragraph recommends edit only while edit is visible. The text below is unchanged when all three tools are available; restrictions, their removal, and tool registration changes take effect on the next assembly. The same check works for direct agent restrictions and subagent `toolFilter`, including PTC capabilities behind `run_code`. The read-before-mutation sentences in write/edit describe the observation policy, not a requirement to invoke the tool named `read`. They remain when `read` is hidden: the policy still guards mutations, and another observing operation, such as `str_replace_editor` with `command: view`, can establish the same file observation. Tool visibility does not disable that precondition.
|
||||
At assembly time, each guidance section checks `ctx.tools.get(name, scope)` and renders only while its tool is visible to that agent. The write paragraph recommends edit only while edit is visible. The text below is unchanged when all three tools are available; restrictions, their removal, and tool registration changes take effect on the next assembly. The same check works for direct agent restrictions and historical subagent filters restored from descriptors, including PTC capabilities behind `run_code`. The read-before-mutation sentences in write/edit describe the observation policy, not a requirement to invoke the tool named `read`. They remain when `read` is hidden: the policy still guards mutations, and another observing operation, such as `str_replace_editor` with `command: view`, can establish the same file observation. Tool visibility does not disable that precondition.
|
||||
|
||||
##### Read guidance
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到的内容
|
||||
|
||||
组装时,每个指导段落通过 `ctx.tools.get(name, scope)` 检查对应工具,仅在该 agent 可见时输出。write 段落仅在 edit 可见时推荐 edit。三个工具都可用时,下方原文保持不变;限制的施加、解除和工具注册变化在下次组装时生效。同一检查适用于直接限制 agent 和 subagent 的 `toolFilter`,也适用于通过 `run_code` 暴露的 PTC 能力。 write/edit 中的先读后改句子描述观察策略,并非要求调用名为 `read` 的工具。隐藏 `read` 时仍保留这些句子:策略继续保护修改操作,其他产生观察记录的操作(例如 `str_replace_editor` 的 `command: view`)也能建立同一文件观察记录。工具可见性不会禁用该前置条件。
|
||||
组装时,每个指导段落通过 `ctx.tools.get(name, scope)` 检查对应工具,仅在该 agent 可见时输出。write 段落仅在 edit 可见时推荐 edit。三个工具都可用时,下方原文保持不变;限制的施加、解除和工具注册变化在下次组装时生效。同一检查适用于直接限制 agent 和从历史 descriptor 恢复的 subagent 过滤器,也适用于通过 `run_code` 暴露的 PTC 能力。 write/edit 中的先读后改句子描述观察策略,并非要求调用名为 `read` 的工具。隐藏 `read` 时仍保留这些句子:策略继续保护修改操作,其他产生观察记录的操作(例如 `str_replace_editor` 的 `command: view`)也能建立同一文件观察记录。工具可见性不会禁用该前置条件。
|
||||
|
||||
##### Read 指导
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
en: 2fbf2175168e8244
|
||||
zh: 9c4c1964ae59e661
|
||||
/deepseek-ai-dsh-tool-schedule/known-limitations-and-deferred-work:
|
||||
en: b1631143d20598e5
|
||||
zh: 39da52b36da27b03
|
||||
en: 7623c6ce4dc40597
|
||||
zh: ad91348cb37d0ec6
|
||||
/deepseek-ai-dsh-tool-schedule/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: b2ee0fbaa165910d
|
||||
@@ -117,7 +117,6 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
- **Deletion does not retract a queued message** — a reminder the Host already delivered stays in the Session inbox after `schedule_delete`.
|
||||
- **Reminder timing belongs to the Host** — the tools expose no target-time correction, clock source, or delivery retry; those limits are the service's.
|
||||
- **Every tool refuses a delegated caller** — each of `schedule_create`, `schedule_list`, `schedule_update`, and `schedule_delete` reads the calling Agent's delegation depth before dispatch and returns `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }` when that depth is above zero. The guard lives in this package and reads `delegationDepthOf` from `@deepseek-ai/dsh-subagent`, the same accounting the delegation cap enforces, so it holds under any preset that mounts the tools.
|
||||
- **A preset's delegation rows remove the four tools from a child's prompt** — the `standard`, `cordis`, and `ptc` presets declare `toolFilter.deny` for `schedule_create`, `schedule_delete`, `schedule_list`, and `schedule_update` on both the `tool-subagent` and `tool-subagent-fork` rows. The provider applies that filter through `ctx.tools.restrict()` in the child scope, so the four tools leave a delegated child's prompt; delegating further on that chain intersects the same restriction.
|
||||
- **A Session a delegated child owns cannot arm a reminder** — `ScheduleService.create` throws `ScheduleInputError` with code `subagent_session`, and `ScheduleService.update` returns the non-mutating `subagent_session` result, when the Session's Agent has a delegation depth above zero. The delegation depth is the accounting the delegation cap itself reads, and the persisted session header carries it across a cold resume. The rule sits in the service, not in these tools, so other in-process consumers reach it too: the Automation surface included. `schedule_list` and `schedule_delete` still serve that Session, so a reminder stored before this rule stays removable.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -117,7 +117,6 @@ kind: "package-reference"
|
||||
- **删除不会撤回已排队的消息** —— 宿主已经投递的提醒在 `schedule_delete` 之后仍留在 Session 收件箱中。
|
||||
- **提醒时间由宿主负责** —— 这些工具不提供目标时间校正、时钟来源或投递重试;这些限制属于该服务。
|
||||
- **每个工具都拒绝被委派的调用方** —— `schedule_create`、`schedule_list`、`schedule_update` 与 `schedule_delete` 都会在派发前读取调用方 Agent 的委派深度,当该深度大于零时返回 `{ code: 'subagent_session', message: 'A delegated subagent cannot use reminders.' }`。该守卫位于本包内,读取来自 `@deepseek-ai/dsh-subagent` 的 `delegationDepthOf`,即委派封顶本身执行的同一份记账,因此在任何挂载这些工具的 preset 下都成立。
|
||||
- **preset 的委派行让这四个工具从子 agent 的 prompt 中消失** —— `standard`、`cordis` 与 `ptc` preset 在 `tool-subagent` 与 `tool-subagent-fork` 两行上都声明 `toolFilter.deny`,拒绝 `schedule_create`、`schedule_delete`、`schedule_list` 和 `schedule_update`。provider 会在子作用域内通过 `ctx.tools.restrict()` 应用该过滤,因此这四个工具会从被委派子 agent 的 prompt 中消失;在子链上继续委派会沿链求交同一限制。
|
||||
- **被委派子 agent 拥有的 Session 不能设置提醒** —— 当 Session 的 Agent 委派深度大于零时,`ScheduleService.create` 会抛出 `ScheduleInputError`(code 为 `subagent_session`),`ScheduleService.update` 则返回非变更的 `subagent_session` 结果。委派深度正是委派封顶本身读取的同一份记账,持久化 session header 让它在冷恢复后仍然成立。这条规则在服务层而非这些工具里,因此其它进程内消费方(包括自动化任务页面)也会命中。`schedule_list` 与 `schedule_delete` 仍可服务该 Session,所以在这条规则之前存储的提醒仍可删除。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -63,7 +63,7 @@ try {
|
||||
});
|
||||
const unregister = ctx.subagents.registerProvider({
|
||||
name: "built-local",
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
prepareContinuable() { return Promise.resolve({}); },
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ async function settleSubagent(
|
||||
const result = Promise.withResolvers<SubagentResult>()
|
||||
const disposeProvider = ctx.subagents.registerProvider({
|
||||
name: info.provider,
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
async start() {
|
||||
return {
|
||||
@@ -712,7 +712,7 @@ describe('HarnessSdkJsonRpcServer', () => {
|
||||
name: 'reused',
|
||||
capabilities: {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false,
|
||||
depthLimit: false, toolFilter: false, persona: false,
|
||||
depthLimit: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
start() {
|
||||
@@ -811,7 +811,7 @@ describe('HarnessSdkJsonRpcServer', () => {
|
||||
name: 'reused-provider',
|
||||
capabilities: {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false,
|
||||
depthLimit: false, toolFilter: false, persona: false,
|
||||
depthLimit: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.resolve({
|
||||
@@ -831,7 +831,7 @@ describe('HarnessSdkJsonRpcServer', () => {
|
||||
name: 'reused-provider',
|
||||
capabilities: {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false,
|
||||
depthLimit: false, toolFilter: false, persona: false,
|
||||
depthLimit: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.resolve({
|
||||
@@ -915,7 +915,7 @@ describe('HarnessSdkJsonRpcServer', () => {
|
||||
name: 'fork',
|
||||
capabilities: {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false,
|
||||
depthLimit: false, toolFilter: false, persona: false,
|
||||
depthLimit: false,
|
||||
},
|
||||
inheritsParentContext: true,
|
||||
start: () => Promise.resolve({
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 78623be74507229d
|
||||
zh: b0ed9c9d0c6eb040
|
||||
/deepseek-ai-dsh-subagent-acp/use-this-package/when-to-choose-it:
|
||||
en: 56946ab7637bb8f9
|
||||
zh: c54c4da98092dd9c
|
||||
en: d18adbe82d4a362d
|
||||
zh: 3b9c0cca1f67a587
|
||||
/deepseek-ai-dsh-subagent-acp/use-this-package/configuration:
|
||||
en: 8d725f040ad14301
|
||||
zh: 379664b42a38b3e5
|
||||
@@ -57,8 +57,8 @@
|
||||
en: 05e6180f1498fdf9
|
||||
zh: 2b05d2b97bfa67da
|
||||
/deepseek-ai-dsh-subagent-acp/model-experience/child-agent-request/what-the-model-sees:
|
||||
en: f7f619ca78d74c6f
|
||||
zh: f642dc7b4ff69a10
|
||||
en: 5d8a1acb2e0510ce
|
||||
zh: 34b446c2770f35ff
|
||||
/deepseek-ai-dsh-subagent-acp/model-experience/child-agent-request/token-effect:
|
||||
en: c0b78d334d5d0a9c
|
||||
zh: d54bf504933e132d
|
||||
@@ -78,8 +78,8 @@
|
||||
en: f5d5f97a153f7c70
|
||||
zh: 3d6bc05b9eadfbc2
|
||||
/deepseek-ai-dsh-subagent-acp/known-limitations-and-deferred-work:
|
||||
en: 53a0e7fb49fe6e23
|
||||
zh: 51096d4eb51fb4ba
|
||||
en: eb44a9bebc00b3f7
|
||||
zh: 6e65866e85086207
|
||||
/deepseek-ai-dsh-subagent-acp/known-limitations-and-deferred-work/dev-note:
|
||||
en: d8323b3200db6ac9
|
||||
zh: f8a5273b887b9552
|
||||
@@ -29,7 +29,7 @@ Mount this provider when a composition needs a fully isolated, out-of-process ch
|
||||
|
||||
### When to choose it
|
||||
|
||||
Choose this backend when the child must run with its own runtime, model, and tools in a separate process — for example an ACP agent from another project — or when you want delegation that cannot touch the parent harness. Choose an in-process backend when the child must share the parent's composition or honor parent-enforced capabilities: this provider advertises no optional start-time capabilities, so the seam rejects requests for `agentOptions`, structured output, depth caps, tool filters, or personas rather than silently omitting them.
|
||||
Choose this backend when the child must run with its own runtime, model, and tools in a separate process — for example an ACP agent from another project — or when you want delegation that cannot touch the parent harness. Choose an in-process backend when the child must share the parent's composition or honor parent-enforced capabilities: this provider advertises no optional start-time capabilities, so the seam rejects requests for `agentPreset`, `agentOptions`, structured output, or depth caps rather than silently omitting them.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -124,7 +124,7 @@ Read these pages when the package-level contract is not enough. They move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The remote child receives the standalone task content through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for `agentOptions`, persona, tool filtering, depth enforcement, or structured output instead of silently omitting them.
|
||||
The remote child receives the standalone task content through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for `agentPreset`, `agentOptions`, depth enforcement, or structured output instead of silently omitting them.
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -157,7 +157,7 @@ These limits define when this backend is a poor fit or needs special operational
|
||||
|
||||
- **A fresh process per run** — there is no process pooling; each delegation pays the full spawn and ACP handshake cost.
|
||||
- **Local workspaces only** — the resolved working directory is a local path handed to a child on the same machine; remote workspace mapping is not designed.
|
||||
- **No optional start-time capabilities** — this provider cannot apply `agentOptions`, `outputSchema`, a depth cap, a tool filter, or a persona inside the remote process, so the seam rejects requests that require them.
|
||||
- **No optional start-time capabilities** — this provider cannot apply `agentPreset`, `agentOptions`, `outputSchema`, or a depth cap inside the remote process, so the seam rejects requests that require them.
|
||||
- **Only committed `agent_message_chunk` text is collected** — the automation server keeps reasoning, tool activity, plans, and other trace data in the child session log rather than emitting them on ACP.
|
||||
- **Permission prompts are auto-answered** (`permission: allow | reject`) — no human is surfaced a child's `session/request_permission`.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 何时选择
|
||||
|
||||
当子 agent 必须在独立进程中运行、拥有自己的运行时、模型和工具时选择此后端——例如来自其他项目的 ACP agent——或者你希望委派完全无法触及父 harness 时。当子 agent 必须共享父级组合或遵守父级强制执行的能力约束时,请选择进程内后端:本提供方不声明任何可选启动时能力,因此 seam 会拒绝要求 `agentOptions`、结构化输出、深度上限、工具过滤或 persona 的请求,而不是静默省略。
|
||||
当子 agent 必须在独立进程中运行、拥有自己的运行时、模型和工具时选择此后端——例如来自其他项目的 ACP agent——或者你希望委派完全无法触及父 harness 时。当子 agent 必须共享父级组合或遵守父级强制执行的能力约束时,请选择进程内后端:本提供方不声明任何可选启动时能力,因此 seam 会拒绝要求 `agentPreset`、`agentOptions`、结构化输出或深度上限 的请求,而不是静默省略。
|
||||
|
||||
### 配置
|
||||
|
||||
@@ -124,7 +124,7 @@ spawn、初始化或新建会话失败会在发布前拒绝,通常先证明 ma
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
远程子 agent 通过 ACP 接收独立任务内容,并使用其自身进程配置的系统提示词、工具和全新会话。它不接收父级对话。本提供方不声明可选启动时能力,因此本地服务会拒绝要求 `agentOptions`、persona、工具过滤、深度强制或结构化输出的请求,而不是静默省略。
|
||||
远程子 agent 通过 ACP 接收独立任务内容,并使用其自身进程配置的系统提示词、工具和全新会话。它不接收父级对话。本提供方不声明可选启动时能力,因此本地服务会拒绝要求 `agentPreset`、`agentOptions`、深度强制或结构化输出的请求,而不是静默省略。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
@@ -157,7 +157,7 @@ spawn、初始化或新建会话失败会在发布前拒绝,通常先证明 ma
|
||||
|
||||
- **每次运行使用全新进程**——没有进程池;每次委派都要付出完整的 spawn 与 ACP 握手成本。
|
||||
- **仅支持本地工作区**——解析后的工作目录是交给同一台机器上子进程的本地路径;远程工作区映射尚未设计。
|
||||
- **不支持可选启动时能力**——本提供方无法在远程进程内应用 `agentOptions`、`outputSchema`、深度上限、工具过滤器或 persona,因此 seam 会拒绝需要它们的请求。
|
||||
- **不支持可选启动时能力**——本提供方无法在远程进程内应用 `agentPreset`、`agentOptions`、`outputSchema` 或深度上限,因此 seam 会拒绝需要它们的请求。
|
||||
- **只收集已提交的 `agent_message_chunk` 文本**——自动化服务器把推理(reasoning)、工具活动、计划和其他 trace 数据保留在子 agent 会话日志中,不通过 ACP 发出。
|
||||
- **权限提示自动应答**(`permission: allow | reject`)——不会把子 agent 的 `session/request_permission` 呈现给人。
|
||||
|
||||
|
||||
@@ -74,8 +74,8 @@ type ResolvedConfig = Required<Config>
|
||||
|
||||
/**
|
||||
* The ACP provider. Advertises NO start-time capabilities: an out-of-process
|
||||
* child cannot honor `agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/
|
||||
* `persona` (the service rejects a request needing any before `start` runs).
|
||||
* child cannot honor `agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`.
|
||||
* The service rejects a request needing any before execution starts.
|
||||
*/
|
||||
class AcpProvider implements SubagentProvider {
|
||||
readonly capabilities: SubagentCapabilities = {
|
||||
@@ -83,8 +83,6 @@ class AcpProvider implements SubagentProvider {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
}
|
||||
// Context contract: an out-of-process ACP child starts fresh — no parent conversation crosses the process boundary.
|
||||
readonly inheritsParentContext = false
|
||||
|
||||
@@ -1560,8 +1560,6 @@ describe('dsh-subagent-acp', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
en: 19f1316ea4bb1bb9
|
||||
zh: 181231ddffdc8494
|
||||
/deepseek-ai-dsh-subagent-claude-code/known-limitations-and-deferred-work:
|
||||
en: 37450e5856da6ead
|
||||
zh: 9eac0422397c10e2
|
||||
en: 421cfbcfb55c7e2b
|
||||
zh: df54ab7743cf6e33
|
||||
/deepseek-ai-dsh-subagent-claude-code/known-limitations-and-deferred-work/dev-note:
|
||||
en: 185beb4ff55f0946
|
||||
zh: 068ca7c57137eb33
|
||||
@@ -173,7 +173,7 @@ These limits define when this provider is a poor fit or needs special operationa
|
||||
- **The SDK platform payload is required at delegation time** — installs that omit optional dependencies, unsupported platforms, and missing or damaged payloads fail at the first query; there is no host-CLI fallback.
|
||||
- **No human interaction path** — `AskUserQuestion` is disabled, permission prompts are denied, MCP elicitation is declined, and blocking dialogs fail closed instead of suspending.
|
||||
- **Assistant payload is final text only** — failed runs can also expose a separate safe diagnostic; reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain outside the parent Session. Task identity and terminal results are retained in the parent log.
|
||||
- **No optional shared capabilities** — `agentOptions`, output schemas, child personas, tool filtering, and harness depth enforcement are rejected by the shared service for this provider.
|
||||
- **No optional shared capabilities** — `agentPreset`, `agentOptions`, output schemas, and harness depth enforcement are rejected by the shared service for this provider.
|
||||
- **No wall-clock timeout or side-effect rollback** — the caller cancels long work, and files or external systems changed before cancellation are not restored.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -173,7 +173,7 @@ Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。
|
||||
- **委派时必须存在 SDK 平台载荷**——省略 optional dependencies 的安装、不受支持的平台以及缺失或损坏的载荷都会在第一次 query 时失败;不会回退到宿主 CLI。
|
||||
- **没有人工交互路径**——`AskUserQuestion` 被禁用,权限提示会被拒绝,MCP elicitation 会被拒绝,阻塞对话会以拒绝方式失败而不会挂起。
|
||||
- **assistant 载荷仅包含最终文本**——失败运行还可公开独立的安全诊断;推理、中间消息、工具通信、用量、stderr 与工作区差异不会进入父 Session。任务身份与终态结果保存在父日志中。
|
||||
- **没有可选的共享能力**——对于本提供方,共享服务会拒绝 `agentOptions`、输出 schema、子任务角色设定、工具筛选和 harness 深度强制约束。
|
||||
- **没有可选的共享能力**——对于本提供方,共享服务会拒绝 `agentPreset`、`agentOptions`、输出 schema 和 harness 深度强制约束。
|
||||
- **没有按实际经过时间触发的超时或副作用回滚**——长时间运行的工作由调用方取消,且取消前已更改的文件或外部系统不会恢复原状。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -42,8 +42,6 @@ describe('product-provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
@@ -54,8 +52,6 @@ describe('product-provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
@@ -66,8 +62,6 @@ describe('product-provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
@@ -78,8 +72,6 @@ describe('product-provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
|
||||
@@ -450,8 +450,6 @@ describe('task admission and package contracts', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
})
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
en: 19f1316ea4bb1bb9
|
||||
zh: 181231ddffdc8494
|
||||
/deepseek-ai-dsh-subagent-codex/known-limitations-and-deferred-work:
|
||||
en: 040fff3c01eae51e
|
||||
zh: e37cf7cc47e9a08c
|
||||
en: 46cbb0a3f4f81a5a
|
||||
zh: a27faaaefe61158f
|
||||
/deepseek-ai-dsh-subagent-codex/known-limitations-and-deferred-work/dev-note:
|
||||
en: cacc98025ed8913e
|
||||
zh: e7673a3087ec0c62
|
||||
@@ -171,7 +171,7 @@ These limits define when this provider is a poor fit or needs special operationa
|
||||
- **Compatibility is pinned by development evidence** — upgrading from the verified 0.153.4 protocol baseline requires regenerating upstream schema evidence and rerunning handshake, answer-selection, approval, cancellation, keyless real-product, and credentialed DeepSeek nonce tests.
|
||||
- **No human approval path** — known unattended approval requests are denied and unknown server requests fail closed; the three Profile modes never create a DSH interaction channel or per-call allow policy.
|
||||
- **Assistant payload is final text only** — failed runs can also expose a separate safe diagnostic; reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain outside the parent Session. Task identity and terminal results are retained in the parent log.
|
||||
- **No optional shared capabilities** — `agentOptions`, output schemas, child personas, tool filtering, and harness depth enforcement are rejected by the shared service for this provider.
|
||||
- **No optional shared capabilities** — `agentPreset`, `agentOptions`, output schemas, and harness depth enforcement are rejected by the shared service for this provider.
|
||||
- **No wall-clock timeout or side-effect rollback** — the caller cancels long work, and files or external systems changed before cancellation are not restored.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -171,7 +171,7 @@ Codex 子级会在一个全新的临时线程中,以单个轮次接收这些
|
||||
- **兼容性由开发证据锁定**——若要从已验证的 0.153.4 协议基线升级,必须重新生成上游 schema 证据,并重新运行握手、答案选择、审批、取消、无密钥真实产品以及带密钥的 DeepSeek 随机数测试。
|
||||
- **没有人工审批路径**——已知的无人值守审批请求会被拒绝,未知服务器请求会以默认拒绝方式使运行失败;三种 Profile 模式都不会创建 DSH 交互通道或逐次调用 allow 策略。
|
||||
- **assistant 载荷仅包含最终文本**——失败运行还可公开独立的安全诊断;推理、中间消息、工具通信、用量、stderr 与工作区差异不会进入父 Session。任务身份与终态结果保存在父日志中。
|
||||
- **没有可选的共享能力**——对于本提供方,共享服务会拒绝 `agentOptions`、输出 schema、子任务角色设定、工具筛选和 harness 深度强制约束。
|
||||
- **没有可选的共享能力**——对于本提供方,共享服务会拒绝 `agentPreset`、`agentOptions`、输出 schema 和 harness 深度强制约束。
|
||||
- **没有按实际经过时间触发的超时或副作用回滚**——长时间运行的工作由调用方取消,且取消前已更改的文件或外部系统不会恢复原状。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -42,8 +42,6 @@ describe('Codex provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
@@ -54,8 +52,6 @@ describe('Codex provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
@@ -66,8 +62,6 @@ describe('Codex provider public Loader composition', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
},
|
||||
|
||||
@@ -441,8 +441,6 @@ describe('task admission and package contracts', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
},
|
||||
inheritsParentContext: false,
|
||||
})
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
en: d6eb16d8fc690671
|
||||
zh: 5865ee3bed0b36ca
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/when-to-choose-it:
|
||||
en: b9690fab7a8019d8
|
||||
zh: 71616c98116ec305
|
||||
en: 8e24d667f376ae01
|
||||
zh: 5b05c7dc46ebb040
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/configuration:
|
||||
en: 67cf17bbc985cad7
|
||||
zh: ea432ae524f4ade9
|
||||
@@ -57,8 +57,8 @@
|
||||
en: 05e6180f1498fdf9
|
||||
zh: 2b05d2b97bfa67da
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/model-experience/child-agent-request/what-the-model-sees:
|
||||
en: df7729fa4e60d6f6
|
||||
zh: 458094fbb7a7ea07
|
||||
en: b8e656d956ac5bd2
|
||||
zh: 26fa79357d1ce452
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/model-experience/child-agent-request/token-effect:
|
||||
en: c0b78d334d5d0a9c
|
||||
zh: 71d1a375a6ab4109
|
||||
@@ -78,8 +78,8 @@
|
||||
en: f5d5f97a153f7c70
|
||||
zh: 3d6bc05b9eadfbc2
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/known-limitations-and-deferred-work:
|
||||
en: 9158e90d1a521c05
|
||||
zh: 246ed0c694ead7ac
|
||||
en: 8f43f79098cfeb37
|
||||
zh: a5ded4086e3d8773
|
||||
/deepseek-ai-dsh-subagent-dsh-sdk/known-limitations-and-deferred-work/dev-note:
|
||||
en: 04a404ccea615ef3
|
||||
zh: df2d9d901d75f024
|
||||
@@ -29,9 +29,9 @@ Mount this provider when a delegation should run as a complete Harness runtime i
|
||||
|
||||
### When to choose it
|
||||
|
||||
Choose this backend when the child must be a full harness peer — its own composition, session persistence, model route, and tools — rather than an agent that shares the parent's process. Choose an in-process backend when the child must share the parent's composition or honor parent-enforced non-route capabilities: this provider accepts agent route options but rejects structured output, depth caps, tool filters, and personas rather than silently omitting them.
|
||||
Choose this backend when the child must be a full harness peer — its own composition, session persistence, model route, and tools — rather than an agent that shares the parent's process. Choose an in-process backend when the child must share the parent's composition or honor parent-enforced non-route capabilities: this provider accepts agent route options but rejects independent presets, structured output, and depth caps rather than silently omitting them.
|
||||
|
||||
The provider advertises `agentOptions: true`, with `agentPreset`/`outputSchema`/`depthLimit`/`toolFilter`/`persona` false, and `inheritsParentContext: false`. Its immutable `agentRouteDefaults` publish the configured provider/model baseline to `dsh-tool-subagent` before model overrides and exact-route preflight; the backend applies the same configuration defaults during activation startup, including `maxTokens`. Agent route values cross the SDK wire as an explicit whitelist; the child remains a fresh runtime in another process, and the child process uses the selected effective directory while its Session retains the parent origin directory. `dsh-tool-subagent` deployments over this provider set `maxDepth: 'provider-managed'` — the child harness owns its own recursion budget.
|
||||
The provider advertises `agentOptions: true`, with `agentPreset`/`outputSchema`/`depthLimit` false, and `inheritsParentContext: false`. Its immutable `agentRouteDefaults` publish the configured provider/model baseline to `dsh-tool-subagent` before model overrides and exact-route preflight; the backend applies the same configuration defaults during activation startup, including `maxTokens`. Agent route values cross the SDK wire as an explicit whitelist; the child remains a fresh runtime in another process, and the child process uses the selected effective directory while its Session retains the parent origin directory. `dsh-tool-subagent` deployments over this provider set `maxDepth: 'provider-managed'` — the child harness owns its own recursion budget.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -137,7 +137,7 @@ Read these pages when the package-level contract is not enough. They move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The child runtime's model receives the standalone task as its user message plus that runtime's own configured system prompt, tools, and fresh session. It receives no parent conversation. A parent tool call may choose the child provider, model, and reasoning effort for this run; the selected route and any deployment-owned output cap are fixed for the new child process. Persona, tool filtering, depth enforcement, and structured output remain unsupported and are rejected instead of silently omitted.
|
||||
The child runtime's model receives the standalone task as its user message plus that runtime's own configured system prompt, tools, and fresh session. It receives no parent conversation. A parent tool call may choose the child provider, model, and reasoning effort for this run; the selected route and any deployment-owned output cap are fixed for the new child process. Independent presets, depth enforcement, and structured output remain unsupported and are rejected instead of silently omitted.
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -169,7 +169,7 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
These limits define when this backend is a poor fit or needs special operational care. They are current package constraints, not a general SDK comparison or a task backlog.
|
||||
|
||||
- **A fresh runtime process per run** — no pooling; a harness runtime boots a full plugin tree, so per-run spawn cost is higher than the ACP backend's typical child.
|
||||
- **No non-route start-time capabilities** — the parent can select the child agent route but cannot enforce `outputSchema`, depth, tool filters, or persona inside the child process; configure the selected child profile and its ordered patches instead.
|
||||
- **No non-route start-time capabilities** — the parent can select the child agent route but cannot enforce `agentPreset`, `outputSchema`, or depth inside the child process; configure the selected child profile and its ordered patches instead.
|
||||
- **The child's transcript stays in the child's own session root** — the parent log retains the external task identity and full result, while the streamed `session.event` channel is consumed for output extraction rather than transcript replication.
|
||||
- **Local child processes only** — the resolved working directory is a local path; a remote runtime would need its own backend.
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ kind: "package-reference"
|
||||
|
||||
### 何时选择
|
||||
|
||||
当子进程必须是完整的 harness 对等体——拥有自己的组合、会话持久化、模型路由与工具——而不是共享父进程的 agent 时,选择此后端。当子进程必须共享父级组合或遵守父级强制的非路由能力时,请选择进程内后端:本提供方接受 agent 路由选项,但会拒绝结构化输出、深度上限、工具过滤或 persona,而不是静默省略。
|
||||
当子进程必须是完整的 harness 对等体——拥有自己的组合、会话持久化、模型路由与工具——而不是共享父进程的 agent 时,选择此后端。当子进程必须共享父级组合或遵守父级强制的非路由能力时,请选择进程内后端:本提供方接受 agent 路由选项,但会拒绝独立 preset、结构化输出或深度上限,而不是静默省略。
|
||||
|
||||
提供方声明 `agentOptions: true`,同时保持 `agentPreset`/`outputSchema`/`depthLimit`/`toolFilter`/`persona` 为 false,并且 `inheritsParentContext: false`。不可变的 `agentRouteDefaults` 会在模型覆盖与确切路由预检前,把配置的 provider/model 基线公开给 `dsh-tool-subagent`;后端在 activation 启动时应用同一份配置默认值,包括 `maxTokens`。agent 路由值通过显式白名单跨越 SDK 协议;子进程仍是另一进程里的全新运行时,子进程使用所选有效目录,而其 Session 保留父级起始目录。基于本提供方的 `dsh-tool-subagent` 部署应设置 `maxDepth: 'provider-managed'`——子 harness 拥有自己的递归预算。
|
||||
提供方声明 `agentOptions: true`,同时保持 `agentPreset`/`outputSchema`/`depthLimit` 为 false,并且 `inheritsParentContext: false`。不可变的 `agentRouteDefaults` 会在模型覆盖与确切路由预检前,把配置的 provider/model 基线公开给 `dsh-tool-subagent`;后端在 activation 启动时应用同一份配置默认值,包括 `maxTokens`。agent 路由值通过显式白名单跨越 SDK 协议;子进程仍是另一进程里的全新运行时,子进程使用所选有效目录,而其 Session 保留父级起始目录。基于本提供方的 `dsh-tool-subagent` 部署应设置 `maxDepth: 'provider-managed'`——子 harness 拥有自己的递归预算。
|
||||
|
||||
### 配置
|
||||
|
||||
@@ -137,7 +137,7 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
子运行时的模型会收到作为用户消息的独立任务,以及该运行时自身配置的系统提示词、工具和全新会话。它不会收到父级对话。父级工具调用可以为本次运行选择子级提供方、模型与推理强度;所选路由和由部署控制的可选输出上限会固定到这个新子进程。persona、工具过滤、深度强制与结构化输出仍不受支持,并会被拒绝而不是静默省略。
|
||||
子运行时的模型会收到作为用户消息的独立任务,以及该运行时自身配置的系统提示词、工具和全新会话。它不会收到父级对话。父级工具调用可以为本次运行选择子级提供方、模型与推理强度;所选路由和由部署控制的可选输出上限会固定到这个新子进程。独立 preset、深度强制与结构化输出仍不受支持,并会被拒绝而不是静默省略。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
@@ -169,7 +169,7 @@ kind: "package-reference"
|
||||
这些限制说明本后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用 SDK 对比或任务积压。
|
||||
|
||||
- **每次运行都使用全新的运行时进程**——不使用进程池;harness 运行时需要启动完整的插件树,因此每次运行的 spawn 成本高于 ACP 后端通常使用的子进程。
|
||||
- **不支持路由之外的启动时能力**——父级可以选择子 agent 路由,但无法在子进程内强制执行 `outputSchema`、深度限制、工具过滤或 persona;应改为配置所选子 profile 及其有序 patch。
|
||||
- **不支持路由之外的启动时能力**——父级可以选择子 agent 路由,但无法在子进程内强制执行 `agentPreset`、`outputSchema` 或深度限制;应改为配置所选子 profile 及其有序 patch。
|
||||
- **子任务转录保留在子进程自己的会话根目录中**——父日志保留外部任务身份与完整结果;流式 `session.event` 通道用于提取输出,不用于复制子任务转录。
|
||||
- **仅支持本地子进程**——解析出的工作目录是本地路径;远程运行时需要独立的后端。
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ function resolveSdkRoute(config: ResolvedConfig, requested: AgentOptions | undef
|
||||
|
||||
/**
|
||||
* The SDK provider. It resolves Agent route options into the child runtime's
|
||||
* process-wide handshake; output schema, depth, tool filter, and persona stay
|
||||
* process-wide handshake; output schema, depth, and independent preset selection stay
|
||||
* unsupported because their ownership does not cross this process boundary.
|
||||
*/
|
||||
class SdkSubagentProvider implements SubagentProvider {
|
||||
|
||||
-2
@@ -21,8 +21,6 @@ export function apply(ctx: Context, config: Config): void {
|
||||
modelSelectionSettings: true,
|
||||
...(config.toolName === undefined ? {} : { toolName: config.toolName }),
|
||||
...(config.agentOptions === undefined ? {} : { agentOptions: config.agentOptions }),
|
||||
...(config.persona === undefined ? {} : { persona: config.persona }),
|
||||
...(config.toolFilter === undefined ? {} : { toolFilter: config.toolFilter }),
|
||||
...(config.maxDepth === undefined ? {} : { maxDepth: config.maxDepth }),
|
||||
}, agent.session)
|
||||
})
|
||||
|
||||
@@ -856,8 +856,6 @@ describe('dsh-subagent-dsh-sdk provider', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
})
|
||||
await fiber.dispose()
|
||||
expect(ctx.subagents.getProvider('sdk-hmr')).toBeUndefined()
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
en: f1f45255fd9f71c1
|
||||
zh: 9a3f016b8fe9754a
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/understand-the-implementation/run-flow:
|
||||
en: ef063b5c5542e129
|
||||
zh: ba564c6b92453b44
|
||||
en: cca92ec964b4314b
|
||||
zh: edd8ecd4b59dbe8f
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/understand-the-implementation/lifecycle-binding:
|
||||
en: 8dad0155ed1b32d2
|
||||
zh: f4d82d5ede03c4d7
|
||||
@@ -54,14 +54,14 @@
|
||||
en: 91a5bd22001586e9
|
||||
zh: 456e5bf20881a70d
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/model-experience/child-agent-history-and-envelope/what-the-model-sees:
|
||||
en: fe88f3e65bb01009
|
||||
zh: 64e05acfe80e81f3
|
||||
en: d4d51c5937bab67c
|
||||
zh: e7c07b5a38d68d8e
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/model-experience/child-agent-history-and-envelope/token-effect:
|
||||
en: db9ae2edb5ee28ce
|
||||
zh: 5a342ffa3e7a7b40
|
||||
en: 19b588baaa87126b
|
||||
zh: 7010e93bbe7d71ba
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/model-experience/child-agent-history-and-envelope/kv-cache-effect:
|
||||
en: 63138e06a9c24fa0
|
||||
zh: dca65fe91ff3534b
|
||||
en: 1e8f8bbab3052b39
|
||||
zh: 1efff4da78d60ef9
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/model-experience/parent-tool-result-indirectly:
|
||||
en: 75d061c2151624a7
|
||||
zh: 71e58e020b60ff90
|
||||
@@ -75,8 +75,8 @@
|
||||
en: f5d5f97a153f7c70
|
||||
zh: 3d6bc05b9eadfbc2
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/known-limitations-and-deferred-work:
|
||||
en: 6b8bc8825865e662
|
||||
zh: 02e075e302d02fb6
|
||||
en: 193d3fea02c7ec47
|
||||
zh: 8a616bcfb97a99c1
|
||||
/deepseek-ai-dsh-subagent-fork-in-process/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -79,7 +79,7 @@ This backend supplies the balanced completed-turn prefix of the parent log as th
|
||||
|
||||
### Run flow
|
||||
|
||||
`prepareContinuable()` captures the parent’s completed-turn prefix once at creation. `startActivation()` creates the child with that seed, and the subagent service applies persona, tool filtering, and structured output for this activation. The provider advertises the same agent options, output, depth, filter, and persona capabilities as spawn.
|
||||
`prepareContinuable()` captures the parent’s completed-turn prefix once at creation. `startActivation()` creates the child with that seed, and the subagent service composes the inherited preset and optional structured output. The provider advertises agent options, output, and depth capabilities; independent preset selection is unsupported.
|
||||
|
||||
### Lifecycle binding
|
||||
|
||||
@@ -109,15 +109,15 @@ Read these pages when the package-level contract is not enough; they move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The child receives the parent's balanced completed-turn prefix, then the new task content verbatim. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and PTC mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited; an optional structured-output request adds a child-only contract; the parent's current in-flight turn is excluded.
|
||||
The child receives the parent's balanced completed-turn prefix, then the new task content verbatim. It joins the parent's preset revision, which supplies persona and tools. Parent agent-local restrictions are not copied. An optional structured-output request adds a child-only tool; the parent's current in-flight turn is excluded.
|
||||
|
||||
#### Token effect
|
||||
|
||||
Forking duplicates retained completed history into the child's request, which then accumulates its own tokens independently. A persona changes repeated prompt cost; filtering changes schema or generated SDK cost; a first-turn fork has no inherited history.
|
||||
Forking duplicates retained completed history into the child's request, which then accumulates its own tokens independently. A first-turn fork has no inherited history.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
The child may reuse the inherited byte-identical prefix under the same provider and model. Persona, tool-filter, generated-SDK, or route changes may invalidate reuse before inherited history; later child history is append-only. Continuable messaging adds no child-only system-prompt section or tool schema; the parent id and return guidance follow inherited history in the initial user task ([cache-preserving fork Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md)).
|
||||
The child may reuse the inherited byte-identical prefix under the same provider and model. Structured output, generated-SDK, route changes, or historical descriptor overrides on cold resume can invalidate reuse before inherited history; later child history is append-only. Continuable messaging adds no child-only system-prompt section or tool schema; the parent id and return guidance follow inherited history in the initial user task ([cache-preserving fork Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md)).
|
||||
|
||||
### Parent tool result, indirectly
|
||||
|
||||
@@ -141,7 +141,7 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
These limits define when the backend is the wrong choice; they are current package constraints.
|
||||
|
||||
- **The seed is a one-time snapshot** — the child sees the parent's completed turns as of the fork and nothing the parent logs afterwards; there is no live context sharing.
|
||||
- **Prefix reuse depends on matching request inputs** — parent and child messaging definitions match byte for byte; explicit persona, tool filtering, generated-SDK, or route changes can still break equality. Rationale: the [cache-preserving fork Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md).
|
||||
- **Prefix reuse depends on matching request inputs** — parent and child messaging definitions match byte for byte; structured output, generated-SDK, route changes, or restored historical overrides can still break equality. Rationale: the [cache-preserving fork Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md).
|
||||
- **Shipped fork tools do not expose child LLM route selection** — they inherit the parent's provider and model so the copied history remains eligible for KV Cache reuse. Route selection stays disabled until a change can preserve reuse or expose a bounded recomputation cost.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -79,7 +79,7 @@ kind: "package-reference"
|
||||
|
||||
### 运行流程
|
||||
|
||||
`prepareContinuable()` 在创建时捕获一次父级已完成轮次前缀。`startActivation()` 以此创建子 agent,并由 subagent 服务应用 persona、工具过滤与本次 activation 的结构化输出设置。提供方声明与 spawn 相同的 `agentOptions`、输出、深度、过滤及 persona 能力。
|
||||
`prepareContinuable()` 在创建时捕获一次父级已完成轮次前缀。`startActivation()` 以此创建子 agent,由 subagent 服务组装继承的 preset 和可选的结构化输出。提供方声明 agent 选项、输出及深度能力;不支持独立选择 preset。
|
||||
|
||||
### 生命周期绑定
|
||||
|
||||
@@ -109,15 +109,15 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
子 agent 先接收由父级已配平的已完成轮次构成的前缀,再逐字接收新的任务内容。配置的 persona 会在子 agent 的全新作用域中遮蔽提示词文本;工具限制会过滤其全局协议 schema、可执行工具查找与 PTC mode SDK 绑定,但不影响独立指导内容。父级的工具视图与权限不会被继承;可选的结构化输出请求会添加仅属于子 agent 的约定;父级当前进行中的轮次会被排除。
|
||||
子 agent 先接收由父级已配平的已完成轮次构成的前缀,再逐字接收新的任务内容。它加入父级的 preset 代际,由 preset 提供 persona 和工具。父级 agent 局部限制不会被复制。可选的结构化输出请求会添加仅属于子 agent 的工具;父级当前进行中的轮次会被排除。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
fork 会把保留的已完成历史复制到子 agent 的请求中,子 agent 随后独立累积自己的 token。persona 会改变重复提示词的成本;过滤会改变 schema 或生成 SDK 的成本;首轮 fork 没有继承历史。
|
||||
fork 会把保留的已完成历史复制到子 agent 的请求中,子 agent 随后独立累积自己的 token。首轮 fork 没有继承历史。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
在提供方与模型相同的前提下,子 agent 可以复用继承的逐字节相同前缀。persona、工具过滤、生成 SDK 或路由变化可能在继承历史之前使复用失效;后续子 agent 历史仅追加。可继续消息不会增加子级专属的系统提示词区段或工具 schema;父级 ID 与返回指导在初始用户任务中位于继承历史之后(见[保持 fork 缓存的 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md))。
|
||||
在提供方与模型相同的前提下,子 agent 可以复用继承的逐字节相同前缀。结构化输出、生成 SDK、路由变化,或冷恢复时的历史 descriptor 覆盖,可能在继承历史之前使复用失效;后续子 agent 历史仅追加。可继续消息不会增加子级专属的系统提示词区段或工具 schema;父级 ID 与返回指导在初始用户任务中位于继承历史之后(见[保持 fork 缓存的 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md))。
|
||||
|
||||
### 父级工具结果(间接)
|
||||
|
||||
@@ -141,7 +141,7 @@ fork 会把保留的已完成历史复制到子 agent 的请求中,子 agent
|
||||
这些限制说明何时选择该后端是错误的;它们是当前包约束。
|
||||
|
||||
- **初始内容是一次性快照**——子 agent 只能看到 fork 时父级已完成的轮次,看不到父级此后记录的任何内容;不会实时共享上下文。
|
||||
- **前缀复用取决于请求输入是否匹配**——父级与子级的消息定义都逐字节相同;显式 persona、工具过滤、生成 SDK 或路由变化仍可破坏相等性。理由见[保持 fork 缓存的 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md)。
|
||||
- **前缀复用取决于请求输入是否匹配**——父级与子级的消息定义都逐字节相同;结构化输出、生成 SDK、路由变化或恢复的历史覆盖仍可破坏相等性。理由见[保持 fork 缓存的 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md)。
|
||||
- **随附 fork 工具不公开子级 LLM(大语言模型)路由选择**——它们继承父级提供方与模型,使复制的历史仍有资格复用 KV Cache。在某项改动能保留复用或公开有界重算成本前,路由选择保持禁用。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
|
||||
@@ -55,8 +55,7 @@ function completedTurnPrefix(parent: Agent): SessionEvent[] {
|
||||
/**
|
||||
* The fork provider. Supports `depthLimit` and `outputSchema` (via the shared
|
||||
* in-process structured runtime), `agentOptions` (merged over the parent
|
||||
* route), and `toolFilter`/`persona` (scoped restrict() and a scoped shadowing
|
||||
* persona section).
|
||||
* route). The child inherits its parent's preset composition.
|
||||
*/
|
||||
class ForkInProcessProvider implements SubagentProvider {
|
||||
readonly capabilities: SubagentCapabilities = {
|
||||
@@ -64,8 +63,6 @@ class ForkInProcessProvider implements SubagentProvider {
|
||||
agentPreset: false,
|
||||
outputSchema: true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
}
|
||||
// Context contract: a forked child IS seeded with the parent's completed-turn prefix.
|
||||
readonly inheritsParentContext = true
|
||||
|
||||
@@ -188,8 +188,6 @@ describe('dsh-subagent-fork-in-process', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
en: 3f53b0d5f4037171
|
||||
zh: e0f2ac931cfbef95
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/use-this-package/what-a-delegation-does:
|
||||
en: 7b4442ee90e0ad7d
|
||||
zh: 03746225d4a641df
|
||||
en: 0c90404ed53cf451
|
||||
zh: 161e38b7d7c10182
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/understand-the-implementation:
|
||||
en: 7f34637751939818
|
||||
zh: 2937553168e1aee5
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/understand-the-implementation/design-concept:
|
||||
en: 67b871bbb7dcc803
|
||||
zh: 79a8fec9117ef52a
|
||||
en: a5b418fb260e2b9f
|
||||
zh: 06dd949e350e9585
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/understand-the-implementation/source-map:
|
||||
en: b7c55a4bff3ae1f0
|
||||
zh: 9d0f514f433250c6
|
||||
@@ -51,14 +51,14 @@
|
||||
en: 05e6180f1498fdf9
|
||||
zh: 2b05d2b97bfa67da
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/model-experience/child-agent-request/what-the-model-sees:
|
||||
en: 17780fc86ebce4e9
|
||||
zh: ab7e731530a02ace
|
||||
en: c9dfab9a4d363fed
|
||||
zh: 6cb3b0914ccb73e2
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/model-experience/child-agent-request/token-effect:
|
||||
en: e74d21e38e4a53a9
|
||||
zh: 6a5e3ef5fe0f4f14
|
||||
en: eb373a4b35a4b548
|
||||
zh: 9537d0e466e8bcd9
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/model-experience/child-agent-request/kv-cache-effect:
|
||||
en: 9e77f0ed85a0e994
|
||||
zh: 0cfa0a365fd7b245
|
||||
en: 6da85bad412c741e
|
||||
zh: c9af9fd1b4e7eed2
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/model-experience/parent-tool-result-indirectly:
|
||||
en: 75d061c2151624a7
|
||||
zh: 71e58e020b60ff90
|
||||
@@ -72,8 +72,8 @@
|
||||
en: f5d5f97a153f7c70
|
||||
zh: 3d6bc05b9eadfbc2
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/known-limitations-and-deferred-work:
|
||||
en: 68720f2a663a4aad
|
||||
zh: a472436c5cc2b801
|
||||
en: 0c0b19dc1ccec027
|
||||
zh: a7799c516dc3b034
|
||||
/deepseek-ai-dsh-subagent-spawn-in-process/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -53,7 +53,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
||||
|
||||
One tool call creates a background child and returns its session id immediately. The child works in its own session; the parent receives its final answer in a completion notice. The child can also send messages through `send_message`. A rejected start leaves no published child. Programmatic callers such as workflows can await the activation result directly.
|
||||
|
||||
An explicit `agentPreset` selects the currently registered preset independently; omission joins the parent's active revision. Only spawn supports this choice. Reusable persona and tools belong in the [preset definition](../../preset/agent-preset-registry/README.md); existing per-child overrides still apply. Cold resume follows the shared [child composition rules](../subagent/README.md#restoring-child-composition).
|
||||
An explicit `agentPreset` selects the currently registered preset independently; omission joins the parent's active revision. Only spawn supports this choice. Persona and tools belong in the [preset definition](../../preset/agent-preset-registry/README.md). Cold resume follows the shared [child composition rules](../subagent/README.md#restoring-child-composition).
|
||||
|
||||
-----
|
||||
|
||||
@@ -67,7 +67,7 @@ This section explains provider preparation and the activation lifecycle owned by
|
||||
|
||||
### Design concept
|
||||
|
||||
This backend registers the provider and prepares a fresh child session. `dsh-subagent` owns activation depth checks, child creation, tool and persona setup, structured output, result collection, and disposal. The caller signal cancels unpublished creation; the published handle cancels and reaches quiescence through `dispose()`.
|
||||
This backend registers the provider and prepares a fresh child session. `dsh-subagent` owns activation depth checks, child creation, preset composition, structured output, result collection, and disposal. The caller signal cancels unpublished creation; the published handle cancels and reaches quiescence through `dispose()`.
|
||||
|
||||
### Source map
|
||||
|
||||
@@ -106,15 +106,15 @@ Read these pages when the package-level contract is not enough; they move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The fresh child receives the task content verbatim as its only user message in a new empty conversation, with the parent provider, model, reasoning effort, output-token limit, and working directory by default. The inherited or selected preset supplies tools and prompt sections. A configured per-child persona shadows inherited prompt text; a tool filter restricts inherited tools in its schemas, executable lookup, and PTC mode SDK bindings while preserving child-owned registrations and independently registered guidance. No parent conversation message is included; the filter is composition, not an inherited authority grant.
|
||||
The fresh child receives the task content verbatim as its only user message in a new empty conversation, with the parent provider, model, reasoning effort, output-token limit, and working directory by default. The inherited or selected preset supplies tools and prompt sections. No parent conversation message is included.
|
||||
|
||||
#### Token effect
|
||||
|
||||
The child pays for a new independent context and history, and no parent-history token is duplicated. A persona changes the child's repeated prompt cost; a tool filter changes its schema or generated SDK cost.
|
||||
The child pays for a new independent context and history, and no parent-history token is duplicated. The preset's prompt and tools determine repeated prompt, schema, and generated SDK costs.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
The child's request cache is independent of the parent's. Child history grows append-only, while preset, persona, tool-filter, generated-SDK, provider, or model changes establish a different child prefix.
|
||||
The child's request cache is independent of the parent's. Child history grows append-only, while preset, generated-SDK, provider, or model changes establish a different child prefix.
|
||||
|
||||
### Parent tool result, indirectly
|
||||
|
||||
@@ -137,7 +137,7 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
|
||||
These limits define when the backend is the wrong choice; they are current package constraints.
|
||||
|
||||
- **Fresh means no parent transcript** — the child inherits cwd, lineage, provider, model, reasoning effort, output-token limit, and explicitly configured persona or tool restrictions, but none of the parent's conversation; use the fork backend when completed-turn context is required.
|
||||
- **Fresh means no parent transcript** — the child inherits cwd, lineage, provider, model, reasoning effort, and output-token limit, but none of the parent's conversation; use the fork backend when completed-turn context is required.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -53,7 +53,7 @@ kind: "package-reference"
|
||||
|
||||
一次工具调用创建后台子 agent 并立即返回子会话 ID。子级在自己的会话中工作,父级通过完成通知收到其最终答案。子级也可以通过 `send_message` 发送消息。被拒绝的启动不会留下已发布的子 agent;工作流等程序调用方可以直接等待 activation 的结果。
|
||||
|
||||
显式 `agentPreset` 独立选择当前注册的 preset;省略时加入父方的活跃代际。只有 spawn 支持此选择。可复用的 persona 与工具放在 [preset 定义](../../preset/agent-preset-registry/README.zh.md)中,现有逐子级覆盖仍会应用。冷恢复遵守共享的[子级组装规则](../subagent/README.zh.md#restoring-child-composition)。
|
||||
显式 `agentPreset` 独立选择当前注册的 preset;省略时加入父方的活跃代际。只有 spawn 支持此选择。persona 与工具放在 [preset 定义](../../preset/agent-preset-registry/README.zh.md)中。冷恢复遵守共享的[子级组装规则](../subagent/README.zh.md#restoring-child-composition)。
|
||||
|
||||
-----
|
||||
|
||||
@@ -67,7 +67,7 @@ kind: "package-reference"
|
||||
|
||||
### 设计理念
|
||||
|
||||
本后端注册提供方并准备全新子会话。`dsh-subagent` 拥有 activation 的深度检查、子 agent 创建、工具和 persona 设置、结构化输出、结果收集及资源释放;发布前的调用方信号取消创建,发布后的句柄通过 `dispose()` 取消并等待完全停稳。
|
||||
本后端注册提供方并准备全新子会话。`dsh-subagent` 拥有 activation 的深度检查、子 agent 创建、preset 组装、结构化输出、结果收集及资源释放;发布前的调用方信号取消创建,发布后的句柄通过 `dispose()` 取消并等待完全停稳。
|
||||
|
||||
### 源码地图
|
||||
|
||||
@@ -106,15 +106,15 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
全新子 agent 逐字接收任务内容,作为新空对话中的唯一用户消息,默认使用父级提供方、模型、推理强度、输出 token 上限与工作目录。继承或选定的 preset 提供工具和提示词片段。配置的逐子级 persona 会遮蔽继承的提示词文本;工具过滤器从其 schema、可执行工具查找与 PTC mode SDK 绑定中过滤继承工具,同时保留子级自有工具,但保留独立注册的指导内容。不包含任何父级对话消息;过滤属于组合,而非继承的权限授予。
|
||||
全新子 agent 逐字接收任务内容,作为新空对话中的唯一用户消息,默认使用父级提供方、模型、推理强度、输出 token 上限与工作目录。继承或选定的 preset 提供工具和提示词片段。不包含任何父级对话消息。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
子 agent 会为全新的独立上下文与历史消耗 token,不复制任何父级历史 token。persona 会改变该子 agent 反复使用的提示词成本;工具过滤器会改变其 schema 或生成 SDK 的成本。
|
||||
子 agent 会为全新的独立上下文与历史消耗 token,不复制任何父级历史 token。preset 的提示词和工具决定重复提示词、schema 和生成 SDK 的成本。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
子 agent 的请求缓存与父级相互独立。子 agent 历史仅追加;preset、persona、工具过滤、生成 SDK、提供方或模型变化会建立不同的子 agent 前缀。
|
||||
子 agent 的请求缓存与父级相互独立。子 agent 历史仅追加;preset、生成 SDK、提供方或模型变化会建立不同的子 agent 前缀。
|
||||
|
||||
### 父级工具结果(间接)
|
||||
|
||||
@@ -137,7 +137,7 @@ kind: "package-reference"
|
||||
|
||||
这些限制说明何时选择该后端是错误的;它们是当前包约束。
|
||||
|
||||
- **全新表示不含父级 transcript(文本记录)**——子 agent 继承 cwd、谱系、提供方、模型、推理强度、输出 token 上限及显式配置的 persona/工具限制,但不继承父级的任何对话;需要已完成轮次上下文时,请使用 fork 后端。
|
||||
- **全新表示不含父级 transcript(文本记录)**——子 agent 继承 cwd、谱系、提供方、模型、推理强度及输出 token 上限,但不继承父级的任何对话;需要已完成轮次上下文时,请使用 fork 后端。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -33,8 +33,7 @@ export const Config: z<Config> = z.object({
|
||||
* The spawn provider. Supports every start-time capability: `depthLimit` (it
|
||||
* constructs the child, so it can enforce a recursion cap), `outputSchema`
|
||||
* (the scoped structured runtime), `agentOptions` (merged over the parent
|
||||
* route), and `toolFilter`/`persona` (scoped `restrict()` and a scoped
|
||||
* shadowing persona section, applied in the child's creation window).
|
||||
* route). The selected preset owns persona and tool composition.
|
||||
*/
|
||||
class SpawnInProcessProvider implements SubagentProvider {
|
||||
readonly capabilities: SubagentCapabilities = {
|
||||
@@ -42,8 +41,6 @@ class SpawnInProcessProvider implements SubagentProvider {
|
||||
agentPreset: true,
|
||||
outputSchema: true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
}
|
||||
// Context contract: a spawned child starts fresh — it never sees the parent conversation.
|
||||
readonly inheritsParentContext = false
|
||||
|
||||
+1
-75
@@ -1,6 +1,6 @@
|
||||
import { mountWorkingDirectoryFixture } from '../../subagent/tests/working-directory-fixture.ts'
|
||||
import { mountLocalActivations, startTestActivation as start } from '../../subagent/tests/local-activation.ts'
|
||||
import { createUserMessage, type GenerateOptions } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context, symbols, type EffectMeta } from '@deepseek-ai/cordis'
|
||||
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
||||
@@ -13,7 +13,6 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
import * as spawn from '../src/index.ts'
|
||||
import { STRUCTURED_OUTPUT_TOOL } from '@deepseek-ai/dsh-subagent'
|
||||
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
type Script = ConstructorParameters<typeof MockAdapter>[0]
|
||||
|
||||
@@ -41,8 +40,6 @@ function text(blocks: readonly { type: string; text?: string }[]): string {
|
||||
return blocks.filter(b => b.type === 'text').map(b => b.text).join('')
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Invoke the child lifecycle effect while its parent-owned setup is still unpublished. */
|
||||
function disposeChildLifecycle(parent: Agent): void {
|
||||
const lifecycle = [...parent.ctx.fiber._disposables]
|
||||
@@ -54,14 +51,6 @@ function disposeChildLifecycle(parent: Agent): void {
|
||||
void lifecycle()
|
||||
}
|
||||
|
||||
|
||||
/** The system prompt a loop-built request carries as its leading system-role message ('' when none). */
|
||||
function systemPromptOf(request: GenerateOptions): string {
|
||||
const head = request.messages[0]
|
||||
if (head?.role !== 'system') return ''
|
||||
return head.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('')
|
||||
}
|
||||
|
||||
describe('dsh-subagent-spawn-in-process', () => {
|
||||
it('runs a fresh child to completion and returns its final assistant output', async () => {
|
||||
// One model call for the child: a plain text answer.
|
||||
@@ -281,8 +270,6 @@ describe('dsh-subagent-spawn-in-process', () => {
|
||||
agentPreset: true,
|
||||
outputSchema: true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -381,67 +368,6 @@ describe('dsh-subagent-spawn-in-process', () => {
|
||||
expect(typeof unwrapped.apply).toBe('function')
|
||||
})
|
||||
|
||||
describe('persona and toolFilter (the scoped child world)', () => {
|
||||
it('a per-child persona shadows the deployment persona in the child request only', async () => {
|
||||
const { ctx, parent, adapter } = await setup([
|
||||
textResponse('parent answer'),
|
||||
textResponse('child answer'),
|
||||
])
|
||||
parent.followup(createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }))
|
||||
await parent.whenIdle()
|
||||
|
||||
const run = await start(ctx, 'spawn', {
|
||||
prompt: [{ type: 'text', text: 'do X' }],
|
||||
parent,
|
||||
persona: 'You are the tersest test runner.',
|
||||
})
|
||||
await run.result
|
||||
const childRequest = adapter.requests.at(-1)!
|
||||
expect(systemPromptOf(childRequest)).toContain('You are the tersest test runner.')
|
||||
// The parent's earlier request carried no such persona.
|
||||
expect(systemPromptOf(adapter.requests[0]!)).not.toContain('tersest test runner')
|
||||
await run.dispose()
|
||||
})
|
||||
|
||||
it('toolFilter hides denied tools from the child prompt AND refuses their execution', async () => {
|
||||
const { ctx, parent, adapter } = await setup([
|
||||
// The child tries the denied tool anyway, then answers.
|
||||
toolCallResponse('c1', 'forbidden_tool', {}),
|
||||
textResponse('done'),
|
||||
])
|
||||
ctx.tools.register(defineContentToolFixture({
|
||||
name: 'forbidden_tool', description: 'global', parameters: {},
|
||||
execute: () => Promise.resolve([{ type: 'text', text: 'ran' }]),
|
||||
}))
|
||||
const run = await start(ctx, 'spawn', {
|
||||
prompt: [{ type: 'text', text: 'do X' }],
|
||||
parent,
|
||||
toolFilter: { deny: ['forbidden_tool'] },
|
||||
})
|
||||
const result = await run.result
|
||||
expect(result.stopReason).toBe('completed')
|
||||
// Not advertised…
|
||||
const childRequest = adapter.requests[0]!
|
||||
expect((childRequest.tools ?? []).map(t => t.name)).not.toContain('forbidden_tool')
|
||||
// …and the attempted call executed as UNKNOWN_TOOL (visible in the log).
|
||||
const child = run.localAgent
|
||||
const toolResult = child.session.snapshotEvents().find(e => e.type === 'tool/result')!
|
||||
expect(JSON.stringify(toolResult.data)).toContain('unknown tool')
|
||||
await run.dispose()
|
||||
})
|
||||
|
||||
it('an unknown toolFilter name fails the spawn loudly with no orphaned child', async () => {
|
||||
const { ctx, parent } = await setup([])
|
||||
const before = ctx.agents.list().length
|
||||
await expect(start(ctx, 'spawn', {
|
||||
prompt: [{ type: 'text', text: 'do X' }],
|
||||
parent,
|
||||
toolFilter: { deny: ['no_such_tool'] },
|
||||
})).rejects.toThrow(/unknown global tool "no_such_tool"/)
|
||||
expect(ctx.agents.list().length).toBe(before)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawning from a DISPOSING parent fails loud with no orphaned child (INACTIVE_EFFECT teaching error)', async () => {
|
||||
const { ctx } = await setup([])
|
||||
// A handle-owned parent we can dispose (config agents dispose with the loop fiber).
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
en: 013644290e74493e
|
||||
zh: 05014c3c3773edeb
|
||||
/deepseek-ai-dsh-subagent/use-this-package/child-presets:
|
||||
en: 117684eafea5af16
|
||||
zh: e9aca1703a86bf75
|
||||
en: a740e5e9bdd661d9
|
||||
zh: 712a5458fc8b3615
|
||||
/deepseek-ai-dsh-subagent/use-this-package/messaging-interrupting-and-discovering:
|
||||
en: 050177fbbaade432
|
||||
zh: 99f2b9f98088314d
|
||||
|
||||
@@ -62,7 +62,7 @@ At capacity, creation or cold resume rejects with `ACTIVATION_LIMIT_REACHED` (br
|
||||
|
||||
### Child presets
|
||||
|
||||
In-process spawn accepts an independent `agentPreset`; fork and external backends reject this option. Without a selection, a local child joins the parent's current preset revision. An explicit id mounts the currently available revision, even when that id matches the parent's. Configure model choices through [`dsh-tool-subagent`](../tool-subagent/README.md#selecting-a-child-preset) and persona and tools in the [preset composition](../../preset/agent-preset-registry/README.md). Existing per-child persona and tool-filter options remain available.
|
||||
In-process spawn accepts an independent `agentPreset`; fork and external backends reject this option. Without a selection, a local child joins the parent's current preset revision. An explicit id mounts the currently available revision, even when that id matches the parent's. Configure model choices through [`dsh-tool-subagent`](../tool-subagent/README.md#selecting-a-child-preset) and persona and tools in the [preset composition](../../preset/agent-preset-registry/README.md). Cold resume preserves persona and tool restrictions recorded in historical child descriptors.
|
||||
|
||||
### Messaging, interrupting, and discovering
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ kind: "package-reference"
|
||||
|
||||
### 子级 preset
|
||||
|
||||
进程内 spawn 接受独立的 `agentPreset`;fork 与外部后端拒绝此选项。省略选择时,本地子级加入父方当前的 preset 代际。显式 id 挂载当前可用代际,即使该 id 与父方相同。通过 [`dsh-tool-subagent`](../tool-subagent/README.zh.md#selecting-a-child-preset) 配置模型可选项,并在 [preset 组装](../../preset/agent-preset-registry/README.zh.md)中配置 persona 与工具。现有逐子级 persona 与 tool-filter 选项仍然可用。
|
||||
进程内 spawn 接受独立的 `agentPreset`;fork 与外部后端拒绝此选项。省略选择时,本地子级加入父方当前的 preset 代际。显式 id 挂载当前可用代际,即使该 id 与父方相同。通过 [`dsh-tool-subagent`](../tool-subagent/README.zh.md#selecting-a-child-preset) 配置模型可选项,并在 [preset 组装](../../preset/agent-preset-registry/README.zh.md)中配置 persona 与工具。冷恢复保留历史子级 descriptor 记录的 persona 和工具限制。
|
||||
|
||||
### 消息、中断与发现
|
||||
|
||||
|
||||
@@ -157,9 +157,9 @@ export function childSessionMeta(
|
||||
export interface ChildComposition {
|
||||
/** Independently mounted preset; omission shares the parent's standing generation. */
|
||||
readonly agentPreset?: string | undefined
|
||||
/** Per-child persona shadowing the deployment persona. */
|
||||
/** Historical descriptor persona reapplied only during cold resume. */
|
||||
readonly persona?: string | undefined
|
||||
/** Per-child tool scoping. */
|
||||
/** Historical descriptor tool restriction reapplied only during cold resume. */
|
||||
readonly toolFilter?: ToolRestriction | undefined
|
||||
}
|
||||
|
||||
@@ -175,12 +175,12 @@ export const SUBAGENT_DELEGATION_CONTEXT
|
||||
+ 'limitation in your reply so the delegating agent can handle it.'
|
||||
|
||||
/**
|
||||
* Compose a child before publication and apply its own persona and tool overrides.
|
||||
* Compose a child before publication and restore historical descriptor overrides.
|
||||
* Explicit selection mounts the named preset; omission synchronously joins the
|
||||
* parent's current generation. Registrations belong to the child's scope.
|
||||
* @param childCtx - the child agent's scoped creation context.
|
||||
* @param parent - the delegating parent used when no preset is selected.
|
||||
* @param composition - independent preset and per-child overrides.
|
||||
* @param composition - independent preset and any overrides read from a historical descriptor.
|
||||
* @returns A promise when mounting an independent preset; otherwise completes synchronously.
|
||||
*/
|
||||
export function applyChildComposition(
|
||||
|
||||
@@ -635,8 +635,6 @@ export class SubagentRuntime extends TypertRemoteService {
|
||||
{ when: request.agentOptions !== undefined, cap: 'agentOptions' },
|
||||
{ when: request.outputSchema !== undefined, cap: 'outputSchema' },
|
||||
{ when: request.maxDepth !== undefined, cap: 'depthLimit' },
|
||||
{ when: request.toolFilter !== undefined, cap: 'toolFilter' },
|
||||
{ when: request.persona !== undefined, cap: 'persona' },
|
||||
]
|
||||
for (const { when, cap } of needs) {
|
||||
if (when && !provider.capabilities[cap]) {
|
||||
|
||||
@@ -398,8 +398,6 @@ export class SubagentManager {
|
||||
...agentProvider !== undefined ? { agentProvider } : {},
|
||||
...agentModel !== undefined ? { agentModel } : {},
|
||||
...agentReasoningEffort !== undefined ? { agentReasoningEffort } : {},
|
||||
...request.persona !== undefined ? { persona: request.persona } : {},
|
||||
...request.toolFilter !== undefined ? { toolFilter: request.toolFilter } : {},
|
||||
})
|
||||
// Capture before the first await: a later parent switch belongs to the
|
||||
// parent's future, not to this child.
|
||||
@@ -450,7 +448,7 @@ export class SubagentManager {
|
||||
descriptor,
|
||||
},
|
||||
agentOptions,
|
||||
composition: { agentPreset: request.agentPreset, persona: request.persona, toolFilter: request.toolFilter },
|
||||
composition: { agentPreset: request.agentPreset },
|
||||
signal: spec.signal,
|
||||
delivery: spec.delivery,
|
||||
outputSchema: spec.request.outputSchema,
|
||||
|
||||
@@ -50,7 +50,7 @@ function normalizeSubagentDiagnostic(result: SubagentResult): SubagentResult {
|
||||
/**
|
||||
* The capability advertisement of an out-of-process backend: NONE. A child in
|
||||
* another process cannot honor parent-enforced start features
|
||||
* (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
|
||||
* (`agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`), so the service rejects a
|
||||
* request needing any of them before `start` runs — never accepted-then-ignored.
|
||||
*/
|
||||
export const NO_START_CAPABILITIES: SubagentCapabilities = Object.freeze({
|
||||
@@ -58,8 +58,6 @@ export const NO_START_CAPABILITIES: SubagentCapabilities = Object.freeze({
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { Agent, AgentOptions } from '@deepseek-ai/dsh-agent'
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import type { ContentBlock, MessageId } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { ObjectJsonSchema, ToolRestriction } from '@deepseek-ai/dsh-tools'
|
||||
import type { ObjectJsonSchema } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
/** Identifies one accepted subagent run across its lifecycle event pair. */
|
||||
export type SubagentRunId = Branded<'SubagentRunId'>
|
||||
@@ -126,8 +126,6 @@ export interface SubagentCapabilities {
|
||||
readonly agentPreset: boolean
|
||||
readonly outputSchema: boolean
|
||||
readonly depthLimit: boolean
|
||||
readonly toolFilter: boolean
|
||||
readonly persona: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,22 +180,6 @@ export interface SubagentStartRequest {
|
||||
* start otherwise.
|
||||
*/
|
||||
readonly maxDepth?: number
|
||||
/**
|
||||
* Optional child tool scoping. Requires {@link SubagentCapabilities.toolFilter};
|
||||
* rejected at start otherwise. In-process backends apply it as a scoped
|
||||
* `tools.restrict()` in the child's creation window: the named tools vanish
|
||||
* from the child's prompt AND refuse to execute (one visibility), with loud
|
||||
* unknown-name validation.
|
||||
*/
|
||||
readonly toolFilter?: ToolRestriction
|
||||
/**
|
||||
* Optional per-child persona. Requires {@link SubagentCapabilities.persona};
|
||||
* rejected at start otherwise. In-process backends register it as a scoped
|
||||
* `deployment:persona-prefix` section on the child, SHADOWING the deployment's
|
||||
* persona for this child alone — same template semantics as the deployment
|
||||
* persona (strict `{{…}}` interpolation against the registered variables).
|
||||
*/
|
||||
readonly persona?: string
|
||||
}
|
||||
|
||||
/** Provider-facing request with an absolute directory selected before startup. */
|
||||
|
||||
@@ -104,21 +104,6 @@ describe('a child agent composed in-process', () => {
|
||||
await run.dispose()
|
||||
})
|
||||
|
||||
it('honours a tool filter over the preset tools it inherited', async () => {
|
||||
const { adapter, parent } = await setupPresetHost()
|
||||
|
||||
const run = await startPreparedActivation(
|
||||
{ ...spawnRequest(parent), toolFilter: { deny: ['preset_only'] } },
|
||||
{},
|
||||
)
|
||||
await run.result
|
||||
|
||||
// The capability filter is the only thing bounding a delegated child, and
|
||||
// every tool it can name now arrives from the preset rather than the host.
|
||||
expect((adapter.requests.at(-1)?.tools ?? []).map(tool => tool.name)).toEqual([])
|
||||
await run.dispose()
|
||||
})
|
||||
|
||||
it('follows a parent that switched preset while blank', async () => {
|
||||
const { ctx, adapter, parent } = await setupPresetHost()
|
||||
// A DIFFERENT preset, so the assertion below distinguishes reading the
|
||||
@@ -134,14 +119,13 @@ describe('a child agent composed in-process', () => {
|
||||
await run.dispose()
|
||||
})
|
||||
|
||||
it('preserves runtime result tools when filtering an independently selected preset', async () => {
|
||||
it('registers runtime result tools alongside an independently selected preset', async () => {
|
||||
const { adapter, parent } = await setupPresetHost([
|
||||
toolCallResponse('submit-result', 'structured_output', { answer: 42 }),
|
||||
])
|
||||
const run = await startPreparedActivation({
|
||||
...spawnRequest(parent),
|
||||
agentPreset: 'reviewing',
|
||||
toolFilter: { deny: ['reviewing_only'] },
|
||||
outputSchema: {
|
||||
type: 'object',
|
||||
properties: { answer: { type: 'number' } },
|
||||
@@ -151,7 +135,7 @@ describe('a child agent composed in-process', () => {
|
||||
})
|
||||
try {
|
||||
expect((await run.result).structured).toEqual({ answer: 42 })
|
||||
expect(adapter.requests[0]?.tools?.map(tool => tool.name)).toEqual(['structured_output'])
|
||||
expect(adapter.requests[0]?.tools?.map(tool => tool.name)).toEqual(['reviewing_only', 'structured_output'])
|
||||
} finally {
|
||||
await run.dispose()
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ describe('continuable child presets', () => {
|
||||
child.session.append('subagent/descriptor', snapshotSubagentDescriptor({
|
||||
mode: 'continuable', provider: 'separate', label: 'historical',
|
||||
agentProvider: 'mock', agentModel: 'mock', persona: 'Historical child persona.',
|
||||
toolFilter: { deny: ['reviewing_only'] },
|
||||
}))
|
||||
ctx.agentPresets.composeFrom(childCtx, parent.ctx)
|
||||
},
|
||||
@@ -244,7 +245,7 @@ describe('continuable child presets', () => {
|
||||
const stored = await settled(ctx, childId)
|
||||
expect(stored.meta.agentPreset).toBe(agentPreset)
|
||||
expect(stored.events.some(event => event.type === 'agent-preset/selected')).toBe(false)
|
||||
expect(adapter.requests[0]?.tools?.map(tool => tool.name)).toEqual(['reviewing_only'])
|
||||
expect((adapter.requests[0]?.tools ?? []).map(tool => tool.name)).toEqual([])
|
||||
expect(JSON.stringify(adapter.requests[0]?.messages.filter(message => message.role === 'system')))
|
||||
.toContain('Historical child persona.')
|
||||
})
|
||||
|
||||
@@ -568,7 +568,7 @@ describe('SubagentRuntime.startActivation', () => {
|
||||
const start = vi.fn(async () => { throw new Error('must not dispatch') })
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'one-shot',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start,
|
||||
})
|
||||
@@ -784,8 +784,6 @@ describe('SubagentRuntime.startActivation', () => {
|
||||
|
||||
it('omits undeclared composition fields from the descriptor', async () => {
|
||||
const { ctx } = await setup([])
|
||||
// A routeless parent declares no provider/model, and this start declares no
|
||||
// persona or tool filter, so the descriptor records only what exists.
|
||||
const routeless = await ctx.agentLoop.create(SessionId('routeless'), {})
|
||||
const started = await ctx.subagents.startActivation({ ...startSpec(routeless), delivery: 'parent' })
|
||||
const child = await vi.waitFor(() => {
|
||||
@@ -804,42 +802,6 @@ describe('SubagentRuntime.startActivation', () => {
|
||||
await drainManager(ctx)
|
||||
})
|
||||
|
||||
it('records a declared tool filter in the descriptor', async () => {
|
||||
const { ctx } = await setup([])
|
||||
// Register one global tool so the filter names something real.
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'noop',
|
||||
description: 'does nothing',
|
||||
parameters: {},
|
||||
output: {
|
||||
schema: { type: 'object', additionalProperties: false, properties: {} },
|
||||
render: () => [{ type: 'text', text: 'noop' }],
|
||||
},
|
||||
execute: () => Promise.resolve({}),
|
||||
}))
|
||||
const routeless = await ctx.agentLoop.create(SessionId('routeless-filtered'), {})
|
||||
const started = await ctx.subagents.startActivation({
|
||||
delivery: 'parent',
|
||||
...startSpec(routeless),
|
||||
request: { prompt: message('filtered work'), parent: routeless, toolFilter: { deny: ['noop'] } },
|
||||
})
|
||||
const child = await vi.waitFor(() => {
|
||||
const found = ctx.agents.get(started.childId)
|
||||
expect(found).toBeDefined()
|
||||
return found!
|
||||
})
|
||||
|
||||
expect(child.session.snapshotEvents().find(event => event.type === 'subagent/descriptor')?.data)
|
||||
.toEqual({
|
||||
version: SUBAGENT_DESCRIPTOR_VERSION,
|
||||
mode: 'continuable',
|
||||
provider: 'spawn',
|
||||
label: 'child task',
|
||||
toolFilter: { deny: ['noop'] },
|
||||
})
|
||||
await drainManager(ctx)
|
||||
})
|
||||
|
||||
it('cold-resumes without inventing a model route the descriptor never declared', async () => {
|
||||
const { ctx, root } = await setup([textResponse('first')])
|
||||
const routeless = await ctx.agentLoop.create(SessionId('routeless-resume'), {})
|
||||
@@ -899,29 +861,6 @@ describe('SubagentRuntime.startActivation', () => {
|
||||
expect(loaded.inheritedEventCount).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('records the declared persona in the descriptor and reapplies it on cold resume', async () => {
|
||||
const { ctx, parent } = await setup([textResponse('scoped'), textResponse('resumed')])
|
||||
const started = await ctx.subagents.startActivation({
|
||||
delivery: 'parent',
|
||||
...startSpec(parent),
|
||||
request: {
|
||||
prompt: message('scoped work'),
|
||||
parent,
|
||||
persona: 'You are scoped.',
|
||||
},
|
||||
})
|
||||
await waitNoActivation(ctx, started.childId)
|
||||
|
||||
const loaded = await loadStoredSession(ctx.sessionPersistence, started.childId)
|
||||
const descriptor = loaded.events.find(event => event.type === 'subagent/descriptor')
|
||||
expect(descriptor?.data).toMatchObject({ persona: 'You are scoped.' })
|
||||
|
||||
// Cold resume reconstructs the declared composition from that descriptor.
|
||||
await queuePrompt(ctx, parent, started.childId, message('resume it'))
|
||||
await waitNoActivation(ctx, started.childId)
|
||||
const resumed = await loadStoredSession(ctx.sessionPersistence, started.childId)
|
||||
expect(hasUserText(resumed.events, 'resume it')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('continuable image Queue prompts', () => {
|
||||
@@ -1111,7 +1050,7 @@ describe('direct-child Queue residency routing', () => {
|
||||
const { ctx, parent } = await setup([textResponse('first'), textResponse('after resume')])
|
||||
const disposeProvider = ctx.subagents.registerProvider({
|
||||
name: 'retired',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => { throw new Error('one-shot start is not used') },
|
||||
prepareContinuable: () => Promise.resolve({}),
|
||||
@@ -3627,7 +3566,7 @@ describe('continuable errors', () => {
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'external', inheritsParentContext: false,
|
||||
capabilities: {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false,
|
||||
agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false,
|
||||
},
|
||||
start: async ({ signal }) => {
|
||||
entered.resolve(signal)
|
||||
|
||||
@@ -12,7 +12,7 @@ import { externalTestParent } from './external-activation-helpers.ts'
|
||||
import { subagentManager } from './manager-internals.ts'
|
||||
|
||||
const capabilities: SubagentCapabilities = {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false,
|
||||
agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false,
|
||||
}
|
||||
|
||||
async function setup(start: (request: SubagentStartRequest) => Promise<SubagentRun>) {
|
||||
@@ -338,7 +338,6 @@ describe('external subagent activations', () => {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it('records external membership at creation without publishing settlement updates', async () => {
|
||||
const backend = execution()
|
||||
const fixture = await setup(async () => backend.run)
|
||||
@@ -363,7 +362,6 @@ it('records external membership at creation without publishing settlement update
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
it('refuses a reserved Session id for an external provider before dispatch', async () => {
|
||||
const start = vi.fn()
|
||||
const fixture = await setup(start)
|
||||
|
||||
@@ -20,7 +20,7 @@ async function setup(start: (request: SubagentStartRequest) => Promise<SubagentR
|
||||
const parent = await externalTestParent(ctx)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'external', inheritsParentContext: false,
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
start,
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -52,7 +52,7 @@ export function startPreparedActivation(request: SubagentStartRequest, prepared:
|
||||
const unregister = ctx.get('subagents')!.registerProvider({
|
||||
name: provider,
|
||||
capabilities: {
|
||||
agentOptions: true, agentPreset: prepared.seed === undefined, outputSchema: true, depthLimit: true, toolFilter: true, persona: true,
|
||||
agentOptions: true, agentPreset: prepared.seed === undefined, outputSchema: true, depthLimit: true,
|
||||
},
|
||||
inheritsParentContext: prepared.seed !== undefined,
|
||||
prepareContinuable: () => Promise.resolve(prepared),
|
||||
|
||||
@@ -24,8 +24,6 @@ describe('NO_START_CAPABILITIES', () => {
|
||||
agentPreset: false,
|
||||
outputSchema: false,
|
||||
depthLimit: false,
|
||||
toolFilter: false,
|
||||
persona: false,
|
||||
})
|
||||
expect(Object.isFrozen(NO_START_CAPABILITIES)).toBe(true)
|
||||
})
|
||||
@@ -75,7 +73,6 @@ describe('child cwd resolution', () => {
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
describe('settleRunResult', () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent
|
||||
import SubagentRuntime from '../src/index.ts'
|
||||
import { mountLocalActivations } from './local-activation.ts'
|
||||
|
||||
it.each(['absent', 'filtered', 'unused'] as const)('returns the closing answer when send_message is %s', async (availability) => {
|
||||
it.each(['absent', 'unused'] as const)('returns the closing answer when send_message is %s', async (availability) => {
|
||||
const ctx = new Context()
|
||||
await mountLocalActivations(ctx)
|
||||
await mountAgentLoopTestDependencies(ctx)
|
||||
@@ -28,7 +28,6 @@ it.each(['absent', 'filtered', 'unused'] as const)('returns the closing answer w
|
||||
provider: 'spawn', label: 'Compute the answer', delivery: 'parent', signal: new AbortController().signal,
|
||||
request: {
|
||||
parent, prompt: [{ type: 'text', text: 'Find the answer.' }],
|
||||
...availability === 'filtered' ? { toolFilter: { deny: ['send_message'] } } : {},
|
||||
},
|
||||
})
|
||||
try {
|
||||
|
||||
@@ -31,11 +31,11 @@ function fakeParent(id = 'parent-1'): Agent {
|
||||
|
||||
const ALL_CAPS: SubagentCapabilities = {
|
||||
agentOptions: true, agentPreset: true, outputSchema: true,
|
||||
depthLimit: true, toolFilter: true, persona: true,
|
||||
depthLimit: true,
|
||||
}
|
||||
const NO_CAPS: SubagentCapabilities = {
|
||||
agentOptions: false, agentPreset: false, outputSchema: false,
|
||||
depthLimit: false, toolFilter: false, persona: false,
|
||||
depthLimit: false,
|
||||
}
|
||||
|
||||
function baseRequest(overrides: Partial<SubagentStartRequest> = {}): SubagentStartRequest {
|
||||
@@ -238,8 +238,6 @@ describe('SubagentRuntime', () => {
|
||||
['agentOptions', { agentOptions: { model: 'child-model' } }],
|
||||
['outputSchema', { outputSchema: { type: 'object', properties: {} } }],
|
||||
['depthLimit', { maxDepth: 1 }],
|
||||
['toolFilter', { toolFilter: { deny: ['bash'] } }],
|
||||
['persona', { persona: 'reviewer' }],
|
||||
] as const)('rejects unsupported %s before provider startup', async (_capability, override) => {
|
||||
const { subagents } = await service()
|
||||
const provider = new StubProvider('weak', NO_CAPS)
|
||||
|
||||
@@ -64,7 +64,7 @@ async function setup(script: Script, options: SetupOptions = {}) {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
const disposeProvider = ctx.subagents.registerProvider({
|
||||
name: 'spawn',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
prepareContinuable: () => Promise.resolve({}),
|
||||
})
|
||||
@@ -438,7 +438,7 @@ describe('in-process structured output', () => {
|
||||
const delegated = Promise.withResolvers<SubagentResult>()
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'delayed-answer',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.resolve({
|
||||
id: SessionId('delegated-answer'),
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 686fb5136bef0a3b
|
||||
zh: d25d6569174a01b5
|
||||
/deepseek-ai-dsh-tool-subagent/summary:
|
||||
en: 958d2b1c56ac6ec7
|
||||
zh: cc1997a9ac09e7a5
|
||||
en: c09d40329253a9d8
|
||||
zh: 91c1bbe5557bdda9
|
||||
/deepseek-ai-dsh-tool-subagent/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
@@ -18,14 +18,14 @@
|
||||
en: 93f1701f5209ecb5
|
||||
zh: 6310fd012400aead
|
||||
/deepseek-ai-dsh-tool-subagent/use-this-package/minimal-configuration:
|
||||
en: 4babf53ca389f1ef
|
||||
zh: d6f7d2fadad0a25f
|
||||
en: 6a82283a51912841
|
||||
zh: 35728acc16931bb4
|
||||
/deepseek-ai-dsh-tool-subagent/use-this-package/managed-delegation:
|
||||
en: 0233562878187324
|
||||
zh: c28c8bf333a9adb2
|
||||
en: bb715d037cde14bd
|
||||
zh: a88e98c07115fbd1
|
||||
/deepseek-ai-dsh-tool-subagent/use-this-package/selecting-a-child-preset:
|
||||
en: 27e92fa7a46c06d8
|
||||
zh: 739b1fc19ce5c90a
|
||||
en: 1950ef92bab0f46e
|
||||
zh: 593f5f64ed9b5631
|
||||
/deepseek-ai-dsh-tool-subagent/use-this-package/selecting-a-child-llm:
|
||||
en: 0d37e3b3957515e4
|
||||
zh: ea1a94b2f29e7d9a
|
||||
@@ -117,8 +117,8 @@
|
||||
en: f5d5f97a153f7c70
|
||||
zh: 3d6bc05b9eadfbc2
|
||||
/deepseek-ai-dsh-tool-subagent/known-limitations-and-deferred-work:
|
||||
en: 8dcae464eb95d9ae
|
||||
zh: 05fd445c9d8459e2
|
||||
en: c17c91a188bb0d8c
|
||||
zh: d77c1ffcf1eca737
|
||||
/deepseek-ai-dsh-tool-subagent/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Use this package to give an agent a named tool that delegates work to a configured backend. Every call starts a managed activation and returns a child id; the runtime owns completion notices and cleanup. Local children support messages and restoration, while external backends execute one task and return their final answer through the completion notice. Supported backends can expose approved child LLM routes. Each instance can set child persona, tool access, and depth limits.
|
||||
Use this package to give an agent a named tool that delegates work to a configured backend. Every call starts a managed activation and returns a child id; the runtime owns completion notices and cleanup. Local children support messages and restoration, while external backends execute one task and return their final answer through the completion notice. Supported backends can expose approved child LLM routes. Each instance can select a child preset and depth limit.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -50,8 +50,6 @@ Load the subagent service, an in-process or remote backend, and this tool; then
|
||||
| `agentOptions` | — | Configured child `provider`, `model`, adapter-owned `reasoningEffort`, and positive `maxTokens` defaults; requires provider `agentOptions` support and overlays any provider-owned route defaults |
|
||||
| `agentPreset` | — | Default independent child preset id; requires in-process spawn |
|
||||
| `allowedAgentPresets` | — | Non-empty preset-id allowlist exposed as the model's optional `agent_preset` enum; must include `agentPreset` when both are set |
|
||||
| `persona` | — | Per-child persona; requires the provider's `persona` capability |
|
||||
| `toolFilter` | — | Per-child global-tool restriction; requires the `toolFilter` capability |
|
||||
| `maxDepth` | Host setting (`1`) | Absolute delegation-depth cap (`0` forbids delegation); `'provider-managed'` sends no cap to an out-of-process provider |
|
||||
|
||||
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-subagent) is the exhaustive source for every accepted field and its JSDoc.
|
||||
@@ -62,7 +60,7 @@ Every call returns `started subagent <childId>` after the runtime accepts the ch
|
||||
|
||||
Every backend’s completion notice carries its final answer. Local Spawn and Fork children can also send messages, accept more work through `send_message` while active, or resume after settling. Codex, Claude Code, ACP, and DSH SDK backends do not accept follow-up messages.
|
||||
|
||||
`maxDepth` caps recursion (`0` forbids delegation); omission reads the current Host `subagent.maxDepth` setting, initially `1`, at each delegation. A numeric depth requires a provider with the `depthLimit` capability; `'provider-managed'` leaves the budget to an out-of-process provider. `persona` and `toolFilter` configure every child when the provider supports them, and the tool stays visible at the cap — each attempted start checks the calling agent's current depth and rejects with an errored result.
|
||||
`maxDepth` caps recursion (`0` forbids delegation); omission reads the current Host `subagent.maxDepth` setting, initially `1`, at each delegation. A numeric depth requires a provider with the `depthLimit` capability; `'provider-managed'` leaves the budget to an out-of-process provider. The tool stays visible at the cap; each attempted start checks the calling agent's current depth and rejects with an errored result.
|
||||
|
||||
<a id="selecting-a-child-preset"></a>
|
||||
### Selecting a child preset
|
||||
@@ -71,7 +69,7 @@ Set `agentPreset` to give every child an independent preset by default. Set `all
|
||||
|
||||
The configured default and allowlist must name declared presets. A tool inside a preset checks these ids and reads their descriptions during Agent initialization; a Host-mounted tool performs this check when it loads. This check does not activate candidate presets. Activation failures reject the selected preset when the child is composed. Without either selection field, the tool does not require the preset service. An omitted model choice uses the configured `agentPreset`, or joins the parent's current preset revision when no default is configured. Any explicit id mounts the currently available revision, even when it matches the parent's id.
|
||||
|
||||
Compose reusable persona and tools in the [preset plugin list](../../preset/agent-preset-registry/README.md). Existing tool-level `persona` and `toolFilter` options still apply in the child scope after that composition. Local children use the shared [preset restoration rules](../subagent/README.md#restoring-child-composition).
|
||||
Compose persona and tools in the [preset plugin list](../../preset/agent-preset-registry/README.md). Tool configuration containing `persona` or `toolFilter` fails at load with a migration error. Local children use the shared [preset restoration rules](../subagent/README.md#restoring-child-composition).
|
||||
|
||||
### Selecting a child LLM
|
||||
|
||||
@@ -221,7 +219,7 @@ These limits define what this tool does not return or enforce; they are current
|
||||
|
||||
- **This tool does not wait for results** — use the completion notice or child messages. Programmatic result waiting belongs to the subagent service.
|
||||
- **Shipped fork tools cannot select a child LLM route** — they inherit the parent's provider and model to keep the copied conversation prefix eligible for KV Cache reuse. Re-enable selection only when route changes preserve reuse or expose a bounded recomputation cost.
|
||||
- **Per-child overrides are fixed per instance** — configured `persona`, `toolFilter`, and depth limits require a distinctly named tool to vary independently of the allowed presets. LLM selection requires an enabled per-Session preference and a provider that advertises `agentOptions`; both in-process providers and DSH SDK advertise it, while ACP, Codex, and Claude Code reject it rather than ignore it.
|
||||
- **Depth limits are fixed per instance** — independently named tools can set different caps. LLM selection requires an enabled per-Session preference and a provider that advertises `agentOptions`; both in-process providers and DSH SDK advertise it, while ACP, Codex, and Claude Code reject it rather than ignore it.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
使用本包可为 agent 提供具名工具,把工作委派给配置的后端。每次调用都会启动受管理的 activation 并返回子任务 id;运行时负责完成通知与清理。本地子任务支持消息和恢复,外部后端执行一次任务,并通过完成通知返回最终回答。受支持的后端可以公开获准的子级 LLM 路由。每个实例均可设置子 agent 的 persona、工具权限与深度限制。
|
||||
使用本包可为 agent 提供具名工具,把工作委派给配置的后端。每次调用都会启动受管理的 activation 并返回子任务 id;运行时负责完成通知与清理。本地子任务支持消息和恢复,外部后端执行一次任务,并通过完成通知返回最终回答。受支持的后端可以公开获准的子级 LLM 路由。每个实例可选择子级 preset 和深度限制。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -50,8 +50,6 @@ kind: "package-reference"
|
||||
| `agentOptions` | — | 配置的子级 `provider`、`model`、适配器所有的 `reasoningEffort` 与正整数 `maxTokens` 默认值;要求提供方支持 `agentOptions`,并会覆盖提供方持有的路由默认值 |
|
||||
| `agentPreset` | — | 独立子级 preset 的默认 id;要求进程内 spawn |
|
||||
| `allowedAgentPresets` | — | 非空 preset-id 白名单,作为模型可选的 `agent_preset` 枚举;与 `agentPreset` 同时配置时必须包含后者 |
|
||||
| `persona` | — | 每个子 agent 独立的 persona;要求提供方具备 `persona` 能力 |
|
||||
| `toolFilter` | — | 每个子 agent 独立的全局工具限制;要求提供方具备 `toolFilter` 能力 |
|
||||
| `maxDepth` | Host 设置(`1`) | 绝对委派深度上限(`0` 禁止委派);`'provider-managed'` 不向进程外提供方发送上限 |
|
||||
|
||||
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-subagent)是每个受支持字段及其 JSDoc 的穷尽式真源。
|
||||
@@ -62,7 +60,7 @@ kind: "package-reference"
|
||||
|
||||
所有后端的完成通知都会附带最终回答。本地 Spawn 和 Fork 子任务还可以发送消息,在活跃期间通过 `send_message` 接受更多工作,或在结束后恢复。Codex、Claude Code、ACP 与 DSH SDK 后端不接受后续消息。
|
||||
|
||||
`maxDepth` 限制递归深度(`0` 禁止委派);省略时,每次委派读取 Host 当前的 `subagent.maxDepth` 设置,初始值为 `1`。数值深度要求提供方具备 `depthLimit` 能力;`'provider-managed'` 把预算留给进程外提供方。当提供方支持时,`persona` 与 `toolFilter` 会配置每个子 agent;工具在达到上限时仍然可见——每次尝试启动都会检查调用 agent 的当前深度,被拒绝时返回出错的工具结果。
|
||||
`maxDepth` 限制递归深度(`0` 禁止委派);省略时,每次委派读取 Host 当前的 `subagent.maxDepth` 设置,初始值为 `1`。数值深度要求提供方具备 `depthLimit` 能力;`'provider-managed'` 把预算留给进程外提供方。工具在达到上限时仍然可见;每次尝试启动都会检查调用 agent 的当前深度,被拒绝时返回出错的工具结果。
|
||||
|
||||
<a id="selecting-a-child-preset"></a>
|
||||
### 选择子级 preset
|
||||
@@ -71,7 +69,7 @@ kind: "package-reference"
|
||||
|
||||
配置的默认值与白名单必须引用已声明 preset。preset 内的工具在 Agent 初始化期间检查这些 id 并读取描述;直接挂载在 Host 的工具在加载时检查。此检查不激活候选 preset。创建子级时,选中 preset 的激活失败会拒绝组装。两个选择字段都省略时,工具不要求 preset 服务。模型省略选择时使用配置的 `agentPreset`;若也没有默认值,则加入父方当前 preset 代际。任何显式 id 都挂载当前可用代际,即使与父方 id 相同。
|
||||
|
||||
在 [preset 插件列表](../../preset/agent-preset-registry/README.zh.md)中组合可复用的 persona 与工具。现有工具级 `persona` 与 `toolFilter` 仍在该组装之后应用于子级作用域。本地子级采用共享的 [preset 恢复规则](../subagent/README.zh.md#restoring-child-composition)。
|
||||
在 [preset 插件列表](../../preset/agent-preset-registry/README.zh.md)中配置 persona 与工具。包含 `persona` 或 `toolFilter` 的工具配置在加载时会报迁移错误。本地子级采用共享的 [preset 恢复规则](../subagent/README.zh.md#restoring-child-composition)。
|
||||
|
||||
### 选择子级 LLM
|
||||
|
||||
@@ -221,7 +219,7 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
|
||||
|
||||
- **本工具不等待结果**——使用完成通知或子任务消息。程序化结果等待由 subagent 服务提供。
|
||||
- **随附 fork 工具不能选择子级 LLM 路由**——它们继承父级提供方与模型,使复制的对话前缀仍有资格复用 KV Cache。仅当路由变更能保留复用或公开有界重算成本时,才重新启用选择。
|
||||
- **逐子级覆盖按实例固定**——配置的 `persona`、`toolFilter` 与深度上限若需独立于允许的 preset 变化,仍需要另一名称不同的工具。LLM 选择要求启用逐 Session 偏好,且提供方必须声明 `agentOptions`;两个进程内提供方和 DSH SDK 会声明该能力,而 ACP、Codex 与 Claude Code 会拒绝它,而不是忽略它。
|
||||
- **深度上限按实例固定**——不同名称的工具可设置不同上限。LLM 选择要求启用逐 Session 偏好,且提供方必须声明 `agentOptions`;两个进程内提供方和 DSH SDK 会声明该能力,而 ACP、Codex 与 Claude Code 会拒绝它,而不是忽略它。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -63,22 +63,6 @@ export interface Config extends PresetSelectionConfig {
|
||||
* Agent options applied to every child; omitted fields use child-loop defaults.
|
||||
*/
|
||||
agentOptions?: AgentOptions
|
||||
/**
|
||||
* Per-child persona that shadows `deployment:persona-prefix`. Requires the
|
||||
* provider's `persona` capability; omission preserves the deployment persona.
|
||||
*/
|
||||
persona?: string
|
||||
/**
|
||||
* Tool filter applied to every child. Filtered tools disappear from its
|
||||
* prompt and reject execution. Requires the provider's `toolFilter`
|
||||
* capability; unknown names fail startup.
|
||||
*/
|
||||
toolFilter?: {
|
||||
/** Global tool names the child keeps; everything else is removed. */
|
||||
allow?: string[]
|
||||
/** Global tool names removed from the child. */
|
||||
deny?: string[]
|
||||
}
|
||||
/**
|
||||
* Maximum child depth: a non-negative safe integer (`0` forbids delegation),
|
||||
* or `'provider-managed'` to send no cap. A numeric cap
|
||||
@@ -108,14 +92,8 @@ export const Config: z<Config> = z.object({
|
||||
reasoningEffort: ReturnType<typeof ReasoningEffortId>
|
||||
maxTokens: number
|
||||
}),
|
||||
persona: z.string(),
|
||||
agentPreset: z.string().min(1),
|
||||
allowedAgentPresets: (z.array(z.string().min(1)).min(1) as z<string[] | undefined>).default(undefined),
|
||||
// Preserve omission; Schemastery's `{ allow: [] }` default would deny every tool.
|
||||
toolFilter: z.object({
|
||||
allow: z.array(z.string()).default(undefined as unknown as string[]),
|
||||
deny: z.array(z.string()).default(undefined as unknown as string[]),
|
||||
}).default(undefined as unknown as { allow: string[]; deny: string[] }),
|
||||
maxDepth: z.union([z.natural().max(Number.MAX_SAFE_INTEGER), z.const('provider-managed' as const)]),
|
||||
})
|
||||
|
||||
@@ -165,6 +143,11 @@ function providerWording(inheritsConversation: boolean): { description: string;
|
||||
* @param session - unpublished Session supplied by a direct Agent setup; omit for a standing composition.
|
||||
*/
|
||||
export async function apply(ctx: Context, config: Config, session?: Session): Promise<void> {
|
||||
for (const field of ['persona', 'toolFilter']) {
|
||||
if (Object.hasOwn(config, field)) {
|
||||
throw new Error(`tool-subagent: ${field} is no longer supported; configure persona and tool plugins in an Agent Preset and select it with agentPreset (spawn only)`)
|
||||
}
|
||||
}
|
||||
const presets = new SubagentPresetSelection(config)
|
||||
if (presets.configured && ctx.get('agentPresets') === undefined) {
|
||||
throw new Error('tool-subagent: agent preset selection requires the agent-presets service')
|
||||
@@ -172,10 +155,6 @@ export async function apply(ctx: Context, config: Config, session?: Session): Pr
|
||||
// Direct apply() bypasses Schemastery's numeric constraints. A direct-apply
|
||||
// omission stays capless (the schema default only runs through the loader).
|
||||
if (config.maxDepth !== 'provider-managed') assertSubagentMaxDepth(config.maxDepth)
|
||||
// Reject an empty explicit filter at load instead of failing every delegation.
|
||||
if (config.toolFilter !== undefined && config.toolFilter.allow === undefined && config.toolFilter.deny === undefined) {
|
||||
throw new Error('tool-subagent: `toolFilter` is configured but names neither `allow` nor `deny` — remove the key or fill the filter')
|
||||
}
|
||||
const toolName = config.toolName ?? 'subagent'
|
||||
|
||||
const modelSelectionCapable = config.modelSelectionSettings === true
|
||||
@@ -344,8 +323,6 @@ export async function apply(ctx: Context, config: Config, session?: Session): Pr
|
||||
parent,
|
||||
...agentPreset === undefined ? {} : { agentPreset },
|
||||
...requestedChildAgentOptions !== undefined ? { agentOptions: requestedChildAgentOptions } : {},
|
||||
...config.persona !== undefined ? { persona: config.persona } : {},
|
||||
...config.toolFilter !== undefined ? { toolFilter: config.toolFilter } : {},
|
||||
...maxDepth !== undefined ? { maxDepth } : {},
|
||||
}
|
||||
|
||||
|
||||
@@ -412,14 +412,13 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it('reads the saved default depth at each delegation without remounting the tool', async () => {
|
||||
const ctx = await boot(false)
|
||||
const depths: Array<number | undefined> = []
|
||||
try {
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'capture-depth',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
depths.push(request.maxDepth)
|
||||
|
||||
@@ -49,16 +49,16 @@ async function setup(config: Omit<tool.Config, 'provider'>, providerConfig: Part
|
||||
const args = { description: 'review task', prompt: 'Review this change.' }
|
||||
|
||||
describe('spawn preset selection', () => {
|
||||
it('advertises configured ids and forwards a model selection with existing overrides', async () => {
|
||||
it('advertises configured ids and forwards a model selection without child composition overrides', async () => {
|
||||
const { ctx, onStart } = await setup({
|
||||
allowedAgentPresets: ['writer', 'reviewer'], persona: 'child override', toolFilter: { allow: [] },
|
||||
allowedAgentPresets: ['writer', 'reviewer'],
|
||||
})
|
||||
const schema = ctx.tools.schemas().find(schema => schema.name === 'subagent')
|
||||
expect(JSON.stringify(schema)).toContain('agent_preset')
|
||||
expect(JSON.stringify(schema)).toContain('reviewer persona')
|
||||
expect((await callSubagent(ctx, { ...args, agent_preset: 'reviewer' })).isError).not.toBe(true)
|
||||
expect(onStart).toHaveBeenCalledWith(expect.objectContaining({
|
||||
agentPreset: 'reviewer', persona: 'child override', toolFilter: { allow: [] },
|
||||
agentPreset: 'reviewer',
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ const DEFAULT_CAPABILITIES: SubagentCapabilities = {
|
||||
agentPreset: false,
|
||||
outputSchema: true,
|
||||
depthLimit: true,
|
||||
toolFilter: true,
|
||||
persona: true,
|
||||
}
|
||||
|
||||
/** Options for one scripted provider fixture. */
|
||||
|
||||
@@ -227,7 +227,7 @@ describe('dsh-tool-subagent', () => {
|
||||
await disposeSetupProvider(ctx)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'mock',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
agentRouteDefaults: { provider: 'beta', model: 'replacement-model' },
|
||||
start: replacementStart,
|
||||
@@ -313,7 +313,7 @@ describe('dsh-tool-subagent', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'bare',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
seen = request
|
||||
@@ -454,7 +454,7 @@ describe('dsh-tool-subagent', () => {
|
||||
// the provider survives.
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'continuable',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => { throw new Error('lifecycle test does not start a child') },
|
||||
prepareContinuable: async () => ({}),
|
||||
@@ -522,7 +522,7 @@ describe('dsh-tool-subagent', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'spy',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
if (request.signal.aborted) sawAborted()
|
||||
@@ -576,14 +576,14 @@ describe('dsh-tool-subagent', () => {
|
||||
expect(unwrapped.Config).toBeDefined()
|
||||
})
|
||||
|
||||
it('passes persona/toolFilter/maxDepth config through to the start request', async () => {
|
||||
let seen: { persona?: string; toolFilter?: unknown; maxDepth?: number } | undefined
|
||||
it('passes maxDepth config through to the start request', async () => {
|
||||
let seen: { maxDepth?: number } | undefined
|
||||
const ctx = await projectedContext()
|
||||
await mountWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'capture2',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
seen = request
|
||||
@@ -596,14 +596,10 @@ describe('dsh-tool-subagent', () => {
|
||||
})
|
||||
await ctx.plugin(tool, {
|
||||
provider: 'capture2',
|
||||
persona: 'You are the child.',
|
||||
toolFilter: { deny: ['subagent'] },
|
||||
maxDepth: 2,
|
||||
})
|
||||
|
||||
await callSubagent(ctx, { description: 'd', prompt: 'p' })
|
||||
expect(seen?.persona).toBe('You are the child.')
|
||||
expect(seen?.toolFilter).toMatchObject({ deny: ['subagent'] })
|
||||
expect(seen?.maxDepth).toBe(2)
|
||||
})
|
||||
|
||||
@@ -629,33 +625,18 @@ describe('dsh-tool-subagent', () => {
|
||||
})).rejects.toThrow('subagent maxDepth must be a non-negative safe integer')
|
||||
})
|
||||
|
||||
it('a partial toolFilter (deny only) does not materialize an empty allow-list (deny-all trap)', async () => {
|
||||
let seen: { toolFilter?: { readonly allow?: readonly string[]; readonly deny?: readonly string[] } } | undefined
|
||||
const ctx = await projectedContext()
|
||||
await mountWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'capture3',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: true, persona: false },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
seen = request
|
||||
return {
|
||||
id: SessionId('capture3-child'),
|
||||
result: Promise.resolve({ output: [{ type: 'text', text: 'ok' }], stopReason: 'completed' as const }),
|
||||
dispose: async () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
await ctx.plugin(tool, { provider: 'capture3', toolFilter: { deny: ['subagent'] }, maxDepth: 'provider-managed' })
|
||||
await callSubagent(ctx, { description: 'd', prompt: 'p' })
|
||||
expect(seen?.toolFilter).toEqual({ deny: ['subagent'] })
|
||||
expect(seen?.toolFilter).not.toHaveProperty('allow')
|
||||
it.each([
|
||||
{ persona: 'Child persona' },
|
||||
{ toolFilter: { deny: ['subagent'] } },
|
||||
{ toolFilter: {} },
|
||||
{ persona: undefined },
|
||||
])('rejects removed composition configuration at plugin load: %j', async (removed) => {
|
||||
await expect(setup({ provider: 'mock', ...removed }))
|
||||
.rejects.toThrow(/no longer supported; configure persona and tool plugins in an Agent Preset/)
|
||||
})
|
||||
|
||||
it('an omitted agentOptions does not materialize an empty object onto the request', async () => {
|
||||
// Same schemastery trap as toolFilter, adjacent field: an omitted
|
||||
// `agentOptions` config key materializes `{}` without the forced default,
|
||||
// An omitted `agentOptions` materializes `{}` without the forced default,
|
||||
// which reads as present and puts a dishonest `agentOptions: {}` on every
|
||||
// start request.
|
||||
let seen: { agentOptions?: unknown } | undefined
|
||||
@@ -664,7 +645,7 @@ describe('dsh-tool-subagent', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'capture4',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
seen = request
|
||||
@@ -681,19 +662,6 @@ describe('dsh-tool-subagent', () => {
|
||||
expect(seen).not.toHaveProperty('agentOptions')
|
||||
})
|
||||
|
||||
it('an explicit empty toolFilter fails at plugin load, not at first delegation', async () => {
|
||||
const ctx = await projectedContext()
|
||||
await mountWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'p',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: true, persona: false },
|
||||
inheritsParentContext: false,
|
||||
start: () => { throw new Error('unreachable') },
|
||||
})
|
||||
const fiber = ctx.plugin(tool, { provider: 'p', toolFilter: {} })
|
||||
await expect(fiber).rejects.toThrow(/names neither `allow` nor `deny`/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dsh-tool-subagent local activation', () => {
|
||||
@@ -792,7 +760,7 @@ describe('dsh-tool-subagent local activation', () => {
|
||||
let survivingChildId: ReturnType<typeof SessionId> | undefined
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'gated',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: async () => { throw new Error('continuable policy must not start a one-shot child') },
|
||||
prepareContinuable: async (request) => {
|
||||
@@ -855,7 +823,7 @@ describe('depth budget configuration', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'capture',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
requests.push(request)
|
||||
@@ -875,14 +843,6 @@ describe('depth budget configuration', () => {
|
||||
await callSubagent(ctx, { description: 'd', prompt: 'p' })
|
||||
expect(requests[0]?.label).toBe('d')
|
||||
expect(requests[0]?.maxDepth).toBe(1)
|
||||
expect(requests[0]?.toolFilter).toBeUndefined()
|
||||
})
|
||||
|
||||
it('forwards an explicit tool filter unchanged instead of encoding the depth policy into it', async () => {
|
||||
const { ctx, requests } = await captureSetup({ toolFilter: { deny: ['dangerous'] }, maxDepth: 0 })
|
||||
await callSubagent(ctx, { description: 'd', prompt: 'p' })
|
||||
expect(requests[0]?.maxDepth).toBe(0)
|
||||
expect(requests[0]?.toolFilter).toEqual({ deny: ['dangerous'] })
|
||||
})
|
||||
|
||||
it('rejects a numeric maxDepth on a provider without the depthLimit capability at mount', async () => {
|
||||
@@ -891,7 +851,7 @@ describe('depth budget configuration', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'no-depth',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => { throw new Error('unreachable') },
|
||||
})
|
||||
@@ -906,7 +866,7 @@ describe('depth budget configuration', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'external',
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
start: async (request) => {
|
||||
requests.push(request)
|
||||
@@ -920,6 +880,5 @@ describe('depth budget configuration', () => {
|
||||
await ctx.plugin(tool, { provider: 'external', maxDepth: 'provider-managed' })
|
||||
await callSubagent(ctx, { description: 'd', prompt: 'p' })
|
||||
expect(requests[0]?.maxDepth).toBeUndefined()
|
||||
expect(requests[0]?.toolFilter).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -625,7 +625,7 @@ describe('dsh-tool-workflow', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'spawn',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.reject(new Error('the parked-script fixture must not start a child')),
|
||||
})
|
||||
@@ -661,7 +661,7 @@ describe('dsh-tool-workflow', () => {
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'spawn',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: true },
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.reject(new Error('the scriptonly fixture must not start a child')),
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ try {
|
||||
let selectedStarts = 0
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'built-selected',
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: false, toolFilter: false, persona: false },
|
||||
capabilities: { agentOptions: true, agentPreset: false, outputSchema: true, depthLimit: false },
|
||||
inheritsParentContext: false,
|
||||
async start() {
|
||||
selectedStarts += 1
|
||||
|
||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user