feat(subagent): durable child catalog and list_agents
Implements the durable-subagent-catalog RFC: SubagentControlService.listChildren() enumerates a parent's direct continuable children from one sessionQuery trace, validates each child's sole subagent/descriptor event (now carrying the durable creation label), and returns one ordered SubagentListEntry[] with per-child corrupt/unsupported/unavailable diagnostics. The list_agents tool ships as a separately loadable plugin of dsh-tool-subagent-control requiring sessionQuery at load; send_message stays usable without it.
This commit is contained in:
@@ -794,7 +794,7 @@ A ready child settled. Scope-filtered dispatch uses the same delegating parent c
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:164`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-added` — emit
|
||||
|
||||
@@ -811,7 +811,7 @@ A provider became resolvable in the registry.
|
||||
|
||||
Types: [SubagentProvider](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:110`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:138`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-removed` — emit
|
||||
|
||||
@@ -826,7 +826,7 @@ A provider left the registry. Accepted runs remain holder-owned.
|
||||
'subagent/provider-removed'(name: string): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:116`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:144`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/start` — emit
|
||||
|
||||
@@ -848,7 +848,7 @@ A provider established a ready child. For in-process providers, `ctx.agents.get(
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:127`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:155`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `system-prompt/*`
|
||||
|
||||
|
||||
@@ -1992,6 +1992,17 @@ async followup( parent: Agent, childId: SessionId, content: ContentBlock[], opti
|
||||
*/
|
||||
async drainContinuableDescendants(parents: readonly Agent[]): Promise<void>
|
||||
|
||||
/**
|
||||
* Enumerate one session's direct continuable children from the durable,
|
||||
* live-preferred corpus without loading or resuming an Agent. The lineage
|
||||
* trace supplies stable candidate order and live status; each candidate is
|
||||
* then inspected independently for exactly one supported descriptor in its
|
||||
* own suffix.
|
||||
* @param parentSessionId - parent whose direct children are listed.
|
||||
* @returns child and diagnostic entries in lineage-trace order.
|
||||
*/
|
||||
async listChildren(parentSessionId: SessionId): Promise<SubagentListEntry[]>
|
||||
|
||||
/**
|
||||
* Register a provider under its name. Registration is effect-scoped and HMR
|
||||
* safe; removing a provider blocks new starts but does not revoke runs that
|
||||
@@ -2026,9 +2037,9 @@ list(): string[]
|
||||
async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [MessageId](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) · [SubagentFollowupOptions](../core-data-structures/subagent.md) · [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [MessageId](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) · [SubagentFollowupOptions](../core-data-structures/subagent.md) · [SubagentListEntry](../core-data-structures/subagent.md) · [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:141`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:169`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `ctx.subprocess` — `SubprocessService` (abstract seam)
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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 docs/core-data-structures/subagent.md
|
||||
subagent.md: 379bc9fdd5ff14f9d516d3ddcf37a353f5318026
|
||||
subagent.zh.md: 3cd43deadba095e67e6a0dd3b483ea676206fbbf
|
||||
subagent.md: ae0d84af09e65bf284780722bab02282fd6c3f37
|
||||
subagent.zh.md: 9d2ba038885110a52cc3c7153cc908b05ea8b15f
|
||||
|
||||
@@ -200,7 +200,11 @@ interface ContinuableCreateSpec {
|
||||
}
|
||||
```
|
||||
|
||||
The descriptor (`SubagentDescriptorData` in [descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts)) snapshots explicit fields — provider name, resolved child `agentOptions.provider`/`model`, optional `persona`/`toolFilter` — never the merge-extensible `AgentOptions` object, so an unrelated extension value cannot break continuation and a later composition input is a deliberate version change. It omits `subagentDepth` (cold resume trusts the persisted header's `delegationDepth` as the monotone floor) and `outputSchema` (a one-shot result contract, not durable composition). The continuation manager appends the model-hidden `subagent/descriptor` event after any provider-supplied lineage and before the initial prompt is admitted; `header.seedLength` remains the fork-lineage boundary, so descriptor lookup reads the child's own suffix. The event is log-only: no `surfaceOp`, never in model history, and retained across compaction by the append-only log.
|
||||
The descriptor (`SubagentDescriptorData` in [descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts)) snapshots explicit fields — provider name, the delegation `description` as the durable creation `label`, resolved child `agentOptions.provider`/`model`, optional `persona`/`toolFilter` — never the merge-extensible `AgentOptions` object, so an unrelated extension value cannot break continuation and a later composition input is a deliberate version change. It omits `subagentDepth` (cold resume trusts the persisted header's `delegationDepth` as the monotone floor) and `outputSchema` (a one-shot result contract, not durable composition). The continuation manager appends the model-hidden `subagent/descriptor` event after any provider-supplied lineage and before the initial prompt is admitted; `header.seedLength` remains the fork-lineage boundary, so descriptor lookup reads the child's own suffix. The event is log-only: no `surfaceOp`, never in model history, and retained across compaction by the append-only log.
|
||||
|
||||
## Durable enumeration: `listChildren()` and `SubagentListEntry`
|
||||
|
||||
`SubagentService.listChildren(parentSessionId)` enumerates the parent's direct continuable children from one `ctx.sessionQuery.traceSession()` observation, without loading or resuming any Agent. Session lineage is broader than subagent identity — ordinary forks and one-shot children share `parentSession` — so exactly one supported `subagent/descriptor` event in the child's own suffix (after `seedLength`, so a fork seed cannot leak an ancestor's descriptor) is the sole subagent discriminator. The result is one `SubagentListEntry[]` in the trace's `createdAt`-then-id candidate order: a valid descriptor yields a `child` entry whose `status` snapshots the logical record (`running` = live in `ctx.sessions`, `complete` = persisted only, resumable by `send_message`); a per-child inspection failure yields a `diagnostic` entry (`corrupt`, `unsupported`, or `unavailable`) so one damaged sibling cannot hide healthy children; a missing descriptor yields no entry. A failure while building the initial trace fails the whole call — per-child isolation begins only after a trustworthy candidate set exists. The service keeps `sessionQuery` optional for by-id continuation: `listChildren()` throws `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` when it is absent, while the model-facing `list_agents` adapter (the separately loadable `/list-agents` plugin of [dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control)) requires the service at plugin load. Listing does not consult the continuation manager's Activation map or provider availability; `send_message` remains the authoritative delivery-time operation, and a listed `running` child may still reject delivery as an ownership conflict.
|
||||
|
||||
## The terminal result: `SubagentResult`
|
||||
|
||||
|
||||
@@ -200,7 +200,11 @@ interface ContinuableCreateSpec {
|
||||
}
|
||||
```
|
||||
|
||||
描述符([descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts) 中的 `SubagentDescriptorData`)会对显式字段建立快照——提供方名称、已解析的子 agent `agentOptions.provider`/`model`、可选的 `persona`/`toolFilter`——绝不会对可合并扩展的 `AgentOptions` 对象建立快照,因此无关的扩展值不会破坏继续执行,后续新增组合配置输入则是一次有意的版本更改。它省略 `subagentDepth`(冷恢复以持久化 header 中的 `delegationDepth` 作为单调下界)和 `outputSchema`(单次结果契约,而非持久化组合配置)。继续执行管理器会在任何提供方提供的谱系之后、初始 prompt 获准之前,追加对模型隐藏的 `subagent/descriptor` 事件;`header.seedLength` 仍是 fork 谱系边界,因此描述符查找会读取子 agent 自身的后缀。该事件只进入日志:不含 `surfaceOp`,绝不进入模型历史,并由仅追加日志跨压缩保留。
|
||||
描述符([descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts) 中的 `SubagentDescriptorData`)会对显式字段建立快照——提供方名称、作为持久化创建 `label` 的委派 `description`、已解析的子 agent `agentOptions.provider`/`model`、可选的 `persona`/`toolFilter`——绝不会对可合并扩展的 `AgentOptions` 对象建立快照,因此无关的扩展值不会破坏继续执行,后续新增组合配置输入则是一次有意的版本更改。它省略 `subagentDepth`(冷恢复以持久化 header 中的 `delegationDepth` 作为单调下界)和 `outputSchema`(单次结果契约,而非持久化组合配置)。继续执行管理器会在任何提供方提供的谱系之后、初始 prompt 获准之前,追加对模型隐藏的 `subagent/descriptor` 事件;`header.seedLength` 仍是 fork 谱系边界,因此描述符查找会读取子 agent 自身的后缀。该事件只进入日志:不含 `surfaceOp`,绝不进入模型历史,并由仅追加日志跨压缩保留。
|
||||
|
||||
## 持久化枚举:`listChildren()` 与 `SubagentListEntry`
|
||||
|
||||
`SubagentService.listChildren(parentSessionId)` 从一次 `ctx.sessionQuery.traceSession()` 观测中枚举 parent 的直接可继续 child,而不会加载或恢复任何 Agent。会话谱系的范围比 subagent 身份更广——普通 fork 和一次性 child 都会共享 `parentSession`——因此,child 自身后缀中恰好一个受支持的 `subagent/descriptor` 事件(位于 `seedLength` 之后,避免 fork seed 泄漏祖先描述符)是唯一的 subagent 判别信息。结果是一个按追踪结果中 `createdAt`、再按 id 排列候选顺序的 `SubagentListEntry[]`:有效描述符生成 `child` 条目,其 `status` 是逻辑记录的快照(`running` 表示在 `ctx.sessions` 中存活,`complete` 表示只存在于持久化存储中并可由 `send_message` 恢复);逐 child 检查失败生成 `diagnostic` 条目(`corrupt`、`unsupported` 或 `unavailable`),因此一个损坏的 sibling 不会隐藏健康 child;缺少描述符则不生成条目。构建初始追踪时的失败会让整个调用失败——只有得到可信候选集后才开始逐 child 隔离。服务将 `sessionQuery` 保持为按 id 继续执行时的可选依赖:缺少该服务时,`listChildren()` 抛出 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`,而面向模型的 `list_agents` 适配器([dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control) 中可单独加载的 `/list-agents` 插件)会在插件加载时要求该服务。枚举不会查询继续执行管理器的 Activation map 或提供方可用性;`send_message` 仍是消息送达时的权威操作,列表中的 `running` child 仍可能因所有权冲突而拒绝投递。
|
||||
|
||||
## 终态结果:`SubagentResult`
|
||||
|
||||
|
||||
@@ -41,10 +41,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `settings/document-updated` | `emit` | [`packages/settings/settings/src/index.ts:150`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` |
|
||||
| `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:137`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
|
||||
| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:188`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:136`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc), [`subagent`](../packages/subagent/subagent) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:110`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:116`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:127`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`subagent`](../packages/subagent/subagent) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:164`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc), [`subagent`](../packages/subagent/subagent) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:138`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:144`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:155`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`subagent`](../packages/subagent/subagent) |
|
||||
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:29`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:35`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
|
||||
| `telemetry/record` | `waterfall` | [`packages/telemetry/session-telemetry/src/index.ts:41`](../packages/telemetry/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/telemetry/session-telemetry) (`waterfall`) | - |
|
||||
|
||||
@@ -31,7 +31,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-session-query` | `session_event_read`, `session_event_search`, `session_event_trace`, `session_search`, `session_trace` | `ctx.tools`, `ctx.systemPrompt`, `ctx.sessionQuery`, `a calling Agent for workspace authority` | `tool/call`, `tool/result` | - | The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `apps/cli/config/base.cordis.yml` and `examples/acp-agent/cordis.yml`. |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `send_message` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The one globally named follow-up tool over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once. |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.sessionQuery (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once, plus `list_agents` from its separately loaded `/list-agents` plugin (which additionally requires session query). |
|
||||
| `@deepseek-ai/dsh-tool-tasks` | `task_kill`, `task_list`, `task_output` | `ctx.tools`, `ctx.tasks`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`. |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. |
|
||||
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflows`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
|
||||
@@ -1149,6 +1149,19 @@ The registered tool name is the load-time `toolName` config (default `subagent`)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-subagent-control`
|
||||
|
||||
### `list_agents`
|
||||
|
||||
List your background subagents: every subagent you started that can receive `send_message`, whether it is still working (running) or has finished its current turn (complete — a follow-up message starts a new turn on the same conversation). Children that could not be read are reported as diagnostics instead of being silently dropped.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts)
|
||||
|
||||
### `send_message`
|
||||
|
||||
Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. The subagent does not reply to you, so use this only to give it more work. A failure means the message was NOT delivered.
|
||||
@@ -1175,7 +1188,7 @@ Send a message to a background subagent by its subagent id, continuing the same
|
||||
|
||||
Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts)
|
||||
|
||||
The one globally named follow-up tool over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once.
|
||||
The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` once, plus `list_agents` from its separately loaded `/list-agents` plugin (which additionally requires session query).
|
||||
|
||||
## `@deepseek-ai/dsh-tool-tasks`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user