Merge remote-tracking branch 'origin/master' into feature/subagent-policy-inheritance

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml
#	examples/acp-agent/tests/acp.snapshot.ts
This commit is contained in:
kingwl
2026-07-26 16:12:38 +08:00
1297 changed files with 43626 additions and 9926 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 5e3bddc67d213d74766a75da65cc44a21c8bb149
README.zh.md: 4391809ee83c822fcada25f0bdc021af44be9354

View File

@@ -1,5 +1,7 @@
# subagent/ — subagent capability family
English | [中文](README.zh.md)
The subagent seam: an agent delegating work to a child agent. Like the [bash](../bash/README.md) and [llm](../llm/README.md) families this is a capability seam (see [capability seams](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)) — but with one defining difference: **multiple provider implementations coexist in one context**, registered by name, rather than the single-implementation bash shape. The registry mirrors the LLM adapter registry.
| Package | Role | ctx key |

View File

@@ -0,0 +1,19 @@
# subagent/subagent 能力族
[English](README.md) | 中文
subagent seam 允许 agent智能体把工作委派给子 agent。与 [bash](../bash/README.md) 和 [llm](../llm/README.md) 能力族一样,这也是一种能力 seam见[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)),但有一个关键差异:**多个提供方实现在同一上下文中共存,并按名称注册**,而不是采用 bash 的单实现形态。该注册表仿照 LLM大语言模型适配器注册表。
| 包 | 角色 | ctx 键 |
|---|---|---|
| `subagent/` | 抽象 subagent seam具名提供方注册表与词汇 | `ctx.subagents` |
| `subagent-inprocess/` | 共享进程内运行驱动器(不提供提供方;每次运行使用一个清理 effect | 无 |
| `subagent-spawn/` | 进程内后端:全新的子 agent | (注册到 `ctx.subagents` |
| `subagent-fork/` | 进程内后端:以父 agent 已完成轮次的前缀作为初始内容的子 agent | (注册到 `ctx.subagents` |
| `subagent-subprocess/` | 共享进程外机制环境变量清理、dispose资源释放阶梯、隔离配置目录纯库不注册任何内容 | 无 |
| `subagent-acp/` | 进程外后端:在派生子进程中运行并通过 ACPAgent Client Protocol驱动的子 agent | (注册到 `ctx.subagents` |
| `tool-subagent/` | 面向模型的 `subagent` 委派工具,基于 `ctx.subagents` | (注册到 `ctx.tools` |
接口位于 `subagent/subagent/`。进程内 `subagent-spawn` / `subagent-fork` 后端共享 `subagent-inprocess` 驱动器(一个自身不提供提供方的库:两者都依赖它,彼此不依赖),进程外 `subagent-acp` 后端则构建于 `subagent-subprocess` 库之上凭据环境变量清理、dispose 阶梯、隔离配置目录)。测试只用包内 fixture测试前置数据替换子 agent 边界。
提案与设计理由见 [.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: d1ba03cf5256ad4889c4893bfe11af42bd627f9d
README.zh.md: 5763ee9a22c1d0bfe12c7da2b7d996911b55cc49

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent-acp
English | [中文](README.zh.md)
The ACP provider runs each subagent in a fresh subprocess and drives it as an Agent Client Protocol client. It is the out-of-process alternative to spawn and fork: the child has its own runtime, session, model configuration, and tools.
## Start and ownership

View File

@@ -0,0 +1,103 @@
# @deepseek-ai/dsh-subagent-acp
[English](README.md) | 中文
ACPAgent Client Protocol提供方会在全新的子进程中运行每个 subagent并作为 Agent Client Protocol 客户端驱动它。这是 spawn 与 fork 的进程外替代方案:子 agent智能体拥有自己的运行时、会话、模型配置和工具。
## 启动与所有权
`start(request)` 先解析子 agent 的工作目录,再依次执行 `spawn` → ACP `initialize``newSession`,然后才兑现。因此,兑现表示远程会话已就绪,所有权也已转移给调用方。派生、初始化、新建会话或发布前取消失败时,只有在子进程已回收后才会拒绝;工作目录解析失败则会在派生任何内容前拒绝。
工作目录优先使用已配置的 `cwd` 覆盖值,否则使用执行委派的父会话 cwd绝不使用服务器进程自身的 cwd因为同一个服务器进程会服务来自多个工作区的会话。从父级取得的值必须是绝对路径指向 harness 可以进入的目录(具备搜索权限,这是子进程 cwd 的要求);解析后的同一路径同时作为子进程 cwd 和 ACP `session/new` 工作区。
返回的运行 id 在父级命名空间中生成。子服务器的会话 id 只用于 ACP 协议调用,因为 ACP 只保证它在该全新子进程中唯一;若将其用作父级生命周期 id可能与另一个远程运行或本地 agent 冲突。
发布后,提供方发送提示词,并把流式 `agent_message_chunk` 文本收集到 `SubagentResult.output`。提示词/传输失败会以 `stopReason: 'error'` 兑现;如果必需的请求信号或 dispose 请求了取消,则以 `aborted` 兑现。
`dispose()` 是幂等的。它会移除信号监听器,在可行时请求 ACP 取消,关闭 stdin并等待 `disposeEofGraceMs`。随后 POSIX 先升级到 SIGTERM等待 `disposeGraceMs` 后再使用 SIGKILLWindows 会直接强制终止,因为 Node 会把两个信号都映射到 `TerminateProcess`。强制终止后,各平台最多再等待 `disposeGraceMs` 以确认退出;若信号出错或未退出,则拒绝。每次运行都使用全新进程;尚未实现进程池。
## 能力与上下文
ACP 不声明任何启动时能力,因为当前进程无法强制执行远程子 agent 的深度、工具过滤、persona 或结构化输出运行时。它也报告 `inheritsParentContext: false`:远程会话从全新状态开始,唯一源自父级的输入是上述工作区 cwd对话上下文不会跨越进程边界。
## 配置
| 键 | 默认值 | 含义 |
|---|---|---|
| `providerName` | `acp` | `ctx.subagents` 上的注册表名称。 |
| `command` | 必填 | 每次运行时派生的可执行文件。 |
| `args` | `[]` | 命令参数。 |
| `cwd` | 父会话 cwd | 子进程及其 ACP 会话的工作目录覆盖值;不得为空。相对值会在加载时以 harness 启动目录为基准解析,结果必须指向 harness 可以进入的目录。 |
| `permission` | `reject` | 自动回答权限请求:拒绝,或选择第一个允许形态的选项。 |
| `env` | `{}` | 显式子进程环境,叠加到已清理凭据的父进程环境之上。 |
| `disposeEofGraceMs` | `6000` | stdin EOF 之后、平台终止之前的宽限时间。 |
| `disposeGraceMs` | `3000` | 终止后的退出确认宽限时间POSIX 在 SIGTERM 后、SIGKILL 前也会等待同样时长。 |
```yaml
- id: subagent-acp
name: '@deepseek-ai/dsh-subagent-acp'
config:
providerName: acp
command: node
args: ['--import', 'tsx', './packages/examples/acp-demo/src/bin.ts', '--config', './examples/acp-agent/cordis.yml']
permission: reject
env:
DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY
```
## 结束原因映射
| ACP | Harness |
|---|---|
| `end_turn` | `completed` |
| `max_tokens` | `max-tokens` |
| `refusal` | `refusal` |
| `cancelled` | `aborted` |
| `max_turn_requests` 或未知值 | `error` |
## 进程边界
子进程环境由 [`buildChildEnv`](../subagent-subprocess/README.md) 构建:先移除名称形似凭据的环境变量,再应用显式 `config.env` 值。ACP 协议是真正的序列化边界;同进程 subagent 值不会为防御目的而克隆。
本包没有默认导出。否则 Cordis loader 的解包会隐藏具名 `inject` 元数据;见[事故复盘 0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)。
无密钥测试通过真实 stdio 驱动脚本化 ACP 子进程,其中包括一个由 Loader 组合的 stdio 应用,用于端到端证明父会话 cwd 继承。带密钥 e2e 会驱动仓库中的真实 ACP agent没有 `DEEPSEEK_API_KEY` 时自行跳过。
## 模型体验
### 子 agent 请求
#### 模型看到的内容
远程子 agent 通过 ACP 接收独立任务内容,并使用其自身进程配置的系统提示词、工具和全新会话。它不接收父级对话。该提供方不声明任何可选启动时能力,因此本地服务会拒绝要求 persona、工具过滤、深度强制或结构化输出的请求而不是静默省略这些要求。
#### Token 影响
子 agent 为独立的完整上下文及其多步骤历史支付 token 成本。这些 token 绝不会进入父级上下文。
#### KV Cache 影响
与父级请求缓存相互独立。每个 ACP 子 agent 只能在其自身提供方、模型、组合和历史均相同时复用前缀;其余情况下,子 agent 步骤仅追加增长。
### 父级工具结果(间接)
#### 模型看到的内容
通过 `dsh-tool-subagent`,父级只接收子 agent 最终的流式 assistant 文本,或该消费方给出的精确结束原因错误;不接收中间消息或工具流量。发布前已经取消的请求会精确变为 `Error: subagent request was aborted before the ACP child started`;其他启动失败按原样传递为 `Error: <message>`
#### Token 影响
父级输入只增加最终结果或错误,其内容依赖数据,并保留到上下文压缩为止。该提供方自身不会添加父级 schema。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
- **每次运行使用全新进程**:持久进程池属于后续优化(见 [seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md))。
- **仅支持本地工作区**:解析后的 cwd 是交给同一台机器上子进程的本地路径;远程 ACP agent 的工作区映射需要独立的后端能力,本包尚未设计。
- **不支持可选启动时能力**:该提供方无法在远程进程内应用本地 harness 的 `outputSchema`、深度上限、工具过滤器或 persona因此不会声明这些能力服务会拒绝需要它们的请求。
- **只收集已提交的 `agent_message_chunk` 文本**:自动化服务器把推理、工具活动、计划和其他 trace 数据保留在子 agent 会话日志中,不通过 ACP 发出。
- **权限提示自动回答**`permission: allow | reject`):当前版本不会把子 agent 的 `session/request_permission` 呈现给人。
- **没有快照层回放覆盖率**`TODO(acp-subagent-replay)`ACP 子 agent 拥有独立进程和独立回放形态,该工作延期处理。

View File

@@ -102,7 +102,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
await run.dispose()
expect(result.stopReason).toBe('completed')
// Verify the WORLD: the child process actually wrote the file in its cwd.
// Assert the filesystem effect independently of the model response.
const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
expect(proof).toContain('ACP_CHILD_WAS_HERE')
}, 180_000)

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: b448dc309bff07c744443530a648c7c30e4d20d9
README.zh.md: be6730ebe23510a017ffa80563016133dc87cae3

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent-fork
English | [中文](README.zh.md)
The fork provider creates an in-process child seeded with the parent's completed conversation turns. It shares all run mechanics with spawn; the session seed is the only behavioral difference.
## Seed boundary

View File

@@ -0,0 +1,61 @@
# @deepseek-ai/dsh-subagent-fork
[English](README.md) | 中文
fork 提供方会创建一个进程内子 agent智能体并以父 agent 已完成的对话轮次作为初始内容。它与 spawn 共用全部运行机制;唯一的行为差异是会话初始内容。
## 初始内容边界
subagent 启动时,父 agent 当前的工具调用轮次仍未结束:其日志包含 assistant 工具调用,但尚无匹配的工具结果或 `turn/end`。直接复制这份原始日志会给子 agent 一个无效且不平衡的会话。
因此fork 会计算截至最后一个 `turn/end` 的连续前缀。子 agent 能看到父 agent 所有已完成轮次,但看不到进行中的轮次。如果父 agent 尚未完成任何轮次,初始内容为空,子 agent 的行为与全新 spawn 相同。
初始内容只传递对话历史。子 agent 仍会获得全新的扁平注册作用域;它不继承父 agent 的工具限制或权限。
## 启动与能力
`start(request)` 将已完成轮次的初始内容传给 [`startInProcessRun`](../subagent-inprocess/README.md),并等待子 agent 发布。共享驱动器负责取消、深度、定制、结果读取和 dispose资源释放
fork 声明 `{ outputSchema: true, depthLimit: true, toolFilter: true, persona: true }`,与 spawn 相同。
## 配置
| 键 | 含义 |
|---|---|
| `providerName` | `ctx.subagents` 上的注册表名称(默认 `fork`)。 |
运行生命周期、模型继承与深度跟踪均为共享行为,见 [`dsh-subagent-spawn`](../subagent-spawn/README.md)。
## 模型体验
### 子 agent 历史与包络
#### 模型看到的内容
子 agent 先接收父 agent 平衡的已完成轮次界面前缀,再逐字接收新的任务内容。配置的 persona 会在子 agent 的全新作用域中遮蔽提示词文本;工具限制会过滤其全局协议 schema、可执行工具查找和 Code Mode SDK 绑定,但不影响独立注册的指导内容。父 agent 的工具视图与权限不会被继承。可选的结构化输出请求会添加仅属于子 agent 的契约。父 agent 当前进行中的轮次会被排除。
#### Token 影响
fork 会把保留的已完成历史复制到独立的子 agent 请求中;随后子 agent 独立累积自己的 token。persona 会改变重复提示词的成本,过滤会改变 schema 或生成 SDK 的成本,而首轮 fork 没有继承历史。
#### KV Cache 影响
在提供方和模型相同的前提下,子 agent 可以复用继承的逐字节相同前缀。persona、工具过滤、生成 SDK 或路由变化可能在继承历史之前使复用失效;后续子 agent 历史仅追加。
### 父 agent 工具结果(间接)
#### 模型看到的内容
父 agent 只通过 `dsh-tool-subagent` 接收子 agent 自身的最终输出,不接收继承的前缀或中间工作。
#### Token 影响
父 agent 输入增加一个依赖数据的最终结果并保留到上下文压缩compaction为止。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
- **运行不公开 `sendMessage`/`resume`**:进程内运行不具备这些可选运行时能力。
- **初始内容是一次性快照**:子 agent 只能看到 fork 时父 agent 已完成的轮次,看不到父 agent 此后记录的任何内容;不会实时共享上下文。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 31b917f7edc08182c72b1e4c0ed403a155dc477f
README.zh.md: 81cce8f804f5d6cb4a4915f3aecca278dd357db1

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent-inprocess
English | [中文](README.zh.md)
This package is the shared run driver for the two in-process providers. Spawn passes no session seed; fork passes the parent's completed-turn prefix. Everything else—depth, child creation, optional child customization, result reading, cancellation, and disposal—has one implementation here.
## Start contract

View File

@@ -0,0 +1,114 @@
# @deepseek-ai/dsh-subagent-inprocess
[English](README.md) | 中文
本包是两个进程内提供方共用的运行驱动器。spawn 不传入会话初始内容fork 传入父 agent智能体已完成轮次的前缀。其余机制包括深度、子 agent 创建、可选的子 agent 定制、结果读取、取消和 dispose资源释放都在此共用同一套实现。
## 启动契约
`startInProcessRun(request, options): Promise<SubagentRun>` 只在子 agent 发布到 `ctx.agents` 后才兑现。启动被拒绝时agent 工厂的未发布创建事务已经完全停稳,因此调用方绝不会收到创建到一半的句柄。
驱动器按以下顺序运行:
1. 校验父 agent 深度和可选的绝对 `maxDepth`,然后把子 agent 深度推导为父 agent 深度加一,并将其持久化到子 agent 会话 header。
2. 直接调用 `parent.ctx.agents.create`,把必需的请求信号传入工厂的创建事务。
3. 在该事务未发布的设置窗口中,安装请求的 persona、工具限制和结构化输出运行时。
4. 发布子 agent保留返回的 `AgentHandle`,并通过先调用 `child.followup(prompt)`、再调用 `child.whenIdle()` 来驱动一项任务。
5. 读取子 agent 自身最后一条 assistant 消息,以及由消息触发的最新轮次原因;排除任何 fork 初始内容和后续由插件拥有的零步骤轮次。
子 agent 会获得父 agent 的工作目录/会话谱系;除非 `request.agentOptions` 覆盖,否则还会继承父 agent 模型。它获得全新的扁平注册作用域:父级所有权不会导入父 agent 的工具限制,也不会建立权限子集。
子 agent 还会继承父 agent 的会话策略覆盖项。驱动器在自己的第一个 await 之前同步捕获 `ctx.sandboxPolicy.overrideOf(parent.session)``ctx.approval.overrideOf(parent.session)`——委派时刻即快照点,因此与异步的子 agent 创建过程赛跑的父 agent 切换属于父 agent 的未来——再由一个一次性、前置安装的 `agent/prompt-submit` 监听器通过 `stampOverride` 盖章写入捕获值(两个服务均以可选方式消费:未挂载它们的组合照旧进行无策略委派)。锚定在子 agent 的第一个轮次内,使盖章事件被包围在轮次内(具备持久性)并先于第一次请求;前置安装使其位于具备否决能力的监听器之前,因此作出拒绝的 UserPromptSubmit 钩子无法在未盖章的情况下关闭第一个轮次;盖章事件在日志中位于 fork 初始内容携带的任何切换之后,因此既有的「最后一个事件生效」折叠即可解析陈旧初始内容的时序。只复制覆盖链,因此未切换过的父 agent 不盖任何章,子 agent 继续跟随实时部署默认值。嵌套按构造即可组合:每次捕获折叠的都是发起委派的会话已经盖过章的日志(参见[设计原理](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md))。
## 取消与所有权
必需的请求信号同时覆盖启动阶段和实时运行。发布前,`AgentCreationTransaction` 会观察该信号、回滚并拒绝。工厂返回前会移除仅用于创建阶段的监听器;驱动器随即再次检查信号,然后安装最小化的实时运行监听器,从而消除交接竞态。发布后,中止会取消子 agent。
兑现后,调用方拥有该运行。提供方插件卸载不会撤销它。`dispose()` 会移除实时中止监听器、记录取消,并委托给返回的 `AgentHandle.dispose()`;后者通过可复用的完全停稳事务停止循环、移除 agent 和会话,并展开有作用域的注册。取消决定所有尚未完成的进行中结果,并将其报告为 `aborted`;已经完成的轮次仍保持完成状态。
## Spawn 与 fork 输入
`InProcessRunOptions` 的形态为 `{ seed?: SessionEvent[] }`。spawn 省略该值。fork 提供平衡的已完成轮次前缀,并记录其长度,确保结果读取器不会把作为初始内容的父 agent 消息误认为子 agent 输出。
深度强制在 `startInProcessRun` 内部完成:它通过 `delegationDepthOf` 读取父 agent 深度(持久化的 `SessionHeader.delegationDepth` 具有权威性;运行时 `AgentOptions.subagentDepth` 可以加深但绝不能降低该值,因此恢复后的子 agent 会保留预算),缺失值按顶层深度零处理,拒绝格式错误的存储值,并报告尝试的子 agent 深度超过 `maxDepth`。超过安全整数范围、无法表示的深度会触发 `RangeError`。子 agent 深度写入子 agent header因此会在持久化和恢复后保留。
## 结构化输出
`attachStructuredRuntime(childCtx, schema)` 会在子 agent 作用域中安装完整契约:
- 使用请求 schema 注册的 `structured_output` 工具会校验并暂存模型值。
- 一个顺序为 190 的系统提示词段会告诉子 agent该工具调用就是终态答案。
- 两项贡献都是普通的子 agent 作用域注册。专家级 `system-prompt/assemble` 监听器可以替换它们,因此负责为该子 agent 保留结构化输出协议。
- `tools/result` 观察器只会在该次执行的权威最终工具结果成功后提交暂存值Code Mode 子分派外层的 `run_code` 结果也包括在内。
- 单调工具防护会在捕获值后阻止后续调用,`agent/turn-stop` 则在结构化结果提交后结束轮次。
正常结束却始终未提交必需结构化值的轮次会报告 `error`;驱动器不会重新提示。所有注册都附着于子 agent fiber并随其一同消失。
## 模型体验
### 子 agent 请求
#### 模型看到的内容
共享驱动器把任务逐字作为子 agent 的用户消息发送;若有请求,还会在未发布子 agent 的全新作用域中遮蔽 persona并限制全局工具 schema、查找、执行和 Code Mode SDK 绑定。父 agent 的限制不会被继承独立的工具指导段仍会保留。spawn 不提供历史fork 提供平衡的初始内容。
#### Token 影响
子 agent 输入与父 agent 隔离,并通过子 agent 自身的步骤增长。persona 会改变重复提示词文本;过滤会改变 schema 或生成 SDK 的成本,但不影响独立注册的指导内容。
#### KV Cache 影响
与父 agent 请求缓存相互独立。子 agent 后续历史仅追加,而 persona、工具过滤、生成 SDK、提供方或模型变化会建立不同的子 agent 前缀。
### 结构化输出系统提示词、schema 与结果
#### 模型看到的内容
结构化运行会添加下方的结构化输出指令。它还会添加子 agent 作用域的 `structured_output` 定义,其精确描述为 `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.`,参数使用请求的 schema。该仅运行时存在的定义不在已生成并随产品发布的[工具包索引](../../../docs/tool-catalog.md#tool-package-map)中。其规范确认值是 `{ recorded: true }`,渲染为 `Structured output recorded.`;后续调用会变为 ``Error: structured output already recorded: the run is complete, so `<tool>` is not executed``。
##### 结构化输出指令
```markdown
When you have your final answer, you MUST report it by calling the `structured_output` tool with arguments matching its parameter schema exactly. Do not finish with a plain text answer: only the tool call counts as your result.
```
#### Token 影响
固定指令和能力 token 仅由该子 agent 支付。结果文本进入子 agent 历史,而只有捕获的值会成为父 agent 结果。
#### KV Cache 影响
只要结构化输出指令和 schema 不变,子 agent 内部的前缀就保持稳定。更改 schema 或能力可能从该早期片段开始使子 agent 缓存失效;结果会分别追加到子 agent 和父 agent 历史中。
### 父 agent 启动错误(间接)
#### 模型看到的内容
通过 `dsh-tool-subagent`,无效深度状态会精确变为 `Error: agent subagentDepth must be a non-negative safe integer`、`Error: subagent child depth exceeds the safe-integer range` 或 `Error: subagent depth <attempted> exceeds maxDepth <max>`。发布前取消的中止原因会通过注册表的 `Error: <message>` 包装传递。
#### Token 影响
启动成功时为零 token只有失败的父 agent 工具调用会保留这段文本。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
### 父 agent 结果(间接)
#### 模型看到的内容
驱动器只提取子 agent 自身最后的 assistant 输出或捕获的结构化值;作为初始内容的父 agent 消息和子 agent 中间工作不会成为结果。
#### Token 影响
父 agent 通过消费方接收一个依赖数据的结果;其他所有子 agent token 都留在子 agent 会话中。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
- **运行不公开 `sendMessage`/`resume`**:进程内运行不具备这些可选运行时能力。
- **结构化捕获只接受 `defineTool` schema 子集**:不支持的 JSON Schema 构造会在子 agent 创建前失败;需要更广 schema 词汇的提供方必须采用不同的运行时。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 868f829edbcfe2eb4d66ccd0ff9988924c70298b
README.zh.md: 823291baae0ca09b86565516af1843aff033f354

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent-spawn
English | [中文](README.zh.md)
The spawn provider creates a fresh child `Agent` in the current process. The child has its own session, sees no parent conversation history, and reuses the host's agent factory and LLM/tool services.
## Behavior

View File

@@ -0,0 +1,56 @@
# @deepseek-ai/dsh-subagent-spawn
[English](README.md) | 中文
spawn 提供方会在当前进程中创建一个全新的子 `Agent`。子 agent智能体有自己的会话看不到父 agent 的对话历史,并复用宿主的 agent 工厂及 LLM大语言模型/工具服务。
## 行为
`start(request)` 不提供初始内容,直接委托给 [`startInProcessRun`](../subagent-inprocess/README.md),并在子 agent 发布后才返回。子 agent 获得父 agent 的工作目录/会话谱系,并默认继承父 agent 模型(除非覆盖),但以空对话开始运行。
共享驱动器负责深度检查、persona 与工具过滤器设置、结构化输出、必需信号取消、单次执行、结果读取和完全停稳后的 dispose资源释放。启动失败不会留下已发布的子 agent提供方插件在完成后卸载也不会撤销由持有方拥有的运行。
## 能力
spawn 声明 `{ outputSchema: true, depthLimit: true, toolFilter: true, persona: true }`,因为它控制子 agent 的创建窗口,能够强制执行全部四项功能。
## 配置
| 键 | 含义 |
|---|---|
| `providerName` | `ctx.subagents` 上的注册表名称(默认 `spawn`)。 |
## 模型体验
### 子 agent 请求
#### 模型看到的内容
全新的子 agent 逐字接收独立任务内容,默认继承父 agent 的模型和工作区,并看到带有已配置子 agent 作用域 persona 遮蔽的全局提示词。工具过滤器会为该子 agent 移除全局协议 schema、可执行工具查找和 Code Mode SDK 绑定,但保留独立注册的指导内容。它不接收任何父 agent 对话消息;过滤控制的是可见性与组合,并非从父 agent 继承的权限授权。
#### Token 影响
子 agent 为全新的独立上下文和历史支付 token 成本;不会复制父 agent 历史 token。persona 会改变该子 agent 的重复提示词成本,过滤则会改变其 schema 或生成 SDK 的成本。
#### KV Cache 影响
与父 agent 请求缓存相互独立。子 agent 历史仅追加persona、工具过滤、生成 SDK、提供方或模型变化会建立不同的子 agent 前缀。
### 父 agent 工具结果(间接)
#### 模型看到的内容
通过 `dsh-tool-subagent`,父 agent 只接收子 agent 的最终输出或结束原因错误。
#### Token 影响
父 agent 输入增加一个依赖数据的结果并保留到上下文压缩compaction为止。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
- **运行不公开 `sendMessage`/`resume`**:进程内运行不具备这些可选运行时能力。
- **全新表示不含父 agent transcript**:子 agent 会继承 cwd、谱系、模型及显式配置的 persona/工具限制,但不继承父 agent 的任何对话;需要已完成轮次上下文时,请使用 fork 提供方。

View File

@@ -6,14 +6,7 @@ import type { Context } from 'cordis'
import { spawnHarness, waitForIdle } from './harness.ts'
import { SessionId } from '@deepseek-ai/dsh-session'
/**
* With-key smoke for the in-process spawn backend: a REAL parent agent delegates
* to a REAL child (via the `subagent` tool → spawn backend) that uses the REAL
* bash tool to write a file, and we verify the WORLD (the file on disk) — not
* the agent's self-report. This is the "green units, broken product" guard:
* mocks prove the plumbing, only a real model proves a parent can actually drive
* a child to do real work. Key-gated (self-skips without DEEPSEEK_API_KEY).
*/
/** Key-gated smoke for a real parent delegating filesystem work to a real child. */
let ctx: Context | undefined
let workdir: string | undefined
@@ -37,7 +30,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('spawn backend with-key smoke', (
+ 'After the subagent finishes, tell me it is done.' }])
await waitForIdle(ctx, parent)
// Verify the WORLD: the child actually wrote the file.
// Assert the filesystem effect independently of the model response.
const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
expect(proof).toContain('SUBAGENT_WAS_HERE')

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 6ae1778af1ca38a6c49c7f462e536a9c16c7e6bb
README.zh.md: 01847710df7c12ace7f87e45abc8e83958469740

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent-subprocess
English | [中文](README.zh.md)
Shared machinery for **out-of-process subagent backends** — providers that spawn an external agent as a child process, such as the [ACP backend](../subagent-acp/README.md). A pure library (no provider, no registration, no Config): what every spawn-a-CLI-child backend needs to keep the parent deployment's credentials out of the child, tear the child down to quiescence, and isolate it from the host user's on-disk CLI state. Design rationale: [the Claude Code / Codex subagent backends Agent Note](../../../.agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md).
Every tunable is a **parameter**: the dispose ladder takes its grace periods per call, the config-dir helper takes an optional pinned path. Defaults live in each consuming plugin's Config (defaulted, validated fields changeable from `cordis.yml`), never in this library.

View File

@@ -0,0 +1,55 @@
# @deepseek-ai/dsh-subagent-subprocess
[English](README.md) | 中文
用于**进程外 subagent 后端** 的共享机制:这类提供方会把外部 agent智能体作为子进程派生例如 [ACP 后端](../subagent-acp/README.md)。这是纯库(无提供方、无注册、无 Config提供所有「派生 CLI 子进程」后端都需要的机制:阻止父级部署凭据进入子进程、把子进程清理至完全停稳,以及将子进程与宿主用户的磁盘 CLI 状态隔离。设计理由见 [Claude Code / Codex subagent 后端 Agent Note](../../../.agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md)。
每个可调项都是**参数**dispose资源释放阶梯每次调用时接收宽限时间配置目录辅助函数接收可选的固定路径。默认值位于各个消费插件的 Config 中(带默认值且经过校验的字段,可从 `cordis.yml` 修改),绝不位于本库。
## 导出内容
### `buildChildEnv(extra)`
凭据环境变量清理采用与 [bash 执行器](../../bash/bash-local/README.md)相同的模式:子进程环境等于环境继承值移除名称形似凭据的变量(`/KEY|SECRET|TOKEN/i`)后,再把 `extra` 叠加到清理结果之后。`PATH``HOME``TMPDIR`、locale 和代理变量会保留,使子 CLI 正常运行;父级自身的秘密绝不会隐式泄漏,而显式提供的凭据(后端 `env` 配置中子进程自己的密钥)仍会传给子进程。
### `spawnFailure(child)`
派生失败捕获:返回一个 promise它会以子进程的第一个 `error` 事件兑现(绝不拒绝)。`ENOENT` 等派生失败是事件而非抛出的异常;没有监听器时 Node 会使父进程崩溃。因此,请在调用 `spawn()` 的同一个 tick 内调用此函数,并在运行结果路径中将其纳入竞速;错误命令随后会作为普通的子进程级失败结算。对于正常派生的子进程,该 promise 永不结算。
### `disposeChildProcess(child, graces)`
平台感知的 dispose 阶梯只会在子进程确实退出后兑现:达到完全停稳,而不只是发出请求(见[防御性模式](../../../docs/defensive-patterns.md)
1. stdin EOF如果 stdin 已建立管道),然后等待 `graces.disposeEofGraceMs`:可协作的子进程自行完全停稳,同时保留其 flush 与嵌套子进程清理;
2. 在 POSIX 上发送 `SIGTERM`,然后等待 `graces.disposeGraceMs`
3. 强制终止POSIX 使用 `SIGKILL`Windows 使用 Node 映射的 `TerminateProcess`;然后最多等待 `graces.disposeGraceMs` 以确认退出。信号错误或未退出会导致 dispose 拒绝。
两个宽限时间(`DisposeLadderGraces`)来自消费插件的 `disposeEofGraceMs`/`disposeGraceMs` Config 字段。POSIX 在优雅信号和强制信号之后都使用 `disposeGraceMs`Windows 跳过冗余的优雅信号但用该值限定强制退出确认时间。EOF 窗口有意独立设置且通常更宽,因为协作式清理可能要等待捕获信号的孙进程和最后一次 flush。
退出等待逻辑位于该阶梯内部。无论结算结果如何,它们都会清理自己的 timer 和监听器,因此升级过程不会在子进程上累积监听器。
### `createIsolatedConfigDir(prefix, pinnedPath?)`
为外部 CLI 子进程创建每次运行独立的隔离配置目录(`CLAUDE_CONFIG_DIR` / `CODEX_HOME` 式重定向的目标),使子进程行为只取决于部署配置,绝不取决于宿主上任何 `~/.claude` / `~/.codex` 式状态。返回一个 `IsolatedConfigDir` 句柄:`path` 写入子进程环境,`remove()` 在 dispose 时运行。
- **全新(默认)**OS 临时根目录下的私有0700`mkdtemp` 目录;`remove()` 会尽力删除它,且绝不拒绝(留下临时目录胜过 dispose 失败),并且是幂等的。
- **固定**(设置 `pinnedPath`):原样返回该路径,绝不创建、绝不移除。通过固定目录在运行间共享子进程状态的部署负责该目录的生命周期。
## 测试
`tests/subagent-subprocess.spec.ts`环境变量清理和配置目录辅助函数使用真实进程环境与真实文件系统运行rm 失败路径在 fs 边界注入拒绝,因为真实递归 rm 失败无法跨平台稳定触发,而且 root 会忽略权限位);退出等待和平台终止路径使用可脚本化的假子进程。[ACP 后端测试套件](../subagent-acp/README.md)会针对真实子进程端到端执行这些机制。
## 模型体验
通过基于进程的 subagent 后端间接产生影响;这些后端的子进程组合受凭据清理和隔离配置目录约束。
#### KV Cache 影响
不会直接使缓存失效;具名消费方负责请求前缀的任何变化。
## 已知限制与延期工作
- **凭据清理基于名称**:只移除匹配 `KEY` / `SECRET` / `TOKEN` 的变量;除非后端提供更严格的环境,否则 `PASSWORD` 等名称不同的秘密仍会传入。
- **信号只针对直接子进程**:清理依赖可协作的 CLI 在退出前回收其后代;重新托管或独立脱离的孙进程可能比该阶梯存活更久。
- **全新配置目录的清理是尽力而为**`rm` 失败时会在 OS 临时根目录下留下私有状态,而不会使 dispose 失败。
- **固定配置目录完全由操作方负责**:辅助函数既不创建、校验、锁定,也不移除这些目录,因此并发运行可能共享该状态并发生竞态。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 3d5d5e7498b1700c07486cc6e894e72fed681bec
README.zh.md: 3eb3d2bb379ec1a39035f42ffbda08bed6698823

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-subagent
English | [中文](README.zh.md)
The subagent seam lets one agent delegate work to a child through a named provider. Callers use one service API (`ctx.subagents`); providers decide whether the child runs in this process, in another process, or through a future transport.
## Package roles

View File

@@ -0,0 +1,82 @@
# @deepseek-ai/dsh-subagent
[English](README.md) | 中文
subagent seam 允许一个 agent智能体通过具名提供方把工作委派给子 agent。调用方使用统一的服务 API`ctx.subagents`);提供方决定子 agent 在当前进程、另一进程还是未来的传输之上运行。
## 包角色
该能力族把稳定接口与实现、面向模型的工具分开:
| 包 | 角色 |
|---|---|
| `@deepseek-ai/dsh-subagent` | 提供方注册表、请求/结果类型和生命周期事件。 |
| `@deepseek-ai/dsh-subagent-spawn` | 全新的进程内子 agent。 |
| `@deepseek-ai/dsh-subagent-fork` | 以父 agent 已完成轮次作为初始内容的进程内子 agent。 |
| `@deepseek-ai/dsh-subagent-acp` | 全新的进程外 ACPAgent Client Protocol子 agent。 |
| `@deepseek-ai/dsh-tool-subagent` | 基于一个已配置提供方、面向模型的工具。 |
多个提供方可以使用不同名称共存。因此,部署可以同时公开低成本的进程内子 agent 和隔离的 ACP 子 agent而无需改变服务契约。
## 服务 API
`SubagentService` 有四个主要操作:
| 成员 | 含义 |
|---|---|
| `registerProvider(provider)` | 按名称注册一个可信的同进程实现。注册受 effect 作用域约束;移除注册会阻止新的启动,但不会撤销已返回给调用方的运行。重复名称会立即失败。 |
| `getProvider(name)` | 返回提供方;不存在时返回 `undefined`。 |
| `list()` | 按插入顺序返回提供方名称。 |
| `start(name, request)` | 校验请求的能力和语义值,然后等待提供方,直到真实子 agent 就绪。兑现时返回由持有方拥有的 `SubagentRun`;拒绝表示提供方已清理所有局部启动资源。 |
`SubagentStartRequest.signal` 是必填项,也是规范取消通道。发布前中止会使 `start()` 在回滚后拒绝;发布后中止会取消实时子 agent。请求还可以选择模型、要求结构化输出、限制委派深度、约束子 agent 工具或设置子 agent persona。
同进程请求、描述符、结果和事件 payload 都是以不可变方式借用的可信类型值。服务不会克隆或冻结它们序列化和不可信输入校验属于真实的进程、worker、持久化和模型边界。
## 能力
启动时功能通过 `provider.capabilities` 声明,因为服务必须在创建子 agent 前拒绝不受支持的请求:
- `outputSchema`:强制执行结构化最终结果;
- `depthLimit`:强制执行 `maxDepth`
- `toolFilter`:应用请求的子 agent 工具限制;
- `persona`:应用每个子 agent 独立的 persona。
## 委派深度
该 seam 拥有实现和消费方共享的深度词汇:`AgentOptions.subagentDepth` 声明、`assertSubagentMaxDepth``delegationDepthOf(agent)`。持久化的 `SessionHeader.delegationDepth` 具有权威性且单调:运行时选项可以加深计数,但绝不能降低它,因此恢复后的子 agent 不会被重新计为顶层。
运行时功能是 `SubagentRun` 上的可选方法:`sendMessage?` 会引导实时子 agent`resume?` 则异步创建延续运行。方法是否存在就是能力检查。
`inheritsParentContext` 只用于描述,不能强制执行。它仅说明子 agent 是否能看到父级已完成的对话历史(`fork` 可以;`spawn` 和 ACP 不可以),不表示是否继承工具、服务或权限。
## 所有权与生命周期
`provider.start(request): Promise<SubagentRun>` 是所有权转移边界。兑现前,提供方拥有设置过程,并且每次失败时都必须取消、回滚并使局部资源完全停稳。兑现后,调用方拥有该运行,并且必须在每条路径上调用 `dispose()`
`SubagentRun.result` 兑现为 `{ output, structured?, stopReason }`。子 agent 级失败会以非 `completed` 原因兑现;只有 seam 无法表示的基础设施故障才可以拒绝。`dispose()` 是幂等的,会取消剩余工作,并等待子 agent 资源完全停稳。
本地运行会在 `start()` 兑现前发布普通的子 agent/会话,把该共享会话 id 作为 `SubagentRun.id` 返回,以 `SubagentRun.localAgent` 公开准确的子 agent并把 `request.parent.session.id` 记录到子 agent 的 `parentSession` header。远程提供方则生成父级作用域的生命周期 id并返回 `localAgent: undefined`
服务只会发出 `subagent/start`,而且是在 `start()` 兑现后。它在同步通知前附加结果观察器,因此即使子 agent 已经结算,也仍会先产生 `subagent/start`,再产生 `subagent/end`。这对事件共享服务生成的 `runId`;其 `local` 标志取自提供方准确 `localAgent` 的快照,因此观察器绝不会从可复用的提供方/会话名称推断运行身份或本地性。
运行事件受执行委派的父级作用域约束。每个监听器都独立隔离:同步抛出或返回的 promise 被拒绝时,只会记录日志,不会阻塞同级监听器或改变运行。
提供方新增和移除还会发出 `subagent/provider-added``subagent/provider-removed`。面向模型的工具等消费方使用这些事件,因为 Cordis 可能并发加载同级插件;配置顺序不能证明注册顺序。
## 收集模型
面向模型的工具默认同步收集:先等待子 agent 结果,再 dispose 运行,然后才返回。后台委派不会改变该 seam消费方把启动过程和最终运行注册到通用 `ctx.tasks` 运行时,随后使用共享任务工具进行收集和取消。完整契约见[后台 subagent 任务 Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md)、[能力 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)和 `src/types.ts`
## 模型体验
通过 `dsh-tool-subagent` 间接产生影响;它渲染提供方特定的 schema以及前台或通用后台结果同时子 agent 工作上下文只留在子 agent 中。
#### KV Cache 影响
不会直接使缓存失效;具名消费方负责请求前缀的任何变化。
## 已知限制与延期工作
- **运行时引导和延续只是 seam 能力**:当前工具中没有消费 `sendMessage``resume` 的面向模型消费方。
- **生命周期事件只供观察**:影响运行的 `subagent/end` 延续或决策接口仍需等待具体消费方。

View File

@@ -1,7 +1,5 @@
/**
* Subagent seam vocabulary: the request/result/capability types a
* {@link SubagentProvider} consumes and produces. No runtime code — types
* only, per the package convention.
* Request, result, and capability contracts for {@link SubagentProvider}.
*
* @module @deepseek-ai/dsh-subagent/types
*/
@@ -17,7 +15,7 @@ export type SubagentRunId = Branded<'SubagentRunId'>
/**
* Brand a string as a {@link SubagentRunId}.
* @param id - the raw id string (the service mints UUIDs; tests may pass fixtures).
* @param id - the raw run id.
* @returns the same string, branded.
*/
export function SubagentRunId(id: string): SubagentRunId {
@@ -30,16 +28,13 @@ export function SubagentRunId(id: string): SubagentRunId {
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
* degradation" rule). These static flags cover features needed before a run exists; runtime
* capabilities such as steering and resume are optional {@link SubagentRun} methods whose presence
* is the capability.
* is the capability. Each flag corresponds one-to-one to a {@link SubagentStartRequest} option:
* `depthLimit` to `maxDepth`; the other names match.
*/
export interface SubagentCapabilities {
/** Honor {@link SubagentStartRequest.outputSchema} (structured final output). */
readonly outputSchema: boolean
/** Enforce {@link SubagentStartRequest.maxDepth} (recursion cap). */
readonly depthLimit: boolean
/** Enforce {@link SubagentStartRequest.toolFilter} (child tool scoping). */
readonly toolFilter: boolean
/** Honor {@link SubagentStartRequest.persona} (a per-child persona). */
readonly persona: boolean
}
@@ -50,16 +45,12 @@ export interface SubagentCapabilities {
* passes it to {@link SubagentProvider.start}.
*/
export interface SubagentStartRequest {
/** The task/prompt for the child agent (a user message in the child session). */
/** Content delivered as the child's user message. */
readonly prompt: ContentBlock[]
/**
* The spawning ("parent") agent — the one whose tool call started this
* subagent. REQUIRED: in-process backends read `parent.session.header` for
* the working directory, the `parentSession` lineage to stamp on the child,
* and the parent's delegation depth. The out-of-process backend (ACP) reads
* exactly one field — the session header's cwd, the child's workspace when
* no deployment `cwd` override is configured; nothing else crosses the
* process boundary.
* The spawning agent. In-process providers derive workspace, lineage, and
* delegation depth from its durable session state. ACP reads only its cwd,
* and only when no deployment `cwd` override is configured.
*/
readonly parent: Agent
/**
@@ -70,7 +61,6 @@ export interface SubagentStartRequest {
* afterward.
*/
readonly signal: AbortSignal
/** Per-child agent options (model and plugin-defined extension fields). */
readonly agentOptions?: AgentOptions
/**
* Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
@@ -112,9 +102,9 @@ export interface SubagentStartRequest {
export interface SubagentStopReasonMap {
/** The child finished its turn normally. */
completed: 'completed'
/** The run was cancelled by its request signal or by disposal. */
/** Cancelled through the request signal or disposal. */
aborted: 'aborted'
/** The child failed (model error, transport error). */
/** Model or transport failure. */
error: 'error'
/** The child hit its token ceiling before finishing. */
'max-tokens': 'max-tokens'
@@ -170,9 +160,8 @@ export interface SubagentRun {
*/
readonly result: Promise<SubagentResult>
/**
* Cancel remaining work, reach child quiescence, and release the run's
* resources (in-process: dispose the owned agent and remove its session;
* ACP: kill and reap the subprocess). Idempotent.
* Cancel remaining work, reach child quiescence, and release resources.
* Idempotent.
*/
dispose(): Promise<void>
/**
@@ -188,12 +177,9 @@ export interface SubagentRun {
}
/**
* A subagent backend: one transport for running a child agent (in-process
* spawn/fork, ACP to another process, …). Implementations register under a
* unique name via {@link SubagentService.registerProvider}; multiple providers
* coexist in one context (unlike the single-implementation bash seam). The
* Providers are trusted same-process implementations; callers treat their
* descriptors and returned values as borrowed immutable data.
* One registered transport for running child agents. Providers are trusted
* same-process implementations; callers treat descriptors and returned values
* as borrowed immutable data.
*/
export interface SubagentProvider {
/** Unique registry name (e.g. `spawn`, `fork`, `acp`). */

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 0f1bc6eae00dce7b1832f76d0267edd2c6ef2a93
README.zh.md: 49fca6af9fd039d4436f8c209a328d2fb0efcf07

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-tool-subagent
English | [中文](README.zh.md)
The model-facing delegation tool over one configured `ctx.subagents` provider. Changing the provider changes transport without changing the execution contract.
## Provider selection and lifecycle

View File

@@ -0,0 +1,81 @@
# @deepseek-ai/dsh-tool-subagent
[English](README.md) | 中文
基于一个已配置 `ctx.subagents` 提供方、面向模型的委派工具。更换提供方只会改变传输,不会改变执行契约。
## 提供方选择与生命周期
每个插件实例把一个 `provider` 绑定到一个 `toolName`;模型不会收到提供方选择器。如需公开另一种传输,请加载另一个名称不同的实例。工具只在其提供方存在时注册,从而避免对同级加载顺序和提供方重新加载的依赖。工具描述遵循 `provider.inheritsParentContext`:全新子 agent智能体需要独立提示词而 fork 子 agent 已能看到父级已完成轮次。
前台调用会让执行信号贯穿启动和执行,等待 `run.result`,并且在返回前总会等待 `run.dispose()`。只有 `completed` 会返回规范值 `{ kind: 'foreground', runId, output: JsonValue[] }`并渲染为相同的最终文本中止、拒绝、token 上限和其他失败都会变成出错的工具结果,不包含局部输出。
设置 `run_in_background: true` 后,工具会在启动提供方前注册父级拥有的任务,并返回规范值 `{ kind: 'background', taskId }`,渲染为 `started background subagent task <id>`。任务拥有的信号覆盖待处理的启动阶段,以及启动调用返回后的子 agent。`task_kill` 和所有者 dispose资源释放会中止它。结算会等待启动回滚或子 agent dispose然后把完成的最终文本映射为完成、中止映射为 `killed`、其他失败映射为 `failed`。任务不提供增量读取;通用任务工具负责后续状态、收集、取消和通知。见[后台 subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md)。
`toolFilter` 会改变子 agent 的全局工具层,但不是从父级派生的权限上限。见 [agent 作用域的安全非目标](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals)。
## 配置
| 键 | 含义 |
|---|---|
| `provider`(必填) | 提供方名称(`spawn``fork``acp` 等)。 |
| `toolName` | 面向模型的名称,默认 `subagent`;每个已加载实例必须不同。 |
| `enableRunInBackground` | 公开后台模式,默认 `true`;禁用时也会拒绝强制后台调用。 |
| `agentOptions` | 默认子 agent 选项,目前包括 `model`。 |
| `persona` | 每个子 agent 独立的 persona要求提供方具备 `persona` 能力。 |
| `toolFilter` | 每个子 agent 独立的全局工具限制;要求提供方具备 `toolFilter` 能力。 |
| `maxDepth` | 绝对委派深度上限,默认 `3``0` 禁止委派);数值上限要求 `depthLimit` 能力,缺失时挂载失败。对于预算由子 harness 拥有的进程外提供方,`'provider-managed'` 不发送上限。工具在达到上限时仍然可见;每次尝试启动都会检查调用 agent 的当前深度,被拒绝时返回出错的工具结果。 |
## 并发
前台调用与后台调用互斥。子 agent 可能共享父级工作区或外部资源,一元分类器无法证明同级委派的效果彼此不相交。见[并行工具调用 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md)。
## 模型体验
### 工具 schema
#### 模型看到的内容
当提供方存在时,以当前实例配置的名称公开已生成的默认 [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent)。提供方是否继承上下文会改变工具描述和提示词描述;启用后台模式会添加 `run_in_background`
#### Token 影响
每个父级请求支付固定 schema 成本;每个提供方实例增加一个 schema。
#### KV Cache 影响
只要提供方实例、名称、描述和 schema 不变,前缀就保持稳定。提供方注册生命周期可能从首个变化的工具定义开始,使父级复用失效。
### 前台结果
#### 模型看到的内容
调用会保留描述和提示词。成功时只包含子 agent 的最终文本;其他结果变为 `Error: <message>`。子 agent 中间步骤不会进入父级。
#### Token 影响
提示词和结果会留在父级历史中直到上下文压缩compaction子 agent 工作上下文留在子 agent 中。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
### 后台任务结果
#### 模型看到的内容
启动时精确返回 `started background subagent task <id>`。通用任务接口提供后续状态、最终输出、取消响应和通知。
#### Token 影响
确认消息会被保留;最终输出只在收集或注入时进入父级历史。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
- **后台运行只公开最终输出**:子 agent 中间步骤留在子 agent 会话中。
- **等待中实例的重复名称发现较晚**`TODO(subagent-dup-toolname)`):若要阻止提供方注册回滚,需要一份预期名称注册表。
- **每个实例的子 agent 策略固定**其他模型、persona、工具过滤器或深度上限都需要另一个名称不同的工具。